use super::{partitions_writer, PromotedIndexBlock, SSTableWriter};
use crate::error::{Error, Result};
use crate::storage::write_engine::mutation::{DecoratedKey, PartitionTombstone};
use std::path::PathBuf;
#[derive(Debug)]
pub(super) struct PendingBtiPartition {
pub(super) raw_key: Vec<u8>,
pub(super) data_offset: u64,
pub(super) row_index: Option<PendingRowIndex>,
}
#[derive(Debug)]
pub(super) struct PendingRowIndex {
pub(super) blocks: Vec<partitions_writer::RowIndexBlock>,
pub(super) partition_deletion: Option<(i32, i64)>,
}
impl SSTableWriter {
pub(super) fn queue_bti_partition(
&mut self,
key: &DecoratedKey,
data_offset: u64,
promoted_blocks: &[PromotedIndexBlock],
partition_tombstone: Option<&PartitionTombstone>,
) {
let Some(ref mut pending) = self.bti_pending else {
return;
};
let row_index = if promoted_blocks.len() >= 2 {
let mut blocks = Vec::with_capacity(promoted_blocks.len());
let mut all_have_sep = true;
for b in promoted_blocks {
match &b.oss50_separator {
Some(sep) if !sep.is_empty() => {
blocks.push(partitions_writer::RowIndexBlock {
separator_key: sep.clone(),
block_offset: b.offset,
open_marker: None,
});
}
_ => {
all_have_sep = false;
break;
}
}
}
let strictly_ascending = blocks
.windows(2)
.all(|w| w[0].separator_key < w[1].separator_key);
if all_have_sep && strictly_ascending && !blocks.is_empty() {
let partition_deletion =
partition_tombstone.map(|pt| (pt.local_deletion_time, pt.deletion_time));
Some(PendingRowIndex {
blocks,
partition_deletion,
})
} else {
None
}
} else {
None
};
pending.push(PendingBtiPartition {
raw_key: key.key.clone(),
data_offset,
row_index,
});
}
pub(super) async fn write_bti_components(
bti_pending: Option<Vec<PendingBtiPartition>>,
partitions_trie: Option<partitions_writer::PartitionsTrieWriter>,
cpath: impl Fn(&str) -> PathBuf,
) -> Result<(Option<PathBuf>, Option<PathBuf>)> {
let Some(pending) = bti_pending else {
return Ok((None, None));
};
if pending.is_empty() {
return Err(Error::InvalidInput(
"cannot publish an empty BTI SSTable: a `da` SSTable requires a readable \
Partitions.db trie (with an 8-byte root footer), which has no valid \
zero-partition form. Write at least one partition, or use the BIG format \
for empty SSTables."
.to_string(),
));
}
let mut rows_writer = partitions_writer::RowsTrieWriter::new();
for p in &pending {
if let Some(ri) = &p.row_index {
rows_writer.add_partition_row_index(
&p.raw_key,
p.data_offset,
ri.blocks.clone(),
ri.partition_deletion,
);
}
}
let (rows_bytes, rows_offsets) = rows_writer.finish()?;
let mut trie = partitions_trie.unwrap_or_default();
let mut wide_idx = 0usize;
for p in &pending {
if p.row_index.is_some() {
let rows_offset = rows_offsets[wide_idx];
wide_idx += 1;
trie.add_partition_with_payload(
&p.raw_key,
partitions_writer::PartitionPayload::RowsOffset(rows_offset),
);
} else {
trie.add_partition_with_payload(
&p.raw_key,
partitions_writer::PartitionPayload::DataOffset(p.data_offset),
);
}
}
let partitions_bytes = trie.finish()?;
let part_path = cpath("Partitions.db");
tokio::fs::write(&part_path, partitions_bytes).await?;
Self::fsync_component(&part_path).await?;
let rows_path = cpath("Rows.db");
tokio::fs::write(&rows_path, rows_bytes).await?;
Self::fsync_component(&rows_path).await?;
Ok((Some(part_path), Some(rows_path)))
}
}