mirror of
https://github.com/aljazceru/turso.git
synced 2025-12-25 20:14:21 +01:00
Introduces a completion group abstraction that allows grouping multiple I/O completions together for coordinated tracking and error handling. This enables: - Tracking completion status of multiple I/O operations as a group - Detecting when all operations in a group have finished - Aborting all operations in a group atomically - Retrieving errors from any completion in the group The implementation uses intrusive linked lists for efficient membership tracking and atomic counters for outstanding operation counts. Each completion can be linked to a group using the new .link() method. This lays the groundwork for batch I/O operations and coordinated transaction handling in the storage layer.
171 lines
5.2 KiB
Rust
171 lines
5.2 KiB
Rust
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("Schema is locked for write")]
|
|
SchemaLocked,
|
|
#[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<std::io::Error> for LimboError {
|
|
fn from(value: std::io::Error) -> Self {
|
|
Self::CompletionError(CompletionError::IOError(value.kind()))
|
|
}
|
|
}
|
|
|
|
#[cfg(target_family = "unix")]
|
|
impl From<rustix::io::Errno> 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<std::io::Error> 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<turso_ext::ResultCode> 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);
|
|
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;
|