fwob-v2 1.5.2

Fixed-page compressed storage for fast FWOB range queries and bulk append
Documentation
use std::{fs::File, ops::Range, path::Path};

use fwob_core::{
    FileInfo, FormatVersion, Key, Maintenance, OwnedFrame, Reader as CoreReader, ReaderBackend,
    ReaderOptions, Result as CoreResult, Schema, VerificationReport, Writer as CoreWriter,
    WriterBackend, WriterFactory,
};

use crate::{CodecSelection, EncodingSelection, Reader, Writer, WriterOptions, FILE_HEADER_LEN};

pub struct ReaderAdapter {
    reader: Reader<File>,
}

impl ReaderAdapter {
    pub fn open(path: impl AsRef<Path>) -> crate::Result<(Self, CompatibleWriterFactory)> {
        let mut reader = Reader::open(path)?;
        let header = reader.header().clone();
        let mut options = WriterOptions::new("");
        options.page_size = header.page_size;
        if header.page_count > 0 {
            let first = reader.read_page_header(0)?;
            options.codec = first.codec;
            options.codec_selection = CodecSelection::Fixed(first.codec);
            options.encoding = first.encoding;
            options.encoding_selection = EncodingSelection::Fixed(first.encoding);
        }
        Ok((
            Self { reader },
            CompatibleWriterFactory {
                schema: header.schema,
                options,
            },
        ))
    }
}

impl FileInfo for ReaderAdapter {
    fn format_version(&self) -> FormatVersion {
        FormatVersion::V2
    }

    fn schema(&self) -> &Schema {
        &self.reader.header().schema
    }

    fn title(&self) -> &str {
        &self.reader.header().title
    }

    fn frame_count(&self) -> u64 {
        self.reader.header().frame_count
    }

    fn string_table(&self) -> &[String] {
        &self.reader.header().string_table
    }
}

impl ReaderBackend for ReaderAdapter {
    fn read_frame(&mut self, index: u64) -> CoreResult<Option<OwnedFrame>> {
        self.reader
            .read_frame_at(index)
            .map_err(fwob_core::FwobError::backend)
    }

    fn read_key(&mut self, index: u64) -> CoreResult<Option<Key>> {
        self.reader
            .read_key_at(index)
            .map_err(fwob_core::FwobError::backend)
    }

    fn first_frame(&mut self) -> CoreResult<Option<OwnedFrame>> {
        self.reader
            .first_frame()
            .map_err(fwob_core::FwobError::backend)
    }

    fn last_frame(&mut self) -> CoreResult<Option<OwnedFrame>> {
        self.reader
            .last_frame()
            .map_err(fwob_core::FwobError::backend)
    }

    fn first_key(&mut self) -> CoreResult<Option<Key>> {
        self.reader
            .first_key()
            .map_err(fwob_core::FwobError::backend)
    }

    fn last_key(&mut self) -> CoreResult<Option<Key>> {
        self.reader
            .last_key()
            .map_err(fwob_core::FwobError::backend)
    }

    fn lower_bound(&mut self, key: Key) -> CoreResult<u64> {
        self.reader
            .lower_bound(key)
            .map_err(fwob_core::FwobError::backend)
    }

    fn upper_bound(&mut self, key: Key) -> CoreResult<u64> {
        self.reader
            .upper_bound(key)
            .map_err(fwob_core::FwobError::backend)
    }

    fn equal_range(&mut self, key: Key) -> CoreResult<Range<u64>> {
        let (start, end) = self
            .reader
            .equal_range(key)
            .map_err(fwob_core::FwobError::backend)?;
        Ok(start..end)
    }
}

pub struct CompatibleWriterFactory {
    schema: Schema,
    options: WriterOptions,
}

impl WriterFactory for CompatibleWriterFactory {
    fn create(
        &mut self,
        path: &Path,
        title: &str,
        string_table: &[String],
    ) -> CoreResult<CoreWriter> {
        let mut options = self.options.clone();
        options.title = title.to_owned();
        options.string_table = string_table.to_vec();
        create_writer(path, self.schema.clone(), options).map_err(fwob_core::FwobError::backend)
    }
}

