use std::{fmt, io};
use crate::{PropertyKeyId, catalog::PropertyFamily, value::PropertyType};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum IdFamily {
Element,
Relation,
Incidence,
Role,
Label,
RelationType,
PropertyKey,
Projection,
Index,
}
impl fmt::Display for IdFamily {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(match self {
Self::Element => "element",
Self::Relation => "relation",
Self::Incidence => "incidence",
Self::Role => "role",
Self::Label => "label",
Self::RelationType => "relation type",
Self::PropertyKey => "property key",
Self::Projection => "projection",
Self::Index => "index",
})
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum StorageError {
AlreadyExists,
NotFound,
Io {
operation: &'static str,
source: io::Error,
},
InvalidStore {
message: String,
},
UnsupportedFormat {
found: u32,
expected: u32,
},
LogCorrupt {
lsn: u64,
reason: &'static str,
},
BaseGenerationMismatch {
expected: u64,
found: u64,
},
}
impl StorageError {
pub(crate) const fn io(operation: &'static str, source: io::Error) -> Self {
Self::Io { operation, source }
}
pub(crate) fn invalid_store(message: impl Into<String>) -> Self {
Self::InvalidStore {
message: message.into(),
}
}
}
impl fmt::Display for StorageError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::AlreadyExists => formatter.write_str("database already exists"),
Self::NotFound => formatter.write_str("database not found"),
Self::Io { operation, source } => write!(formatter, "{operation} failed: {source}"),
Self::InvalidStore { message } => write!(formatter, "invalid store: {message}"),
Self::UnsupportedFormat { found, expected } => write!(
formatter,
"unsupported OXGDB format version: found {found}, this build requires {expected}"
),
Self::LogCorrupt { lsn, reason } => {
write!(formatter, "delta-log corrupt at lsn {lsn}: {reason}")
}
Self::BaseGenerationMismatch { expected, found } => write!(
formatter,
"base generation mismatch: superblock names {expected}, record has {found}"
),
}
}
}
impl std::error::Error for StorageError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Io { source, .. } => Some(source),
Self::AlreadyExists
| Self::NotFound
| Self::InvalidStore { .. }
| Self::UnsupportedFormat { .. }
| Self::LogCorrupt { .. }
| Self::BaseGenerationMismatch { .. } => None,
}
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum CatalogError {
UnknownId {
family: IdFamily,
id: u64,
},
UnknownName {
kind: &'static str,
name: String,
},
DuplicateName,
DuplicateId,
SchemaConflict {
name: String,
reason: &'static str,
},
}
impl fmt::Display for CatalogError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::UnknownId { family, id } => write!(formatter, "unknown {family} {id}"),
Self::UnknownName { kind, name } => write!(formatter, "unknown {kind} {name:?}"),
Self::DuplicateName => formatter.write_str("duplicate catalog name"),
Self::DuplicateId => formatter.write_str("duplicate ID"),
Self::SchemaConflict { name, reason } => {
write!(formatter, "schema conflict for {name:?}: {reason}")
}
}
}
}
impl std::error::Error for CatalogError {}
#[derive(Debug)]
#[non_exhaustive]
pub enum TxnError {
WriterLockHeld,
IdOverflow {
family: IdFamily,
},
TransactionIdOverflow,
CommitSeqOverflow,
}
impl fmt::Display for TxnError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::WriterLockHeld => formatter.write_str("database writer lock is held"),
Self::IdOverflow { family } => write!(formatter, "database {family} ID overflow"),
Self::TransactionIdOverflow => formatter.write_str("transaction ID overflow"),
Self::CommitSeqOverflow => formatter.write_str("commit sequence overflow"),
}
}
}
impl std::error::Error for TxnError {}
#[derive(Debug)]
#[non_exhaustive]
pub enum QueryError {
Empty,
Unsupported {
message: String,
},
PropertyTypeMismatch {
expected: PropertyType,
actual: PropertyType,
},
WrongPropertyFamily {
expected: PropertyFamily,
actual: PropertyFamily,
},
InvalidProjection {
message: String,
},
Traversal {
reason: &'static str,
},
MissingProperty {
key: PropertyKeyId,
},
ValueOutOfRange,
NoEqualityIndex {
key: PropertyKeyId,
},
}
impl fmt::Display for QueryError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Empty => formatter.write_str("empty query"),
Self::Unsupported { message } => write!(formatter, "unsupported query: {message}"),
Self::PropertyTypeMismatch { expected, actual } => {
write!(
formatter,
"property type mismatch: expected {expected:?}, got {actual:?}"
)
}
Self::WrongPropertyFamily { expected, actual } => {
write!(
formatter,
"property family mismatch: expected {expected:?}, got {actual:?}"
)
}
Self::InvalidProjection { message } => {
write!(formatter, "invalid projection: {message}")
}
Self::Traversal { reason } => write!(formatter, "traversal error: {reason}"),
Self::MissingProperty { key } => {
write!(formatter, "missing property {}", key.get())
}
Self::ValueOutOfRange => formatter.write_str("value out of representable i64 range"),
Self::NoEqualityIndex { key } => {
write!(
formatter,
"no equality index for property key {}",
key.get()
)
}
}
}
}
impl std::error::Error for QueryError {}
#[derive(Debug)]
#[non_exhaustive]
pub enum DbError {
Storage(StorageError),
Catalog(CatalogError),
Txn(TxnError),
Query(QueryError),
}
impl DbError {
pub(crate) const fn io(operation: &'static str, source: io::Error) -> Self {
Self::Storage(StorageError::Io { operation, source })
}
pub(crate) fn unsupported(message: impl Into<String>) -> Self {
Self::Query(QueryError::Unsupported {
message: message.into(),
})
}
pub(crate) fn invalid_projection(message: impl Into<String>) -> Self {
Self::Query(QueryError::InvalidProjection {
message: message.into(),
})
}
pub(crate) fn invalid_store(message: impl Into<String>) -> Self {
Self::Storage(StorageError::InvalidStore {
message: message.into(),
})
}
pub(crate) const fn traversal(reason: &'static str) -> Self {
Self::Query(QueryError::Traversal { reason })
}
pub(crate) fn unknown(id: impl Into<(IdFamily, u64)>) -> Self {
let (family, id) = id.into();
Self::Catalog(CatalogError::UnknownId { family, id })
}
#[must_use]
pub(crate) const fn id_overflow(family: IdFamily) -> Self {
Self::Txn(TxnError::IdOverflow { family })
}
}
impl fmt::Display for DbError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Storage(error) => fmt::Display::fmt(error, formatter),
Self::Catalog(error) => fmt::Display::fmt(error, formatter),
Self::Txn(error) => fmt::Display::fmt(error, formatter),
Self::Query(error) => fmt::Display::fmt(error, formatter),
}
}
}
impl std::error::Error for DbError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Storage(error) => std::error::Error::source(error),
Self::Catalog(error) => std::error::Error::source(error),
Self::Txn(error) => std::error::Error::source(error),
Self::Query(error) => std::error::Error::source(error),
}
}
}
impl From<StorageError> for DbError {
fn from(error: StorageError) -> Self {
Self::Storage(error)
}
}
impl From<CatalogError> for DbError {
fn from(error: CatalogError) -> Self {
Self::Catalog(error)
}
}
impl From<TxnError> for DbError {
fn from(error: TxnError) -> Self {
Self::Txn(error)
}
}
impl From<QueryError> for DbError {
fn from(error: QueryError) -> Self {
Self::Query(error)
}
}