use super::TableRecord;
use crate::cache::Cache;
use crate::comparator::SharedComparator;
use crate::encryption::EncryptionProvider;
use crate::fs::Fs;
use crate::path::Path;
use crate::table::{RecoverParams, Table};
use crate::{Checksum, InternalValue, SeqNo, TableId, UserKey, UserValue};
use alloc::sync::Arc;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Section {
pub name: Vec<u8>,
pub offset: u64,
pub len: u64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BlockRef {
pub end_key: UserKey,
pub seqno: SeqNo,
pub offset: u64,
pub size: u32,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct VerifiedFrame {
pub block_type: crate::table::block::BlockType,
pub uncompressed_length: u32,
pub payload: Vec<u8>,
pub ecc_status: crate::table::block::EccStatus,
pub ecc_recovery: Option<crate::table::block::EccRecoveryKind>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[expect(
clippy::struct_excessive_bools,
reason = "each flag is an independent property the table records, not a state"
)]
pub struct TableProperties {
pub created_at: u128,
pub initial_level: u8,
pub data_compression: crate::CompressionType,
pub index_compression: crate::CompressionType,
pub data_restart_interval: u8,
pub index_restart_interval: u8,
pub kv_checksum: Option<crate::runtime_config::ChecksumAlgorithm>,
pub ecc: Option<crate::table::block::EccParams>,
pub columnar: bool,
pub bulk_ingested: Option<bool>,
pub recency: Option<TableId>,
pub lineage: Option<Vec<TableId>>,
pub lineage_prev: Option<TableId>,
pub lineage_transformed: bool,
pub lineage_last: bool,
pub partitioned_index: bool,
pub seqno_bounds: bool,
pub zone_map: bool,
pub seqnos: (SeqNo, SeqNo),
pub highest_kv_seqno: SeqNo,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RangeDelete {
pub start: UserKey,
pub end: UserKey,
pub seqno: SeqNo,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BurrKind {
Membership,
Retrieval,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BurrLayer {
pub m: u32,
pub thresholds: Vec<u8>,
pub rows: Vec<u64>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BurrSolution {
pub kind: BurrKind,
pub r: u8,
pub w: u8,
pub b: u8,
pub root_seed: u64,
pub layers: Vec<BurrLayer>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct FilterPartition {
pub entry: BlockRef,
pub solution: BurrSolution,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Filter {
Full(BurrSolution),
Partitioned(Vec<FilterPartition>),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Locator {
pub precision: u8,
pub block_id_bits: u8,
pub slot_bits: u8,
pub solution: BurrSolution,
}
pub struct TableContext {
pub(crate) fs: Arc<dyn Fs>,
pub(crate) encryption: Option<Arc<dyn EncryptionProvider>>,
#[cfg(zstd_any)]
pub(crate) dictionaries: crate::compression::ZstdDictionaries,
pub(crate) comparator: SharedComparator,
}
impl TableContext {
pub fn new(
state: &super::ManifestState,
fs: Arc<dyn Fs>,
encryption: Option<Arc<dyn EncryptionProvider>>,
#[cfg(zstd_any)] dictionaries: crate::compression::ZstdDictionaries,
comparator: SharedComparator,
) -> crate::Result<Self> {
let supplied = comparator.name();
if state.comparator_name != supplied {
return Err(crate::Error::ComparatorMismatch {
stored: state.comparator_name.clone(),
supplied,
});
}
Ok(Self {
fs,
encryption,
#[cfg(zstd_any)]
dictionaries,
comparator,
})
}
}
pub struct TableExport {
table: Table,
}
impl TableExport {
pub fn open(
path: &Path,
record: &TableRecord,
restriction: Option<&UserKey>,
context: &TableContext,
) -> crate::Result<Self> {
let mut params = RecoverParams::new(
path.to_path_buf(),
Checksum::from_raw(record.checksum),
record.id,
context.fs.clone(),
context.comparator.clone(),
Arc::new(Cache::with_capacity_bytes(0)),
);
params.global_seqno = record.global_seqno;
params.encryption.clone_from(&context.encryption);
#[cfg(zstd_any)]
{
params.zstd_dictionaries = context.dictionaries.clone();
}
let table = Table::recover(params)?;
let table = match restriction {
Some(bound) => table.with_restriction(bound.clone()),
None => table,
};
let live_from = table.punch_offset()?;
let digest = crate::repair::compute_table_checksum_from(&*context.fs, path, live_from)?;
let describes = |candidate: u128| {
candidate == record.checksum
|| matches!(
crate::scrub::heal_attest::attests(
&*context.fs,
path,
context.encryption.as_deref(),
record.id,
Checksum::from_raw(candidate),
Checksum::from_raw(record.checksum),
),
crate::scrub::heal_attest::AttestResult::Attests
)
};
#[cfg(feature = "page_ecc")]
let accounted = describes(digest) || describes(table.export_repaired_digest()?);
#[cfg(not(feature = "page_ecc"))]
let accounted = describes(digest);
if !accounted {
return Err(crate::Error::ChecksumMismatch {
got: Checksum::from_raw(digest),
expected: Checksum::from_raw(record.checksum),
});
}
Ok(Self { table })
}
#[must_use]
pub fn id(&self) -> TableId {
self.table.id()
}
#[must_use]
pub fn is_columnar(&self) -> bool {
self.table.metadata.columnar
}
pub fn live_from(&self) -> crate::Result<u64> {
self.table.punch_offset()
}
pub fn sections(&self) -> crate::Result<Vec<Section>> {
self.table.export_sections()
}
pub fn properties(&self) -> crate::Result<TableProperties> {
self.table.export_properties()
}
pub fn meta(&self) -> crate::Result<Vec<(UserKey, UserValue)>> {
self.table.export_meta()
}
pub fn data_blocks(&self) -> crate::Result<Vec<BlockRef>> {
self.table.export_data_blocks()
}
pub fn rows(&self, block: &BlockRef) -> crate::Result<Vec<InternalValue>> {
self.table.export_rows(block)
}
#[cfg(feature = "columnar")]
pub fn columnar_batch(
&self,
block: &BlockRef,
) -> crate::Result<crate::table::columnar::ColumnBatch> {
self.table.export_columnar_batch(block)
}
#[cfg(feature = "columnar")]
pub fn deleted_rows_in(&self, block: &BlockRef) -> crate::Result<Vec<u32>> {
self.table.export_deleted_rows_in(block)
}
#[must_use]
pub fn range_tombstones(&self) -> Vec<RangeDelete> {
self.table.export_range_tombstones()
}
pub fn linked_blob_files(
&self,
) -> crate::Result<Option<Vec<crate::table::writer::LinkedFile>>> {
self.table.list_blob_file_references()
}
pub fn frame(
&self,
offset: u64,
size: u32,
block_type: crate::table::block::BlockType,
) -> crate::Result<VerifiedFrame> {
self.table.export_frame(offset, size, block_type)
}
pub fn filter(&self) -> crate::Result<Option<Filter>> {
self.table.export_filter()
}
pub fn locator(&self) -> crate::Result<Option<Locator>> {
self.table.export_locator()
}
}
pub fn decode_burr(bytes: &[u8], kind: BurrKind) -> crate::Result<BurrSolution> {
use crate::table::filter::ribbon::burr::wire;
let tag = match kind {
BurrKind::Membership => wire::BURR_FILTER_TYPE_BYTE,
BurrKind::Retrieval => wire::BURR_RETRIEVAL_TYPE_BYTE,
};
let decoded = wire::decode_as(bytes, tag)?;
let layers = decoded
.layers
.iter()
.map(|layer| {
let m = u32::try_from(layer.m)
.map_err(|_| crate::Error::InvalidHeader("BurrFilter layer m"))?;
let rows = layer
.z_bytes
.chunks_exact(8)
.map(|word| {
<[u8; 8]>::try_from(word)
.map(u64::from_le_bytes)
.map_err(|_| crate::Error::InvalidHeader("BurrFilter layer payload"))
})
.collect::<crate::Result<Vec<u64>>>()?;
Ok(BurrLayer {
m,
thresholds: layer.thresholds.to_vec(),
rows,
})
})
.collect::<crate::Result<Vec<BurrLayer>>>()?;
Ok(BurrSolution {
kind,
r: decoded.r,
w: decoded.w,
b: decoded.b,
root_seed: decoded.root_seed,
layers,
})
}