use sqlx::QueryBuilder;
use thiserror::Error;
use crate::{runtime::sql::Bindings, Bind};
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum QueryError {
#[error("IO")]
Io(#[source] sqlx::Error),
#[error("not found")]
NotFound(#[source] sqlx::Error),
#[error("sql")]
Sql(#[source] SqlError),
#[error("violation")]
Violation(#[source] ViolationError),
#[error("sqlx")]
Other(#[source] sqlx::Error),
#[error("internal error")]
InternalError(#[source] sqlx::Error),
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum ViolationError {
#[error("uniqueness violation")]
Unique(#[source] sqlx::Error),
#[error("foreign key violation")]
ForeignKey(#[source] sqlx::Error),
#[error("integrity check")]
Check(#[source] sqlx::Error),
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum SqlError {
#[error("data exception")]
DataException(#[source] sqlx::Error),
#[error("integrity constraint")]
IntegrityConstraint(#[source] sqlx::Error),
#[error("syntax")]
Syntax(#[source] sqlx::Error),
#[error("other")]
Other(#[source] sqlx::Error),
}
impl From<sqlx::Error> for QueryError {
fn from(err: sqlx::Error) -> Self {
use sqlx::Error as E;
match err {
E::RowNotFound => Self::NotFound(err),
E::Io(_)
| E::Protocol(_)
| E::Tls(_)
| E::Configuration(_)
| E::PoolTimedOut
| E::PoolClosed
| E::WorkerCrashed => Self::Io(err),
E::Database(ref e) => {
if e.is_unique_violation() {
return Self::Violation(ViolationError::Unique(err));
}
if e.is_foreign_key_violation() {
return Self::Violation(ViolationError::ForeignKey(err));
}
if e.is_check_violation() {
return Self::Violation(ViolationError::Check(err));
}
if let Some(c) = e.code() {
if c.len() < 5 {
return Self::InternalError(err);
}
return match &c.as_ref()[0..1] {
"22" => Self::Sql(SqlError::DataException(err)),
"23" => Self::Sql(SqlError::IntegrityConstraint(err)),
"42" => Self::Sql(SqlError::Syntax(err)),
_ => Self::Sql(SqlError::Other(err)),
};
}
Self::Other(err)
}
_ => Self::Other(err),
}
}
}
pub enum Cardinality {
None,
One,
Many,
}
pub enum Operation {
Select,
Insert,
Update,
Upsert,
Delete,
Other,
}
pub struct Query<T: Bind> {
pub op: Operation,
pub cardinality: Cardinality,
pub(crate) builder: QueryBuilder<'static, T::Database>,
pub(crate) bindings: Bindings<T>,
}
impl<T: Bind> Query<T> {
pub(crate) fn new(
op: Operation,
cardinality: Cardinality,
builder: QueryBuilder<'static, T::Database>,
bindings: Bindings<T>,
) -> Self {
Self {
op,
cardinality,
builder,
bindings,
}
}
pub fn sql(&self) -> &str {
self.builder.sql()
}
#[cfg(test)]
pub const fn bindings(&self) -> &Bindings<T> {
&self.bindings
}
}