1use super::boundary::BoundaryDetector;
7use super::cid::Cid;
8use super::config::Config;
9use super::encoding::INIT_LEVEL;
10use super::engine::ProllyEngine;
11use super::error::Error;
12use super::node::Node;
13use super::store::{AsyncStore, NodePublication, PublicationOrigin, Store};
14use super::tree::Tree;
15
16use rayon::prelude::*;
17
18mod size;
19pub(crate) use size::EncodedNodeSizer;
20mod streaming;
21pub(crate) use streaming::{EmittedNode, HierarchicalEmitter, LevelEmitter};
22
23pub(crate) const SORTED_BUILDER_NODE_BATCH: usize = 4_096;
24const PARALLEL_BOUNDARY_HASH_MIN_ENTRIES: usize = 1_024;
25
26#[derive(Debug)]
27struct BuiltNode {
28 cid: Cid,
29 first_key: Vec<u8>,
30 count: u64,
31 bytes: Vec<u8>,
32}
33
34pub(crate) struct DeferredNode {
35 pub(crate) cid: Cid,
36 pub(crate) bytes: Vec<u8>,
37 pub(crate) node: Node,
38}
39
40#[derive(Clone, Debug)]
41pub(crate) struct NodeSummary {
42 pub(crate) cid: Cid,
43 pub(crate) first_key: Vec<u8>,
44 pub(crate) count: u64,
45}
46
47pub struct BatchBuilder<S: Store> {
69 store: S,
70 config: Config,
71 origin: PublicationOrigin,
72 entries: Vec<(Vec<u8>, Vec<u8>)>,
74}
75
76pub struct SortedBatchBuilder<S: Store> {
83 store: S,
84 config: Config,
85 origin: PublicationOrigin,
86 pending_entry: Option<(Vec<u8>, Vec<u8>)>,
87 pending_nodes: Vec<BuiltNode>,
88 hierarchy: HierarchicalEmitter,
89}
90
91pub struct AsyncBatchBuilder<S: AsyncStore> {
97 engine: ProllyEngine<S>,
98 origin: PublicationOrigin,
99 entries: Vec<(Vec<u8>, Vec<u8>)>,
100}
101
102pub struct AsyncSortedBatchBuilder<S: AsyncStore> {
107 engine: ProllyEngine<S>,
108 origin: PublicationOrigin,
109 pending_entry: Option<(Vec<u8>, Vec<u8>)>,
110 pending_nodes: Vec<DeferredNode>,
111 hierarchy: HierarchicalEmitter,
112}
113
114impl<S> AsyncBatchBuilder<S>
115where
116 S: AsyncStore,
117 S::Error: Send + Sync,
118{
119 pub fn new(store: S, config: Config) -> Self {
121 Self::new_with_origin(store, config, PublicationOrigin::TreeBuild)
122 }
123
124 pub(crate) fn new_with_origin(store: S, config: Config, origin: PublicationOrigin) -> Self {
125 Self {
126 engine: ProllyEngine::new(store, config),
127 origin,
128 entries: Vec::new(),
129 }
130 }
131
132 pub fn add(&mut self, key: Vec<u8>, val: Vec<u8>) {
134 self.entries.push((key, val));
135 }
136
137 pub async fn build(self) -> Result<Tree, Error> {
139 self.engine
140 .build_from_entries_with_origin(self.entries, self.origin)
141 .await
142 }
143}
144
145impl<S> AsyncSortedBatchBuilder<S>
146where
147 S: AsyncStore,
148 S::Error: Send + Sync,
149{
150 pub fn new(store: S, config: Config) -> Self {
152 Self::new_with_origin(store, config, PublicationOrigin::TreeBuild)
153 }
154
155 pub(crate) fn new_with_origin(store: S, config: Config, origin: PublicationOrigin) -> Self {
156 let hierarchy = HierarchicalEmitter::new(config.clone())
157 .expect("configuration contains a registered persisted tree format");
158 Self {
159 engine: ProllyEngine::new(store, config),
160 origin,
161 pending_entry: None,
162 pending_nodes: Vec::new(),
163 hierarchy,
164 }
165 }
166
167 pub async fn add(&mut self, key: Vec<u8>, val: Vec<u8>) -> Result<(), Error> {
172 if let Some((previous, previous_value)) = &mut self.pending_entry {
173 if key < *previous {
174 return Err(Error::UnsortedInput {
175 previous: previous.clone(),
176 next: key,
177 });
178 }
179 if key == *previous {
180 *previous_value = val;
181 return Ok(());
182 }
183 }
184
185 self.flush_pending_entry().await?;
186 self.pending_entry = Some((key, val));
187 Ok(())
188 }
189
190 async fn flush_pending_entry(&mut self) -> Result<(), Error> {
191 let Some((key, val)) = self.pending_entry.take() else {
192 return Ok(());
193 };
194 let emitted = self.hierarchy.push_leaf(key, val)?;
195 self.collect_emitted(emitted).await
196 }
197
198 async fn collect_emitted(&mut self, emitted: Vec<EmittedNode>) -> Result<(), Error> {
199 self.pending_nodes
200 .extend(emitted.into_iter().map(|emitted| DeferredNode {
201 cid: emitted.summary.cid,
202 bytes: emitted.bytes,
203 node: emitted.node,
204 }));
205 if self.pending_nodes.len() >= SORTED_BUILDER_NODE_BATCH {
206 self.flush_pending_nodes().await?;
207 }
208 Ok(())
209 }
210
211 async fn flush_pending_nodes(&mut self) -> Result<(), Error> {
212 self.engine
213 .publish_builder_nodes(&self.pending_nodes, self.origin)
214 .await?;
215 self.pending_nodes.clear();
216 Ok(())
217 }
218
219 pub async fn build(mut self) -> Result<Tree, Error> {
221 self.flush_pending_entry().await?;
222 let (root, emitted) = self.hierarchy.finish()?;
223 self.collect_emitted(emitted).await?;
224 self.flush_pending_nodes().await?;
225 Ok(Tree {
226 root: root.map(|summary| summary.cid),
227 config: self.engine.config().clone(),
228 })
229 }
230}
231
232impl<S: Store + Clone + Send + Sync> BatchBuilder<S>
233where
234 S::Error: Send + Sync,
235{
236 pub fn new(store: S, config: Config) -> Self {
243 Self::new_with_origin(store, config, PublicationOrigin::TreeBuild)
244 }
245
246 pub(crate) fn new_with_origin(store: S, config: Config, origin: PublicationOrigin) -> Self {
247 Self {
248 store,
249 config,
250 origin,
251 entries: Vec::new(),
252 }
253 }
254
255 pub fn add(&mut self, key: Vec<u8>, val: Vec<u8>) {
264 self.entries.push((key, val));
265 }
266
267 pub fn build(mut self) -> Result<Tree, Error> {
280 if self.entries.is_empty() {
282 return Ok(Tree {
283 root: None,
284 config: self.config,
285 });
286 }
287
288 self.entries.sort_by(|a, b| a.0.cmp(&b.0));
290 let mut unique = Vec::with_capacity(self.entries.len());
291 for (key, value) in self.entries {
292 if unique
293 .last()
294 .is_some_and(|(previous, _): &(Vec<u8>, Vec<u8>)| previous == &key)
295 {
296 unique.last_mut().expect("duplicate has a predecessor").1 = value;
297 } else {
298 unique.push((key, value));
299 }
300 }
301 self.entries = unique;
302
303 let chunks = self.parallel_chunk(&self.entries)?;
305
306 self.build_from_chunks(chunks)
308 }
309
310 fn parallel_chunk(&self, entries: &[(Vec<u8>, Vec<u8>)]) -> Result<Vec<NodeSummary>, Error> {
322 if entries.is_empty() {
323 return Ok(vec![]);
324 }
325
326 let chunk_ranges =
327 chunk_ranges_for_entries_parallel(&self.config, INIT_LEVEL, entries, None)?;
328
329 let config = &self.config;
331
332 let nodes: Vec<BuiltNode> = chunk_ranges
333 .par_iter()
334 .map(|range| {
335 let mut node = new_builder_node(config, true, INIT_LEVEL);
336 reserve_node_entries(&mut node, *range.end() - *range.start() + 1);
337
338 for i in range.clone() {
339 node.keys.push(entries[i].0.clone());
340 node.vals.push(entries[i].1.clone());
341 }
342
343 let first_key = node.keys.first().cloned().unwrap_or_default();
344 let bytes = node.to_bytes();
345 let cid = Cid::from_bytes(&bytes);
346 BuiltNode {
347 cid,
348 first_key,
349 count: (range.end() - range.start() + 1) as u64,
350 bytes,
351 }
352 })
353 .collect();
354
355 self.persist_nodes(&nodes)?;
356 Ok(nodes
357 .into_iter()
358 .map(|node| NodeSummary {
359 cid: node.cid,
360 first_key: node.first_key,
361 count: node.count,
362 })
363 .collect())
364 }
365
366 pub(crate) fn build_from_chunks(
379 &self,
380 mut level_nodes: Vec<NodeSummary>,
381 ) -> Result<Tree, Error> {
382 if level_nodes.is_empty() {
384 return Ok(Tree {
385 root: None,
386 config: self.config.clone(),
387 });
388 }
389
390 if level_nodes.len() == 1 {
392 return Ok(Tree {
393 root: Some(level_nodes.remove(0).cid),
394 config: self.config.clone(),
395 });
396 }
397
398 let mut level = INIT_LEVEL;
399
400 while level_nodes.len() > 1 {
402 level += 1;
403 level_nodes = self.build_level(level_nodes, level)?;
404 }
405
406 Ok(Tree {
407 root: level_nodes.into_iter().next().map(|node| node.cid),
408 config: self.config.clone(),
409 })
410 }
411
412 #[cfg(test)]
418 pub(crate) fn build_from_chunks_serial(
419 &self,
420 level_nodes: Vec<NodeSummary>,
421 ) -> Result<(Tree, usize, usize), Error> {
422 let (tree, pending) = self.build_from_chunks_serial_deferred(level_nodes)?;
423 let written_nodes = pending.len();
424 let written_bytes = pending.iter().map(|node| node.bytes.len()).sum();
425 let entries = pending
426 .iter()
427 .map(|node| (node.cid.as_bytes(), node.bytes.as_slice()))
428 .collect::<Vec<_>>();
429 self.store
430 .publish_nodes(NodePublication::new(&entries, self.origin))
431 .map_err(|error| Error::Store(Box::new(error)))?;
432 Ok((tree, written_nodes, written_bytes))
433 }
434
435 pub(crate) fn build_from_chunks_serial_deferred(
436 &self,
437 mut level_nodes: Vec<NodeSummary>,
438 ) -> Result<(Tree, Vec<DeferredNode>), Error> {
439 if level_nodes.is_empty() {
440 return Ok((
441 Tree {
442 root: None,
443 config: self.config.clone(),
444 },
445 Vec::new(),
446 ));
447 }
448 if level_nodes.len() == 1 {
449 return Ok((
450 Tree {
451 root: Some(level_nodes.remove(0).cid),
452 config: self.config.clone(),
453 },
454 Vec::new(),
455 ));
456 }
457
458 let mut level = INIT_LEVEL;
459 let mut pending = Vec::<DeferredNode>::new();
460 while level_nodes.len() > 1 {
461 level += 1;
462 level_nodes = self.build_level_serial(level_nodes, level, &mut pending)?;
463 }
464
465 Ok((
466 Tree {
467 root: level_nodes.into_iter().next().map(|node| node.cid),
468 config: self.config.clone(),
469 },
470 pending,
471 ))
472 }
473
474 fn build_level(
487 &self,
488 children: Vec<NodeSummary>,
489 level: u8,
490 ) -> Result<Vec<NodeSummary>, Error> {
491 if children.is_empty() {
492 return Ok(vec![]);
493 }
494
495 let internal_entries = children
496 .iter()
497 .map(|child| (child.first_key.clone(), child.cid.as_bytes().to_vec()))
498 .collect::<Vec<_>>();
499 let child_counts = children.iter().map(|child| child.count).collect::<Vec<_>>();
500 let chunk_ranges = chunk_ranges_for_entries_parallel(
501 &self.config,
502 level,
503 &internal_entries,
504 Some(&child_counts),
505 )?;
506
507 let config = &self.config;
508 let nodes: Vec<BuiltNode> = chunk_ranges
509 .par_iter()
510 .map(|range| {
511 let start = *range.start();
512 let end = *range.end();
513
514 let mut node = new_builder_node(config, false, level);
515 reserve_node_entries(&mut node, end - start + 1);
516
517 for child in children.iter().take(end + 1).skip(start) {
518 node.keys.push(child.first_key.clone());
519 node.vals.push(child.cid.0.to_vec());
520 node.child_counts.push(child.count);
521 }
522
523 let first_key = node.keys.first().cloned().unwrap_or_default();
524 let bytes = node.to_bytes();
525 let cid = Cid::from_bytes(&bytes);
526 BuiltNode {
527 cid,
528 first_key,
529 count: children[start..=end].iter().map(|child| child.count).sum(),
530 bytes,
531 }
532 })
533 .collect();
534
535 self.persist_nodes(&nodes)?;
536 Ok(nodes
537 .into_iter()
538 .map(|node| NodeSummary {
539 cid: node.cid,
540 first_key: node.first_key,
541 count: node.count,
542 })
543 .collect())
544 }
545
546 fn build_level_serial(
547 &self,
548 children: Vec<NodeSummary>,
549 level: u8,
550 pending: &mut Vec<DeferredNode>,
551 ) -> Result<Vec<NodeSummary>, Error> {
552 let mut emitter = LevelEmitter::new(self.config.clone(), false, level)?;
553 let mut summaries = Vec::new();
554 for child in children {
555 emitter.push_child_with(child, |emitted| {
556 summaries.push(emitted.summary.clone());
557 pending.push(DeferredNode {
558 cid: emitted.summary.cid,
559 bytes: emitted.bytes,
560 node: emitted.node,
561 });
562 })?;
563 }
564 if let Some(emitted) = emitter.finish()? {
565 summaries.push(emitted.summary.clone());
566 pending.push(DeferredNode {
567 cid: emitted.summary.cid,
568 bytes: emitted.bytes,
569 node: emitted.node,
570 });
571 }
572 Ok(summaries)
573 }
574
575 pub(crate) fn build_level_serial_deferred(
576 &self,
577 children: Vec<NodeSummary>,
578 level: u8,
579 ) -> Result<(Vec<NodeSummary>, Vec<DeferredNode>), Error> {
580 let mut pending = Vec::new();
581 let summaries = self.build_level_serial(children, level, &mut pending)?;
582 Ok((summaries, pending))
583 }
584
585 fn persist_nodes(&self, nodes: &[BuiltNode]) -> Result<(), Error> {
586 persist_nodes(&self.store, nodes, self.origin)
587 }
588}
589
590impl<S: Store + Clone + Send + Sync> SortedBatchBuilder<S>
591where
592 S::Error: Send + Sync,
593{
594 pub fn new(store: S, config: Config) -> Self {
596 Self::new_with_origin(store, config, PublicationOrigin::TreeBuild)
597 }
598
599 pub(crate) fn new_with_origin(store: S, config: Config, origin: PublicationOrigin) -> Self {
600 let hierarchy = HierarchicalEmitter::new(config.clone())
601 .expect("configuration contains a registered persisted tree format");
602 Self {
603 store,
604 config,
605 origin,
606 pending_entry: None,
607 pending_nodes: Vec::new(),
608 hierarchy,
609 }
610 }
611
612 pub fn add(&mut self, key: Vec<u8>, val: Vec<u8>) -> Result<(), Error> {
618 if let Some((previous, previous_value)) = &mut self.pending_entry {
619 if key < *previous {
620 return Err(Error::UnsortedInput {
621 previous: previous.clone(),
622 next: key,
623 });
624 }
625 if key == *previous {
626 *previous_value = val;
627 return Ok(());
628 }
629 }
630
631 self.flush_pending_entry()?;
632 self.pending_entry = Some((key, val));
633 Ok(())
634 }
635
636 #[cfg(test)]
639 pub(crate) fn add_unique_sorted(&mut self, key: Vec<u8>, val: Vec<u8>) -> Result<(), Error> {
640 debug_assert!(match self.pending_entry.as_ref() {
641 Some((previous, _)) => previous < &key,
642 None => true,
643 });
644 self.flush_pending_entry()?;
645 self.pending_entry = Some((key, val));
646 Ok(())
647 }
648
649 #[cfg(test)]
653 pub(crate) fn add_complete_leaf(&mut self, leaf: NodeSummary) -> Result<bool, Error> {
654 self.flush_pending_entry()?;
655 if !self.hierarchy.leaf_is_empty() {
656 return Ok(false);
657 }
658 let emitted = self.hierarchy.push_complete_leaf(leaf)?;
659 self.collect_emitted(emitted)?;
660 Ok(true)
661 }
662
663 fn flush_pending_entry(&mut self) -> Result<(), Error> {
664 let Some((key, val)) = self.pending_entry.take() else {
665 return Ok(());
666 };
667 let emitted = self.hierarchy.push_leaf(key, val)?;
668 self.collect_emitted(emitted)?;
669 Ok(())
670 }
671
672 pub fn build(mut self) -> Result<Tree, Error> {
674 self.flush_pending_entry()?;
675 let (root, emitted) = self.hierarchy.finish()?;
676 self.collect_emitted(emitted)?;
677 self.flush_pending_nodes()?;
678 Ok(Tree {
679 root: root.map(|summary| summary.cid),
680 config: self.config,
681 })
682 }
683
684 fn collect_emitted(&mut self, emitted: Vec<EmittedNode>) -> Result<(), Error> {
685 self.pending_nodes
686 .extend(emitted.into_iter().map(|emitted| BuiltNode {
687 cid: emitted.summary.cid,
688 first_key: emitted.summary.first_key,
689 count: emitted.summary.count,
690 bytes: emitted.bytes,
691 }));
692 if self.pending_nodes.len() >= SORTED_BUILDER_NODE_BATCH {
693 self.flush_pending_nodes()?;
694 }
695 Ok(())
696 }
697
698 fn flush_pending_nodes(&mut self) -> Result<(), Error> {
699 persist_nodes(&self.store, &self.pending_nodes, self.origin)?;
700 self.pending_nodes.clear();
701 Ok(())
702 }
703}
704
705fn new_builder_node(config: &Config, leaf: bool, level: u8) -> Node {
706 Node::builder()
707 .leaf(leaf)
708 .level(level)
709 .tree_format(config.format.clone())
710 .build()
711}
712fn chunk_ranges_for_entries_parallel(
713 config: &Config,
714 level: u8,
715 entries: &[(Vec<u8>, Vec<u8>)],
716 child_counts: Option<&[u64]>,
717) -> Result<Vec<std::ops::RangeInclusive<usize>>, Error> {
718 if entries.len() < PARALLEL_BOUNDARY_HASH_MIN_ENTRIES {
719 return chunk_ranges_for_entries_impl(config, level, entries, child_counts, false);
720 }
721 chunk_ranges_for_entries_impl(config, level, entries, child_counts, true)
722}
723
724fn chunk_ranges_for_entries_impl(
725 config: &Config,
726 level: u8,
727 entries: &[(Vec<u8>, Vec<u8>)],
728 child_counts: Option<&[u64]>,
729 parallel_hashing: bool,
730) -> Result<Vec<std::ops::RangeInclusive<usize>>, Error> {
731 if level == 0 {
732 if child_counts.is_some() {
733 return Err(Error::InvalidNode);
734 }
735 } else if child_counts.map_or(true, |counts| counts.len() != entries.len()) {
736 return Err(Error::InvalidNode);
737 }
738 let mut detector = BoundaryDetector::new(config.format.chunking.clone(), level.into())?;
739 if parallel_hashing && detector.supports_independent_hashing() {
740 let boundaries_and_upper_bytes = entries
741 .par_iter()
742 .map(|(key, value)| {
743 (
744 detector
745 .independent_hash_boundary(key, value)
746 .expect("independent boundary support was checked"),
747 (key.len() as u64)
748 .saturating_add(value.len() as u64)
749 .saturating_add(64),
750 )
751 })
752 .collect::<Vec<_>>();
753 let max_upper_bytes = boundaries_and_upper_bytes
754 .iter()
755 .map(|(_, bytes)| *bytes)
756 .max()
757 .unwrap_or(0);
758 let spec = &config.format.chunking;
759 let empty_size = EncodedNodeSizer::new(config.format.clone(), level == 0, level)?.size();
760 if empty_size.saturating_add(max_upper_bytes.saturating_mul(spec.max))
761 <= spec.hard_max_node_bytes
762 {
763 return Ok(chunk_ranges_from_independent_counts(
764 spec,
765 &boundaries_and_upper_bytes,
766 ));
767 }
768 let hash_boundaries = boundaries_and_upper_bytes
769 .into_iter()
770 .map(|(boundary, _)| boundary)
771 .collect::<Vec<_>>();
772 return chunk_ranges_from_independent_hashes(
773 config,
774 level,
775 entries,
776 child_counts,
777 &hash_boundaries,
778 );
779 }
780
781 let mut ranges = Vec::new();
782 let mut start = 0;
783 let mut sizer = EncodedNodeSizer::new(config.format.clone(), level == 0, level)?;
784 for (index, (key, value)) in entries.iter().enumerate() {
785 let child_count = child_counts.map(|counts| counts[index]);
786 let previous_key = (index > start).then(|| entries[index - 1].0.as_slice());
787 let mut encoded_size = sizer.size_after(previous_key, key, value, child_count)?;
788 if index > start && encoded_size > config.format.chunking.hard_max_node_bytes {
789 ranges.push(start..=(index - 1));
790 start = index;
791 detector.reset();
792 sizer.reset();
793 encoded_size = sizer.size_after(None, key, value, child_count)?;
794 }
795 if encoded_size > config.format.chunking.hard_max_node_bytes {
796 return Err(Error::EntryTooLarge {
797 encoded_bytes: encoded_size,
798 limit: config.format.chunking.hard_max_node_bytes,
799 });
800 }
801 let encoded_entry_bytes = encoded_size.saturating_sub(sizer.size());
802 sizer.push_sized(key, value, child_count, encoded_size)?;
803 if detector.observe(key, value, encoded_entry_bytes as usize)? {
804 ranges.push(start..=index);
805 start = index + 1;
806 sizer.reset();
807 }
808 }
809 if start < entries.len() {
810 ranges.push(start..=(entries.len() - 1));
811 }
812 Ok(ranges)
813}
814
815fn chunk_ranges_from_independent_counts(
816 spec: &super::format::ChunkingSpec,
817 boundaries_and_upper_bytes: &[(bool, u64)],
818) -> Vec<std::ops::RangeInclusive<usize>> {
819 let mut ranges = Vec::new();
820 let mut start = 0;
821 for (index, (hash_boundary, _)) in boundaries_and_upper_bytes.iter().enumerate() {
822 let count = (index - start + 1) as u64;
823 if count >= spec.max || (count >= spec.min && *hash_boundary) {
824 ranges.push(start..=index);
825 start = index + 1;
826 }
827 }
828 if start < boundaries_and_upper_bytes.len() {
829 ranges.push(start..=(boundaries_and_upper_bytes.len() - 1));
830 }
831 ranges
832}
833
834fn chunk_ranges_from_independent_hashes(
835 config: &Config,
836 level: u8,
837 entries: &[(Vec<u8>, Vec<u8>)],
838 child_counts: Option<&[u64]>,
839 hash_boundaries: &[bool],
840) -> Result<Vec<std::ops::RangeInclusive<usize>>, Error> {
841 debug_assert_eq!(entries.len(), hash_boundaries.len());
842 let spec = &config.format.chunking;
843 let mut ranges = Vec::new();
844 let mut start = 0;
845 let mut sizer = EncodedNodeSizer::new(config.format.clone(), level == 0, level)?;
846
847 for (index, ((key, value), hash_boundary)) in entries.iter().zip(hash_boundaries).enumerate() {
848 let child_count = child_counts.map(|counts| counts[index]);
849 let previous_key = (index > start).then(|| entries[index - 1].0.as_slice());
850 let mut encoded_size = sizer.size_after(previous_key, key, value, child_count)?;
851 if index > start && encoded_size > spec.hard_max_node_bytes {
852 ranges.push(start..=(index - 1));
853 start = index;
854 sizer.reset();
855 encoded_size = sizer.size_after(None, key, value, child_count)?;
856 }
857 if encoded_size > spec.hard_max_node_bytes {
858 return Err(Error::EntryTooLarge {
859 encoded_bytes: encoded_size,
860 limit: spec.hard_max_node_bytes,
861 });
862 }
863 sizer.push_sized(key, value, child_count, encoded_size)?;
864 let count = (index - start + 1) as u64;
865 let boundary = encoded_size >= spec.hard_max_node_bytes
866 || count >= spec.max
867 || (count >= spec.min && *hash_boundary);
868 if boundary {
869 ranges.push(start..=index);
870 start = index + 1;
871 sizer.reset();
872 }
873 }
874 if start < entries.len() {
875 ranges.push(start..=(entries.len() - 1));
876 }
877 Ok(ranges)
878}
879
880fn reserve_node_entries(node: &mut Node, additional: usize) {
881 node.keys.reserve_exact(additional);
882 node.vals.reserve_exact(additional);
883}
884
885fn persist_nodes<S: Store + Clone>(
886 store: &S,
887 nodes: &[BuiltNode],
888 origin: PublicationOrigin,
889) -> Result<(), Error>
890where
891 S::Error: Send + Sync,
892{
893 if nodes.is_empty() {
894 return Ok(());
895 }
896
897 let entries = nodes
901 .iter()
902 .map(|node| (node.cid.as_bytes(), node.bytes.as_slice()))
903 .collect::<Vec<_>>();
904 store
905 .publish_nodes(NodePublication::new(&entries, origin))
906 .map_err(|error| Error::Store(Box::new(error)))
907}
908
909#[cfg(test)]
910mod tests {
911 use super::*;
912 use crate::prolly::store::BatchOp;
913 use crate::MemStore;
914 use std::collections::BTreeMap;
915 use std::sync::{Arc, Mutex};
916
917 #[test]
918 fn exact_incremental_sizer_matches_serialized_nodes() {
919 for layout in [
920 super::super::format::NodeLayoutSpec::PrefixCompressed,
921 super::super::format::NodeLayoutSpec::Plain,
922 super::super::format::NodeLayoutSpec::OffsetTable,
923 ] {
924 for leaf in [true, false] {
925 let format = super::super::format::TreeFormat {
926 node_layout: layout.clone(),
927 ..Default::default()
928 };
929 let level = if leaf { 0 } else { 1 };
930 let mut sizer = EncodedNodeSizer::new(format.clone(), leaf, level).unwrap();
931 let mut node = Node::builder()
932 .leaf(leaf)
933 .level(level)
934 .tree_format(format)
935 .build();
936
937 assert_eq!(sizer.size(), node.encoded_len() as u64);
938 for index in 0..300_u64 {
939 let key = format!("shared-prefix-{index:020}").into_bytes();
940 let value_len = match index {
941 126 => 127,
942 127 => 128,
943 198 => 16_383,
944 199 => 16_384,
945 _ => (index as usize % 41) + 1,
946 };
947 let value = vec![index as u8; value_len];
948 let child_count = (!leaf).then_some(match index {
949 126 => 127,
950 127 => 128,
951 198 => 16_383,
952 199 => 16_384,
953 _ => index + 1,
954 });
955
956 let previous_key = node.keys.last().map(Vec::as_slice);
957 let predicted = sizer
958 .size_after(previous_key, &key, &value, child_count)
959 .unwrap();
960 sizer.push(previous_key, &key, &value, child_count).unwrap();
961 node.keys.push(key);
962 node.vals.push(value);
963 if let Some(count) = child_count {
964 node.child_counts.push(count);
965 }
966
967 assert_eq!(predicted, sizer.size());
968 assert_eq!(
969 sizer.size(),
970 node.encoded_len() as u64,
971 "layout={layout:?} leaf={leaf} index={index}"
972 );
973 }
974
975 for index in 300..16_385_u64 {
976 let key = format!("shared-prefix-{index:020}").into_bytes();
977 let value = *b"x";
978 let child_count = (!leaf).then_some(index + 1);
979 sizer
980 .push(
981 node.keys.last().map(Vec::as_slice),
982 &key,
983 &value,
984 child_count,
985 )
986 .unwrap();
987 node.keys.push(key);
988 node.vals.push(value.to_vec());
989 if let Some(count) = child_count {
990 node.child_counts.push(count);
991 }
992 if matches!(index, 16_382..=16_384) {
993 assert_eq!(
994 sizer.size(),
995 node.encoded_len() as u64,
996 "count varint transition layout={layout:?} leaf={leaf} index={index}"
997 );
998 }
999 }
1000
1001 sizer.reset();
1002 assert_eq!(
1003 sizer.size(),
1004 Node::builder()
1005 .leaf(leaf)
1006 .level(level)
1007 .tree_format(node.format.clone())
1008 .build()
1009 .encoded_len() as u64
1010 );
1011 }
1012 }
1013 }
1014
1015 #[derive(Clone, Default)]
1016 struct CountingStore {
1017 inner: Arc<Mutex<CountingStoreInner>>,
1018 }
1019
1020 #[derive(Default)]
1021 struct CountingStoreInner {
1022 data: BTreeMap<Vec<u8>, Vec<u8>>,
1023 get_calls: usize,
1024 put_calls: usize,
1025 batch_put_calls: usize,
1026 }
1027
1028 #[derive(Debug)]
1029 struct CountingStoreError(String);
1030
1031 impl std::fmt::Display for CountingStoreError {
1032 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1033 write!(f, "CountingStore error: {}", self.0)
1034 }
1035 }
1036
1037 impl std::error::Error for CountingStoreError {}
1038
1039 impl Store for CountingStore {
1040 type Error = CountingStoreError;
1041
1042 fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>, Self::Error> {
1043 let mut inner = self
1044 .inner
1045 .lock()
1046 .map_err(|e| CountingStoreError(format!("lock poisoned: {}", e)))?;
1047 inner.get_calls += 1;
1048 Ok(inner.data.get(key).cloned())
1049 }
1050
1051 fn put(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error> {
1052 let mut inner = self
1053 .inner
1054 .lock()
1055 .map_err(|e| CountingStoreError(format!("lock poisoned: {}", e)))?;
1056 inner.put_calls += 1;
1057 inner.data.insert(key.to_vec(), value.to_vec());
1058 Ok(())
1059 }
1060
1061 fn delete(&self, key: &[u8]) -> Result<(), Self::Error> {
1062 let mut inner = self
1063 .inner
1064 .lock()
1065 .map_err(|e| CountingStoreError(format!("lock poisoned: {}", e)))?;
1066 inner.data.remove(key);
1067 Ok(())
1068 }
1069
1070 fn batch(&self, ops: &[BatchOp]) -> Result<(), Self::Error> {
1071 let mut inner = self
1072 .inner
1073 .lock()
1074 .map_err(|e| CountingStoreError(format!("lock poisoned: {}", e)))?;
1075 for op in ops {
1076 match op {
1077 BatchOp::Upsert { key, value } => {
1078 inner.data.insert(key.to_vec(), value.to_vec());
1079 }
1080 BatchOp::Delete { key } => {
1081 inner.data.remove(*key);
1082 }
1083 }
1084 }
1085 Ok(())
1086 }
1087
1088 fn batch_put(&self, entries: &[(&[u8], &[u8])]) -> Result<(), Self::Error> {
1089 let mut inner = self
1090 .inner
1091 .lock()
1092 .map_err(|e| CountingStoreError(format!("lock poisoned: {}", e)))?;
1093 inner.batch_put_calls += 1;
1094 for (key, value) in entries {
1095 inner.data.insert(key.to_vec(), value.to_vec());
1096 }
1097 Ok(())
1098 }
1099 }
1100
1101 #[test]
1102 fn batch_builder_persists_levels_with_batched_writes_without_readback() {
1103 let store = CountingStore::default();
1104 let config = Config::builder()
1105 .min_chunk_size(2)
1106 .max_chunk_size(4)
1107 .chunking_factor(2)
1108 .build();
1109 let mut builder = BatchBuilder::new(store.clone(), config);
1110
1111 for i in 0..64 {
1112 builder.add(
1113 format!("k{i:03}").into_bytes(),
1114 format!("v{i:03}").into_bytes(),
1115 );
1116 }
1117
1118 let tree = builder.build().unwrap();
1119 assert!(tree.root.is_some());
1120
1121 let inner = store.inner.lock().unwrap();
1122 assert_eq!(inner.get_calls, 0);
1123 assert_eq!(inner.put_calls, 0);
1124 assert!(inner.batch_put_calls > 1);
1125 }
1126
1127 #[test]
1128 fn batch_builder_applies_max_chunk_size_to_current_chunk_not_global_index() {
1129 let store = CountingStore::default();
1130 let config = Config::builder()
1131 .min_chunk_size(4)
1132 .max_chunk_size(4)
1133 .chunking_factor(2)
1134 .build();
1135 let mut builder = BatchBuilder::new(store.clone(), config);
1136
1137 for i in 0..64 {
1138 builder.add(
1139 format!("k{i:03}").into_bytes(),
1140 format!("v{i:03}").into_bytes(),
1141 );
1142 }
1143
1144 let tree = builder.build().unwrap();
1145 assert!(tree.root.is_some());
1146
1147 let inner = store.inner.lock().unwrap();
1148 let mut leaf_lengths = Vec::new();
1149 let mut level_one_lengths = Vec::new();
1150 let mut root_length = None;
1151
1152 for bytes in inner.data.values() {
1153 let node = Node::from_bytes(bytes).unwrap();
1154 if node.leaf {
1155 leaf_lengths.push(node.len());
1156 } else if node.level == 1 {
1157 level_one_lengths.push(node.len());
1158 }
1159
1160 if Some(Cid::from_bytes(bytes)) == tree.root {
1161 root_length = Some(node.len());
1162 }
1163 }
1164
1165 leaf_lengths.sort_unstable();
1166 level_one_lengths.sort_unstable();
1167
1168 assert_eq!(leaf_lengths, vec![4; 16]);
1169 assert_eq!(level_one_lengths, vec![4; 4]);
1170 assert_eq!(root_length, Some(4));
1171 }
1172
1173 #[test]
1174 fn builder_node_entry_reservation_preserves_node_shape() {
1175 let config = Config::default();
1176 let mut node = new_builder_node(&config, true, INIT_LEVEL);
1177
1178 reserve_node_entries(&mut node, 17);
1179
1180 assert!(node.keys.capacity() >= 17);
1181 assert!(node.vals.capacity() >= 17);
1182 assert!(node.keys.is_empty());
1183 assert!(node.vals.is_empty());
1184 assert!(node.leaf);
1185 assert_eq!(node.level, INIT_LEVEL);
1186 }
1187
1188 #[test]
1189 fn batch_builder_parallel_internal_level_preserves_child_order() {
1190 let store = CountingStore::default();
1191 let config = Config::builder()
1192 .min_chunk_size(4)
1193 .max_chunk_size(4)
1194 .chunking_factor(u32::MAX)
1195 .build();
1196 let builder = BatchBuilder::new(store.clone(), config);
1197 let children = (0..16)
1198 .map(|idx| NodeSummary {
1199 cid: Cid::from_bytes(format!("child-{idx:03}").as_bytes()),
1200 first_key: format!("k{idx:03}").into_bytes(),
1201 count: 1,
1202 })
1203 .collect::<Vec<_>>();
1204
1205 let level = builder.build_level(children, 1).unwrap();
1206
1207 assert_eq!(level.len(), 4);
1208 let inner = store.inner.lock().unwrap();
1209 for (group_idx, summary) in level.iter().enumerate() {
1210 assert_eq!(
1211 summary.first_key,
1212 format!("k{:03}", group_idx * 4).into_bytes()
1213 );
1214 let bytes = inner.data.get(summary.cid.as_bytes()).unwrap();
1215 let node = Node::from_bytes(bytes).unwrap();
1216 let expected_keys = (group_idx * 4..group_idx * 4 + 4)
1217 .map(|idx| format!("k{idx:03}").into_bytes())
1218 .collect::<Vec<_>>();
1219 assert_eq!(node.keys, expected_keys);
1220 assert_eq!(node.vals.len(), 4);
1221 }
1222 }
1223
1224 #[test]
1225 fn serial_internal_build_matches_parallel_root() {
1226 let config = Config::builder()
1227 .min_chunk_size(4)
1228 .max_chunk_size(16)
1229 .chunking_factor(8)
1230 .build();
1231 let parallel_store = CountingStore::default();
1232 let serial_store = CountingStore::default();
1233 let children = (0..257)
1234 .map(|idx| NodeSummary {
1235 cid: Cid::from_bytes(format!("child-{idx:03}").as_bytes()),
1236 first_key: format!("k{idx:03}").into_bytes(),
1237 count: 1,
1238 })
1239 .collect::<Vec<_>>();
1240
1241 let parallel = BatchBuilder::new(parallel_store, config.clone())
1242 .build_from_chunks(children.clone())
1243 .unwrap();
1244 let (serial, written_nodes, written_bytes) =
1245 BatchBuilder::new(serial_store.clone(), config)
1246 .build_from_chunks_serial(children)
1247 .unwrap();
1248
1249 assert_eq!(serial.root, parallel.root);
1250 assert!(written_nodes > 1);
1251 assert!(written_bytes > 0);
1252 assert_eq!(serial_store.inner.lock().unwrap().batch_put_calls, 1);
1253 }
1254
1255 #[test]
1256 fn sorted_batch_builder_matches_batch_builder_for_sorted_entries() {
1257 let config = Config::builder()
1258 .min_chunk_size(4)
1259 .max_chunk_size(16)
1260 .chunking_factor(8)
1261 .build();
1262 let batch_store = CountingStore::default();
1263 let sorted_store = CountingStore::default();
1264 let mut batch = BatchBuilder::new(batch_store, config.clone());
1265 let mut sorted = SortedBatchBuilder::new(sorted_store, config);
1266
1267 for i in 0..257 {
1268 let key = format!("k{i:04}").into_bytes();
1269 let val = format!("value-{i:04}").into_bytes();
1270 batch.add(key.clone(), val.clone());
1271 sorted.add(key, val).unwrap();
1272 }
1273
1274 let batch_tree = batch.build().unwrap();
1275 let sorted_tree = sorted.build().unwrap();
1276
1277 assert_eq!(batch_tree.root, sorted_tree.root);
1278 }
1279
1280 #[test]
1281 fn builders_coalesce_duplicate_keys_with_last_value_winning() {
1282 let config = Config::builder()
1283 .min_chunk_size(2)
1284 .max_chunk_size(8)
1285 .chunking_factor(4)
1286 .build();
1287 let entries = vec![
1288 (b"a".to_vec(), b"old".to_vec()),
1289 (b"a".to_vec(), b"new".to_vec()),
1290 (b"b".to_vec(), b"value".to_vec()),
1291 ];
1292
1293 let batch_store = Arc::new(MemStore::new());
1294 let mut batch = BatchBuilder::new(batch_store, config.clone());
1295 for (key, value) in entries.clone() {
1296 batch.add(key, value);
1297 }
1298 let batch_tree = batch.build().unwrap();
1299
1300 let sorted_store = Arc::new(MemStore::new());
1301 let mut sorted = SortedBatchBuilder::new(sorted_store, config.clone());
1302 for (key, value) in entries {
1303 sorted.add(key, value).unwrap();
1304 }
1305 let sorted_tree = sorted.build().unwrap();
1306
1307 let unique_store = Arc::new(MemStore::new());
1308 let mut unique = BatchBuilder::new(unique_store, config);
1309 unique.add(b"a".to_vec(), b"new".to_vec());
1310 unique.add(b"b".to_vec(), b"value".to_vec());
1311 let unique_tree = unique.build().unwrap();
1312
1313 assert_eq!(batch_tree.root, unique_tree.root);
1314 assert_eq!(sorted_tree.root, unique_tree.root);
1315 }
1316
1317 #[test]
1318 fn sorted_batch_builder_rejects_out_of_order_keys() {
1319 let store = CountingStore::default();
1320 let config = Config::default();
1321 let mut builder = SortedBatchBuilder::new(store, config);
1322
1323 builder.add(b"b".to_vec(), b"1".to_vec()).unwrap();
1324 let err = builder.add(b"a".to_vec(), b"2".to_vec()).unwrap_err();
1325
1326 assert!(matches!(err, Error::UnsortedInput { .. }));
1327 }
1328
1329 #[test]
1330 fn sorted_builder_keeps_only_height_bounded_hierarchy_state() {
1331 let store = CountingStore::default();
1332 let config = Config::builder()
1333 .min_chunk_size(8)
1334 .max_chunk_size(16)
1335 .chunking_factor(16)
1336 .build();
1337 let mut builder = SortedBatchBuilder::new(store, config);
1338 let mut max_levels = 0;
1339 let mut max_buffered_entries = 0;
1340
1341 for index in 0..100_000 {
1342 builder
1343 .add(
1344 format!("key-{index:08}").into_bytes(),
1345 format!("value-{index:08}").into_bytes(),
1346 )
1347 .unwrap();
1348 max_levels = max_levels.max(builder.hierarchy.active_levels());
1349 max_buffered_entries = max_buffered_entries.max(builder.hierarchy.buffered_entries());
1350 }
1351
1352 let tree = builder.build().unwrap();
1353 assert!(tree.root.is_some());
1354 assert!(max_levels <= 8, "active levels={max_levels}");
1355 assert!(
1356 max_buffered_entries <= max_levels * 16,
1357 "buffered entries={max_buffered_entries}, active levels={max_levels}"
1358 );
1359 }
1360
1361 #[test]
1362 fn sorted_builder_reuses_cascade_scratch_allocation() {
1363 let store = CountingStore::default();
1364 let config = Config::builder()
1365 .min_chunk_size(2)
1366 .max_chunk_size(4)
1367 .chunking_factor(4)
1368 .build();
1369 let mut builder = SortedBatchBuilder::new(store, config);
1370
1371 for index in 0..32 {
1372 builder
1373 .add(
1374 format!("key-{index:08}").into_bytes(),
1375 format!("value-{index:08}").into_bytes(),
1376 )
1377 .unwrap();
1378 }
1379 let allocation = builder.hierarchy.cascade_scratch_allocation();
1380 assert!(allocation.0 > 0);
1381
1382 for index in 32..10_000 {
1383 builder
1384 .add(
1385 format!("key-{index:08}").into_bytes(),
1386 format!("value-{index:08}").into_bytes(),
1387 )
1388 .unwrap();
1389 assert_eq!(builder.hierarchy.cascade_scratch_allocation(), allocation);
1390 }
1391 }
1392}