use std::{
fs::File,
io::{BufReader, BufWriter, Write},
path::Path,
};
use crate::{Database, StorageError};
pub mod catalog;
pub mod data;
pub mod expression;
pub mod format;
pub mod io;
pub mod value;
pub use catalog::{read_catalog, read_catalog_v, write_catalog};
pub use data::{read_data, write_data};
pub use expression::{read_expression, write_expression};
pub use format::{read_header, write_header};
impl Database {
pub fn save_binary<P: AsRef<Path>>(&self, path: P) -> Result<(), StorageError> {
let file = File::create(path)
.map_err(|e| StorageError::NotImplemented(format!("Failed to create file: {}", e)))?;
let mut writer = BufWriter::new(file);
write_header(&mut writer)?;
write_catalog(&mut writer, self)?;
write_data(&mut writer, self)?;
writer
.flush()
.map_err(|e| StorageError::NotImplemented(format!("Failed to flush: {}", e)))?;
Ok(())
}
pub fn load_binary<P: AsRef<Path>>(path: P) -> Result<Self, StorageError> {
let file = File::open(path.as_ref()).map_err(|e| {
StorageError::NotImplemented(format!("Failed to open file {:?}: {}", path.as_ref(), e))
})?;
let mut reader = BufReader::new(file);
let version = read_header(&mut reader)?;
let mut db = read_catalog_v(&mut reader, version)?;
read_data(&mut reader, &mut db, version)?;
Ok(db)
}
pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<(), StorageError> {
#[cfg(feature = "compression")]
{
self.save_compressed(path)
}
#[cfg(not(feature = "compression"))]
{
self.save_binary(path)
}
}
pub fn save_uncompressed<P: AsRef<Path>>(&self, path: P) -> Result<(), StorageError> {
self.save_binary(path)
}
#[cfg(feature = "compression")]
pub fn save_compressed<P: AsRef<Path>>(&self, path: P) -> Result<(), StorageError> {
let mut uncompressed_data = Vec::new();
{
let mut writer = BufWriter::new(&mut uncompressed_data);
write_header(&mut writer)?;
write_catalog(&mut writer, self)?;
write_data(&mut writer, self)?;
writer
.flush()
.map_err(|e| StorageError::NotImplemented(format!("Failed to flush: {}", e)))?;
}
let compressed_data = zstd::encode_all(&uncompressed_data[..], 3)
.map_err(|e| StorageError::NotImplemented(format!("Compression failed: {}", e)))?;
std::fs::write(path.as_ref(), compressed_data)
.map_err(|e| StorageError::NotImplemented(format!("Failed to write file: {}", e)))?;
Ok(())
}
#[cfg(feature = "compression")]
pub fn load_compressed<P: AsRef<Path>>(path: P) -> Result<Self, StorageError> {
let compressed_data = std::fs::read(path.as_ref()).map_err(|e| {
StorageError::NotImplemented(format!("Failed to read file {:?}: {}", path.as_ref(), e))
})?;
let uncompressed_data = zstd::decode_all(&compressed_data[..])
.map_err(|e| StorageError::NotImplemented(format!("Decompression failed: {}", e)))?;
let mut reader = BufReader::new(&uncompressed_data[..]);
let version = read_header(&mut reader)?;
let mut db = read_catalog_v(&mut reader, version)?;
read_data(&mut reader, &mut db, version)?;
Ok(db)
}
}
#[cfg(test)]
mod tests {
use super::{
format::{MAGIC, VERSION},
io::*,
*,
};
#[test]
fn test_header_roundtrip() {
let mut buf = Vec::new();
write_header(&mut buf).unwrap();
assert_eq!(buf.len(), 16);
assert_eq!(&buf[0..5], MAGIC);
assert_eq!(buf[5], VERSION);
let mut reader = &buf[..];
let version = read_header(&mut reader).unwrap();
assert_eq!(version, VERSION);
}
#[test]
fn test_primitives() {
let mut buf = Vec::new();
write_u32(&mut buf, 12345).unwrap();
write_i64(&mut buf, -9876543210).unwrap();
write_f64(&mut buf, 3.14159).unwrap();
write_bool(&mut buf, true).unwrap();
write_string(&mut buf, "Hello, VBSQL!").unwrap();
let mut reader = &buf[..];
assert_eq!(read_u32(&mut reader).unwrap(), 12345);
assert_eq!(read_i64(&mut reader).unwrap(), -9876543210);
assert!((read_f64(&mut reader).unwrap() - 3.14159).abs() < 1e-10);
assert!(read_bool(&mut reader).unwrap());
assert_eq!(read_string(&mut reader).unwrap(), "Hello, VBSQL!");
}
}