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}