atmosphere_core/
query.rs

1//! Provides structures and enums for handling and executing SQL queries, along with error
2//! handling.
3//!
4//! This module includes custom error types for different database-related errors, enums for query
5//! operations and cardinality, and a struct for building and managing queries for database tables.
6
7use miette::Diagnostic;
8use sqlx::QueryBuilder;
9use thiserror::Error;
10
11use crate::{Bind, Result, Table, runtime::sql::Bindings};
12
13/// Errors that can occur while executing a database query.
14///
15/// This enum includes variants for IO errors, row not found errors, SQLSTATE errors, violation
16/// errors, and others, allowing for detailed categorization and handling of different database
17/// errors.
18#[derive(Debug, Diagnostic, Error)]
19#[non_exhaustive]
20pub enum QueryError {
21    /// Database communication (IO / Protocol / TLS) related errors
22    #[error("IO")]
23    #[diagnostic(code(atmosphere::query::io))]
24    Io(#[source] sqlx::Error),
25
26    /// Row not found errors
27    #[error("not found")]
28    #[diagnostic(code(atmosphere::query::not_found))]
29    NotFound(#[source] sqlx::Error),
30
31    /// SQLSTATE errors
32    #[error("sql")]
33    #[diagnostic(transparent)]
34    Sql(#[source] SqlError),
35
36    /// Violation errors
37    #[error("violation")]
38    #[diagnostic(transparent)]
39    Violation(#[source] ViolationError),
40
41    /// Catch-all for sqlx errors
42    #[error("sqlx")]
43    #[diagnostic(code(atmosphere::query::sqlx))]
44    Other(#[source] sqlx::Error),
45
46    /// Atmosphere internal error
47    #[error("internal error")]
48    #[diagnostic(code(atmosphere::query::internal))]
49    InternalError(#[source] sqlx::Error),
50}
51
52/// Represents errors related to constraint violations in the database.
53///
54/// Includes uniqueness violations, foreign key violations, and integrity check errors,
55/// encapsulating different types of constraint-related issues that can occur during database
56/// operations.
57#[derive(Debug, Diagnostic, Error)]
58#[non_exhaustive]
59pub enum ViolationError {
60    /// Row uniqueness violated
61    #[error("uniqueness violation")]
62    #[diagnostic(code(atmosphere::violation::uniqueness))]
63    Unique(#[source] sqlx::Error),
64
65    /// Foreign key violation
66    #[error("foreign key violation")]
67    #[diagnostic(code(atmosphere::violation::foreign_key))]
68    ForeignKey(#[source] sqlx::Error),
69
70    /// Integritry check failed
71    #[error("integrity check")]
72    #[diagnostic(code(atmosphere::violation::integrity))]
73    Check(#[source] sqlx::Error),
74}
75
76/// Encapsulates errors derived from SQLSTATE codes.
77///
78/// This enum categorizes various SQL errors such as data exceptions, integrity constraints, syntax
79/// errors, and others, based on their SQLSTATE classification.
80#[derive(Debug, Diagnostic, Error)]
81#[non_exhaustive]
82pub enum SqlError {
83    /// SQLSTATE Class 22
84    #[error("data exception")]
85    #[diagnostic(code(atmosphere::sqlstate::data))]
86    DataException(#[source] sqlx::Error),
87
88    /// SQLSTATE Class 23
89    #[error("integrity constraint")]
90    #[diagnostic(code(atmosphere::sqlstate::integrity))]
91    IntegrityConstraint(#[source] sqlx::Error),
92
93    /// SQLSTATE Class 42
94    #[error("syntax")]
95    #[diagnostic(code(atmosphere::sqlstate::syntax))]
96    Syntax(#[source] sqlx::Error),
97
98    /// All other classes
99    #[error("other")]
100    #[diagnostic(code(atmosphere::sqlstate::other))]
101    Other(#[source] sqlx::Error),
102}
103
104impl From<sqlx::Error> for QueryError {
105    fn from(err: sqlx::Error) -> Self {
106        use sqlx::Error as E;
107
108        match err {
109            E::RowNotFound => Self::NotFound(err),
110            E::Io(_)
111            | E::Protocol(_)
112            | E::Tls(_)
113            | E::Configuration(_)
114            | E::PoolTimedOut
115            | E::PoolClosed
116            | E::WorkerCrashed => Self::Io(err),
117            E::Database(ref e) => {
118                if e.is_unique_violation() {
119                    return Self::Violation(ViolationError::Unique(err));
120                }
121
122                if e.is_foreign_key_violation() {
123                    return Self::Violation(ViolationError::ForeignKey(err));
124                }
125
126                if e.is_check_violation() {
127                    return Self::Violation(ViolationError::Check(err));
128                }
129
130                // SQLSTATE code handling
131                // See https://en.wikipedia.org/wiki/SQLSTATE for reference
132
133                if let Some(c) = e.code() {
134                    if c.len() < 5 {
135                        return Self::InternalError(err);
136                    }
137
138                    return match &c.as_ref()[0..1] {
139                        "22" => Self::Sql(SqlError::DataException(err)),
140                        "23" => Self::Sql(SqlError::IntegrityConstraint(err)),
141                        "42" => Self::Sql(SqlError::Syntax(err)),
142                        _ => Self::Sql(SqlError::Other(err)),
143                    };
144                }
145
146                Self::Other(err)
147            }
148            _ => Self::Other(err),
149        }
150    }
151}
152
153/// Describes the cardinality of the rows affected by a query.
154#[derive(Clone, Copy, Debug, PartialEq, Eq)]
155pub enum Cardinality {
156    None,
157    One,
158    Many,
159}
160
161/// Describes the types of operations that a query performs.
162#[derive(Clone, Copy, Debug, PartialEq, Eq)]
163pub enum Operation {
164    Select,
165    Insert,
166    Update,
167    Upsert,
168    Delete,
169    Other,
170}
171
172/// Represents a atmosphere query over a database table.
173pub struct Query<T: Bind> {
174    pub op: Operation,
175    pub cardinality: Cardinality,
176    pub(crate) builder: QueryBuilder<'static, crate::Driver>,
177    pub(crate) bindings: Bindings<T>,
178}
179
180impl<T: Bind> Query<T> {
181    pub(crate) fn new(
182        op: Operation,
183        cardinality: Cardinality,
184        builder: QueryBuilder<'static, crate::Driver>,
185        bindings: Bindings<T>,
186    ) -> Self {
187        Self {
188            op,
189            cardinality,
190            builder,
191            bindings,
192        }
193    }
194
195    /// Access the generated sql
196    pub fn sql(&self) -> &str {
197        self.builder.sql()
198    }
199
200    /// Access the column bindings
201    pub const fn bindings(&self) -> &Bindings<T> {
202        &self.bindings
203    }
204}
205
206/// Describes possible results of executing a query.
207pub enum QueryResult<'t, T: Table + Bind> {
208    Execution(&'t Result<<crate::Driver as sqlx::Database>::QueryResult>),
209    Optional(&'t Result<Option<T>>),
210    One(&'t Result<T>),
211    Many(&'t Result<Vec<T>>),
212}