sqlstate_inline/
class.rs

1// © 2022 Christoph Grenz <https://grenz-bonn.de>
2//
3// SPDX-License-Identifier: MPL-2.0
4
5use core::fmt;
6use core::ops::Deref;
7use core::str::FromStr;
8
9use crate::category::Category;
10use crate::character::Char;
11use crate::error::ParseError;
12use crate::sqlstate::SqlState;
13
14/// Class code for a given SQLSTATE code.
15///
16/// The class consists of the first two characters of an SQLSTATE code.
17///
18/// This struct can be treated like a 2-byte fixed-length string restricted to `A`-`Z` and `0`-`9`.
19/// It is dereferencable as a [`str`] and may be converted into a `[u8; 2]` or `&[u8]` using
20/// [`Into`] and [`AsRef`].
21#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
22pub struct Class {
23	pub(crate) code: [Char; 2],
24}
25
26impl Class {
27	/// Creates a `Class` from a string.
28	///
29	/// # Errors
30	/// Returns an `Err` if the string isn't of length 2 or contains characters other than `A`-`Z`
31	/// and `0`-`9`.
32	///
33	/// # Examples
34	/// ```
35	/// # use sqlstate_inline::Class;
36	/// # fn main() -> Result<(), Box<dyn std::error::Error + 'static>> {
37	/// let input = "08";
38	/// let class = Class::from_str(input)?;
39	///
40	/// assert_eq!(input, &*class);
41	/// # Ok(())
42	/// # }
43	/// ```
44	#[inline]
45	pub const fn from_str(value: &str) -> Result<Self, ParseError> {
46		Self::from_bytes(value.as_bytes())
47	}
48
49	/// Creates a `Class` from an ASCII byte string.
50	///
51	/// # Errors
52	/// Returns an `Err` if the slice isn't of length 2 or contains ASCII character codes other
53	/// than `A`-`Z` and `0`-`9`.
54	///
55	/// # Examples
56	/// ```
57	/// # use sqlstate_inline::Class;
58	/// # fn main() -> Result<(), Box<dyn std::error::Error + 'static>> {
59	/// let input = b"08";
60	/// let class = Class::from_bytes(input)?;
61	///
62	/// assert_eq!(input, AsRef::<[u8]>::as_ref(&class));
63	/// # Ok(())
64	/// # }
65	/// ```
66	#[inline]
67	pub const fn from_bytes(value: &[u8]) -> Result<Self, ParseError> {
68		match Char::new_array(value) {
69			Ok(code) => Ok(Self { code }),
70			Err(e) => Err(e),
71		}
72	}
73
74	/// Creates a `Class` from an ASCII byte array.
75	///
76	/// # Errors
77	/// Returns an `Err` if the array contains ASCII character codes other than `A`-`Z` and `0`-`9`.
78	///
79	/// # Examples
80	/// ```
81	/// # use sqlstate_inline::Class;
82	/// # fn main() -> Result<(), Box<dyn std::error::Error + 'static>> {
83	/// let input = [b'2', b'4'];
84	/// let class = Class::from_byte_array(input)?;
85	///
86	/// assert_eq!(&input, AsRef::<[u8; 2]>::as_ref(&class));
87	/// # Ok(())
88	/// # }
89	/// ```
90	///
91	/// # See also
92	/// [`Class::from_bytes()`]
93	#[inline]
94	pub const fn from_byte_array(value: [u8; 2]) -> Result<Self, ParseError> {
95		match Char::new_array(&value) {
96			Ok(code) => Ok(Self { code }),
97			Err(e) => Err(e),
98		}
99	}
100
101	/// Returns the general category for this code class.
102	///
103	/// # Examples
104	/// ```
105	/// # use sqlstate_inline::{Class, Category};
106	/// let class = Class::CONNECTION_EXCEPTION;
107	/// assert_eq!(class.category(), Category::Exception);
108	/// ```
109	#[inline]
110	pub const fn category(&self) -> Category {
111		match self {
112			sqlstate![0 0] => Category::Success,
113			sqlstate![0 1] => Category::Warning,
114			sqlstate![0 2] => Category::NoData,
115			_ => Category::Exception,
116		}
117	}
118
119	/// Returns whether a SQLSTATE is in this class.
120	///
121	/// # Examples
122	/// ```
123	/// # use sqlstate_inline::{Class, SqlState};
124	/// let connection_refused = SqlState("08001");
125	/// let syntax_error = SqlState("42000");
126	/// let class = Class::CONNECTION_EXCEPTION;
127	///
128	/// assert_eq!(class.contains(connection_refused), true);
129	/// assert_eq!(class.contains(syntax_error), false);
130	/// ```
131	#[inline]
132	pub const fn contains(&self, sqlstate: SqlState) -> bool {
133		let class = sqlstate.class();
134		let self_arrayref = Char::array_as_bytes(&self.code);
135		let other_arrayref = Char::array_as_bytes(&class.code);
136		self_arrayref[0] == other_arrayref[0] && self_arrayref[1] == other_arrayref[1]
137	}
138
139	/// Returns whether this class is implementation-specific and not reserved for standard conditions.
140	///
141	/// All classes starting with `5`-`9` or `I`-`Z` are implementation-specific.
142	///
143	/// # Examples
144	/// ```
145	/// # use sqlstate_inline::{Class, Category};
146	/// let class = Class::CONNECTION_EXCEPTION;
147	/// assert_eq!(class.is_implementation_specific(), false);
148	///
149	/// let class = Class::PGSQL_OPERATOR_INTERVENTION;
150	/// assert_eq!(class.is_implementation_specific(), true);
151	///
152	/// let class = Class("XR");
153	/// assert_eq!(class.is_implementation_specific(), true);
154	/// ```
155	#[inline]
156	pub const fn is_implementation_specific(&self) -> bool {
157		!matches!(
158			self.code[0],
159			Char::_0
160				| Char::_1 | Char::_2
161				| Char::_3 | Char::_4
162				| Char::A | Char::B
163				| Char::C | Char::D
164				| Char::E | Char::F
165				| Char::G | Char::H
166		)
167	}
168}
169
170/// # SQL standard class constants
171impl Class {
172	/// Class `00`: Successful completion
173	pub const SUCCESSFUL_COMPLETION: Self = sqlstate![0 0];
174	/// Class `01`: Warning
175	pub const WARNING: Self = sqlstate![0 1];
176	/// Class `02`: No Data
177	pub const NO_DATA: Self = sqlstate![0 2];
178	/// Class `07`: Dynamic SQL error
179	pub const DYNAMIC_SQL_ERROR: Self = sqlstate![0 7];
180	/// Class `08`: Connection exception
181	pub const CONNECTION_EXCEPTION: Self = sqlstate![0 8];
182	/// Class `09`: Triggered action exception
183	pub const TRIGGERED_ACTION_EXCEPTION: Self = sqlstate![0 9];
184	/// Class `0A`: Feature not supported
185	pub const FEATURE_NOT_SUPPORTED: Self = sqlstate![0 A];
186	/// Class `0D`: Invalid target type specification
187	pub const INVALID_TARGET_TYPE_SPEC: Self = sqlstate![0 D];
188	/// Class `0E`: Invalid schema name list specification
189	pub const INVALID_SCHEMA_NAME_LIST_SPEC: Self = sqlstate![0 E];
190	/// Class `0F`: Locator exception
191	pub const LOCATOR_EXCEPTION: Self = sqlstate![0 F];
192	/// Class `0K`: Resignal when handler not active (SQL/PSM)
193	pub const RESIGNAL_WHEN_HANDLER_NOT_ACTIVE: Self = sqlstate![0 K];
194	/// Class `0L`: Invalid grantor
195	pub const INVALID_GRANTOR: Self = sqlstate![0 L];
196	/// Class `0M`: Invalid SQL-invoked procedure reference
197	pub const INVALID_SQL_INVOKED_PROCEDURE_REF: Self = sqlstate![0 M];
198	/// Class `0N`: SQL/XML mapping error (SQL/XML)
199	pub const SQL_XML_MAPPING_ERROR: Self = sqlstate![0 N];
200	/// Class `0P`: Invalid role specification
201	pub const INVALID_ROLE_SPEC: Self = sqlstate![0 P];
202	/// Class `0S`: Invalid transform group name specification
203	pub const INVALID_TRANSFORM_GROUP_NAME_SPEC: Self = sqlstate![0 S];
204	/// Class `0T`: Target table disagrees with cursor specification
205	pub const TARGET_TABLE_DISAGREES_WITH_CURSOR_SPEC: Self = sqlstate![0 T];
206	/// Class `0U`: Attempt to assign to non-updatable column
207	pub const ATTEMPT_TO_ASSIGN_TO_NON_UPDATABLE_COLUMN: Self = sqlstate![0 U];
208	/// Class `0V`: Attempt to assign to ordering column
209	pub const ATTEMPT_TO_ASSSIGN_TO_ORDERING_COLUMN: Self = sqlstate![0 V];
210	/// Class `0W`: Prohibited statement encoutered during trigger execution
211	pub const PROHIBITED_STATEMENT_DURING_TRIGGER_EXEC: Self = sqlstate![0 W];
212	/// Class `0X`: Invalid foreign server specification (SQL/MED)
213	pub const INVALID_FOREIGN_SERVER_SPEC: Self = sqlstate![0 X];
214	/// Class `0Y`: Pass-through specific condition (SQL/MED)
215	pub const PASSTHROUGH_SPECIFIC_CONDITION: Self = sqlstate![0 Y];
216	/// Class `0Z`: Diagnostics exception
217	pub const DIAGNOSTICS_EXCEPTION: Self = sqlstate![0 Z];
218	/// Class `10`: XQuery error (SQL/XML)
219	pub const XQUERY_ERROR: Self = sqlstate![1 0];
220	/// Class `20`: Case not found for case statement (SQL/PSM)
221	pub const CASE_NOT_FOUND_FOR_CASE_STATEMENT: Self = sqlstate![2 0];
222	/// Class `21`: Cardinality violation
223	///
224	/// Subquery row count mismatch, column count mismatch, etc.
225	pub const CARDINALITY_VIOLATION: Self = sqlstate![2 1];
226	/// Class `22`: Data exception
227	///
228	/// Overflow, truncation, out of range, etc.
229	pub const DATA_EXCEPTION: Self = sqlstate![2 2];
230	/// Class `23`: Integrity constraint violation
231	///
232	/// `RESTRICT` violation, foreign key constraint violation, etc.
233	pub const INTEGRITY_CONSTRAINT_VIOLATION: Self = sqlstate![2 3];
234	/// Class `24`: Invalid cursor state
235	pub const INVALID_CURSOR_STATE: Self = sqlstate![2 4];
236	/// Class `25`: Invalid transaction state
237	pub const INVALID_TRANSACTION_STATE: Self = sqlstate![2 5];
238	/// Class `26`: Invalid SQL statement name
239	pub const INVALID_SQL_STATEMENT_NAME: Self = sqlstate![2 6];
240	/// Class `27`: Invalid SQL statement name
241	pub const TRIGGERED_DATA_CHANGE_VIOLATION: Self = sqlstate![2 7];
242	/// Class `28`: Invalid authorization specification
243	pub const INVALID_AUTHORIZATION_SPEC: Self = sqlstate![2 8];
244	/// Class `2B`: Dependent privilege descriptors still exist
245	pub const DEPENDENT_PRIVILEGE_DESCRIPTORS_STILL_EXIST: Self = sqlstate![2 B];
246	/// Class `2C`: Invalid character set name
247	pub const INVALID_CHARACTER_SET_NAME: Self = sqlstate![2 C];
248	/// Class `2D`: Invalid transaction termination
249	pub const INVALID_TRANSACTION_TERMINATION: Self = sqlstate![2 D];
250	/// Class `2E`: Invalid connection name
251	pub const INVALID_CONNECTION_NAME: Self = sqlstate![2 E];
252	/// Class `2F`: SQL routine exception
253	pub const SQL_ROUTINE_EXCEPTION: Self = sqlstate![2 F];
254	/// Class `2H`: Invalid collation name
255	pub const INVALID_COLLATION_NAME: Self = sqlstate![2 H];
256	/// Class `30`: Invalid SQL statement identifier
257	pub const INVALID_SQL_STATEMENT_IDENTIFIER: Self = sqlstate![3 0];
258	/// Class `33`: Invalid SQL descriptor name
259	pub const INVALID_SQL_DESCRIPTOR_NAME: Self = sqlstate![3 3];
260	/// Class `34`: Invalid cursor name
261	pub const INVALID_CURSOR_NAME: Self = sqlstate![3 4];
262	/// Class `35`: Invalid connection number
263	pub const INVALID_CONNECTION_NUMBER: Self = sqlstate![3 5];
264	/// Class `36`: Cursor sensitivity exception
265	pub const CURSOR_SENSITIVITY_EXCEPTION: Self = sqlstate![3 6];
266	/// Class `38`: External routine exception
267	pub const EXTERNAL_ROUTINE_EXCEPTION: Self = sqlstate![3 8];
268	/// Class `39`: External routine invocation exception
269	pub const EXTERNAL_ROUTINE_INVOCATION_EXCEPTION: Self = sqlstate![3 9];
270	/// Class `3B`: External routine invocation exception
271	pub const SAVEPOINT_EXCEPTION: Self = sqlstate![3 B];
272	/// Class `3C`: Ambiguous cursor name
273	pub const AMBIGUOUS_CURSOR_NAME: Self = sqlstate![3 C];
274	/// Class `3D`: Invalid catalog name
275	///
276	/// Empty or invalid database name, etc.
277	pub const INVALID_CATALOG_NAME: Self = sqlstate![3 D];
278	/// Class `3F`: Invalid schema name
279	pub const INVALID_SCHEMA_NAME: Self = sqlstate![3 F];
280	/// Class `40`: Transaction rollback
281	pub const TRANSACTION_ROLLBACK: Self = sqlstate![4 0];
282	/// Class `42`: Syntax error or access rule violation
283	///
284	/// Includes SQL query syntax errors, miscellaneous query errors, and access right violations.
285	pub const SYNTAX_ERROR_OR_ACCESS_RULE_VIOLATION: Self = sqlstate![4 2];
286	/// Class `44`: With check option violation
287	pub const WITH_CHECK_OPTION_VIOLATION: Self = sqlstate![4 4];
288	/// Class `45`: Unhandled user-defined exception (SQL/PSM)
289	pub const UNHANDLED_USER_DEFINED_EXCEPTION: Self = sqlstate![4 5];
290	/// Class `46`: OLB-specific error (SQL/OLB)
291	///
292	/// Includes Java DDL errors from SQL/JRT.
293	pub const OLB_SPECIFIC_ERROR: Self = sqlstate![4 6];
294	/// Class `HW`: Datalink exception (SQL/MED)
295	pub const DATALINK_EXCEPTION: Self = sqlstate![H W];
296	/// Class `HV`: FDW-specific condition (SQL/MED)
297	pub const FDW_SPECIFIC_CONDITION: Self = sqlstate![H V];
298	/// Class `HY`: CLI-specific condition (SQL/CLI)
299	///
300	/// CLI stands for "Call-Level Interface". This mainly subsumes API errors
301	pub const CLI_SPECIFIC_CONDITION: Self = sqlstate![H Y];
302}
303
304/// # IBM DB2-specific class constants
305impl Class {
306	/// DB2-specific: Class `51`: Invalid application state
307	pub const DB2_INVALID_APPLICATION_STATE: Self = sqlstate![5 1];
308	/// DB2-specific: Class `53`: Invalid operand or inconsistent specification
309	pub const DB2_INVALID_OPERAND_OR_INCONSISTENT_SPEC: Self = sqlstate![5 3];
310	/// DB2-specific: Class `54`: SQL or product limit exceeded
311	pub const DB2_SQL_OR_PRODUCT_LIMIT_EXCEEDED: Self = sqlstate![5 4];
312	/// DB2-specific: Class `55`: Object not in prerequisite state
313	pub const DB2_OBJECT_NOT_IN_PREREQUISITE_STATE: Self = sqlstate![5 5];
314	/// DB2-specific: Class `56`: Miscellaneous SQL or product error
315	pub const DB2_MISCELLANEOUS_SQL_OR_PRODUCT_ERROR: Self = sqlstate![5 6];
316	/// DB2-specific: Class `57`: Resource not available or operator intervention
317	pub const DB2_RESOURCE_NOT_AVAILABLE_OR_OPERATOR_INTERVENTION: Self = sqlstate![5 7];
318	/// DB2-specific: Class `58`: System error
319	pub const DB2_SYSTEM_ERROR: Self = sqlstate![5 8];
320	/// DB2-specific: Class `5U`: Common utilities and tools
321	pub const DB2_COMMON_UTILITIES_AND_TOOLS: Self = sqlstate![5 U];
322}
323
324/// # PostgreSQL-specific class constants
325impl Class {
326	/// Postgres-specific: Class `53`: Invalid operand or inconsistent specification
327	pub const PGSQL_INSUFFICIENT_RESOURCES: Self = sqlstate![5 3];
328	/// Postgres-specific: Class `54`: Program limit exceeded
329	pub const PGSQL_PROGRAM_LIMIT_EXCEEDED: Self = sqlstate![5 4];
330	/// Postgres-specific: Class `55`: Object not in prerequisite state
331	pub const PGSQL_OBJECT_NOT_IN_PREREQUISITE_STATE: Self = sqlstate![5 5];
332	/// Postgres-specific: Class `57`: Operator intervention
333	pub const PGSQL_OPERATOR_INTERVENTION: Self = sqlstate![5 7];
334	/// Postgres-specific: Class `58`: System error (errors external to PostgreSQL itself)
335	pub const PGSQL_SYSTEM_ERROR: Self = sqlstate![5 8];
336	/// Postgres-specific: Class `72`: Snapshot Failure
337	pub const PGSQL_SNAPSHOT_FAILURE: Self = sqlstate![7 2];
338	/// Postgres-specific: Class `F0`: Configuration File Error
339	pub const PGSQL_CONFIGURATION_FILE_ERROR: Self = sqlstate![F 0];
340	/// Postgres-specific: Class `P0`: PL/pgSQL error
341	pub const PGSQL_PL_PGSQL_ERROR: Self = sqlstate![P 0];
342	/// Postgres-specific: Class `XX`: Internal error
343	pub const PGSQL_INTERNAL_ERROR: Self = sqlstate![X X];
344}
345
346/// # MySQL/MariaDB-specific class constants
347impl Class {
348	/// MySQL-specific: Class `70`: Interruption
349	pub const MYSQL_INTERRUPTION: Self = sqlstate![7 0];
350	/// MySQL-specific: Class `XA`: X/Open XA related error
351	pub const MYSQL_XA_ERROR: Self = sqlstate![X A];
352}
353
354/// # ODBC-specific class constants
355impl Class {
356	/// ODBC-specific: Class `IM`: Driver error
357	pub const ODBC_DRIVER_ERROR: Self = sqlstate![I M];
358}
359
360/// # Oracle-specific class constants
361impl Class {
362	/// Oracle-specific: Class `60`: System error
363	pub const ORACLE_SYSTEM_ERROR: Self = sqlstate![6 0];
364	/// Oracle-specific: Class `61`: Resource error
365	pub const ORACLE_RESOURCE_ERROR: Self = sqlstate![6 1];
366	/// Oracle-specific: Class `62`: Path name server and detached process error
367	pub const ORACLE_PATH_NAME_SERVER_AND_DETACHED_PROCESS_ERROR: Self = sqlstate![6 2];
368	/// Oracle-specific: Class `63`: Oracle*XA or two-task interface error
369	pub const ORACLE_CA_OR_TWO_TASK_INTERFACE_ERROR: Self = sqlstate![6 3];
370	/// Oracle-specific: Class `64`: control file, database file, redo file, archival or media recovery error
371	pub const ORACLE_FILE_OR_MEDIA_RECOVERY_ERROR: Self = sqlstate![6 4];
372	/// Oracle-specific: Class `65`: PL/SQL error
373	pub const ORACLE_PL_SQL_ERROR: Self = sqlstate![6 5];
374	/// Oracle-specific: Class `66`: SQL*Net driver error
375	pub const ORACLE_SQL_NET_DRIVER_ERROR: Self = sqlstate![6 6];
376	/// Oracle-specific: Class `67`: Licensing error
377	pub const ORACLE_LICENSING_ERROR: Self = sqlstate![6 7];
378	/// Oracle-specific: Class `69`: SQL*Connect error
379	pub const ORACLE_SQL_CONNECT_ERROR: Self = sqlstate![6 9];
380	/// Oracle-specific: Class `72`: SQL execute phase error
381	pub const ORACLE_SQL_EXECUTE_PHASE_ERROR: Self = sqlstate![7 2];
382	/// Oracle-specific: Class `82`: Internal error
383	pub const ORACLE_INTERNAL_ERROR: Self = sqlstate![8 2];
384	/// Oracle-specific: Class `90`: Debug event
385	pub const ORACLE_DEBUG_EVENT: Self = sqlstate![9 0];
386}
387
388impl fmt::Debug for Class {
389	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
390		f.debug_tuple("Class").field(&&**self).finish()
391	}
392}
393
394impl fmt::Display for Class {
395	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
396		f.write_str(self)
397	}
398}
399
400impl AsRef<str> for Class {
401	#[inline]
402	fn as_ref(&self) -> &str {
403		self
404	}
405}
406
407impl AsRef<[u8]> for Class {
408	#[inline]
409	fn as_ref(&self) -> &[u8] {
410		Char::array_as_bytes(&self.code)
411	}
412}
413
414impl AsRef<[u8; 2]> for Class {
415	#[inline]
416	fn as_ref(&self) -> &[u8; 2] {
417		Char::array_as_bytes(&self.code)
418	}
419}
420
421impl From<Class> for [u8; 2] {
422	#[inline]
423	fn from(class: Class) -> Self {
424		class.code.map(Char::as_byte)
425	}
426}
427
428impl Deref for Class {
429	type Target = str;
430	#[inline]
431	fn deref(&self) -> &Self::Target {
432		Char::array_as_str(&self.code)
433	}
434}
435
436impl PartialEq<str> for Class {
437	fn eq(&self, other: &str) -> bool {
438		&**self == other
439	}
440}
441
442impl PartialEq<[u8]> for Class {
443	fn eq(&self, other: &[u8]) -> bool {
444		AsRef::<[u8]>::as_ref(self) == other
445	}
446}
447
448impl FromStr for Class {
449	type Err = ParseError;
450
451	fn from_str(s: &str) -> Result<Self, Self::Err> {
452		Self::from_bytes(s.as_bytes())
453	}
454}
455
456impl TryFrom<&[u8]> for Class {
457	type Error = ParseError;
458
459	fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
460		Self::from_bytes(bytes)
461	}
462}
463
464impl TryFrom<[u8; 2]> for Class {
465	type Error = ParseError;
466
467	fn try_from(bytes: [u8; 2]) -> Result<Self, Self::Error> {
468		Self::from_byte_array(bytes)
469	}
470}
471
472impl TryFrom<&str> for Class {
473	type Error = ParseError;
474
475	fn try_from(string: &str) -> Result<Self, Self::Error> {
476		Self::from_str(string)
477	}
478}
479
480#[cfg(feature = "serde")]
481impl serde::Serialize for Class {
482	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
483	where
484		S: serde::Serializer,
485	{
486		serializer.serialize_str(self)
487	}
488}
489
490#[cfg(feature = "serde")]
491impl<'de> serde::Deserialize<'de> for Class {
492	fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
493	where
494		D: serde::Deserializer<'de>,
495	{
496		deserializer
497			.deserialize_str(crate::character::de::ArrayVisitor::new())
498			.map(|code| Self { code })
499	}
500}
501
502// Statically assert the intended memory layouts (2 bytes with multiple niches)
503const _: () = assert!(core::mem::size_of::<Class>() == 2);
504const _: () = assert!(core::mem::size_of::<Option<Class>>() == 2);
505const _: () = assert!(core::mem::size_of::<Option<Option<Class>>>() == 2);