Skip to main content

sqlstate/
lib.rs

1#![doc = "# sqlstate\n\n[![Crates.io](https://img.shields.io/crates/v/sqlstate.svg)](https://crates.io/crates/sqlstate)\n[![Documentation](https://docs.rs/sqlstate/badge.svg)](https://docs.rs/sqlstate)\n\n[SQLSTATE](https://en.wikipedia.org/wiki/SQLSTATE) codes with PostgreSQL, MySQL and MariaDB constants.\n\n```rust\nlet code: sqlstate::SqlState = \"40001\".parse().unwrap();\nassert!(matches!(code, sqlstate::pg::T_R_SERIALIZATION_FAILURE));\n```\n"include_str!("../README.md")]
2#![no_std]
3#![warn(
4    unreachable_pub,
5    missing_debug_implementations,
6    missing_copy_implementations,
7    missing_docs,
8    clippy::unwrap_used
9)]
10#![cfg_attr(test, allow(clippy::unwrap_used))]
11
12use core::error::Error as StdError;
13use core::fmt;
14use core::str::FromStr;
15
16macro_rules! codes {
17    ($($(#[$doc:meta])* $name:ident = $code:literal;)*) => {
18        $(
19            $(#[$doc])*
20            pub const $name: $crate::SqlState =
21                $crate::SqlState::new(*$code).expect("invalid SQLSTATE");
22        )*
23    };
24}
25
26pub mod mysql_like;
27pub mod pg;
28
29/// A [SQLSTATE](https://en.wikipedia.org/wiki/SQLSTATE) error code, including
30/// custom codes outside the standard, usable as a `match` pattern.
31///
32/// ```rust
33/// use sqlstate::{SqlState, SqlStateClass};
34///
35/// const CUSTOM: SqlState = SqlState::new(*b"P1001").unwrap();
36///
37/// let code: SqlState = "P1001".parse().unwrap();
38/// assert!(matches!(code, CUSTOM));
39/// assert_eq!(code.as_str(), "P1001");
40/// assert_eq!(code.class().as_str(), "P1");
41/// assert!(!code.class().is_standard());
42/// assert_eq!(code.subclass(), "001");
43///
44/// let code: SqlState = "23505".parse().unwrap();
45/// assert_eq!(code.class(), SqlStateClass::INTEGRITY_CONSTRAINT_VIOLATION);
46/// ```
47#[derive(#[automatically_derived]#[doc(hidden)]unsafe impl ::core::clone::TrivialClone
    for SqlState {
}
#[automatically_derived]impl ::core::clone::Clone for SqlState {
    #[inline]
    #[rustc_trivial_field_reads]
    fn clone(&self) -> Self {
        let _: ::core::clone::AssertParamIsClone<[u8; 5]>;
        *self
    }
}Clone, #[automatically_derived]impl ::core::marker::Copy for SqlState { }Copy, #[automatically_derived]impl ::core::marker::StructuralPartialEq for SqlState
    {
}
#[automatically_derived]impl ::core::cmp::PartialEq for SqlState {
    #[inline]
    fn eq(&self, other: &Self) -> bool { self.0 == other.0 }
}PartialEq, #[automatically_derived]impl ::core::cmp::Eq for SqlState {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {
        let _: ::core::cmp::AssertParamIsEq<[u8; 5]>;
    }
}Eq, #[automatically_derived]impl ::core::hash::Hash for SqlState {
    #[inline]
    fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) {
        ::core::hash::Hash::hash(&self.0, state)
    }
}Hash, #[automatically_derived]impl ::core::cmp::PartialOrd for SqlState {
    #[inline]
    fn partial_cmp(&self, other: &Self)
        -> ::core::option::Option<::core::cmp::Ordering> {
        ::core::option::Option::Some(::core::cmp::Ord::cmp(self, other))
    }
}PartialOrd, #[automatically_derived]impl ::core::cmp::Ord for SqlState {
    #[inline]
    fn cmp(&self, other: &Self) -> ::core::cmp::Ordering {
        ::core::cmp::Ord::cmp(&self.0, &other.0)
    }
}Ord)]
48pub struct SqlState([u8; 5]);
49
50impl SqlState {
51    /// Returns `None` unless every byte is an ASCII digit or uppercase letter.
52    pub const fn new(code: [u8; 5]) -> Option<Self> {
53        let mut i = 0;
54        while i < code.len() {
55            if !#[allow(non_exhaustive_omitted_patterns)] match code[i] {
    b'0'..=b'9' | b'A'..=b'Z' => true,
    _ => false,
}matches!(code[i], b'0'..=b'9' | b'A'..=b'Z') {
56                return None;
57            }
58            i += 1;
59        }
60        Some(Self(code))
61    }
62
63    /// The five character code.
64    pub fn as_str(&self) -> &str {
65        core::str::from_utf8(&self.0).expect("a SQLSTATE consists of ASCII bytes")
66    }
67
68    /// The [`SqlStateClass`], made of the first two characters.
69    pub fn class(&self) -> SqlStateClass {
70        SqlStateClass([self.0[0], self.0[1]])
71    }
72
73    /// The subclass, made of the last three characters, where `000` means none.
74    pub fn subclass(&self) -> &str {
75        &self.as_str()[2..]
76    }
77}
78
79impl fmt::Debug for SqlState {
80    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81        f.debug_tuple("SqlState").field(&self.as_str()).finish()
82    }
83}
84
85impl fmt::Display for SqlState {
86    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87        f.write_str(self.as_str())
88    }
89}
90
91impl FromStr for SqlState {
92    type Err = InvalidSqlState;
93
94    fn from_str(s: &str) -> Result<Self, Self::Err> {
95        <[u8; 5]>::try_from(s.as_bytes())
96            .ok()
97            .and_then(Self::new)
98            .ok_or(InvalidSqlState)
99    }
100}
101
102/// The error parsing a [`SqlState`] from a string that is not five characters
103/// from `0`-`9` and `A`-`Z`.
104#[derive(#[automatically_derived]impl ::core::fmt::Debug for InvalidSqlState {
    #[inline]
    #[rustc_trivial_field_reads]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::write_str(f, "InvalidSqlState")
    }
}Debug, #[automatically_derived]#[doc(hidden)]unsafe impl ::core::clone::TrivialClone
    for InvalidSqlState {
}
#[automatically_derived]impl ::core::clone::Clone for InvalidSqlState {
    #[inline]
    #[rustc_trivial_field_reads]
    fn clone(&self) -> Self { *self }
}Clone, #[automatically_derived]impl ::core::marker::Copy for InvalidSqlState { }Copy, #[automatically_derived]impl ::core::marker::StructuralPartialEq for
    InvalidSqlState {
}
#[automatically_derived]impl ::core::cmp::PartialEq for InvalidSqlState {
    #[inline]
    fn eq(&self, other: &Self) -> bool { true }
}PartialEq, #[automatically_derived]impl ::core::cmp::Eq for InvalidSqlState { }Eq)]
105pub struct InvalidSqlState;
106
107impl fmt::Display for InvalidSqlState {
108    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109        f.write_str("a SQLSTATE consists of five characters from `0`-`9` and `A`-`Z`")
110    }
111}
112
113impl StdError for InvalidSqlState {}
114
115/// The class of a [`SqlState`], with a constant for each class
116/// [SQL:2011](https://en.wikipedia.org/wiki/SQLSTATE) defines.
117#[derive(#[automatically_derived]#[doc(hidden)]unsafe impl ::core::clone::TrivialClone
    for SqlStateClass {
}
#[automatically_derived]impl ::core::clone::Clone for SqlStateClass {
    #[inline]
    #[rustc_trivial_field_reads]
    fn clone(&self) -> Self {
        let _: ::core::clone::AssertParamIsClone<[u8; 2]>;
        *self
    }
}Clone, #[automatically_derived]impl ::core::marker::Copy for SqlStateClass { }Copy, #[automatically_derived]impl ::core::marker::StructuralPartialEq for
    SqlStateClass {
}
#[automatically_derived]impl ::core::cmp::PartialEq for SqlStateClass {
    #[inline]
    fn eq(&self, other: &Self) -> bool { self.0 == other.0 }
}PartialEq, #[automatically_derived]impl ::core::cmp::Eq for SqlStateClass {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {
        let _: ::core::cmp::AssertParamIsEq<[u8; 2]>;
    }
}Eq, #[automatically_derived]impl ::core::hash::Hash for SqlStateClass {
    #[inline]
    fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) {
        ::core::hash::Hash::hash(&self.0, state)
    }
}Hash, #[automatically_derived]impl ::core::cmp::PartialOrd for SqlStateClass {
    #[inline]
    fn partial_cmp(&self, other: &Self)
        -> ::core::option::Option<::core::cmp::Ordering> {
        ::core::option::Option::Some(::core::cmp::Ord::cmp(self, other))
    }
}PartialOrd, #[automatically_derived]impl ::core::cmp::Ord for SqlStateClass {
    #[inline]
    fn cmp(&self, other: &Self) -> ::core::cmp::Ordering {
        ::core::cmp::Ord::cmp(&self.0, &other.0)
    }
}Ord)]
118pub struct SqlStateClass([u8; 2]);
119
120impl SqlStateClass {
121    /// The two character code of the class.
122    pub fn as_str(&self) -> &str {
123        core::str::from_utf8(&self.0).expect("a SQLSTATE consists of ASCII bytes")
124    }
125
126    /// Whether the class starts with `0`-`4` or `A`-`H`, which the SQL standard reserves.
127    pub fn is_standard(&self) -> bool {
128        #[allow(non_exhaustive_omitted_patterns)] match self.0[0] {
    b'0'..=b'4' | b'A'..=b'H' => true,
    _ => false,
}matches!(self.0[0], b'0'..=b'4' | b'A'..=b'H')
129    }
130}
131
132impl fmt::Debug for SqlStateClass {
133    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
134        f.debug_tuple("SqlStateClass")
135            .field(&self.as_str())
136            .finish()
137    }
138}
139
140impl fmt::Display for SqlStateClass {
141    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142        f.write_str(self.as_str())
143    }
144}
145
146macro_rules! sql_state_classes {
147    ($($name:ident = $code:literal, $condition:literal;)*) => {
148        impl SqlStateClass {
149            $(
150                #[doc = concat!("`", $code, "`, ", $condition, ".")]
151                pub const $name: Self = {
152                    let code = $code.as_bytes();
153                    Self([code[0], code[1]])
154                };
155            )*
156        }
157    };
158}
159
160impl SqlStateClass {
    #[doc = "`00`, successful completion."]
    pub const SUCCESSFUL_COMPLETION: Self =
        { let code = "00".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`01`, warning."]
    pub const WARNING: Self =
        { let code = "01".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`02`, no data."]
    pub const NO_DATA: Self =
        { let code = "02".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`07`, dynamic SQL error."]
    pub const DYNAMIC_SQL_ERROR: Self =
        { let code = "07".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`08`, connection exception."]
    pub const CONNECTION_EXCEPTION: Self =
        { let code = "08".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`09`, triggered action exception."]
    pub const TRIGGERED_ACTION_EXCEPTION: Self =
        { let code = "09".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`0A`, feature not supported."]
    pub const FEATURE_NOT_SUPPORTED: Self =
        { let code = "0A".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`0D`, invalid target type specification."]
    pub const INVALID_TARGET_TYPE_SPECIFICATION: Self =
        { let code = "0D".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`0E`, invalid schema name list specification."]
    pub const INVALID_SCHEMA_NAME_LIST_SPECIFICATION: Self =
        { let code = "0E".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`0F`, locator exception."]
    pub const LOCATOR_EXCEPTION: Self =
        { let code = "0F".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`0K`, resignal when handler not active."]
    pub const RESIGNAL_WHEN_HANDLER_NOT_ACTIVE: Self =
        { let code = "0K".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`0L`, invalid grantor."]
    pub const INVALID_GRANTOR: Self =
        { let code = "0L".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`0M`, invalid SQL-invoked procedure reference."]
    pub const INVALID_SQL_INVOKED_PROCEDURE_REFERENCE: Self =
        { let code = "0M".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`0N`, SQL/XML mapping error."]
    pub const SQL_XML_MAPPING_ERROR: Self =
        { let code = "0N".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`0P`, invalid role specification."]
    pub const INVALID_ROLE_SPECIFICATION: Self =
        { let code = "0P".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`0S`, invalid transform group name specification."]
    pub const INVALID_TRANSFORM_GROUP_NAME_SPECIFICATION: Self =
        { let code = "0S".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`0T`, target table disagrees with cursor specification."]
    pub const TARGET_TABLE_DISAGREES_WITH_CURSOR_SPECIFICATION: Self =
        { let code = "0T".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`0U`, attempt to assign to non-updatable column."]
    pub const ATTEMPT_TO_ASSIGN_TO_NON_UPDATABLE_COLUMN: Self =
        { let code = "0U".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`0V`, attempt to assign to ordering column."]
    pub const ATTEMPT_TO_ASSIGN_TO_ORDERING_COLUMN: Self =
        { let code = "0V".as_bytes(); Self([code[0], code[1]]) };
    #[doc =
    "`0W`, prohibited statement encountered during trigger execution."]
    pub const PROHIBITED_STATEMENT_ENCOUNTERED_DURING_TRIGGER_EXECUTION: Self
        =
        { let code = "0W".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`0X`, invalid foreign server specification."]
    pub const INVALID_FOREIGN_SERVER_SPECIFICATION: Self =
        { let code = "0X".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`0Y`, pass-through specific condition."]
    pub const PASS_THROUGH_SPECIFIC_CONDITION: Self =
        { let code = "0Y".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`0Z`, diagnostics exception."]
    pub const DIAGNOSTICS_EXCEPTION: Self =
        { let code = "0Z".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`10`, XQuery error."]
    pub const XQUERY_ERROR: Self =
        { let code = "10".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`20`, case not found for case statement."]
    pub const CASE_NOT_FOUND_FOR_CASE_STATEMENT: Self =
        { let code = "20".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`21`, cardinality violation."]
    pub const CARDINALITY_VIOLATION: Self =
        { let code = "21".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`22`, data exception."]
    pub const DATA_EXCEPTION: Self =
        { let code = "22".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`23`, integrity constraint violation."]
    pub const INTEGRITY_CONSTRAINT_VIOLATION: Self =
        { let code = "23".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`24`, invalid cursor state."]
    pub const INVALID_CURSOR_STATE: Self =
        { let code = "24".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`25`, invalid transaction state."]
    pub const INVALID_TRANSACTION_STATE: Self =
        { let code = "25".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`26`, invalid SQL statement name."]
    pub const INVALID_SQL_STATEMENT_NAME: Self =
        { let code = "26".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`27`, triggered data change violation."]
    pub const TRIGGERED_DATA_CHANGE_VIOLATION: Self =
        { let code = "27".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`28`, invalid authorization specification."]
    pub const INVALID_AUTHORIZATION_SPECIFICATION: Self =
        { let code = "28".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`2B`, dependent privilege descriptors still exist."]
    pub const DEPENDENT_PRIVILEGE_DESCRIPTORS_STILL_EXIST: Self =
        { let code = "2B".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`2C`, invalid character set name."]
    pub const INVALID_CHARACTER_SET_NAME: Self =
        { let code = "2C".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`2D`, invalid transaction termination."]
    pub const INVALID_TRANSACTION_TERMINATION: Self =
        { let code = "2D".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`2E`, invalid connection name."]
    pub const INVALID_CONNECTION_NAME: Self =
        { let code = "2E".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`2F`, SQL routine exception."]
    pub const SQL_ROUTINE_EXCEPTION: Self =
        { let code = "2F".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`2H`, invalid collation name."]
    pub const INVALID_COLLATION_NAME: Self =
        { let code = "2H".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`30`, invalid SQL statement identifier."]
    pub const INVALID_SQL_STATEMENT_IDENTIFIER: Self =
        { let code = "30".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`33`, invalid SQL descriptor name."]
    pub const INVALID_SQL_DESCRIPTOR_NAME: Self =
        { let code = "33".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`34`, invalid cursor name."]
    pub const INVALID_CURSOR_NAME: Self =
        { let code = "34".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`35`, invalid condition number."]
    pub const INVALID_CONDITION_NUMBER: Self =
        { let code = "35".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`36`, cursor sensitivity exception."]
    pub const CURSOR_SENSITIVITY_EXCEPTION: Self =
        { let code = "36".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`38`, external routine exception."]
    pub const EXTERNAL_ROUTINE_EXCEPTION: Self =
        { let code = "38".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`39`, external routine invocation exception."]
    pub const EXTERNAL_ROUTINE_INVOCATION_EXCEPTION: Self =
        { let code = "39".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`3B`, savepoint exception."]
    pub const SAVEPOINT_EXCEPTION: Self =
        { let code = "3B".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`3C`, ambiguous cursor name."]
    pub const AMBIGUOUS_CURSOR_NAME: Self =
        { let code = "3C".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`3D`, invalid catalog name."]
    pub const INVALID_CATALOG_NAME: Self =
        { let code = "3D".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`3F`, invalid schema name."]
    pub const INVALID_SCHEMA_NAME: Self =
        { let code = "3F".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`40`, transaction rollback."]
    pub const TRANSACTION_ROLLBACK: Self =
        { let code = "40".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`42`, syntax error or access rule violation."]
    pub const SYNTAX_ERROR_OR_ACCESS_RULE_VIOLATION: Self =
        { let code = "42".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`44`, with check option violation."]
    pub const WITH_CHECK_OPTION_VIOLATION: Self =
        { let code = "44".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`45`, unhandled user-defined exception."]
    pub const UNHANDLED_USER_DEFINED_EXCEPTION: Self =
        { let code = "45".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`46`, OLB-specific error."]
    pub const OLB_SPECIFIC_ERROR: Self =
        { let code = "46".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`HV`, FDW-specific condition."]
    pub const FDW_SPECIFIC_CONDITION: Self =
        { let code = "HV".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`HW`, datalink exception."]
    pub const DATALINK_EXCEPTION: Self =
        { let code = "HW".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`HY`, CLI-specific condition."]
    pub const CLI_SPECIFIC_CONDITION: Self =
        { let code = "HY".as_bytes(); Self([code[0], code[1]]) };
    #[doc = "`HZ`, remote database access."]
    pub const REMOTE_DATABASE_ACCESS: Self =
        { let code = "HZ".as_bytes(); Self([code[0], code[1]]) };
}sql_state_classes! {
161    SUCCESSFUL_COMPLETION = "00", "successful completion";
162    WARNING = "01", "warning";
163    NO_DATA = "02", "no data";
164    DYNAMIC_SQL_ERROR = "07", "dynamic SQL error";
165    CONNECTION_EXCEPTION = "08", "connection exception";
166    TRIGGERED_ACTION_EXCEPTION = "09", "triggered action exception";
167    FEATURE_NOT_SUPPORTED = "0A", "feature not supported";
168    INVALID_TARGET_TYPE_SPECIFICATION = "0D", "invalid target type specification";
169    INVALID_SCHEMA_NAME_LIST_SPECIFICATION = "0E", "invalid schema name list specification";
170    LOCATOR_EXCEPTION = "0F", "locator exception";
171    RESIGNAL_WHEN_HANDLER_NOT_ACTIVE = "0K", "resignal when handler not active";
172    INVALID_GRANTOR = "0L", "invalid grantor";
173    INVALID_SQL_INVOKED_PROCEDURE_REFERENCE = "0M", "invalid SQL-invoked procedure reference";
174    SQL_XML_MAPPING_ERROR = "0N", "SQL/XML mapping error";
175    INVALID_ROLE_SPECIFICATION = "0P", "invalid role specification";
176    INVALID_TRANSFORM_GROUP_NAME_SPECIFICATION = "0S", "invalid transform group name specification";
177    TARGET_TABLE_DISAGREES_WITH_CURSOR_SPECIFICATION = "0T", "target table disagrees with cursor specification";
178    ATTEMPT_TO_ASSIGN_TO_NON_UPDATABLE_COLUMN = "0U", "attempt to assign to non-updatable column";
179    ATTEMPT_TO_ASSIGN_TO_ORDERING_COLUMN = "0V", "attempt to assign to ordering column";
180    PROHIBITED_STATEMENT_ENCOUNTERED_DURING_TRIGGER_EXECUTION = "0W", "prohibited statement encountered during trigger execution";
181    INVALID_FOREIGN_SERVER_SPECIFICATION = "0X", "invalid foreign server specification";
182    PASS_THROUGH_SPECIFIC_CONDITION = "0Y", "pass-through specific condition";
183    DIAGNOSTICS_EXCEPTION = "0Z", "diagnostics exception";
184    XQUERY_ERROR = "10", "XQuery error";
185    CASE_NOT_FOUND_FOR_CASE_STATEMENT = "20", "case not found for case statement";
186    CARDINALITY_VIOLATION = "21", "cardinality violation";
187    DATA_EXCEPTION = "22", "data exception";
188    INTEGRITY_CONSTRAINT_VIOLATION = "23", "integrity constraint violation";
189    INVALID_CURSOR_STATE = "24", "invalid cursor state";
190    INVALID_TRANSACTION_STATE = "25", "invalid transaction state";
191    INVALID_SQL_STATEMENT_NAME = "26", "invalid SQL statement name";
192    TRIGGERED_DATA_CHANGE_VIOLATION = "27", "triggered data change violation";
193    INVALID_AUTHORIZATION_SPECIFICATION = "28", "invalid authorization specification";
194    DEPENDENT_PRIVILEGE_DESCRIPTORS_STILL_EXIST = "2B", "dependent privilege descriptors still exist";
195    INVALID_CHARACTER_SET_NAME = "2C", "invalid character set name";
196    INVALID_TRANSACTION_TERMINATION = "2D", "invalid transaction termination";
197    INVALID_CONNECTION_NAME = "2E", "invalid connection name";
198    SQL_ROUTINE_EXCEPTION = "2F", "SQL routine exception";
199    INVALID_COLLATION_NAME = "2H", "invalid collation name";
200    INVALID_SQL_STATEMENT_IDENTIFIER = "30", "invalid SQL statement identifier";
201    INVALID_SQL_DESCRIPTOR_NAME = "33", "invalid SQL descriptor name";
202    INVALID_CURSOR_NAME = "34", "invalid cursor name";
203    INVALID_CONDITION_NUMBER = "35", "invalid condition number";
204    CURSOR_SENSITIVITY_EXCEPTION = "36", "cursor sensitivity exception";
205    EXTERNAL_ROUTINE_EXCEPTION = "38", "external routine exception";
206    EXTERNAL_ROUTINE_INVOCATION_EXCEPTION = "39", "external routine invocation exception";
207    SAVEPOINT_EXCEPTION = "3B", "savepoint exception";
208    AMBIGUOUS_CURSOR_NAME = "3C", "ambiguous cursor name";
209    INVALID_CATALOG_NAME = "3D", "invalid catalog name";
210    INVALID_SCHEMA_NAME = "3F", "invalid schema name";
211    TRANSACTION_ROLLBACK = "40", "transaction rollback";
212    SYNTAX_ERROR_OR_ACCESS_RULE_VIOLATION = "42", "syntax error or access rule violation";
213    WITH_CHECK_OPTION_VIOLATION = "44", "with check option violation";
214    UNHANDLED_USER_DEFINED_EXCEPTION = "45", "unhandled user-defined exception";
215    OLB_SPECIFIC_ERROR = "46", "OLB-specific error";
216    FDW_SPECIFIC_CONDITION = "HV", "FDW-specific condition";
217    DATALINK_EXCEPTION = "HW", "datalink exception";
218    CLI_SPECIFIC_CONDITION = "HY", "CLI-specific condition";
219    REMOTE_DATABASE_ACCESS = "HZ", "remote database access";
220}
221
222#[cfg(test)]
223mod tests {
224    use super::*;
225
226    #[test]
227    fn new_accepts_only_digits_and_uppercase_letters() {
228        assert!(SqlState::new(*b"09AZ0").is_some());
229        for invalid in [b'/', b':', b'@', b'[', b'a', b'z', b' ', 0, 0xC3] {
230            let mut code = *b"23505";
231            code[4] = invalid;
232            assert_eq!(SqlState::new(code), None, "byte {invalid:#04x}");
233        }
234    }
235
236    #[test]
237    fn from_str_requires_exactly_five_bytes() {
238        assert_eq!("23505".parse::<SqlState>().unwrap().as_str(), "23505");
239        for invalid in ["", "2350", "235050", "2350\u{e9}", "2350\u{0}", "23505 "] {
240            assert_eq!(
241                invalid.parse::<SqlState>(),
242                Err(InvalidSqlState),
243                "{invalid:?}"
244            );
245        }
246    }
247
248    #[test]
249    fn class_and_subclass_split_after_two_characters() {
250        let code: SqlState = "40P01".parse().unwrap();
251        assert_eq!(code.class(), SqlStateClass::TRANSACTION_ROLLBACK);
252        assert_eq!(code.class().as_str(), "40");
253        assert_eq!(code.subclass(), "P01");
254    }
255
256    #[test]
257    fn standard_classes_start_with_0_to_4_or_a_to_h() {
258        for code in [
259            "00000", "40001", "0A000", "B0000", "G0000", "HV000", "H0000",
260        ] {
261            let code: SqlState = code.parse().unwrap();
262            assert!(code.class().is_standard(), "{code}");
263        }
264        for code in ["50000", "9Z000", "I0000", "P0001", "XX000"] {
265            let code: SqlState = code.parse().unwrap();
266            assert!(!code.class().is_standard(), "{code}");
267        }
268    }
269
270    #[test]
271    fn constants_match_as_patterns() {
272        const UNIQUE_VIOLATION: SqlState = SqlState::new(*b"23505").unwrap();
273        let code: SqlState = "23505".parse().unwrap();
274        assert!(matches!(code, UNIQUE_VIOLATION));
275        assert!(matches!(
276            code.class(),
277            SqlStateClass::INTEGRITY_CONSTRAINT_VIOLATION
278        ));
279        let other: SqlState = "22012".parse().unwrap();
280        assert!(!matches!(other, UNIQUE_VIOLATION));
281        assert!(!matches!(
282            other.class(),
283            SqlStateClass::INTEGRITY_CONSTRAINT_VIOLATION
284        ));
285    }
286}