// Stract is an open source web search engine. // Copyright (C) 2024 Stract ApS // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . use anyhow::Result; use std::{ fs::File, path::{Path, PathBuf}, }; pub struct Wal { writer: file_store::iterable::IterableStoreWriter, path: PathBuf, } impl Wal { pub fn open>(file: P) -> Result { let path = file.as_ref().to_path_buf(); let file = if file.as_ref().exists() { File::open(file)? } else { File::create(file)? }; Ok(Wal { writer: file_store::iterable::IterableStoreWriter::new(file), path, }) } pub fn clear(&mut self) -> Result<()> { std::fs::remove_file(&self.path)?; self.writer = file_store::iterable::IterableStoreWriter::new(File::create(&self.path)?); Ok(()) } } impl Wal where T: bincode::Encode, { pub fn write(&mut self, item: &T) -> Result<()> { self.writer.write(item)?; self.writer.flush()?; Ok(()) } pub fn batch_write<'a>(&'a mut self, items: impl Iterator) -> Result<()> { for item in items { self.writer.write(item)?; } self.writer.flush()?; Ok(()) } } impl Wal where T: bincode::Decode, { pub fn iter(&self) -> Result> { WalIterator::open(&self.path) } } pub struct WalIterator { iter: file_store::iterable::IterableStoreReader, } impl WalIterator { pub fn open>(file: P) -> Result { let iter = file_store::iterable::IterableStoreReader::open(file)?; Ok(Self { iter }) } } impl Iterator for WalIterator where T: bincode::Decode, { type Item = T; fn next(&mut self) -> Option { self.iter.next() } } #[cfg(test)] mod tests { use super::*; use anyhow::Result; #[test] fn test_write_read() -> Result<()> { let temp_dir = file_store::gen_temp_dir().unwrap(); let mut writer = Wal::open(temp_dir.as_ref().join("test-wal"))?; writer.write(&1u64)?; writer.write(&2u64)?; writer.write(&3u64)?; let res: Vec<_> = writer.iter()?.collect(); assert_eq!(&res, &[1, 2, 3]); writer.write(&4u64)?; let res: Vec<_> = writer.iter()?.collect(); assert_eq!(&res, &[1, 2, 3, 4]); Ok(()) } #[test] fn test_empty_write() -> Result<()> { let temp_dir = file_store::gen_temp_dir().unwrap(); let writer: Wal = Wal::open(temp_dir.as_ref().join("test-wal"))?; let res: Vec<_> = writer.iter()?.collect(); assert!(res.is_empty()); Ok(()) } #[test] fn test_clear() -> Result<()> { let temp_dir = file_store::gen_temp_dir().unwrap(); let mut writer = Wal::open(temp_dir.as_ref().join("test-wal"))?; writer.write(&1u64)?; writer.write(&2u64)?; writer.write(&3u64)?; let res: Vec<_> = writer.iter()?.collect(); assert_eq!(&res, &[1, 2, 3]); writer.clear()?; let res: Vec<_> = writer.iter()?.collect(); assert!(res.is_empty()); Ok(()) } }