use super::*;
use crate::cache::Cache;
use crate::descriptor_table::DescriptorTable;
use crate::fs::StdFs;
use crate::table::Writer;
#[cfg(feature = "columnar")]
use crate::{SeqNo, hash::hash64};
use alloc::sync::Arc;
use test_log::test;
#[cfg(feature = "metrics")]
use crate::metrics::Metrics;
fn recover_at(file: &Path, checksum: Checksum, table_id: TableId) -> crate::Result<Table> {
#[cfg(feature = "metrics")]
let metrics = Arc::new(Metrics::default());
let mut params = crate::table::RecoverParams::new(
file.to_path_buf(),
checksum,
table_id,
Arc::new(StdFs),
crate::comparator::default_comparator(),
Arc::new(Cache::with_capacity_bytes(1_000_000)),
);
params.descriptor_table = Some(Arc::new(DescriptorTable::new(10)));
#[cfg(feature = "metrics")]
{
params.metrics = metrics;
}
Table::recover(params)
}
#[cfg(feature = "columnar")]
#[test]
fn relocate_reuses_blocks_and_masks_deleted_rows() -> crate::Result<()> {
let dir = tempfile::tempdir()?;
let src_path = dir.path().join("src");
let out_path = dir.path().join("out");
let n = 96u32;
let deleted = [4u32, 7, 40, 95];
let mut writer = Writer::new(src_path.clone(), 0, 0, Arc::new(StdFs))?
.use_columnar(true)
.use_zone_map(true);
for i in 0..n {
let key = format!("k{i:04}").into_bytes();
writer.write(InternalValue::from_components(
key,
b"val",
1,
crate::ValueType::Value,
))?;
}
let (_, src_checksum) = writer.finish()?.expect("source table written");
let source = recover_at(&src_path, src_checksum, 0)?;
let mut bitmap = DeleteBitmap::new();
for &row in &deleted {
bitmap.insert(row);
}
let out_checksum =
source.relocate_columnar_with_deletes(&out_path, &StdFs, 1, &bitmap, SyncMode::Normal)?;
let relocated = recover_at(&out_path, out_checksum, 1)?;
assert_eq!(relocated.metadata.id, 1, "meta carries the new table id");
assert!(relocated.metadata.columnar, "columnar flag preserved");
for i in 0..n {
let key = format!("k{i:04}").into_bytes();
let got = relocated.get(&key, SeqNo::MAX, hash64(&key))?;
if deleted.contains(&i) {
assert!(
got.is_none(),
"deleted row {i} must read absent after relocate"
);
} else {
let got = got.expect("live row must survive relocate");
assert_eq!(&*got.value, b"val", "live value preserved verbatim");
}
}
Ok(())
}
#[cfg(feature = "columnar")]
#[test]
fn relocated_mor_table_passes_metadata_bounds_cross_check() -> crate::Result<()> {
let dir = tempfile::tempdir()?;
let src_path = dir.path().join("src");
let out_path = dir.path().join("out");
let n = 96u32;
let mut writer = Writer::new(src_path.clone(), 0, 0, Arc::new(StdFs))?
.use_columnar(true)
.use_zone_map(true);
for i in 0..n {
writer.write(InternalValue::from_components(
format!("k{i:04}").into_bytes(),
b"val",
1,
crate::ValueType::Value,
))?;
}
let (_, src_checksum) = writer.finish()?.expect("source table written");
let source = recover_at(&src_path, src_checksum, 0)?;
let mut bitmap = DeleteBitmap::new();
for &row in &[4u32, 7, 40, 95] {
bitmap.insert(row);
}
let out_checksum =
source.relocate_columnar_with_deletes(&out_path, &StdFs, 1, &bitmap, SyncMode::Normal)?;
let relocated = recover_at(&out_path, out_checksum, 1)?;
if let Err((gate, e)) = relocated.verify_reconcile_gates(None, false) {
panic!("a healthy relocated table must pass every gate, {gate:?} refused it: {e}");
}
Ok(())
}
#[cfg(feature = "columnar")]
#[test]
fn a_relocated_table_carries_a_complete_single_output_lineage() -> crate::Result<()> {
let dir = tempfile::tempdir()?;
let src_path = dir.path().join("src");
let out_path = dir.path().join("out");
let mut writer = Writer::new(src_path.clone(), 7, 0, Arc::new(StdFs))?
.use_columnar(true)
.use_zone_map(true);
for i in 0..96u32 {
writer.write(InternalValue::from_components(
format!("k{i:04}").into_bytes(),
b"val",
1,
crate::ValueType::Value,
))?;
}
let (_, src_checksum) = writer.finish()?.expect("source table written");
let source = recover_at(&src_path, src_checksum, 7)?;
let mut bitmap = DeleteBitmap::new();
bitmap.insert(4);
let out_checksum =
source.relocate_columnar_with_deletes(&out_path, &StdFs, 9, &bitmap, SyncMode::Normal)?;
let relocated = recover_at(&out_path, out_checksum, 9)?;
assert_eq!(
relocated.metadata.lineage.as_deref(),
Some(&[7u64][..]),
"the copy's lineage names exactly its source",
);
assert_eq!(
relocated.metadata.lineage_prev, None,
"a single-output run has no previous output",
);
assert!(
relocated.metadata.lineage_last,
"the single output closes its run",
);
assert_eq!(
relocated.metadata.recency,
Some(7),
"the copy's content position is its source's, not its own newer id",
);
Ok(())
}
#[cfg(feature = "columnar")]
#[test]
fn relocate_rejects_a_restricted_view() -> crate::Result<()> {
let dir = tempfile::tempdir()?;
let src_path = dir.path().join("src");
let mut writer = Writer::new(src_path.clone(), 0, 0, Arc::new(StdFs))?
.use_columnar(true)
.use_zone_map(true);
for i in 0..32u32 {
writer.write(InternalValue::from_components(
format!("k{i:04}").into_bytes(),
b"val",
1,
crate::ValueType::Value,
))?;
}
let (_, checksum) = writer.finish()?.expect("table written");
let source = recover_at(&src_path, checksum, 0)?;
let restricted = source.reopen_restricted(crate::UserKey::from(&b"k0016"[..]))?;
let out_path = dir.path().join("out");
let mut bitmap = DeleteBitmap::new();
bitmap.insert(0);
let err = restricted
.relocate_columnar_with_deletes(&out_path, &StdFs, 1, &bitmap, SyncMode::Normal)
.unwrap_err();
assert!(
matches!(err, crate::Error::FeatureUnsupported(_)),
"a restricted view must be rejected, got {err:?}",
);
Ok(())
}
#[test]
fn relocate_rejects_row_major_segment() -> crate::Result<()> {
let dir = tempfile::tempdir()?;
let src_path = dir.path().join("src");
let mut writer = Writer::new(src_path.clone(), 0, 0, Arc::new(StdFs))?.use_zone_map(true);
writer.write(InternalValue::from_components(
b"a",
b"v",
1,
crate::ValueType::Value,
))?;
let (_, checksum) = writer.finish()?.expect("table written");
let source = recover_at(&src_path, checksum, 0)?;
let out_path = dir.path().join("out");
let mut bitmap = DeleteBitmap::new();
bitmap.insert(0);
let err = source
.relocate_columnar_with_deletes(&out_path, &StdFs, 1, &bitmap, SyncMode::Normal)
.unwrap_err();
assert!(
matches!(err, crate::Error::FeatureUnsupported(_)),
"row-major segment must be rejected, got {err:?}",
);
Ok(())
}
#[cfg(feature = "columnar")]
#[test]
fn relocated_mor_table_passes_the_blob_link_cross_check() -> crate::Result<()> {
use crate::blob_tree::handle::BlobIndirection;
use crate::coding::Encode;
use crate::vlog::ValueHandle;
let dir = tempfile::tempdir()?;
let src_path = dir.path().join("src");
let out_path = dir.path().join("out");
let n = 8u32;
let mut writer = Writer::new(src_path.clone(), 0, 0, Arc::new(StdFs))?
.use_columnar(true)
.use_zone_map(true);
let mut bytes_sum = 0u64;
let mut on_disk_sum = 0u64;
for i in 0..n {
let ind = BlobIndirection {
vhandle: ValueHandle {
blob_file_id: 5,
offset: u64::from(i) * 100,
on_disk_size: 40,
},
size: 80,
};
bytes_sum += u64::from(ind.size);
on_disk_sum += u64::from(ind.vhandle.on_disk_size);
let mut val = alloc::vec::Vec::new();
ind.encode_into(&mut val)?;
writer.write(InternalValue::from_components(
format!("k{i:04}").into_bytes(),
val,
1,
crate::ValueType::Indirection,
))?;
}
writer.link_blob_file(5, n as usize, bytes_sum, on_disk_sum);
let (_, src_checksum) = writer.finish()?.expect("source table written");
let source = recover_at(&src_path, src_checksum, 0)?;
let mut bitmap = DeleteBitmap::new();
bitmap.insert(3);
let out_checksum =
source.relocate_columnar_with_deletes(&out_path, &StdFs, 1, &bitmap, SyncMode::Normal)?;
let relocated = recover_at(&out_path, out_checksum, 1)?;
relocated
.verify_blob_links()
.expect("a healthy relocated MoR table passes the blob-link cross-check");
Ok(())
}