use crate::core::QueryError;
#[derive(Debug, thiserror::Error)]
pub enum SqlError {
#[error("`Op::In` requires `SqlValue::List`")]
InRequiresList,
#[error("`Op::IsNull` requires `SqlValue::Bool` (true = IS NULL, false = IS NOT NULL)")]
IsNullRequiresBool,
#[error("`Op::Between` requires `SqlValue::List([lo, hi])` with exactly two elements")]
BetweenRequiresTwoElementList,
#[error("`Op::JsonHasKey` requires `SqlValue::String`")]
JsonKeyRequiresString,
#[error("`Op::JsonHasAnyKey` / `Op::JsonHasAllKeys` require `SqlValue::List` of strings")]
JsonKeysRequiresList,
#[error("`Op::JsonContains` / `Op::JsonContainedBy` require `SqlValue::Json`")]
JsonOpRequiresJson,
#[error(
"`Op::ArrayContains` / `Op::ArrayContainedBy` / `Op::ArrayOverlap` require `SqlValue::Array`"
)]
ArrayOpRequiresArray,
#[error("bulk UPDATE requires a primary key on the model")]
MissingPrimaryKey,
#[error("relation aggregate `{kind}` requires a target column")]
RelAggregateMissingColumn { kind: &'static str },
#[error("empty `IN` list is not supported")]
EmptyInList,
#[error("INSERT requires at least one column")]
EmptyInsert,
#[error("INSERT columns ({columns}) and values ({values}) length mismatch")]
InsertShapeMismatch { columns: usize, values: usize },
#[error("UPDATE requires at least one assignment in `set`")]
EmptyUpdateSet,
#[error("bulk INSERT requires at least one row")]
EmptyBulkInsert,
#[error("bulk INSERT requires every row's `Auto<T>` PKs to agree on Set vs Unset; mixed Set/Unset is not supported")]
BulkAutoMixed,
#[error("bulk INSERT RETURNING returned {actual} rows but {expected} were inserted")]
BulkInsertReturningMismatch { expected: usize, actual: usize },
#[error("`WhereExpr::Or` with an empty branch list matches no rows; was that intentional?")]
EmptyOrBranch,
#[error("`WhereExpr::Xor` with an empty branch list matches no rows; was that intentional?")]
EmptyXorBranch,
#[error(
"{dialect} dialect query compilation is not implemented yet — \
lands in a future rustango v0.23.0 batch."
)]
DialectQueryCompilationNotImplemented { dialect: &'static str },
#[error("operator `{op}` is not supported by the `{dialect}` dialect")]
OperatorNotSupportedInDialect {
op: &'static str,
dialect: &'static str,
},
#[error("ON CONFLICT shape `{shape}` is not supported by the `{dialect}` dialect")]
ConflictNotSupportedInDialect {
shape: &'static str,
dialect: &'static str,
},
#[error("operator `{op}` is not supported by the `{dialect}` dialect")]
OpNotSupportedInDialect {
op: &'static str,
dialect: &'static str,
},
#[error("aggregate `{aggregate}` is not supported by the `{dialect}` dialect")]
AggregateNotSupportedInDialect {
aggregate: &'static str,
dialect: &'static str,
},
#[error("function `{func}` expects {expected} arg(s), got {got}")]
FunctionArityMismatch {
func: &'static str,
expected: &'static str,
got: usize,
},
#[error("CASE expression must have at least one WHEN branch")]
EmptyCaseBranches,
#[error("CASE WHEN branch condition must not be empty")]
EmptyCaseWhenCondition,
#[error(
"`OuterRef(\"{column}\")` used outside of a subquery — \
it can only appear inside Exists / NotExists / InSubquery / \
Subquery wrappers that know the enclosing query's table"
)]
OuterRefOutsideSubquery { column: &'static str },
#[error(
"`Expr::Aggregate(...)` used outside of an aggregate-accepting \
SQL slot — aggregates may only appear in SELECT projection, \
HAVING predicate, or ORDER BY of an aggregating query"
)]
AggregateOutsideAggregateContext,
#[error("JOIN `on` predicate must not be empty")]
EmptyJoinOnCondition,
#[error("aggregate `{aggregate}` is not supported by the `{dialect}` dialect")]
AggregateNotSupported {
aggregate: &'static str,
dialect: &'static str,
},
#[error("nested aggregate wrapper `{wrapper}` is not supported")]
NestedAggregateWrapper { wrapper: &'static str },
#[error("`{kind} JOIN` is not supported by the `{dialect}` dialect")]
JoinKindNotSupported {
kind: &'static str,
dialect: &'static str,
},
#[error(
"`JOIN LATERAL` is not supported by the `{dialect}` dialect (PostgreSQL / MySQL only)"
)]
LateralJoinNotSupported { dialect: &'static str },
}
#[derive(Debug, thiserror::Error)]
pub enum ExecError {
#[error(transparent)]
Query(#[from] QueryError),
#[error(transparent)]
Sql(#[from] SqlError),
#[error(transparent)]
Driver(#[from] sqlx::Error),
#[error("`insert_returning` requires `query.returning` to be non-empty; use `insert` instead")]
EmptyReturning,
#[error("foreign-key target `{table}` has no row with primary key {pk}")]
ForeignKeyTargetMissing {
table: &'static str,
pk: String,
},
#[error("model `{table}` has no `#[rustango(primary_key)]` field — required for FK lookup")]
MissingPrimaryKey { table: &'static str },
#[error("no content type registered for model `{table}` — seed `rustango_content_types` (run migrate)")]
ContentTypeNotRegistered { table: &'static str },
#[error("`{op}` filter matched {count} rows on `{table}`; expected at most 1")]
MultipleRowsReturned {
op: &'static str,
table: &'static str,
count: usize,
},
}
#[cfg(feature = "mysql")]
pub fn is_mysql_dup_index_error(e: &crate::sql::sqlx::Error) -> bool {
if let crate::sql::sqlx::Error::Database(db) = e {
return db.code().as_deref() == Some("42000")
|| db.message().contains("Duplicate key name");
}
false
}
#[cfg(not(feature = "mysql"))]
pub fn is_mysql_dup_index_error(_e: &crate::sql::sqlx::Error) -> bool {
false
}