sqlx_mysql/
error.rs

1use std::error::Error as StdError;
2use std::fmt::{self, Debug, Display, Formatter};
3
4use crate::protocol::response::ErrPacket;
5
6use std::borrow::Cow;
7
8pub(crate) use sqlx_core::error::*;
9
10/// An error returned from the MySQL database.
11pub struct MySqlDatabaseError(pub(super) ErrPacket);
12
13impl MySqlDatabaseError {
14    /// The [SQLSTATE](https://dev.mysql.com/doc/mysql-errors/8.0/en/server-error-reference.html) code for this error.
15    pub fn code(&self) -> Option<&str> {
16        self.0.sql_state.as_deref()
17    }
18
19    /// The [number](https://dev.mysql.com/doc/mysql-errors/8.0/en/server-error-reference.html)
20    /// for this error.
21    ///
22    /// MySQL tends to use SQLSTATE as a general error category, and the error number as a more
23    /// granular indication of the error.
24    pub fn number(&self) -> u16 {
25        self.0.error_code
26    }
27
28    /// The human-readable error message.
29    pub fn message(&self) -> &str {
30        &self.0.error_message
31    }
32}
33
34impl Debug for MySqlDatabaseError {
35    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
36        f.debug_struct("MySqlDatabaseError")
37            .field("code", &self.code())
38            .field("number", &self.number())
39            .field("message", &self.message())
40            .finish()
41    }
42}
43
44impl Display for MySqlDatabaseError {
45    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
46        if let Some(code) = &self.code() {
47            write!(f, "{} ({}): {}", self.number(), code, self.message())
48        } else {
49            write!(f, "{}: {}", self.number(), self.message())
50        }
51    }
52}
53
54impl StdError for MySqlDatabaseError {}
55
56impl DatabaseError for MySqlDatabaseError {
57    #[inline]
58    fn message(&self) -> &str {
59        self.message()
60    }
61
62    #[inline]
63    fn code(&self) -> Option<Cow<'_, str>> {
64        self.code().map(Cow::Borrowed)
65    }
66
67    #[doc(hidden)]
68    fn as_error(&self) -> &(dyn StdError + Send + Sync + 'static) {
69        self
70    }
71
72    #[doc(hidden)]
73    fn as_error_mut(&mut self) -> &mut (dyn StdError + Send + Sync + 'static) {
74        self
75    }
76
77    #[doc(hidden)]
78    fn into_error(self: Box<Self>) -> Box<dyn StdError + Send + Sync + 'static> {
79        self
80    }
81
82    fn kind(&self) -> ErrorKind {
83        match self.number() {
84            error_codes::ER_DUP_KEY
85            | error_codes::ER_DUP_ENTRY
86            | error_codes::ER_DUP_UNIQUE
87            | error_codes::ER_DUP_ENTRY_WITH_KEY_NAME
88            | error_codes::ER_DUP_UNKNOWN_IN_INDEX => ErrorKind::UniqueViolation,
89
90            error_codes::ER_NO_REFERENCED_ROW
91            | error_codes::ER_NO_REFERENCED_ROW_2
92            | error_codes::ER_ROW_IS_REFERENCED
93            | error_codes::ER_ROW_IS_REFERENCED_2
94            | error_codes::ER_FK_COLUMN_NOT_NULL
95            | error_codes::ER_FK_CANNOT_DELETE_PARENT => ErrorKind::ForeignKeyViolation,
96
97            error_codes::ER_BAD_NULL_ERROR | error_codes::ER_NO_DEFAULT_FOR_FIELD => {
98                ErrorKind::NotNullViolation
99            }
100
101            error_codes::ER_CHECK_CONSTRAINT_VIOLATED => ErrorKind::CheckViolation,
102
103            // https://mariadb.com/kb/en/e4025/
104            error_codes::mariadb::ER_CONSTRAINT_FAILED
105                // MySQL uses this code for a completely different error,
106                // but we can differentiate by SQLSTATE:
107                // <https://dev.mysql.com/doc/mysql-errors/8.4/en/server-error-reference.html#error_er_innodb_autoextend_size_out_of_range
108                if self.0.sql_state.as_deref() == Some("23000") =>
109            {
110                ErrorKind::CheckViolation
111            }
112
113            _ => ErrorKind::Other,
114        }
115    }
116}
117
118/// The MySQL server uses SQLSTATEs as a generic error category,
119/// and returns a `error_code` instead within the error packet.
120///
121/// For reference: <https://dev.mysql.com/doc/mysql-errors/8.0/en/server-error-reference.html>.
122pub(crate) mod error_codes {
123    /// Caused when a DDL operation creates duplicated keys.
124    pub const ER_DUP_KEY: u16 = 1022;
125    /// Caused when a DML operation tries create a duplicated entry for a key,
126    /// be it a unique or primary one.
127    pub const ER_DUP_ENTRY: u16 = 1062;
128    /// Similar to `ER_DUP_ENTRY`, but only present in NDB clusters.
129    ///
130    /// See: <https://github.com/mysql/mysql-server/blob/fbdaa4def30d269bc4de5b85de61de34b11c0afc/mysql-test/suite/stress/include/ddl7.inc#L68>.
131    pub const ER_DUP_UNIQUE: u16 = 1169;
132    /// Similar to `ER_DUP_ENTRY`, but with a formatted string message.
133    ///
134    /// See: <https://bugs.mysql.com/bug.php?id=46976>.
135    pub const ER_DUP_ENTRY_WITH_KEY_NAME: u16 = 1586;
136    /// Caused when a DDL operation to add a unique index fails,
137    /// because duplicate items were created by concurrent DML operations.
138    /// When this happens, the key is unknown, so the server can't use `ER_DUP_KEY`.
139    ///
140    /// For example: an `INSERT` operation creates duplicate `name` fields when `ALTER`ing a table and making `name` unique.
141    pub const ER_DUP_UNKNOWN_IN_INDEX: u16 = 1859;
142
143    /// Caused when inserting an entry with a column with a value that does not reference a foreign row.
144    pub const ER_NO_REFERENCED_ROW: u16 = 1216;
145    /// Caused when deleting a row that is referenced in other tables.
146    pub const ER_ROW_IS_REFERENCED: u16 = 1217;
147    /// Caused when deleting a row that is referenced in other tables.
148    /// This differs from `ER_ROW_IS_REFERENCED` in that the error message contains the affected constraint.
149    pub const ER_ROW_IS_REFERENCED_2: u16 = 1451;
150    /// Caused when inserting an entry with a column with a value that does not reference a foreign row.
151    /// This differs from `ER_NO_REFERENCED_ROW` in that the error message contains the affected constraint.
152    pub const ER_NO_REFERENCED_ROW_2: u16 = 1452;
153    /// Caused when creating a FK with `ON DELETE SET NULL` or `ON UPDATE SET NULL` to a column that is `NOT NULL`, or vice-versa.
154    pub const ER_FK_COLUMN_NOT_NULL: u16 = 1830;
155    /// Removed in 5.7.3.
156    pub const ER_FK_CANNOT_DELETE_PARENT: u16 = 1834;
157
158    /// Caused when inserting a NULL value to a column marked as NOT NULL.
159    pub const ER_BAD_NULL_ERROR: u16 = 1048;
160    /// Caused when inserting a DEFAULT value to a column marked as NOT NULL, which also doesn't have a default value set.
161    pub const ER_NO_DEFAULT_FOR_FIELD: u16 = 1364;
162
163    /// Caused when a check constraint is violated.
164    ///
165    /// Only available after 8.0.16.
166    pub const ER_CHECK_CONSTRAINT_VIOLATED: u16 = 3819;
167
168    pub(crate) mod mariadb {
169        /// Error code emitted by MariaDB for constraint errors: <https://mariadb.com/kb/en/e4025/>
170        ///
171        /// MySQL emits this code for a completely different error:
172        /// <https://dev.mysql.com/doc/mysql-errors/8.4/en/server-error-reference.html#error_er_innodb_autoextend_size_out_of_range>
173        ///
174        /// You also check that SQLSTATE is `23000`.
175        pub const ER_CONSTRAINT_FAILED: u16 = 4025;
176    }
177}