52 static_assert(std::is_integral_v<Value> &&
sizeof(Value) == 8,
53 "id auxiliary values are fixed-width 64-bit integers");
54 using MemoryKey = std::pair<std::string, std::optional<std::string>>;
55 using MemoryMap = std::map<MemoryKey, Value>;
58 std::uint64_t hash = 0;
59 std::uint64_t offset_plus_one = 0;
62 std::uint64_t hash = 0;
63 std::uint64_t first_size = 0;
64 std::uint64_t second_size = 0;
66 std::uint64_t flags = 0;
68 static_assert(
sizeof(Slot) == 16 &&
sizeof(Header) == 40,
69 "id scratch layout must have no padding");
70 static constexpr std::uint64_t kLive = 1;
71 static constexpr std::uint64_t kPair = 2;
72 static constexpr std::uint64_t kInitialSlots = 64;
78 IdPairOrderedFold ordered;
79 IdPairSlabFold bounded;
80 Header pending_header;
82 std::uint64_t pending_offset = 0, pending_at = 0;
83 bool pending =
false, slot_pending =
false;
84 FixtureFoldWork* fixture_work =
nullptr;
108 if (owner_->memory_) {
123 if (owner_ != other.owner_)
return false;
124 if (!entry_ || !other.entry_)
return !entry_ && !other.entry_;
125 return owner_->memory_ ? memory_at_ == other.memory_at_ : offset_ == other.offset_;
133 if (owner_->memory_) {
134 memory_at_ = owner_->memory_->begin();
142 if (memory_at_ != owner_->memory_->end())
143 entry_.emplace(
Entry{memory_at_->first.first, memory_at_->second,
144 memory_at_->first.second});
148 while (offset_ < owner_->data_bytes_) {
149 const Header header = owner_->read_header(offset_);
150 next_ = offset_ +
sizeof(Header) + header.first_size + header.second_size;
151 if ((header.flags & kLive) != 0) {
153 row.first.resize(
static_cast<std::size_t
>(header.first_size));
154 owner_->read_data(offset_ +
sizeof(Header), row.first.data(), row.first.size());
155 row.second = header.value;
156 if ((header.flags & kPair) != 0) {
157 row.from_entry.emplace(
static_cast<std::size_t
>(header.second_size),
'\0');
158 owner_->read_data(offset_ +
sizeof(Header) + header.first_size,
159 row.from_entry->data(), row.from_entry->size());
161 entry_.emplace(std::move(row));
168 std::uint64_t offset_ = 0;
169 std::uint64_t next_ = 0;
170 typename MemoryMap::const_iterator memory_at_{};
171 std::optional<Entry> entry_;
179 if (
this != &other) {
187 if ((data_.opened() || index_.opened()) && directory != directory_)
188 throw std::logic_error(
"id table directory changed during run");
189 directory_ = directory;
195 slots_ = slab_ ? fixed_slots_ : 0;
198 if constexpr (MaintainSortedFold) {
199 fold_.ordered.clear();
200 fold_.bounded.reset();
201 fold_.pending = fold_.slot_pending =
false;
203 if (memory_) memory_->clear();
205 slab_->reset(data_resource_);
206 slab_->reset(index_resource_);
207 slab_->extend(index_resource_, slots_ *
sizeof(Slot));
210 std::size_t
size() const noexcept {
return size_; }
211 bool empty() const noexcept {
return size_ == 0; }
214 const auto table = data_bytes_ + slots_ *
sizeof(Slot);
215 if constexpr (MaintainSortedFold) {
216 const auto ordered = fold_.ordered.disk_bytes();
217 const auto maximum = std::numeric_limits<std::uint64_t>::max();
218 return ordered > maximum - table ? maximum : table + ordered;
222 const_iterator
begin()
const {
return const_iterator(
this,
false); }
223 const_iterator
end()
const {
return const_iterator(
this,
true); }
230 if (used_ != 0 || size_ != 0 || data_.opened() || index_.opened())
231 throw std::logic_error(
"id table scratch backend changed after use");
233 throw std::logic_error(
"id table fixture oracle cannot bind production scratch");
234 std::uint64_t limit = 0;
236 limit = slab ? slab->manifest().K_e : 0;
238 limit = slab ? slab->manifest().K_c : 0;
240 throw std::logic_error(
"id table scratch resource pair is invalid");
242 if (slab) slab->require_extend(index, count *
sizeof(Slot));
243 if constexpr (MaintainSortedFold) {
245 fold_.bounded.configure(slab->region(
data).capacity, limit, count);
247 slab->require_extend(index, fold_.bounded.storage_bytes());
251 data_resource_ =
data;
252 index_resource_ = index;
254 fixed_slots_ = count;
267 template<
class Enumerate>
270 std::uint64_t keys = used_;
271 std::uint64_t extent = data_bytes_;
272 std::size_t ordinal = 0;
273 enumerate([&](KeyView
key) {
274 auto first =
key.first;
275 auto second =
key.second;
277 && (first.find(
'\x1f') != std::string_view::npos
278 || second->find(
'\x1f') != std::string_view::npos)) {
280 "order ids cannot contain the control character 0x1f");
286 const auto separator = first.rfind(
'\x1f', 64);
287 if (separator != std::string_view::npos) {
288 second = first.substr(separator + 1);
289 first = first.substr(0, separator);
292 require_id_components(first.size(), second ? second->size() : 0);
293 const auto own_ordinal = ordinal++;
294 if (slots_ != 0 && locate(
key.first,
key.second,
295 hash_for_key(
key.first,
key.second)).second.offset_plus_one != 0)
297 bool duplicate =
false;
298 std::size_t prior = 0;
299 enumerate([&](KeyView earlier) {
300 if (prior++ < own_ordinal && earlier.first ==
key.first
301 && earlier.second ==
key.second) duplicate =
true;
303 if (duplicate)
return;
305 preflight_new_key(keys, extent,
key.first.size(),
306 key.second ?
key.second->size() : 0);
310 std::optional<std::string_view> second = std::nullopt)
const {
311 preflight_keys([&](
const auto& emit) { emit(KeyView{first, second}); });
318 std::string_view last)
const {
321 && (first.find(
'\x1f') != std::string_view::npos
322 || last.find(
'\x1f') != std::string_view::npos)) {
324 "order ids cannot contain the control character 0x1f");
326 require_id_components(first.size(), last.size());
327 constexpr auto maximum = std::numeric_limits<std::uint64_t>::max();
328 if (first.size() >= maximum || last.size() > maximum - first.size() - 1)
331 ? slab_->manifest().B_e : slab_->manifest().B_c));
332 const auto length =
static_cast<std::uint64_t
>(first.size()) + 1 + last.size();
333 std::uint64_t hash = 1469598103934665603ULL;
334 const auto byte = [&](
unsigned char value) { hash ^=
value; hash *= 1099511628211ULL; };
336 for (
unsigned shift = 0; shift < 64; shift += 8)
337 byte(
static_cast<unsigned char>(length >> shift));
338 for (
const unsigned char value : first) byte(
value);
339 byte(
static_cast<unsigned char>(separator));
340 for (
const unsigned char value : last) byte(
value);
341 if (fixture_hash_) hash = *fixture_hash_;
343 const auto found = locate_hash(hash, [&](std::uint64_t offset,
const Header& header) {
344 return (header.flags & kPair) == 0 && header.first_size == length
345 && equal_string(offset +
sizeof(Header), first.size(), first)
346 && equal_string(offset +
sizeof(Header) + first.size(), 1,
347 std::string_view(&separator, 1))
348 && equal_string(offset +
sizeof(Header) + first.size() + 1,
351 if (found.second.offset_plus_one != 0)
return;
354 auto extent = data_bytes_;
355 preflight_new_key(keys, extent, length, 0);
361 static_assert(MaintainSortedFold,
"ordered fold must be selected for this table");
364 for (
const auto& row : *memory_) {
365 sink.
s(row.first.first);
369 sink.
h = fold_.bounded.apply(sink.
h);
371 sink.
h = fold_.ordered.apply(sink.
h);
375 data_.fixture_count_io(io); index_.fixture_count_io(io);
376 if constexpr (MaintainSortedFold) fold_.ordered.fixture_count_io(io);
379 static_assert(MaintainSortedFold,
"fold work requires the ordered-fold table");
380 fold_.fixture_work = work;
387 if (slab_ || used_ != 0 || size_ != 0 || data_.opened() || index_.opened())
388 throw std::logic_error(
"id table fixture backend changed after use");
389 memory_ = std::make_unique<MemoryMap>();
392 if (used_ != 0 || size_ != 0 || data_.opened() || index_.opened())
393 throw std::logic_error(
"id table fixture hash changed after use");
394 fixture_hash_ = hash;
397 std::optional<Value>
lookup(std::string_view
id)
const {
return lookup_key(
id, std::nullopt); }
398 std::optional<Value>
lookup(std::string_view
id, std::string_view from_entry)
const {
399 return lookup_key(
id, from_entry);
401 Value
at(
const std::string&
id)
const {
402 const auto found =
lookup(
id);
403 if (!found)
throw std::out_of_range(
"id auxiliary key is absent");
407 return assign_key(
id, std::nullopt,
value);
410 static_assert(!MaintainSortedFold,
"consumed-cycle sorted fold accepts single keys");
411 return assign_key(
id, from_entry,
value);
414 const auto old =
lookup(
id);
418 const auto old =
lookup(
id, from_entry);
421 bool erase(std::string_view
id) {
return erase_key(
id, std::nullopt); }
422 bool erase(std::string_view
id, std::string_view from_entry) {
423 return erase_key(
id, from_entry);
427 void require_id_components(std::size_t first, std::size_t second)
const {
428 if (first > 64 || second > 64)
431 void preflight_new_key(std::uint64_t& keys, std::uint64_t& extent,
432 std::uint64_t first, std::uint64_t second)
const {
433 if (keys >= key_limit_ || keys >= slots_ / 2
434 || keys >= std::numeric_limits<std::size_t>::max())
435 throw ScratchCapacityError(index_resource_,
438 const auto append = [&](std::uint64_t bytes) {
439 const auto capacity = slab_->region(data_resource_).capacity;
440 if (extent > capacity || bytes > capacity - extent)
441 throw ScratchCapacityError(data_resource_,
444 ? slab_->manifest().B_e : slab_->manifest().B_c));
447 append(
sizeof(Header));
453 swap(data_, other.data_);
454 swap(index_, other.index_);
455 swap(directory_, other.directory_);
456 swap(data_bytes_, other.data_bytes_);
457 swap(slots_, other.slots_);
458 swap(used_, other.used_);
459 swap(size_, other.size_);
460 swap(memory_, other.memory_);
461 swap(slab_, other.slab_);
462 swap(data_resource_, other.data_resource_);
463 swap(index_resource_, other.index_resource_);
464 swap(key_limit_, other.key_limit_);
465 swap(fixed_slots_, other.fixed_slots_);
466 swap(fixture_hash_, other.fixture_hash_);
467 swap(fold_, other.fold_);
469 static MemoryKey memory_key(std::string_view first, std::optional<std::string_view> second) {
470 return {std::string(first), second ? std::optional<std::string>{std::string(*second)}
473 static std::uint64_t key_hash(std::string_view first,
474 std::optional<std::string_view> second)
noexcept {
475 std::uint64_t hash = 1469598103934665603ULL;
476 const auto byte = [&hash](
unsigned char value) {
478 hash *= 1099511628211ULL;
480 const auto string = [&byte](std::string_view
value) {
481 const auto length =
static_cast<std::uint64_t
>(
value.size());
482 for (
unsigned shift = 0; shift < 64; shift += 8)
483 byte(
static_cast<unsigned char>(length >> shift));
484 for (
const unsigned char value_byte :
value) byte(value_byte);
486 byte(second ? 1 : 0);
488 if (second)
string(*second);
491 std::uint64_t hash_for_key(std::string_view first,
492 std::optional<std::string_view> second)
const noexcept {
493 return fixture_hash_ ? *fixture_hash_ : key_hash(first, second);
495 void read_data(std::uint64_t offset,
void* destination, std::size_t length)
const {
496 if (slab_) slab_->read_at(data_resource_, offset, destination, length);
497 else data_.read(offset, destination, length);
499 void write_data(std::uint64_t offset,
const void* source, std::size_t length) {
500 if (slab_) slab_->write_at(data_resource_, offset, source, length);
501 else data_.write(offset, source, length);
503 void read_index(std::uint64_t offset,
void* destination, std::size_t length)
const {
504 if constexpr (MaintainSortedFold) {
507 slot.offset_plus_one = fold_.bounded.read_slot(*slab_, index_resource_, offset /
sizeof(Slot));
508 if (slot.offset_plus_one != 0)
509 read_data(slot.offset_plus_one - 1, &slot.hash,
sizeof(slot.hash));
510 std::memcpy(destination, &slot, length);
514 if (slab_) slab_->read_at(index_resource_, offset, destination, length);
515 else index_.read(offset, destination, length);
517 void write_index(std::uint64_t offset,
const void* source, std::size_t length) {
518 if constexpr (MaintainSortedFold) {
521 std::memcpy(&slot, source, length);
522 fold_.bounded.write_slot(*slab_, index_resource_, offset /
sizeof(Slot), slot.offset_plus_one);
526 if (slab_) slab_->write_at(index_resource_, offset, source, length);
527 else index_.write(offset, source, length);
529 Header read_header(std::uint64_t offset)
const {
530 if (offset > data_bytes_ || data_bytes_ - offset <
sizeof(Header))
531 throw IdPairStoreError(EIO,
"id table truncated record");
533 if constexpr (MaintainSortedFold) {
534 if (fold_.pending && offset == fold_.pending_offset) header = fold_.pending_header;
535 else read_data(offset, &header,
sizeof(header));
537 read_data(offset, &header,
sizeof(header));
539 const auto available = data_bytes_ - offset -
sizeof(Header);
540 if (header.first_size > available || header.second_size > available - header.first_size
541 || (header.flags & ~(kLive | kPair)) != 0
542 || ((header.flags & kPair) == 0 && header.second_size != 0))
543 throw IdPairStoreError(EIO,
"id table invalid record");
546 bool equal_string(std::uint64_t offset, std::uint64_t length, std::string_view
key)
const {
547 if (length !=
key.size())
return false;
548 std::array<char, 512> buffer{};
549 for (std::size_t
at = 0;
at <
key.size();) {
550 const auto count = std::min(buffer.size(),
key.size() -
at);
551 read_data(offset +
at, buffer.data(), count);
552 if (
key.compare(
at, count, buffer.data(), count) != 0)
return false;
559 std::pair<std::uint64_t, Slot> locate(std::string_view first,
560 std::optional<std::string_view> second, std::uint64_t hash)
const {
561 return locate_hash(hash, [&](std::uint64_t offset,
const Header& header) {
562 return ((header.flags & kPair) != 0) == second.has_value()
563 && equal_string(offset +
sizeof(Header), header.first_size, first)
564 && (!second || equal_string(offset +
sizeof(Header) + header.first_size,
565 header.second_size, *second));
568 template<
class Equal>
569 std::pair<std::uint64_t, Slot> locate_hash(std::uint64_t hash,
const Equal& equal)
const {
570 std::uint64_t
at = hash & (slots_ - 1);
571 for (std::uint64_t probes = 0; probes < slots_; ++probes) {
573 if constexpr (MaintainSortedFold) {
574 if (fold_.slot_pending &&
at == fold_.pending_at) slot = fold_.pending_slot;
575 else read_index(
at *
sizeof(Slot), &slot,
sizeof(slot));
577 read_index(
at *
sizeof(Slot), &slot,
sizeof(slot));
579 if (slot.offset_plus_one == 0)
return {
at, slot};
580 if (slot.hash == hash) {
581 const auto offset = slot.offset_plus_one - 1;
582 const Header header = read_header(offset);
583 if (header.hash != slot.hash)
584 throw IdPairStoreError(EIO,
"id table mismatched index");
585 if (equal(offset, header))
return {
at, slot};
587 at = (
at + 1) & (slots_ - 1);
589 throw IdPairStoreError(EIO,
"id table full index");
591 static void place_slot(IdPairScratchFile& index, std::uint64_t slots,
const Slot& slot) {
592 std::uint64_t
at = slot.hash & (
slots - 1);
593 for (std::uint64_t probes = 0; probes <
slots; ++probes) {
595 index.read(
at *
sizeof(Slot), &existing,
sizeof(existing));
596 if (existing.offset_plus_one == 0) {
597 index.write(
at *
sizeof(Slot), &slot,
sizeof(slot));
600 at = (
at + 1) & (slots - 1);
602 throw IdPairStoreError(EIO,
"id table rehash overflow");
605 constexpr auto maximum =
static_cast<std::uint64_t
>(std::numeric_limits<off_t>::max())
607 if (slots_ > maximum / 2)
throw IdPairStoreError(EFBIG,
"id table index exhausted");
608 const auto next_slots = slots_ == 0 ? kInitialSlots : slots_ * 2;
609 IdPairScratchFile next(MaintainSortedFold
611 next.fixture_count_io(index_.fixture_io());
612 next.open(directory_);
613 next.resize(next_slots *
sizeof(Slot));
614 std::array<Slot, 128> buffer{};
615 for (std::uint64_t
at = 0;
at < slots_;) {
616 const auto count = std::min<std::uint64_t>(buffer.size(), slots_ -
at);
617 index_.read(
at *
sizeof(Slot), buffer.data(),
618 static_cast<std::size_t
>(count) *
sizeof(Slot));
619 for (std::size_t i = 0; i < static_cast<std::size_t>(count); ++i)
620 if (buffer[i].offset_plus_one != 0) place_slot(next, next_slots, buffer[i]);
623 index_ = std::move(next);
626 std::optional<Value> lookup_key(std::string_view first,
627 std::optional<std::string_view> second)
const {
629 const auto found = memory_->find(memory_key(first, second));
630 return found == memory_->end() ? std::nullopt : std::optional<Value>{found->second};
632 if (slots_ == 0)
return std::nullopt;
633 const auto found = locate(first, second, hash_for_key(first, second)).second;
634 if (found.offset_plus_one == 0)
return std::nullopt;
635 const Header header = read_header(found.offset_plus_one - 1);
636 return (header.flags & kLive) != 0 ? std::optional<Value>{header.value} : std::nullopt;
638 bool assign_key(std::string_view first, std::optional<std::string_view> second, Value
value) {
640 const auto inserted = memory_->insert_or_assign(memory_key(first, second),
value);
641 if (inserted.second) ++size_;
642 return inserted.second;
646 if constexpr (MaintainSortedFold) flush_pending();
647 const auto hash = hash_for_key(first, second);
648 if (!slab_ && slots_ == 0) grow_index();
649 auto found = locate(first, second, hash);
650 if (found.second.offset_plus_one != 0) {
651 const auto offset = found.second.offset_plus_one - 1;
652 Header header = read_header(offset);
653 const bool inserted = (header.flags & kLive) == 0;
654 header.value =
value;
655 header.flags |= kLive;
656 if constexpr (MaintainSortedFold) publish_fold(first, offset, header);
657 else write_data(offset, &header,
sizeof(header));
658 if (inserted) ++size_;
661 if (used_ >= slots_ / 2) {
662 if (slab_)
throw ScratchCapacityError(index_resource_,
665 found = locate(first, second, hash);
667 if (size_ == std::numeric_limits<std::size_t>::max())
668 throw IdPairStoreError(EFBIG,
"id table size exhausted");
669 const auto second_size = second ? second->size() : 0;
670 constexpr auto maximum =
static_cast<std::uint64_t
>(std::numeric_limits<off_t>::max());
671 if (data_bytes_ > maximum -
sizeof(Header)
672 || first.size() > maximum - data_bytes_ -
sizeof(Header)
673 || second_size > maximum - data_bytes_ -
sizeof(Header) - first.size())
674 throw IdPairStoreError(EFBIG,
"id table data exhausted");
675 Header header{hash,
static_cast<std::uint64_t
>(first.size()),
676 static_cast<std::uint64_t
>(second_size),
value, kLive | (second ? kPair : 0)};
677 if (!slab_) data_.open(directory_);
678 write_data(data_bytes_, &header,
sizeof(header));
679 write_data(data_bytes_ +
sizeof(Header), first.data(), first.size());
681 write_data(data_bytes_ +
sizeof(Header) + first.size(), second->data(), second->size());
682 const Slot slot{hash, data_bytes_ + 1};
683 if constexpr (MaintainSortedFold) publish_fold(first, data_bytes_, header, found.first, slot);
684 else write_index(found.first *
sizeof(Slot), &slot,
sizeof(slot));
685 data_bytes_ +=
sizeof(Header) + first.size() + second_size;
690 bool erase_key(std::string_view first, std::optional<std::string_view> second) {
692 const auto count = memory_->erase(memory_key(first, second));
696 if constexpr (MaintainSortedFold) flush_pending();
697 if (slots_ == 0)
return false;
698 const auto found = locate(first, second, hash_for_key(first, second)).second;
699 if (found.offset_plus_one == 0)
return false;
700 const auto offset = found.offset_plus_one - 1;
701 Header header = read_header(offset);
702 if ((header.flags & kLive) == 0)
return false;
703 header.flags &= ~kLive;
704 if constexpr (MaintainSortedFold) publish_fold(first, offset, header);
705 else write_data(offset, &header,
sizeof(header));
715 void flush_pending() {
716 if (!fold_.pending)
return;
717 write_data(fold_.pending_offset, &fold_.pending_header,
sizeof(Header));
718 if (fold_.slot_pending)
719 write_index(fold_.pending_at *
sizeof(Slot), &fold_.pending_slot,
sizeof(Slot));
720 fold_.pending = fold_.slot_pending =
false;
725 std::uint64_t changed;
726 const Header& prospective;
727 std::array<unsigned char, 512> a{}, b{};
729 Header header(std::uint64_t row)
const {
730 return row == changed ? prospective : owner.read_header(row);
732 unsigned symbol(std::uint64_t row, std::uint64_t
at)
const {
733 const auto h = header(row);
734 if (
at >= h.first_size)
return 0;
735 unsigned char byte = 0;
736 owner.read_data(row +
sizeof(Header) +
at, &
byte, 1);
737 return static_cast<unsigned>(byte) + 1;
739 int compare(std::uint64_t row, std::string_view
key) {
740 if (owner.fold_.fixture_work) ++owner.fold_.fixture_work->row_comparisons;
741 const auto h = header(row);
742 const auto common = std::min<std::uint64_t>(h.first_size,
key.size());
743 for (std::uint64_t
at = 0;
at < common;) {
744 const auto n =
static_cast<std::size_t
>(std::min<std::uint64_t>(a.size(), common -
at));
745 owner.read_data(row +
sizeof(Header) +
at, a.data(), n);
746 const int order = std::char_traits<char>::compare(
747 reinterpret_cast<const char*
>(a.data()),
key.data() +
at, n);
748 if (order != 0)
return order;
751 return h.first_size <
key.size() ? -1 : (h.first_size >
key.size() ? 1 : 0);
753 int compare_rows(std::uint64_t first, std::uint64_t second) {
754 if (owner.fold_.fixture_work) ++owner.fold_.fixture_work->row_comparisons;
755 const auto x = header(first), y = header(second);
756 const auto common = std::min(x.first_size, y.first_size);
757 for (std::uint64_t
at = 0;
at < common;) {
758 const auto n =
static_cast<std::size_t
>(std::min<std::uint64_t>(a.size(), common -
at));
759 owner.read_data(first +
sizeof(Header) +
at, a.data(), n);
760 owner.read_data(second +
sizeof(Header) +
at, b.data(), n);
761 const int order = std::char_traits<char>::compare(
762 reinterpret_cast<const char*
>(a.data()),
763 reinterpret_cast<const char*
>(b.data()), n);
764 if (order != 0)
return order;
767 return x.first_size < y.first_size ? -1 : (x.first_size > y.first_size ? 1 : 0);
769 IdPairOrderedFold::Position difference(std::uint64_t row, std::string_view
key) {
770 const auto h = header(row);
771 const auto common = std::min<std::uint64_t>(h.first_size,
key.size());
772 for (std::uint64_t
at = 0;
at < common;) {
773 const auto n =
static_cast<std::size_t
>(std::min<std::uint64_t>(a.size(), common -
at));
774 owner.read_data(row +
sizeof(Header) +
at, a.data(), n);
775 for (std::size_t i = 0; i < n; ++i) {
776 const auto byte =
static_cast<unsigned char>(
key[
at + i]);
782 if (h.first_size ==
key.size())
throw std::logic_error(
"id fold duplicate row");
786 IdPairOrderedFold::Position difference(std::uint64_t first, std::uint64_t second) {
787 const auto x = header(first), y = header(second);
788 const auto common = std::min(x.first_size, y.first_size);
789 for (std::uint64_t
at = 0;
at < common;) {
790 const auto n =
static_cast<std::size_t
>(std::min<std::uint64_t>(a.size(), common -
at));
791 owner.read_data(first +
sizeof(Header) +
at, a.data(), n);
792 owner.read_data(second +
sizeof(Header) +
at, b.data(), n);
793 for (std::size_t i = 0; i < n; ++i)
798 if (x.first_size == y.first_size)
throw std::logic_error(
"id fold duplicate row");
801 void bytes(BrokerStateByteFold& fold,
const void*
data, std::size_t
size) {
802 if (owner.fold_.fixture_work)
803 owner.fold_.fixture_work->byte_steps += 257 *
static_cast<std::uint64_t
>(
size);
806 void compose(BrokerStateByteFold& before,
const BrokerStateByteFold& after) {
807 if (owner.fold_.fixture_work) ++owner.fold_.fixture_work->compositions;
810 void append(std::uint64_t row, BrokerStateByteFold& fold) {
811 const auto h = header(row);
812 if ((h.flags & kLive) == 0)
return;
815 bytes(fold, &h.first_size,
sizeof(h.first_size));
816 for (std::uint64_t
at = 0;
at < h.first_size;) {
817 const auto n =
static_cast<std::size_t
>(std::min<std::uint64_t>(a.size(), h.first_size -
at));
818 owner.read_data(row +
sizeof(Header) +
at, a.data(), n);
819 bytes(fold, a.data(), n);
822 const auto value =
static_cast<std::int64_t
>(h.value);
826 void publish_fold(std::string_view
key, std::uint64_t offset,
const Header& header) {
827 publish_fold(
key, offset, header, 0, Slot{});
829 void publish_fold(std::string_view
key, std::uint64_t offset,
const Header& header,
830 std::uint64_t
at, Slot slot) {
831 FoldReader reader{*
this, offset, header};
833 fold_.bounded.update(*slab_, index_resource_, used_,
834 slot.offset_plus_one != 0,
key, offset, reader);
837 write_data(offset, &header,
sizeof(header));
838 if (slot.offset_plus_one != 0) write_index(
at *
sizeof(Slot), &slot,
sizeof(slot));
841 fold_.ordered.update(
key, offset, directory_, reader);
843 fold_.pending_header = header; fold_.pending_offset = offset;
844 fold_.pending_slot = slot; fold_.pending_at =
at;
845 fold_.pending =
true; fold_.slot_pending = slot.offset_plus_one != 0;
848 IdPairScratchFile data_{MaintainSortedFold
850 IdPairScratchFile index_{MaintainSortedFold
852 std::string directory_;
853 std::uint64_t data_bytes_ = 0;
854 std::uint64_t slots_ = 0;
855 std::uint64_t used_ = 0;
856 std::size_t size_ = 0;
857 std::unique_ptr<MemoryMap> memory_;
858 std::shared_ptr<ScratchSlab> slab_;
861 std::uint64_t key_limit_ = 0;
862 std::uint64_t fixed_slots_ = 0;
863 std::optional<std::uint64_t> fixture_hash_;
864 std::conditional_t<MaintainSortedFold, FoldState, NoFold> fold_;