use std::fs::File;
use std::io::{BufWriter, Write};
use std::path::PathBuf;
use crate::error::{Error, Result};
use crate::storage::sstable::directory::types::SSTableComponent;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ComponentEntry {
pub component: SSTableComponent,
}
impl ComponentEntry {
pub fn new(component: SSTableComponent) -> Self {
Self { component }
}
pub fn filename(&self) -> &'static str {
self.component.file_extension()
}
}
#[derive(Debug)]
pub struct TocWriter {
path: PathBuf,
}
impl TocWriter {
pub fn new(path: PathBuf) -> Self {
Self { path }
}
pub fn write(&self, components: &[ComponentEntry]) -> Result<()> {
let mut final_components: Vec<ComponentEntry> = components.to_vec();
if !final_components
.iter()
.any(|e| e.component == SSTableComponent::TOC)
{
final_components.push(ComponentEntry::new(SSTableComponent::TOC));
}
final_components.sort_by_key(|entry| match entry.component {
SSTableComponent::Data => 0,
SSTableComponent::Statistics => 1,
SSTableComponent::Digest => 2,
SSTableComponent::TOC => 3,
SSTableComponent::CompressionInfo => 4,
SSTableComponent::Filter => 5,
SSTableComponent::Index => 6,
SSTableComponent::Summary => 7,
SSTableComponent::Partitions => 8,
SSTableComponent::Rows => 9,
});
let file = File::create(&self.path).map_err(|e| {
Error::storage(format!(
"Failed to create TOC.txt file {:?}: {}",
self.path, e
))
})?;
let mut writer = BufWriter::new(file);
for entry in &final_components {
writeln!(writer, "{}", entry.filename()).map_err(|e| {
Error::storage(format!(
"Failed to write component {} to TOC.txt: {}",
entry.filename(),
e
))
})?;
}
writer
.flush()
.map_err(|e| Error::storage(format!("Failed to flush TOC.txt: {}", e)))?;
let file = writer
.into_inner()
.map_err(|e| Error::storage(format!("Failed to extract file from buffer: {}", e)))?;
file.sync_all()
.map_err(|e| Error::storage(format!("Failed to sync TOC.txt to disk: {}", e)))?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::TempDir;
#[test]
fn test_toc_writer_basic() {
let temp_dir = TempDir::new().unwrap();
let toc_path = temp_dir.path().join("nb-1-big-TOC.txt");
let components = vec![
ComponentEntry::new(SSTableComponent::Data),
ComponentEntry::new(SSTableComponent::Index),
ComponentEntry::new(SSTableComponent::Statistics),
];
let writer = TocWriter::new(toc_path.clone());
writer.write(&components).unwrap();
assert!(toc_path.exists());
let contents = fs::read_to_string(&toc_path).unwrap();
let lines: Vec<&str> = contents.lines().collect();
assert_eq!(lines.len(), 4);
assert!(lines.contains(&"TOC.txt"));
assert!(lines.contains(&"Data.db"));
assert!(lines.contains(&"Index.db"));
assert!(lines.contains(&"Statistics.db"));
}
#[test]
fn test_toc_writer_all_components() {
let temp_dir = TempDir::new().unwrap();
let toc_path = temp_dir.path().join("nb-1-big-TOC.txt");
let components = vec![
ComponentEntry::new(SSTableComponent::Data),
ComponentEntry::new(SSTableComponent::Index),
ComponentEntry::new(SSTableComponent::Statistics),
ComponentEntry::new(SSTableComponent::Filter),
ComponentEntry::new(SSTableComponent::Summary),
ComponentEntry::new(SSTableComponent::CompressionInfo),
ComponentEntry::new(SSTableComponent::Digest),
];
let writer = TocWriter::new(toc_path.clone());
writer.write(&components).unwrap();
let contents = fs::read_to_string(&toc_path).unwrap();
let lines: Vec<&str> = contents.lines().collect();
assert_eq!(lines.len(), 8);
assert_eq!(lines[0], "Data.db");
assert_eq!(lines[1], "Statistics.db");
assert_eq!(lines[2], "Digest.crc32");
assert_eq!(lines[3], "TOC.txt");
assert_eq!(lines[4], "CompressionInfo.db");
assert_eq!(lines[5], "Filter.db");
assert_eq!(lines[6], "Index.db");
assert_eq!(lines[7], "Summary.db");
}
#[test]
fn test_toc_writer_already_includes_toc() {
let temp_dir = TempDir::new().unwrap();
let toc_path = temp_dir.path().join("nb-1-big-TOC.txt");
let components = vec![
ComponentEntry::new(SSTableComponent::Data),
ComponentEntry::new(SSTableComponent::TOC),
ComponentEntry::new(SSTableComponent::Statistics),
];
let writer = TocWriter::new(toc_path.clone());
writer.write(&components).unwrap();
let contents = fs::read_to_string(&toc_path).unwrap();
let lines: Vec<&str> = contents.lines().collect();
assert_eq!(lines.len(), 3);
assert_eq!(lines.iter().filter(|&&l| l == "TOC.txt").count(), 1);
}
#[test]
fn test_toc_writer_empty_components() {
let temp_dir = TempDir::new().unwrap();
let toc_path = temp_dir.path().join("nb-1-big-TOC.txt");
let components = vec![];
let writer = TocWriter::new(toc_path.clone());
writer.write(&components).unwrap();
let contents = fs::read_to_string(&toc_path).unwrap();
let lines: Vec<&str> = contents.lines().collect();
assert_eq!(lines.len(), 1);
assert_eq!(lines[0], "TOC.txt");
}
#[test]
fn test_component_entry_filename() {
let data_entry = ComponentEntry::new(SSTableComponent::Data);
assert_eq!(data_entry.filename(), "Data.db");
let digest_entry = ComponentEntry::new(SSTableComponent::Digest);
assert_eq!(digest_entry.filename(), "Digest.crc32");
let toc_entry = ComponentEntry::new(SSTableComponent::TOC);
assert_eq!(toc_entry.filename(), "TOC.txt");
}
}