1#![forbid(unsafe_code)]
2use crate::SqlError;
10
11pub const SUCCESS: &str = "00000";
15pub const FEATURE_NOT_SUPPORTED: &str = "0A000";
17pub const DATA_EXCEPTION: &str = "22000";
19pub const INVALID_AUTHORIZATION: &str = "28000";
21pub const SYNTAX_ERROR: &str = "42000";
23pub const INSUFFICIENT_PRIVILEGE: &str = "42501";
25pub const UNDEFINED_TABLE: &str = "42P01";
27pub const QUERY_CANCELLED: &str = "57014";
29pub const QUERY_TIMEOUT: &str = "57P05";
31pub const SYSTEM_ERROR: &str = "58000";
33pub const INTERNAL_ERROR: &str = "XX000";
35pub const GENERAL_ERROR: &str = "HY000";
37
38pub fn sqlstate_for(error: &SqlError) -> &'static str {
45 match error {
46 SqlError::EmptyQuery => SYNTAX_ERROR,
47 SqlError::EmptyTableName => SYNTAX_ERROR,
48 SqlError::Unsupported { .. } => FEATURE_NOT_SUPPORTED,
49 SqlError::InvalidTableFunction { .. } => SYNTAX_ERROR,
50 SqlError::DataFusion { .. } => INTERNAL_ERROR,
51 SqlError::Optimizer(_) => INTERNAL_ERROR,
52 SqlError::AccessDenied { .. } => INSUFFICIENT_PRIVILEGE,
53 SqlError::OperationCancelled { .. } => QUERY_CANCELLED,
54 SqlError::Timeout { .. } => QUERY_TIMEOUT,
55 }
56}
57
58#[derive(Debug, Clone, PartialEq, Eq)]
62pub struct SqlStateError {
63 pub code: &'static str,
65 pub message: String,
67}
68
69impl SqlStateError {
70 pub fn from_sql_error(error: &SqlError) -> Self {
72 Self {
73 code: sqlstate_for(error),
74 message: error.to_string(),
75 }
76 }
77}
78
79impl std::fmt::Display for SqlStateError {
80 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81 write!(f, "SQLSTATE {} — {}", self.code, self.message)
82 }
83}
84
85impl std::error::Error for SqlStateError {}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90
91 #[test]
92 fn empty_query_maps_to_syntax_error() {
93 let e = SqlError::EmptyQuery;
94 assert_eq!(sqlstate_for(&e), SYNTAX_ERROR);
95 }
96
97 #[test]
98 fn unsupported_maps_to_feature_not_supported() {
99 let e = SqlError::Unsupported {
100 feature: "TABLESAMPLE".into(),
101 };
102 assert_eq!(sqlstate_for(&e), FEATURE_NOT_SUPPORTED);
103 }
104
105 #[test]
106 fn datafusion_maps_to_internal_error() {
107 let e = SqlError::DataFusion {
108 message: "panic in executor".into(),
109 };
110 assert_eq!(sqlstate_for(&e), INTERNAL_ERROR);
111 }
112
113 #[test]
114 fn access_denied_maps_to_insufficient_privilege() {
115 let e = SqlError::AccessDenied {
116 reason: "no read permission".into(),
117 };
118 assert_eq!(sqlstate_for(&e), INSUFFICIENT_PRIVILEGE);
119 }
120
121 #[test]
122 fn cancelled_maps_to_query_cancelled() {
123 let e = SqlError::OperationCancelled { operation_id: 42 };
124 assert_eq!(sqlstate_for(&e), QUERY_CANCELLED);
125 }
126
127 #[test]
128 fn timeout_maps_to_query_timeout() {
129 let e = SqlError::Timeout { timeout_ms: 5000 };
130 assert_eq!(sqlstate_for(&e), QUERY_TIMEOUT);
131 }
132
133 #[test]
134 fn sql_state_error_display() {
135 let e = SqlError::EmptyQuery;
136 let se = SqlStateError::from_sql_error(&e);
137 let s = se.to_string();
138 assert!(s.contains(SYNTAX_ERROR));
139 assert!(s.contains("empty"));
140 }
141
142 #[test]
143 fn sql_state_error_is_std_error() {
144 let e = SqlError::EmptyQuery;
145 let se = SqlStateError::from_sql_error(&e);
146 let _: &dyn std::error::Error = &se;
147 }
148
149 #[test]
150 fn all_sqlstate_codes_are_5_chars() {
151 for code in &[
152 SUCCESS,
153 FEATURE_NOT_SUPPORTED,
154 DATA_EXCEPTION,
155 INVALID_AUTHORIZATION,
156 SYNTAX_ERROR,
157 INSUFFICIENT_PRIVILEGE,
158 UNDEFINED_TABLE,
159 QUERY_CANCELLED,
160 QUERY_TIMEOUT,
161 SYSTEM_ERROR,
162 INTERNAL_ERROR,
163 GENERAL_ERROR,
164 ] {
165 assert_eq!(code.len(), 5, "SQLSTATE {code} must be 5 characters");
166 }
167 }
168}