Skip to main content

alopex_core/storage/format/
mod.rs

1//! # Alopex Unified Data File Format
2//!
3//! Alopex DB が共通で読み書きする `.alopex` バイナリファイル形式の
4//! 定数・型・エラーをまとめ、Native/WASM 両ターゲットで同一フォーマットを提供する。
5//!
6//! ## 主な構造体
7//! - [`FileHeader`], [`FileFooter`] — マジック・バージョン・統計・チェックサムを保持
8//! - [`SectionEntry`], [`SectionIndex`] — セクションメタデータ(圧縮方式・オフセット・長さ)
9//! - [`AlopexFileWriter`] (native) — ヘッダー書き込み → セクション追加 → フッター確定 → アトミックリネーム
10//! - [`AlopexFileReader`] (native/wasm) — ヘッダー/フッター/インデックスを検証し、圧縮後データのチェックサムを照合
11//! - [`ValueSeparator`] — 大型値を専用セクションに分離するユーティリティ
12//!
13//! ## フォーマット特性
14//! - バイトオーダー: Little Endian 固定
15//! - セクションデータのチェックサムは「圧縮後バイト列」に対して計算
16//! - バージョン互換性: `file.version` が [`FileVersion::CURRENT`] を超える場合、 [`FormatError::IncompatibleVersion`] を返す
17//! - 圧縮: None / Snappy(デフォルト)/ Zstd / LZ4 (feature で有効化)
18//! - ターゲット: x86_64 / ARM64 / WASM(WASMは Snappy/None のみをデフォルトサポート)
19//!
20//! ## 典型的な書き込みフロー(native)
21//! ```rust,no_run
22//! # #[cfg(not(target_arch = "wasm32"))]
23//! # {
24//! use alopex_core::storage::format::{
25//!     AlopexFileWriter, FileFlags, FileVersion, SectionType, FormatError,
26//! };
27//!
28//! fn write_file() -> Result<(), FormatError> {
29//!     let mut writer = AlopexFileWriter::new(
30//!         "example.alopex".into(),
31//!         FileVersion::CURRENT,
32//!         FileFlags(0),
33//!     )?;
34//!     // 非圧縮メタデータ
35//!     writer.add_section(SectionType::Metadata, b"meta-v1", false)?;
36//!     // デフォルト圧縮(Snappy)でSSTableを書き込む
37//!     writer.add_section(SectionType::SSTable, b"sstable-bytes", true)?;
38//!     writer.finalize()?;
39//!     Ok(())
40//! }
41//! # }
42//! ```
43//!
44//! ## 典型的な読み取りフロー(native)
45//! ```rust,no_run
46//! # #[cfg(not(target_arch = "wasm32"))]
47//! # {
48//! use alopex_core::storage::format::{AlopexFileReader, FileReader, FileSource, FormatError};
49//!
50//! fn read_and_validate() -> Result<(), FormatError> {
51//!     let reader = AlopexFileReader::open(FileSource::Path("example.alopex".into()))?;
52//!     reader.validate_all()?; // ヘッダー/フッター/各セクションの整合性を検証
53//!     let meta = reader.read_section(0)?; // 解凍済みバイト列
54//!     let data_raw = reader.read_section_raw(1)?; // 圧縮後バイト列
55//!     assert!(!meta.is_empty());
56//!     assert!(!data_raw.is_empty());
57//!     Ok(())
58//! }
59//! # }
60//! ```
61//!
62//! ## WASM 読み取りフローの留意点
63//! - `full_load_threshold_bytes` 未満のサイズは全体をバッファにロード。
64//! - 閾値超過時は IndexedDB などの範囲ローダーでストリーミング読み取り。
65//! - デフォルトで Snappy/None のみをサポートし、Zstd/LZ4 はビルドサイズ・メモリ上限の理由で無効化。
66
67pub mod backpressure;
68pub mod footer;
69pub mod header;
70pub mod ingest;
71pub mod models;
72pub mod reader;
73pub mod section;
74pub mod section_columnar;
75pub mod value_separator;
76#[cfg(not(target_arch = "wasm32"))]
77pub mod writer;
78
79pub use backpressure::{CompactionDebtTracker, WriteThrottleConfig};
80pub use footer::FileFooter;
81pub use header::{FileFlags, FileHeader};
82pub use ingest::{ExternalSectionIngest, KeyRange};
83pub use models::{
84    bincode_config, ColumnDefinition, EphemeralDataGcConfig, IndexDefinition, IntentEntry,
85    IntentSection, IntentType, LockEntry, LockSection, LockType, Metadata, RaftEntryType,
86    RaftLogEntry, RaftLogSection, RangeMetadata, TableSchema, VectorIndexMetadata, VectorMetric,
87};
88#[cfg(not(target_arch = "wasm32"))]
89pub use reader::AlopexFileReader;
90#[cfg(target_arch = "wasm32")]
91pub use reader::{AlopexFileReader, WasmReaderConfig};
92#[cfg(target_arch = "wasm32")]
93pub use reader::{BufferRangeLoader, RangeLoader};
94pub use reader::{FileReader, FileSource, PrefetchFuture};
95pub use section::{SectionEntry, SectionIndex, SectionType};
96#[cfg(not(target_arch = "wasm32"))]
97pub use section_columnar::ColumnarSectionWriter;
98pub use section_columnar::{ColumnarSectionReader, SECTION_TYPE_COLUMNAR};
99pub use value_separator::{LargeValuePointer, ValueRef, ValueSeparationConfig, ValueSeparator};
100#[cfg(not(target_arch = "wasm32"))]
101pub use writer::AlopexFileWriter;
102
103use thiserror::Error;
104
105/// ファイル先頭のマジックナンバー ("ALPX")。
106pub const MAGIC: [u8; 4] = *b"ALPX";
107/// フッター末尾の逆マジックナンバー ("XPLA")。
108pub const REVERSE_MAGIC: [u8; 4] = *b"XPLA";
109
110/// ヘッダー領域の固定サイズ(バイト数)。
111pub const HEADER_SIZE: usize = 64;
112/// フッター領域の固定サイズ(バイト数)。
113pub const FOOTER_SIZE: usize = 64;
114/// SectionEntry(メタデータ1件)の固定サイズ(バイト数)。
115pub const SECTION_ENTRY_SIZE: usize = 40;
116
117/// 形式のメジャー/マイナー/パッチバージョン(初期値: v0.1.0)。
118pub const VERSION_MAJOR: u16 = 0;
119/// 現行マイナーバージョン。
120pub const VERSION_MINOR: u16 = 1;
121/// 現行パッチバージョン。
122pub const VERSION_PATCH: u16 = 0;
123
124/// ファイルバージョン(6バイト)。
125#[repr(C)]
126#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
127pub struct FileVersion {
128    /// メジャーバージョン。
129    pub major: u16,
130    /// マイナーバージョン。
131    pub minor: u16,
132    /// パッチバージョン。
133    pub patch: u16,
134}
135
136impl FileVersion {
137    /// 定数からバージョンを生成するヘルパー。
138    pub const fn new(major: u16, minor: u16, patch: u16) -> Self {
139        Self {
140            major,
141            minor,
142            patch,
143        }
144    }
145
146    /// 現行バージョン定数。
147    pub const CURRENT: Self = Self::new(VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH);
148}
149
150/// 統一データファイル形式のエラー型。
151#[repr(C)]
152#[derive(Debug, Error, PartialEq, Eq)]
153pub enum FormatError {
154    /// マジックナンバーが一致しない。
155    #[error("Invalid magic number: expected ALPX, found {found:?}")]
156    InvalidMagic {
157        /// ファイルから読み取ったマジックナンバー。
158        found: [u8; 4],
159    },
160
161    /// ファイルバージョンがリーダーより新しく互換でない。
162    #[error(
163        "Incompatible version: file version {file:?} is newer than reader version {reader:?}. Please upgrade Alopex DB."
164    )]
165    IncompatibleVersion {
166        /// ファイルに記録されたバージョン。
167        file: FileVersion,
168        /// リーダー(実行バイナリ)がサポートするバージョン。
169        reader: FileVersion,
170    },
171
172    /// セクションのチェックサム不一致。
173    #[error("Section {section_id} is corrupted: expected checksum {expected:#x}, found {found:#x}. The section may be damaged.")]
174    CorruptedSection {
175        /// 対象セクションID。
176        section_id: u32,
177        /// 期待されるチェックサム値。
178        expected: u32,
179        /// 実際に計算されたチェックサム値。
180        found: u32,
181    },
182
183    /// フッターが欠損/不正で書き込みが完了していない。
184    #[error("File appears to be incomplete (missing or invalid footer). This may indicate a crash during write.")]
185    IncompleteWrite,
186
187    /// External ingest 時のキー範囲重複。
188    #[error("Key range [{start:?}, {end:?}) overlaps with existing section {section_id}")]
189    KeyRangeOverlap {
190        /// 追加しようとした開始キー(包含)。
191        start: Vec<u8>,
192        /// 追加しようとした終了キー(排他)。
193        end: Vec<u8>,
194        /// 衝突した既存セクションのID。
195        section_id: u32,
196    },
197
198    /// ビルドでサポートしていない圧縮アルゴリズムが要求された。
199    #[error("Compression algorithm {algorithm} is not supported in this build")]
200    UnsupportedCompression {
201        /// 要求された圧縮アルゴリズムの識別子。
202        algorithm: u8,
203    },
204
205    /// 圧縮処理に失敗した。
206    #[error("Compression failed for algorithm {algorithm}")]
207    CompressionFailed {
208        /// 対象アルゴリズム。
209        algorithm: u8,
210    },
211
212    /// 解凍処理に失敗した。
213    #[error("Decompression failed for algorithm {algorithm}")]
214    DecompressionFailed {
215        /// 対象アルゴリズム。
216        algorithm: u8,
217    },
218
219    /// 未サポートのチェックサムアルゴリズム。
220    #[error("Checksum algorithm {algorithm} is not supported in this build")]
221    UnsupportedChecksum {
222        /// 対象アルゴリズム。
223        algorithm: u8,
224    },
225
226    /// ポインタ情報が不正または未解決。
227    #[error("Invalid pointer: section_id={section_id}, offset={offset}, length={length}")]
228    InvalidPointer {
229        /// セクションID(未解決時は0)。
230        section_id: u32,
231        /// オフセット。
232        offset: u64,
233        /// 長さ。
234        length: u64,
235    },
236
237    /// チェックサム不一致。
238    #[error("Checksum mismatch: expected {expected:#x}, found {found:#x}")]
239    ChecksumMismatch {
240        /// 期待値。
241        expected: u64,
242        /// 実測値。
243        found: u64,
244    },
245
246    /// キー範囲が不正(start >= end など)。
247    #[error("Invalid key range: start={start:?}, end={end:?}")]
248    InvalidKeyRange {
249        /// 範囲開始(包含)。
250        start: Vec<u8>,
251        /// 範囲終了(排他)。
252        end: Vec<u8>,
253    },
254
255    /// 外部セクションの検証に失敗。
256    #[error("Invalid external section: {message}")]
257    IngestValidationFailed {
258        /// 検証失敗理由。
259        message: &'static str,
260    },
261}