Skip to main content

erigon_seg/writer/
domain.rs

1//! High-level [`DomainWriter`]: consume sorted `(key, value)` pairs and emit the full
2//! file triple — `.kv` data, `.bt` index, and (optionally) `.kvei` existence filter.
3//!
4//! Keys must be supplied strictly increasing (sorted and unique), matching a domain
5//! `.kv`'s on-disk invariant. The `.kv` is written first; the `.bt` and `.kvei` are then
6//! built from the finished file using the same code paths verified against real erigon
7//! data.
8
9use std::path::{Path, PathBuf};
10
11use super::bt_writer::{BtOptions, build_bt_from_seg};
12use super::kvei_writer::build_kvei_from_seg;
13use super::seg_writer::SegWriter;
14use crate::error::{Error, Result};
15use crate::seg::Seg;
16
17/// Options for [`DomainWriter`].
18#[derive(Debug, Clone, Copy, Default)]
19pub struct DomainOptions {
20    /// `.bt` index layout/fanout.
21    pub bt: BtOptions,
22    /// If set, a `.kvei` bloom filter is built using this salt; if `None`, no `.kvei` is
23    /// produced.
24    pub salt: Option<u32>,
25    /// Whether to pattern-compress the `.kv` (smaller output, extra passes). Default
26    /// `false` (no-pattern fast path).
27    pub compress: bool,
28}
29
30/// Paths written by [`DomainWriter::finish`].
31#[derive(Debug, Clone)]
32pub struct DomainPaths {
33    /// The seg data file.
34    pub kv: PathBuf,
35    /// The B-tree index.
36    pub bt: PathBuf,
37    /// The existence filter, if one was built.
38    pub kvei: Option<PathBuf>,
39}
40
41/// Builds a domain file set from sorted `(key, value)` pairs.
42pub struct DomainWriter {
43    kv_path: PathBuf,
44    seg: SegWriter,
45    opts: DomainOptions,
46    last_key: Option<Vec<u8>>,
47    key_count: u64,
48}
49
50impl DomainWriter {
51    /// Create a writer that will produce `kv_path` plus sibling `.bt`/`.kvei` files.
52    pub fn create(kv_path: impl AsRef<Path>, opts: DomainOptions) -> Result<DomainWriter> {
53        let kv_path = kv_path.as_ref().to_path_buf();
54        let seg = SegWriter::create_with(&kv_path, opts.compress)?;
55        Ok(DomainWriter {
56            kv_path,
57            seg,
58            opts,
59            last_key: None,
60            key_count: 0,
61        })
62    }
63
64    /// Append one `(key, value)` pair. Keys must be strictly increasing.
65    pub fn add(&mut self, key: &[u8], value: &[u8]) -> Result<()> {
66        if let Some(last) = &self.last_key
67            && key <= last.as_slice()
68        {
69            return Err(Error::format(format!(
70                "DomainWriter: keys must be strictly increasing (got {:02x?} after {:02x?})",
71                key, last
72            )));
73        }
74        self.seg.add_word(key)?;
75        self.seg.add_word(value)?;
76        self.last_key = Some(key.to_vec());
77        self.key_count += 1;
78        Ok(())
79    }
80
81    /// Number of keys added so far.
82    pub fn key_count(&self) -> u64 {
83        self.key_count
84    }
85
86    /// Finalize: write the `.kv`, then build the `.bt` and (if a salt was given) `.kvei`.
87    pub fn finish(self) -> Result<DomainPaths> {
88        let kv_path = self.kv_path;
89        let bt_path = kv_path.with_extension("bt");
90        let opts = self.opts;
91
92        self.seg.finish()?;
93        let seg = Seg::open(&kv_path)?;
94
95        build_bt_from_seg(&seg, &bt_path, opts.bt)?;
96
97        let kvei = match opts.salt {
98            Some(salt) => {
99                let kvei_path = kv_path.with_extension("kvei");
100                build_kvei_from_seg(&seg, salt, &kvei_path)?;
101                Some(kvei_path)
102            }
103            None => None,
104        };
105
106        Ok(DomainPaths {
107            kv: kv_path,
108            bt: bt_path,
109            kvei,
110        })
111    }
112}