#[derive(Debug, PartialEq, thiserror::Error)]
pub enum SqlError {
#[error("parse error: {detail}")]
Parse { detail: String },
#[error("table not found: {name}")]
UnknownTable { name: String },
#[error("unknown column '{column}' in table '{table}'")]
UnknownColumn { table: String, column: String },
#[error("ambiguous column '{column}' — qualify with table name")]
AmbiguousColumn { column: String },
#[error("type mismatch: {detail}")]
TypeMismatch { detail: String },
#[error("unsupported: {detail}")]
Unsupported { detail: String },
#[error("invalid function call: {detail}")]
InvalidFunction { detail: String },
#[error("invalid window frame: {detail}")]
InvalidWindowFrame { detail: String },
#[error("missing required field '{field}' for {context}")]
MissingField { field: String, context: String },
#[error("retryable schema change on {descriptor}")]
RetryableSchemaChanged { descriptor: String },
#[error(
"identifier '{name}' is reserved by NodeDB ({reason}); \
use a quoted identifier (e.g., \"{name}\") to bypass"
)]
ReservedIdentifier { name: String, reason: &'static str },
#[error("unsupported constraint: {feature}; {hint}")]
UnsupportedConstraint { feature: String, hint: String },
#[error(
"WITH RECURSIVE: only UNION / UNION ALL are allowed in the recursive term; \
{op} is not permitted"
)]
InvalidRecursiveSetOp { op: String },
#[error("WITH RECURSIVE: invalid self-reference to '{cte_name}' in recursive term: {reason}")]
InvalidRecursiveSelfRef { cte_name: String, reason: String },
#[error(
"WITH RECURSIVE CTE '{cte_name}': anchor produces {anchor_cols} column(s) \
but {declared_cols} were declared"
)]
RecursiveColumnMismatch {
cte_name: String,
anchor_cols: usize,
declared_cols: usize,
},
#[error(
"WITH RECURSIVE CTE '{cte_name}' exceeded max recursion depth {max_depth}; \
add a stricter termination condition or raise max_recursion_depth"
)]
RecursionDepthExceeded { cte_name: String, max_depth: usize },
#[error(
"collection '{name}' was dropped; \
restore with `{undrop_hint}` before retention elapses \
at {retention_expires_at_ns} ns"
)]
CollectionDeactivated {
name: String,
retention_expires_at_ns: u64,
undrop_hint: String,
},
}
impl From<crate::catalog::SqlCatalogError> for SqlError {
fn from(e: crate::catalog::SqlCatalogError) -> Self {
match e {
crate::catalog::SqlCatalogError::RetryableSchemaChanged { descriptor } => {
Self::RetryableSchemaChanged { descriptor }
}
crate::catalog::SqlCatalogError::CollectionDeactivated {
name,
retention_expires_at_ns,
} => {
let undrop_hint = format!("UNDROP COLLECTION {name}");
Self::CollectionDeactivated {
name,
retention_expires_at_ns,
undrop_hint,
}
}
}
}
}
impl From<nodedb_query::expr_parse::ExprParseError> for SqlError {
fn from(e: nodedb_query::expr_parse::ExprParseError) -> Self {
Self::Parse {
detail: e.to_string(),
}
}
}
impl From<sqlparser::parser::ParserError> for SqlError {
fn from(e: sqlparser::parser::ParserError) -> Self {
Self::Parse {
detail: e.to_string(),
}
}
}
pub type Result<T> = std::result::Result<T, SqlError>;