#![forbid(unsafe_code)]
use crate::SqlError;
pub const SUCCESS: &str = "00000";
pub const FEATURE_NOT_SUPPORTED: &str = "0A000";
pub const DATA_EXCEPTION: &str = "22000";
pub const INVALID_AUTHORIZATION: &str = "28000";
pub const SYNTAX_ERROR: &str = "42000";
pub const INSUFFICIENT_PRIVILEGE: &str = "42501";
pub const UNDEFINED_TABLE: &str = "42P01";
pub const QUERY_CANCELLED: &str = "57014";
pub const QUERY_TIMEOUT: &str = "57P05";
pub const SYSTEM_ERROR: &str = "58000";
pub const INTERNAL_ERROR: &str = "XX000";
pub const GENERAL_ERROR: &str = "HY000";
pub fn sqlstate_for(error: &SqlError) -> &'static str {
match error {
SqlError::EmptyQuery => SYNTAX_ERROR,
SqlError::EmptyTableName => SYNTAX_ERROR,
SqlError::Unsupported { .. } => FEATURE_NOT_SUPPORTED,
SqlError::InvalidTableFunction { .. } => SYNTAX_ERROR,
SqlError::DataFusion { .. } => INTERNAL_ERROR,
SqlError::Optimizer(_) => INTERNAL_ERROR,
SqlError::AccessDenied { .. } => INSUFFICIENT_PRIVILEGE,
SqlError::OperationCancelled { .. } => QUERY_CANCELLED,
SqlError::Timeout { .. } => QUERY_TIMEOUT,
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SqlStateError {
pub code: &'static str,
pub message: String,
}
impl SqlStateError {
pub fn from_sql_error(error: &SqlError) -> Self {
Self {
code: sqlstate_for(error),
message: error.to_string(),
}
}
}
impl std::fmt::Display for SqlStateError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "SQLSTATE {} — {}", self.code, self.message)
}
}
impl std::error::Error for SqlStateError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_query_maps_to_syntax_error() {
let e = SqlError::EmptyQuery;
assert_eq!(sqlstate_for(&e), SYNTAX_ERROR);
}
#[test]
fn unsupported_maps_to_feature_not_supported() {
let e = SqlError::Unsupported {
feature: "TABLESAMPLE".into(),
};
assert_eq!(sqlstate_for(&e), FEATURE_NOT_SUPPORTED);
}
#[test]
fn datafusion_maps_to_internal_error() {
let e = SqlError::DataFusion {
message: "panic in executor".into(),
};
assert_eq!(sqlstate_for(&e), INTERNAL_ERROR);
}
#[test]
fn access_denied_maps_to_insufficient_privilege() {
let e = SqlError::AccessDenied {
reason: "no read permission".into(),
};
assert_eq!(sqlstate_for(&e), INSUFFICIENT_PRIVILEGE);
}
#[test]
fn cancelled_maps_to_query_cancelled() {
let e = SqlError::OperationCancelled { operation_id: 42 };
assert_eq!(sqlstate_for(&e), QUERY_CANCELLED);
}
#[test]
fn timeout_maps_to_query_timeout() {
let e = SqlError::Timeout { timeout_ms: 5000 };
assert_eq!(sqlstate_for(&e), QUERY_TIMEOUT);
}
#[test]
fn sql_state_error_display() {
let e = SqlError::EmptyQuery;
let se = SqlStateError::from_sql_error(&e);
let s = se.to_string();
assert!(s.contains(SYNTAX_ERROR));
assert!(s.contains("empty"));
}
#[test]
fn sql_state_error_is_std_error() {
let e = SqlError::EmptyQuery;
let se = SqlStateError::from_sql_error(&e);
let _: &dyn std::error::Error = &se;
}
#[test]
fn all_sqlstate_codes_are_5_chars() {
for code in &[
SUCCESS,
FEATURE_NOT_SUPPORTED,
DATA_EXCEPTION,
INVALID_AUTHORIZATION,
SYNTAX_ERROR,
INSUFFICIENT_PRIVILEGE,
UNDEFINED_TABLE,
QUERY_CANCELLED,
QUERY_TIMEOUT,
SYSTEM_ERROR,
INTERNAL_ERROR,
GENERAL_ERROR,
] {
assert_eq!(code.len(), 5, "SQLSTATE {code} must be 5 characters");
}
}
}