use std::fmt;
#[derive(Debug, Clone)]
pub struct ValidationError {
pub path: Vec<String>,
pub message: String,
}
#[derive(Debug)]
pub enum DbError {
Io(std::io::Error),
Format(FormatError),
Schema(SchemaError),
Validation(ValidationError),
Transaction(TransactionError),
Query(QueryError),
NotImplemented,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DbErrorKind {
Io,
Format,
Schema,
Validation,
Transaction,
Query,
NotImplemented,
}
impl DbError {
pub fn kind(&self) -> DbErrorKind {
match self {
DbError::Io(_) => DbErrorKind::Io,
DbError::Format(_) => DbErrorKind::Format,
DbError::Schema(_) => DbErrorKind::Schema,
DbError::Validation(_) => DbErrorKind::Validation,
DbError::Transaction(_) => DbErrorKind::Transaction,
DbError::Query(_) => DbErrorKind::Query,
DbError::NotImplemented => DbErrorKind::NotImplemented,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct QueryError {
pub message: String,
}
#[derive(Debug)]
pub enum FormatError {
BadMagic { got: [u8; 4] },
TruncatedHeader { got: usize, expected: usize },
UnsupportedVersion { major: u16, minor: u16 },
TruncatedSuperblock { got: usize, expected: usize },
BadSuperblockMagic { got: [u8; 4] },
BadSuperblockChecksum,
TruncatedSegmentHeader { got: usize, expected: usize },
BadSegmentMagic { got: [u8; 4] },
BadSegmentHeaderChecksum,
BadSegmentPayloadChecksum,
SegmentPayloadPastEof,
InvalidCatalogPayload { message: String },
TruncatedRecordPayload,
RecordPayloadTypeMismatch,
InvalidRecordUtf8,
RecordPayloadUnsupportedType,
UnknownRecordPayloadVersion { got: u16 },
TrailingRecordPayload,
InvalidTxnPayload { message: String },
InvalidCheckpointPayload { message: String },
UncleanLogTail {
safe_end: u64,
reason: &'static str,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TransactionError {
NestedTransaction,
NoActiveTransaction,
}
#[derive(Debug, Clone)]
pub enum SchemaError {
InvalidFieldPath,
DuplicateCollectionName {
name: String,
},
UnknownCollection {
id: u32,
},
UnknownCollectionName {
name: String,
},
InvalidCollectionName,
InvalidSchemaVersion {
expected: u32,
got: u32,
},
SchemaVersionExhausted,
UnexpectedCollectionId {
expected: u32,
got: u32,
},
NoPrimaryKey {
collection_id: u32,
},
PrimaryFieldNotFound {
name: String,
},
PrimaryFieldMissingInSchema {
name: String,
},
RowMissingPrimary {
name: String,
},
RowUnknownField {
name: String,
},
RowMissingField {
name: String,
},
UniqueIndexViolation,
IncompatibleSchemaChange {
message: String,
},
MigrationRequired {
message: String,
},
IndexRowMissing {
collection_id: u32,
index_name: String,
},
PrimaryKeyTypeMismatch {
collection_id: u32,
},
}
impl fmt::Display for ValidationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.path.is_empty() {
return write!(f, "validation error: {}", self.message);
}
write!(
f,
"validation error at {}: {}",
self.path.join("."),
self.message
)
}
}
impl fmt::Display for DbError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DbError::Io(e) => write!(f, "i/o error: {e}"),
DbError::Format(e) => write!(f, "format error: {e}"),
DbError::Schema(e) => write!(f, "schema error: {e}"),
DbError::Validation(e) => write!(f, "{e}"),
DbError::Transaction(e) => write!(f, "transaction error: {e}"),
DbError::Query(e) => write!(f, "query error: {}", e.message),
DbError::NotImplemented => write!(f, "not implemented"),
}
}
}
impl fmt::Display for TransactionError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TransactionError::NestedTransaction => {
write!(f, "nested transactions are not supported")
}
TransactionError::NoActiveTransaction => {
write!(f, "no active transaction")
}
}
}
}
impl fmt::Display for FormatError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FormatError::BadMagic { got } => {
write!(f, "bad magic bytes: expected \"TDB0\", got {:02x?}", got)
}
FormatError::TruncatedHeader { got, expected } => {
write!(f, "truncated header: got {got} bytes, expected {expected}")
}
FormatError::UnsupportedVersion { major, minor } => {
write!(f, "unsupported format version {major}.{minor}")
}
FormatError::TruncatedSuperblock { got, expected } => {
write!(
f,
"truncated superblock: got {got} bytes, expected {expected}"
)
}
FormatError::BadSuperblockMagic { got } => {
write!(
f,
"bad superblock magic bytes: expected \"TSB0\", got {:02x?}",
got
)
}
FormatError::BadSuperblockChecksum => write!(f, "superblock checksum mismatch"),
FormatError::TruncatedSegmentHeader { got, expected } => {
write!(
f,
"truncated segment header: got {got} bytes, expected {expected}"
)
}
FormatError::BadSegmentMagic { got } => {
write!(
f,
"bad segment magic bytes: expected \"TSG0\", got {:02x?}",
got
)
}
FormatError::BadSegmentHeaderChecksum => write!(f, "segment header checksum mismatch"),
FormatError::BadSegmentPayloadChecksum => {
write!(f, "segment payload checksum mismatch")
}
FormatError::SegmentPayloadPastEof => {
write!(f, "segment payload extends past end of file")
}
FormatError::InvalidCatalogPayload { message } => {
write!(f, "invalid catalog payload: {message}")
}
FormatError::TruncatedRecordPayload => write!(f, "truncated record payload"),
FormatError::RecordPayloadTypeMismatch => {
write!(f, "record payload type does not match schema")
}
FormatError::InvalidRecordUtf8 => write!(f, "invalid UTF-8 in record string"),
FormatError::RecordPayloadUnsupportedType => {
write!(f, "unsupported type in record payload v1")
}
FormatError::UnknownRecordPayloadVersion { got } => {
write!(f, "unknown record payload version {got}")
}
FormatError::TrailingRecordPayload => write!(f, "trailing bytes in record payload"),
FormatError::InvalidTxnPayload { message } => {
write!(f, "invalid transaction marker payload: {message}")
}
FormatError::InvalidCheckpointPayload { message } => {
write!(f, "invalid checkpoint payload: {message}")
}
FormatError::UncleanLogTail { safe_end, reason } => {
write!(
f,
"unclean log tail (strict open): {reason}; safe truncate end offset {safe_end}"
)
}
}
}
}
impl fmt::Display for SchemaError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SchemaError::InvalidFieldPath => write!(f, "invalid field path"),
SchemaError::DuplicateCollectionName { name } => {
write!(f, "duplicate collection name: {name:?}")
}
SchemaError::UnknownCollection { id } => {
write!(f, "unknown collection id {id}")
}
SchemaError::UnknownCollectionName { name } => {
write!(f, "unknown collection name {name:?}")
}
SchemaError::InvalidCollectionName => write!(f, "invalid collection name"),
SchemaError::InvalidSchemaVersion { expected, got } => {
write!(f, "invalid schema version: expected {expected}, got {got}")
}
SchemaError::SchemaVersionExhausted => {
write!(f, "schema version limit reached (cannot bump further)")
}
SchemaError::UnexpectedCollectionId { expected, got } => {
write!(
f,
"unexpected collection id in catalog replay: expected {expected}, got {got}"
)
}
SchemaError::NoPrimaryKey { collection_id } => {
write!(
f,
"collection {collection_id} has no primary key (upgrade catalog or re-register)"
)
}
SchemaError::PrimaryFieldNotFound { name } => {
write!(f, "primary field {name:?} not found as a top-level field")
}
SchemaError::PrimaryFieldMissingInSchema { name } => {
write!(
f,
"schema update must retain top-level primary field {name:?}"
)
}
SchemaError::RowMissingPrimary { name } => {
write!(f, "insert row missing primary key field {name:?}")
}
SchemaError::RowUnknownField { name } => {
write!(f, "insert row has unknown field {name:?}")
}
SchemaError::RowMissingField { name } => {
write!(f, "insert row missing field {name:?}")
}
SchemaError::UniqueIndexViolation => write!(f, "unique index violation"),
SchemaError::IncompatibleSchemaChange { message } => {
write!(f, "incompatible schema change: {message}")
}
SchemaError::MigrationRequired { message } => {
write!(f, "migration required: {message}")
}
SchemaError::IndexRowMissing {
collection_id,
index_name,
} => {
write!(
f,
"index {index_name:?} on collection {collection_id} references missing row"
)
}
SchemaError::PrimaryKeyTypeMismatch { collection_id } => {
write!(
f,
"primary key type mismatch for collection {collection_id}"
)
}
}
}
}
impl std::error::Error for DbError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
DbError::Io(e) => Some(e),
DbError::Format(_) => None,
DbError::Schema(_) => None,
DbError::Validation(_) => None,
DbError::Transaction(_) => None,
DbError::Query(_) => None,
DbError::NotImplemented => None,
}
}
}
impl From<std::io::Error> for DbError {
fn from(value: std::io::Error) -> Self {
DbError::Io(value)
}
}