Skip to main content

alopex_core/
error.rs

1//! Error and Result types for AlopexDB.
2use std::path::PathBuf;
3use thiserror::Error;
4
5use crate::columnar::error::ColumnarError;
6
7/// A convenience `Result` type.
8pub type Result<T> = std::result::Result<T, Error>;
9
10/// The error type for AlopexDB operations.
11#[derive(Debug, Error)]
12pub enum Error {
13    /// The requested key was not found.
14    #[error("key not found")]
15    NotFound,
16
17    /// The transaction has already been closed (committed or rolled back).
18    #[error("transaction is closed")]
19    TxnClosed,
20
21    /// Read-only トランザクションで書き込み操作を試みた。
22    #[error("transaction is read-only")]
23    TxnReadOnly,
24
25    /// A transaction conflict occurred (e.g., optimistic concurrency control failure).
26    #[error("transaction conflict")]
27    TxnConflict,
28
29    /// An underlying I/O error occurred.
30    #[error("io error: {0}")]
31    Io(#[from] std::io::Error),
32
33    /// The on-disk format is invalid or corrupted.
34    #[error("invalid format: {0}")]
35    InvalidFormat(String),
36
37    /// A checksum validation failed.
38    #[error("checksum mismatch")]
39    ChecksumMismatch,
40
41    /// WAL recovery stopped before completion.
42    #[error("recovery incomplete: recovered={recovered_entries}, stop_offset={stop_offset}, reason={reason}")]
43    RecoveryIncomplete {
44        /// Number of entries recovered before stopping.
45        recovered_entries: usize,
46        /// Byte offset where recovery stopped.
47        stop_offset: u64,
48        /// Reason for stopping.
49        reason: String,
50    },
51
52    /// On-disk segment is corrupted (e.g., checksum failure).
53    #[error("corrupted segment {segment_id}: {reason}")]
54    CorruptedSegment {
55        /// Segment identifier.
56        segment_id: u64,
57        /// Reason for corruption detection.
58        reason: String,
59    },
60
61    /// A vector with an unexpected dimension was provided.
62    #[error("dimension mismatch: expected {expected}, got {actual}")]
63    DimensionMismatch {
64        /// Expected dimension.
65        expected: usize,
66        /// Dimension of the provided vector.
67        actual: usize,
68    },
69
70    /// A metric that is not supported was requested.
71    #[error("unsupported metric: {metric}")]
72    UnsupportedMetric {
73        /// Name of the unsupported metric.
74        metric: String,
75    },
76
77    /// A vector value is invalid for the requested operation.
78    #[error("invalid vector at index {index}: {reason}")]
79    InvalidVector {
80        /// Zero-based index of the offending vector.
81        index: usize,
82        /// Reason for invalidation.
83        reason: String,
84    },
85
86    /// A filter expression is malformed or unsupported.
87    #[error("invalid filter: {0}")]
88    InvalidFilter(String),
89
90    /// Memory usage exceeded configured limit.
91    #[error("memory limit exceeded: limit={limit}, requested={requested}")]
92    MemoryLimitExceeded {
93        /// Maximum allowed memory (bytes).
94        limit: usize,
95        /// Requested memory (bytes) that triggered the limit.
96        requested: usize,
97    },
98
99    /// A bounded key search inspected its maximum candidate count.
100    #[error("search scan budget exceeded: limit={limit}")]
101    SearchBudgetExceeded {
102        /// Maximum candidate keys permitted for one page.
103        limit: usize,
104    },
105
106    /// A key search page would exceed its response payload budget.
107    #[error("search response size exceeded: limit={limit}, requested={requested}")]
108    SearchResponseTooLarge {
109        /// Maximum combined key and value bytes.
110        limit: usize,
111        /// Combined key and value bytes requested so far.
112        requested: usize,
113    },
114
115    /// A cooperative key search cancellation was observed.
116    #[error("key search cancelled")]
117    SearchCancelled,
118
119    /// External spill operation failed.
120    #[error("spill failed: {reason}")]
121    SpillFailed {
122        /// Stable failure description.
123        reason: String,
124    },
125
126    /// A DataFrame scalar cast failed.
127    #[error("cast failed: from={from_type}, to={to_type}, reason={reason}")]
128    CastFailed {
129        /// Source type name.
130        from_type: String,
131        /// Target type name.
132        to_type: String,
133        /// Stable failure code.
134        reason: String,
135    },
136
137    /// The provided path already exists and cannot be overwritten.
138    #[error("path exists: {0}")]
139    PathExists(PathBuf),
140
141    /// The data directory is already open somewhere else (issue #181).
142    ///
143    /// Alopex's storage engine has exactly one writer per data directory: the
144    /// WAL is a fixed-length ring addressed from an in-memory offset and
145    /// SSTable ids come from a process-local counter, so a second writer
146    /// overwrites the first one's bytes. Opening therefore takes an OS-level
147    /// exclusive lock, and this is what a caller that lost the race sees.
148    ///
149    /// The message deliberately contains the stable, greppable phrase
150    /// `already open by another process`; tests and user-facing tooling match
151    /// on that substring rather than on the whole rendering, which varies with
152    /// the holder diagnostics (unavailable on Windows — see 裁定 D10).
153    #[error(
154        "data directory {path} is already open by another process ({holder}); \
155         an Alopex database can only be opened by one process at a time — \
156         share it through alopex-server instead (lock file: {lock_path})"
157    )]
158    AlreadyOpen {
159        /// The data directory that could not be opened.
160        path: PathBuf,
161        /// The lock file guarding it.
162        lock_path: PathBuf,
163        /// Best-effort description of the current holder, or `unknown`.
164        holder: String,
165    },
166
167    /// An index configuration parameter is invalid.
168    #[error("invalid parameter {param}: {reason}")]
169    InvalidParameter {
170        /// Name of the invalid parameter.
171        param: String,
172        /// Description of the expected range or constraint.
173        reason: String,
174    },
175
176    /// An index with the requested name was not found.
177    #[error("index not found: {name}")]
178    IndexNotFound {
179        /// Name of the missing index.
180        name: String,
181    },
182
183    /// An index failed integrity checks.
184    #[error("corrupted index {name}: {reason}")]
185    CorruptedIndex {
186        /// Name of the corrupted index.
187        name: String,
188        /// Description of the corruption.
189        reason: String,
190    },
191
192    /// The stored index version is unsupported by this binary.
193    #[error("unsupported index version: found {found}, supported {supported}")]
194    UnsupportedIndexVersion {
195        /// Version detected in storage.
196        found: u32,
197        /// Highest supported version.
198        supported: u32,
199    },
200
201    /// An unknown configuration or runtime option was provided.
202    #[error("unknown option: {key}")]
203    UnknownOption {
204        /// Name of the option.
205        key: String,
206    },
207
208    /// A column type does not match the expected layout.
209    #[error("invalid column type for {column}, expected {expected}")]
210    InvalidColumnType {
211        /// Column name.
212        column: String,
213        /// Expected type description.
214        expected: String,
215    },
216
217    /// The index is busy and cannot serve the requested operation.
218    #[error("index busy during {operation}")]
219    IndexBusy {
220        /// Operation that was attempted.
221        operation: String,
222    },
223
224    /// Errors originating from columnar components.
225    #[error("columnar error: {0}")]
226    Columnar(#[from] ColumnarError),
227
228    /// The unified `.alopex` container format rejected a read or write.
229    #[error("container format error: {0}")]
230    ContainerFormat(#[from] crate::storage::format::FormatError),
231
232    /// An S3 operation failed.
233    #[error("S3 error: {0}")]
234    S3(String),
235
236    /// Required credentials are missing.
237    #[error("missing credentials: {0}")]
238    MissingCredentials(String),
239}