use thiserror::Error;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Error, Debug, Clone, PartialEq, Eq)]
pub enum Error {
#[error("table '{0}' not found")]
TableNotFound(String),
#[error("table '{0}' already exists")]
TableAlreadyExists(String),
#[error("table closed")]
TableClosed,
#[error("table columns don't match, expected {expected}, got {got}")]
TableColumnsNotMatch { expected: usize, got: usize },
#[error("cannot truncate table: active transactions have uncommitted changes")]
TableHasActiveTransactions,
#[error("column '{0}' not found")]
ColumnNotFound(String),
#[error("invalid column type")]
InvalidColumnType,
#[error("Vector dimension mismatch: expected {expected}, got {got}")]
VectorDimensionMismatch { expected: u16, got: u16 },
#[error("duplicate column")]
DuplicateColumn,
#[error("invalid value")]
InvalidValue,
#[error("invalid argument: {0}")]
InvalidArgument(String),
#[error("value for column {column} is too long, max {max}, got {got}")]
ValueTooLong {
column: String,
max: usize,
got: usize,
},
#[error("not null constraint failed for column {column}")]
NotNullConstraint { column: String },
#[error("primary key constraint failed with {row_id} already exists in this table")]
PrimaryKeyConstraint { row_id: i64 },
#[error("unique constraint failed for index {index} on column {column} with value {value}")]
UniqueConstraint {
index: String,
column: String,
value: String,
row_id: i64,
},
#[error("CHECK constraint failed for column {column}: {expression}")]
CheckConstraintViolation { column: String, expression: String },
#[error("foreign key constraint violation: column '{column}' in table '{table}' references '{ref_table}({ref_column})' — {detail}")]
ForeignKeyViolation {
table: String,
column: String,
ref_table: String,
ref_column: String,
detail: String,
},
#[error("transaction not started")]
TransactionNotStarted,
#[error("transaction already started")]
TransactionAlreadyStarted,
#[error("transaction already ended")]
TransactionEnded,
#[error("transaction aborted")]
TransactionAborted,
#[error("transaction already committed")]
TransactionCommitted,
#[error("transaction already closed")]
TransactionClosed,
#[error("index '{0}' not found")]
IndexNotFound(String),
#[error("index '{0}' already exists")]
IndexAlreadyExists(String),
#[error("index column not found")]
IndexColumnNotFound,
#[error("index is closed")]
IndexClosed,
#[error("engine is not open")]
EngineNotOpen,
#[error("engine is already open")]
EngineAlreadyOpen,
#[error("view '{0}' already exists")]
ViewAlreadyExists(String),
#[error("view '{0}' not found")]
ViewNotFound(String),
#[error("failed to acquire lock: {0}")]
LockAcquisitionFailed(String),
#[error("query returned no rows")]
NoRowsReturned,
#[error("no statements to execute")]
NoStatementsToExecute,
#[error("column index {index} out of bounds")]
ColumnIndexOutOfBounds { index: usize },
#[error("WAL manager is not running")]
WalNotRunning,
#[error("WAL file is closed")]
WalFileClosed,
#[error("WAL not initialized")]
WalNotInitialized,
#[error("database is locked by another process")]
DatabaseLocked,
#[error("cannot drop primary key column")]
CannotDropPrimaryKey,
#[error("cannot compare NULL with non-NULL value")]
NullComparison,
#[error("cannot compare incompatible types")]
IncomparableTypes,
#[error("not supported: {0}")]
NotSupported(String),
#[error("segment not found")]
SegmentNotFound,
#[error("expression evaluation failed")]
ExpressionEvaluation,
#[error("expression evaluation failed: {message}")]
ExpressionEvaluationWithMessage { message: String },
#[error("type conversion error: cannot convert {from} to {to}")]
TypeConversion { from: String, to: String },
#[error("parse error: {0}")]
Parse(String),
#[error("IO error: {message}")]
Io { message: String },
#[error("{message}")]
Internal { message: String },
#[error("table or view '{0}' not found")]
TableOrViewNotFound(String),
#[error("type error: {0}")]
Type(String),
#[error("division by zero")]
DivisionByZero,
#[error("query cancelled")]
QueryCancelled,
}
impl Error {
pub fn table_columns_not_match(expected: usize, got: usize) -> Self {
Error::TableColumnsNotMatch { expected, got }
}
pub fn value_too_long(column: impl Into<String>, max: usize, got: usize) -> Self {
Error::ValueTooLong {
column: column.into(),
max,
got,
}
}
pub fn not_null_constraint(column: impl Into<String>) -> Self {
Error::NotNullConstraint {
column: column.into(),
}
}
pub fn primary_key_constraint(row_id: i64) -> Self {
Error::PrimaryKeyConstraint { row_id }
}
pub fn unique_constraint(
index: impl Into<String>,
column: impl Into<String>,
value: impl Into<String>,
) -> Self {
Error::UniqueConstraint {
index: index.into(),
column: column.into(),
value: value.into(),
row_id: -1,
}
}
pub fn foreign_key_violation(
table: impl Into<String>,
column: impl Into<String>,
ref_table: impl Into<String>,
ref_column: impl Into<String>,
detail: impl Into<String>,
) -> Self {
Error::ForeignKeyViolation {
table: table.into(),
column: column.into(),
ref_table: ref_table.into(),
ref_column: ref_column.into(),
detail: detail.into(),
}
}
pub fn type_conversion(from: impl Into<String>, to: impl Into<String>) -> Self {
Error::TypeConversion {
from: from.into(),
to: to.into(),
}
}
pub fn parse(message: impl Into<String>) -> Self {
Error::Parse(message.into())
}
pub fn io(message: impl Into<String>) -> Self {
Error::Io {
message: message.into(),
}
}
pub fn internal(message: impl Into<String>) -> Self {
Error::Internal {
message: message.into(),
}
}
pub fn expression_evaluation(message: impl Into<String>) -> Self {
Error::ExpressionEvaluationWithMessage {
message: message.into(),
}
}
pub fn invalid_argument(message: impl Into<String>) -> Self {
Error::InvalidArgument(message.into())
}
pub fn is_not_found(&self) -> bool {
matches!(
self,
Error::TableNotFound(_)
| Error::ColumnNotFound(_)
| Error::IndexNotFound(_)
| Error::IndexColumnNotFound
| Error::SegmentNotFound
| Error::ViewNotFound(_)
| Error::TableOrViewNotFound(_)
)
}
pub fn is_constraint_violation(&self) -> bool {
matches!(
self,
Error::NotNullConstraint { .. }
| Error::PrimaryKeyConstraint { .. }
| Error::UniqueConstraint { .. }
| Error::ForeignKeyViolation { .. }
)
}
pub fn is_pk_or_unique_violation(&self) -> bool {
matches!(
self,
Error::PrimaryKeyConstraint { .. } | Error::UniqueConstraint { .. }
)
}
pub fn is_transaction_error(&self) -> bool {
matches!(
self,
Error::TransactionNotStarted
| Error::TransactionAlreadyStarted
| Error::TransactionEnded
| Error::TransactionAborted
| Error::TransactionCommitted
| Error::TransactionClosed
)
}
}
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Self {
Error::Io {
message: err.to_string(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_display() {
assert_eq!(
Error::TableNotFound("users".to_string()).to_string(),
"table 'users' not found"
);
assert_eq!(
Error::TableAlreadyExists("users".to_string()).to_string(),
"table 'users' already exists"
);
assert_eq!(
Error::ColumnNotFound("email".to_string()).to_string(),
"column 'email' not found"
);
assert_eq!(Error::InvalidValue.to_string(), "invalid value");
assert_eq!(
Error::TransactionNotStarted.to_string(),
"transaction not started"
);
assert_eq!(
Error::IndexNotFound("idx_email".to_string()).to_string(),
"index 'idx_email' not found"
);
assert_eq!(
Error::NullComparison.to_string(),
"cannot compare NULL with non-NULL value"
);
}
#[test]
fn test_structured_error_display() {
let err = Error::table_columns_not_match(5, 3);
assert_eq!(
err.to_string(),
"table columns don't match, expected 5, got 3"
);
let err = Error::value_too_long("name", 100, 150);
assert_eq!(
err.to_string(),
"value for column name is too long, max 100, got 150"
);
let err = Error::not_null_constraint("email");
assert_eq!(
err.to_string(),
"not null constraint failed for column email"
);
let err = Error::primary_key_constraint(42);
assert_eq!(
err.to_string(),
"primary key constraint failed with 42 already exists in this table"
);
let err = Error::unique_constraint("idx_email", "email", "test@example.com");
assert_eq!(
err.to_string(),
"unique constraint failed for index idx_email on column email with value test@example.com"
);
}
#[test]
fn test_error_classification() {
assert!(Error::TableNotFound("t".to_string()).is_not_found());
assert!(Error::ColumnNotFound("c".to_string()).is_not_found());
assert!(Error::IndexNotFound("i".to_string()).is_not_found());
assert!(!Error::InvalidValue.is_not_found());
assert!(Error::not_null_constraint("col").is_constraint_violation());
assert!(Error::primary_key_constraint(1).is_constraint_violation());
assert!(Error::unique_constraint("idx", "col", "val").is_constraint_violation());
assert!(!Error::TableNotFound("t".to_string()).is_constraint_violation());
assert!(Error::TransactionNotStarted.is_transaction_error());
assert!(Error::TransactionCommitted.is_transaction_error());
assert!(!Error::TableNotFound("t".to_string()).is_transaction_error());
}
#[test]
fn test_error_equality() {
assert_eq!(
Error::TableNotFound("t".to_string()),
Error::TableNotFound("t".to_string())
);
assert_ne!(
Error::TableNotFound("t".to_string()),
Error::TableAlreadyExists("t".to_string())
);
let err1 = Error::table_columns_not_match(5, 3);
let err2 = Error::table_columns_not_match(5, 3);
let err3 = Error::table_columns_not_match(5, 4);
assert_eq!(err1, err2);
assert_ne!(err1, err3);
}
#[test]
fn test_io_error_conversion() {
let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
let err: Error = io_err.into();
assert!(matches!(err, Error::Io { .. }));
assert!(err.to_string().contains("file not found"));
}
}