Skip to main content

oxgraph_db/
error.rs

1//! Db error surface.
2
3use std::{fmt, io};
4
5use crate::{
6    ElementId, IncidenceId, IndexId, LabelId, ProjectionId, PropertyKeyId, RelationId,
7    RelationTypeId, RoleId, catalog::PropertyFamily, value::PropertyType,
8};
9
10/// Errors raised by the `OxGraph` database product.
11///
12/// # Performance
13///
14/// Formatting is `O(message length)`.
15#[derive(Debug)]
16#[non_exhaustive]
17pub enum DbError {
18    /// Db files already exist.
19    AlreadyExists,
20    /// Db files do not exist.
21    NotFound,
22    /// The single-writer lock is already held by another writer.
23    WriterLockHeld,
24    /// Canonical ID space is exhausted.
25    IdOverflow,
26    /// Transaction ID space is exhausted.
27    TransactionIdOverflow,
28    /// Commit sequence space is exhausted.
29    CommitSeqOverflow,
30    /// Duplicate catalog name or ID.
31    DuplicateCatalogName,
32    /// Duplicate canonical ID.
33    DuplicateId,
34    /// Unknown element ID.
35    UnknownElement {
36        /// Missing element ID.
37        id: ElementId,
38    },
39    /// Unknown relation ID.
40    UnknownRelation {
41        /// Missing relation ID.
42        id: RelationId,
43    },
44    /// Unknown incidence ID.
45    UnknownIncidence {
46        /// Missing incidence ID.
47        id: IncidenceId,
48    },
49    /// Unknown role ID.
50    UnknownRole {
51        /// Missing role ID.
52        id: RoleId,
53    },
54    /// Unknown label ID.
55    UnknownLabel {
56        /// Missing label ID.
57        id: LabelId,
58    },
59    /// Unknown relation type ID.
60    UnknownRelationType {
61        /// Missing relation type ID.
62        id: RelationTypeId,
63    },
64    /// Unknown property key ID.
65    UnknownPropertyKey {
66        /// Missing property key ID.
67        id: PropertyKeyId,
68    },
69    /// Unknown projection ID.
70    UnknownProjection {
71        /// Missing projection ID.
72        id: ProjectionId,
73    },
74    /// Unknown index ID.
75    UnknownIndex {
76        /// Missing index ID.
77        id: IndexId,
78    },
79    /// Property value type mismatched the catalog schema.
80    PropertyTypeMismatch {
81        /// Expected property type.
82        expected: PropertyType,
83        /// Actual property type.
84        actual: PropertyType,
85    },
86    /// Property subject family mismatched the catalog schema.
87    WrongPropertyFamily {
88        /// Expected subject family.
89        expected: PropertyFamily,
90        /// Actual subject family.
91        actual: PropertyFamily,
92    },
93    /// Projection cannot be materialized as requested.
94    InvalidProjection {
95        /// Deterministic validation message.
96        message: String,
97    },
98    /// Query text is empty.
99    EmptyQuery,
100    /// Query text is outside the pinned profile.
101    UnsupportedQuery {
102        /// Deterministic explanation.
103        message: String,
104    },
105    /// Storage bytes are invalid.
106    InvalidStore {
107        /// Deterministic validation message.
108        message: String,
109    },
110    /// The store's OXGDB format version is not supported by this build. A base
111    /// written under an older format (for example one lacking the persisted
112    /// `SECTION_INDEX_*` postings) is rejected here rather than silently rebuilt.
113    UnsupportedFormat {
114        /// Format version recorded in the store.
115        found: u32,
116        /// Format version this build requires.
117        expected: u32,
118    },
119    /// Wraps an IO error with operation context.
120    Io {
121        /// Operation that failed.
122        operation: &'static str,
123        /// Underlying IO error.
124        source: io::Error,
125    },
126    /// A bounded traversal failed.
127    Traversal {
128        /// Deterministic reason the traversal failed.
129        reason: &'static str,
130    },
131    /// A delta-log record is corrupt beyond the recoverable torn tail.
132    LogCorrupt {
133        /// Log sequence number of the offending record.
134        lsn: u64,
135        /// Deterministic reason the record was rejected.
136        reason: &'static str,
137    },
138    /// A delta-log record names a different base generation than the superblock.
139    BaseGenerationMismatch {
140        /// Base generation named by the superblock.
141        expected: u64,
142        /// Base generation found in the record.
143        found: u64,
144    },
145    /// A catalog name was not found in a bound schema.
146    UnknownName {
147        /// The kind of catalog entry (for example `"role"` or `"property key"`).
148        kind: &'static str,
149        /// The name that was not found.
150        name: String,
151    },
152    /// A required property was absent from a subject.
153    MissingProperty {
154        /// The property key that was required but absent.
155        key: PropertyKeyId,
156    },
157    /// A declared schema item conflicts with an existing catalog entry.
158    SchemaConflict {
159        /// The conflicting catalog name.
160        name: String,
161        /// Deterministic reason the declaration conflicts with the catalog.
162        reason: &'static str,
163    },
164    /// A numeric value was outside the representable `i64` range.
165    ValueOutOfRange,
166    /// A property key has no associated equality index.
167    NoEqualityIndex {
168        /// The property key lacking an equality index.
169        key: PropertyKeyId,
170    },
171}
172
173impl DbError {
174    /// Creates an IO error with operation context.
175    ///
176    /// # Performance
177    ///
178    /// This function is `O(1)`.
179    pub(crate) const fn io(operation: &'static str, source: io::Error) -> Self {
180        Self::Io { operation, source }
181    }
182
183    /// Creates an unsupported-query error.
184    ///
185    /// # Performance
186    ///
187    /// This function is `O(message.len())`.
188    pub(crate) fn unsupported(message: impl Into<String>) -> Self {
189        Self::UnsupportedQuery {
190            message: message.into(),
191        }
192    }
193
194    /// Creates an invalid-projection error.
195    ///
196    /// # Performance
197    ///
198    /// This function is `O(message.len())`.
199    pub(crate) fn invalid_projection(message: impl Into<String>) -> Self {
200        Self::InvalidProjection {
201            message: message.into(),
202        }
203    }
204
205    /// Creates an invalid-store error.
206    ///
207    /// # Performance
208    ///
209    /// This function is `O(message.len())`.
210    pub(crate) fn invalid_store(message: impl Into<String>) -> Self {
211        Self::InvalidStore {
212            message: message.into(),
213        }
214    }
215
216    /// Builds a traversal error from a deterministic reason.
217    ///
218    /// # Performance
219    ///
220    /// This function is `O(1)`.
221    pub(crate) const fn traversal(reason: &'static str) -> Self {
222        Self::Traversal { reason }
223    }
224}
225
226impl fmt::Display for DbError {
227    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
228        match self {
229            Self::AlreadyExists => formatter.write_str("database already exists"),
230            Self::NotFound => formatter.write_str("database not found"),
231            Self::WriterLockHeld => formatter.write_str("database writer lock is held"),
232            Self::IdOverflow => formatter.write_str("database ID overflow"),
233            Self::TransactionIdOverflow => formatter.write_str("transaction ID overflow"),
234            Self::CommitSeqOverflow => formatter.write_str("commit sequence overflow"),
235            Self::DuplicateCatalogName => formatter.write_str("duplicate catalog name"),
236            Self::DuplicateId => formatter.write_str("duplicate ID"),
237            Self::UnknownElement { id } => write!(formatter, "unknown element {}", id.get()),
238            Self::UnknownRelation { id } => write!(formatter, "unknown relation {}", id.get()),
239            Self::UnknownIncidence { id } => write!(formatter, "unknown incidence {}", id.get()),
240            Self::UnknownRole { id } => write!(formatter, "unknown role {}", id.get()),
241            Self::UnknownLabel { id } => write!(formatter, "unknown label {}", id.get()),
242            Self::UnknownRelationType { id } => {
243                write!(formatter, "unknown relation type {}", id.get())
244            }
245            Self::UnknownPropertyKey { id } => {
246                write!(formatter, "unknown property key {}", id.get())
247            }
248            Self::UnknownProjection { id } => write!(formatter, "unknown projection {}", id.get()),
249            Self::UnknownIndex { id } => write!(formatter, "unknown index {}", id.get()),
250            Self::PropertyTypeMismatch { expected, actual } => {
251                write!(
252                    formatter,
253                    "property type mismatch: expected {expected:?}, got {actual:?}"
254                )
255            }
256            Self::WrongPropertyFamily { expected, actual } => {
257                write!(
258                    formatter,
259                    "property family mismatch: expected {expected:?}, got {actual:?}"
260                )
261            }
262            Self::InvalidProjection { message } => {
263                write!(formatter, "invalid projection: {message}")
264            }
265            Self::EmptyQuery => formatter.write_str("empty query"),
266            Self::UnsupportedQuery { message } => write!(formatter, "unsupported query: {message}"),
267            Self::InvalidStore { message } => write!(formatter, "invalid store: {message}"),
268            Self::UnsupportedFormat { found, expected } => write!(
269                formatter,
270                "unsupported OXGDB format version: found {found}, this build requires {expected}"
271            ),
272            Self::Io { operation, source } => write!(formatter, "{operation} failed: {source}"),
273            Self::Traversal { reason } => write!(formatter, "traversal error: {reason}"),
274            Self::LogCorrupt { lsn, reason } => {
275                write!(formatter, "delta-log corrupt at lsn {lsn}: {reason}")
276            }
277            Self::BaseGenerationMismatch { expected, found } => write!(
278                formatter,
279                "base generation mismatch: superblock names {expected}, record has {found}"
280            ),
281            Self::UnknownName { kind, name } => write!(formatter, "unknown {kind} {name:?}"),
282            Self::MissingProperty { key } => {
283                write!(formatter, "missing property {}", key.get())
284            }
285            Self::SchemaConflict { name, reason } => {
286                write!(formatter, "schema conflict for {name:?}: {reason}")
287            }
288            Self::ValueOutOfRange => formatter.write_str("value out of representable i64 range"),
289            Self::NoEqualityIndex { key } => {
290                write!(
291                    formatter,
292                    "no equality index for property key {}",
293                    key.get()
294                )
295            }
296        }
297    }
298}
299
300impl std::error::Error for DbError {
301    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
302        match self {
303            Self::Io { source, .. } => Some(source),
304            Self::Traversal { .. }
305            | Self::AlreadyExists
306            | Self::NotFound
307            | Self::WriterLockHeld
308            | Self::IdOverflow
309            | Self::TransactionIdOverflow
310            | Self::CommitSeqOverflow
311            | Self::DuplicateCatalogName
312            | Self::DuplicateId
313            | Self::UnknownElement { .. }
314            | Self::UnknownRelation { .. }
315            | Self::UnknownIncidence { .. }
316            | Self::UnknownRole { .. }
317            | Self::UnknownLabel { .. }
318            | Self::UnknownRelationType { .. }
319            | Self::UnknownPropertyKey { .. }
320            | Self::UnknownProjection { .. }
321            | Self::UnknownIndex { .. }
322            | Self::PropertyTypeMismatch { .. }
323            | Self::WrongPropertyFamily { .. }
324            | Self::InvalidProjection { .. }
325            | Self::EmptyQuery
326            | Self::UnsupportedQuery { .. }
327            | Self::InvalidStore { .. }
328            | Self::UnsupportedFormat { .. }
329            | Self::LogCorrupt { .. }
330            | Self::BaseGenerationMismatch { .. }
331            | Self::UnknownName { .. }
332            | Self::MissingProperty { .. }
333            | Self::SchemaConflict { .. }
334            | Self::ValueOutOfRange
335            | Self::NoEqualityIndex { .. } => None,
336        }
337    }
338}