pub struct WriterAdapter {
    writer: Writer<File>,
}

impl WriterAdapter {
    pub fn create(
        path: impl AsRef<Path>,
        schema: Schema,
        options: WriterOptions,
    ) -> crate::Result<Self> {
        Ok(Self {
            writer: Writer::create(path, schema, options)?,
        })
    }

    pub fn open_append(path: impl AsRef<Path>, options: WriterOptions) -> crate::Result<Self> {
        Ok(Self {
            writer: Writer::open_append(path, options)?,
        })
    }
}

impl FileInfo for WriterAdapter {
    fn format_version(&self) -> FormatVersion {
        FormatVersion::V2
    }

    fn schema(&self) -> &Schema {
        self.writer.schema()
    }

    fn title(&self) -> &str {
        &self.writer.header().title
    }

    fn frame_count(&self) -> u64 {
        self.writer.frame_count()
    }

    fn string_table(&self) -> &[String] {
        &self.writer.header().string_table
    }
}

impl WriterBackend for WriterAdapter {
    fn append_frame(&mut self, frame: &[u8]) -> CoreResult<()> {
        self.writer
            .append_frame(frame)
            .map_err(fwob_core::FwobError::backend)
    }

    fn append_presorted_frames(&mut self, frames: &[u8]) -> CoreResult<()> {
        self.writer
            .append_presorted_raw_frames(frames)
            .map_err(fwob_core::FwobError::backend)
    }

    fn append_frames_transactional(&mut self, frames: &[u8]) -> CoreResult<()> {
        self.writer
            .append_raw_frames(frames)
            .map_err(fwob_core::FwobError::backend)
    }

    fn finish(self: Box<Self>) -> CoreResult<()> {
        self.writer.finish().map_err(fwob_core::FwobError::backend)
    }
}

pub fn open_reader(path: impl AsRef<Path>) -> crate::Result<CoreReader> {
    let (reader, factory) = ReaderAdapter::open(path)?;
    Ok(CoreReader::from_parts(reader, factory))
}

pub fn create_writer(
    path: impl AsRef<Path>,
    schema: Schema,
    options: WriterOptions,
) -> crate::Result<CoreWriter> {
    Ok(CoreWriter::from_backend(WriterAdapter::create(
        path, schema, options,
    )?))
}

pub fn open_writer(path: impl AsRef<Path>, options: WriterOptions) -> crate::Result<CoreWriter> {
    Ok(CoreWriter::from_backend(WriterAdapter::open_append(
        path, options,
    )?))
}

#[derive(Debug, Default, Clone, Copy)]
pub struct MaintenanceService;

impl Maintenance for MaintenanceService {
    fn format_version(&self) -> FormatVersion {
        FormatVersion::V2
    }

    fn light_verify(&self, path: &Path, _options: ReaderOptions) -> CoreResult<VerificationReport> {
        let metadata_len = std::fs::metadata(path)?.len();
        let mut reader = Reader::open(path).map_err(fwob_core::FwobError::backend)?;
        let header = reader.header().clone();
        let expected_len = FILE_HEADER_LEN + header.page_count * u64::from(header.page_size);
        if metadata_len != expected_len {
            return Err(fwob_core::FwobError::backend(
                crate::V2Error::InvalidFileHeader,
            ));
        }
        if header.page_count > 0 {
            reader
                .read_page_header(header.page_count - 1)
                .map_err(fwob_core::FwobError::backend)?;
        }
        Ok(VerificationReport {
            format_version: FormatVersion::V2,
            frame_count: header.frame_count,
            string_count: header.string_table.len() as u32,
            file_length: metadata_len,
        })
    }

    fn verify(&self, path: &Path, options: ReaderOptions) -> CoreResult<VerificationReport> {
        let report = self.light_verify(path, options)?;
        Reader::open(path)
            .map_err(fwob_core::FwobError::backend)?
            .verify()
            .map_err(fwob_core::FwobError::backend)?;
        Ok(report)
    }

    fn repair(&self, path: &Path, options: ReaderOptions) -> CoreResult<VerificationReport> {
        crate::repair_committed_tail(path).map_err(fwob_core::FwobError::backend)?;
        self.verify(path, options)
    }
}