use thiserror::Error; use crate::storage::page_cache::CacheError; #[derive(Debug, Clone, Error, miette::Diagnostic)] pub enum LimboError { #[error("Corrupt database: {0}")] Corrupt(String), #[error("File is not a database")] NotADB, #[error("Internal error: {0}")] InternalError(String), #[error(transparent)] CacheError(#[from] CacheError), #[error("Database is full: {0}")] DatabaseFull(String), #[error("Parse error: {0}")] ParseError(String), #[error(transparent)] #[diagnostic(transparent)] LexerError(#[from] turso_parser::error::Error), #[error("Conversion error: {0}")] ConversionError(String), #[error("Env variable error: {0}")] EnvVarError(#[from] std::env::VarError), #[error("Transaction error: {0}")] TxError(String), #[error(transparent)] CompletionError(#[from] CompletionError), #[error("Locking error: {0}")] LockingError(String), #[error("Parse error: {0}")] ParseIntError(#[from] std::num::ParseIntError), #[error("Parse error: {0}")] ParseFloatError(#[from] std::num::ParseFloatError), #[error("Parse error: {0}")] InvalidDate(String), #[error("Parse error: {0}")] InvalidTime(String), #[error("Modifier parsing error: {0}")] InvalidModifier(String), #[error("Invalid argument supplied: {0}")] InvalidArgument(String), #[error("Invalid formatter supplied: {0}")] InvalidFormatter(String), #[error("Runtime error: {0}")] Constraint(String), #[error("Extension error: {0}")] ExtensionError(String), #[error("Runtime error: integer overflow")] IntegerOverflow, #[error("Runtime error: database table is locked")] TableLocked, #[error("Error: Resource is read-only")] ReadOnly, #[error("Database is busy")] Busy, #[error("Conflict: {0}")] Conflict(String), #[error("Database schema changed")] SchemaUpdated, #[error( "Database is empty, header does not exist - page 1 should've been allocated before this" )] Page1NotAlloc, #[error("Transaction terminated")] TxTerminated, #[error("Write-write conflict")] WriteWriteConflict, #[error("No such transaction ID: {0}")] NoSuchTransactionID(String), #[error("Null value")] NullValue, #[error("invalid column type")] InvalidColumnType, #[error("Invalid blob size, expected {0}")] InvalidBlobSize(usize), #[error("Planning error: {0}")] PlanningError(String), } // We only propagate the error kind so we can avoid string allocation in hot path and copying/cloning enums is cheaper impl From for LimboError { fn from(value: std::io::Error) -> Self { Self::CompletionError(CompletionError::IOError(value.kind())) } } #[cfg(target_family = "unix")] impl From for LimboError { fn from(value: rustix::io::Errno) -> Self { CompletionError::from(value).into() } } #[cfg(all(target_os = "linux", feature = "io_uring"))] impl From<&'static str> for LimboError { fn from(value: &'static str) -> Self { CompletionError::UringIOError(value).into() } } // We only propagate the error kind impl From for CompletionError { fn from(value: std::io::Error) -> Self { CompletionError::IOError(value.kind()) } } #[derive(Debug, Copy, Clone, PartialEq, Error)] pub enum CompletionError { #[error("I/O error: {0}")] IOError(std::io::ErrorKind), #[cfg(target_family = "unix")] #[error("I/O error: {0}")] RustixIOError(#[from] rustix::io::Errno), #[cfg(all(target_os = "linux", feature = "io_uring"))] #[error("I/O error: {0}")] // TODO: if needed create an enum for IO Uring errors so that we don't have to pass strings around UringIOError(&'static str), #[error("Completion was aborted")] Aborted, #[error("Decryption failed for page={page_idx}")] DecryptionError { page_idx: usize }, #[error("I/O error: partial write")] ShortWrite, #[error("Checksum mismatch on page {page_id}: expected {expected}, got {actual}")] ChecksumMismatch { page_id: usize, expected: u64, actual: u64, }, #[error("tursodb not compiled with checksum feature")] ChecksumNotEnabled, } #[macro_export] macro_rules! bail_parse_error { ($($arg:tt)*) => { return Err($crate::error::LimboError::ParseError(format!($($arg)*))) }; } #[macro_export] macro_rules! bail_corrupt_error { ($($arg:tt)*) => { return Err($crate::error::LimboError::Corrupt(format!($($arg)*))) }; } #[macro_export] macro_rules! bail_constraint_error { ($($arg:tt)*) => { return Err($crate::error::LimboError::Constraint(format!($($arg)*))) }; } impl From for LimboError { fn from(err: turso_ext::ResultCode) -> Self { LimboError::ExtensionError(err.to_string()) } } pub const SQLITE_CONSTRAINT: usize = 19; pub const SQLITE_CONSTRAINT_PRIMARYKEY: usize = SQLITE_CONSTRAINT | (6 << 8); #[allow(dead_code)] pub const SQLITE_CONSTRAINT_FOREIGNKEY: usize = SQLITE_CONSTRAINT | (7 << 8); pub const SQLITE_CONSTRAINT_NOTNULL: usize = SQLITE_CONSTRAINT | (5 << 8); pub const SQLITE_FULL: usize = 13; // we want this in autoincrement - incase if user inserts max allowed int pub const SQLITE_CONSTRAINT_UNIQUE: usize = 2067;