1#[derive(Debug, Clone, thiserror::Error)]
10pub enum SQLError {
11 #[error("{0}")]
12 Parse(String),
13 #[error("{0}")]
15 ParseDiagnostic(Box<pg_query::Diagnostic>),
16 #[error("{0}")]
17 Unsupported(String),
18 #[error("LISTEN and UNLISTEN require a notification subscription")]
20 NotificationRequiresSubscription,
21 #[error("relation \"{0}\" does not exist")]
22 UnknownTable(String),
23 #[error("column \"{0}\" does not exist")]
24 UnknownColumn(String),
25 #[error("column reference \"{0}\" is ambiguous")]
26 AmbiguousColumn(String),
27 #[error("unknown function: {0}")]
28 UnknownFunction(String),
29 #[error("type mismatch: {0}")]
30 TypeMismatch(String),
31 #[error("invalid argument count for `{name}`: expected {expected}, got {actual}")]
32 BadArity {
33 name: String,
34 expected: String,
35 actual: usize,
36 },
37 #[error("No value supplied for parameter ${0}")]
38 MissingParam(usize),
39 #[error("vector dimension mismatch: expected {expected}, got {actual}")]
40 VectorDimMismatch { expected: usize, actual: usize },
41 #[error("{function_name}: column `{table}.{field}` has no text index; create one with CREATE INDEX ... ON {table} USING gin ({field})")]
42 TextIndexRequired {
43 function_name: String,
44 table: String,
45 field: String,
46 },
47 #[error("{0}")]
48 Cancelled(#[from] uqa_core::QueryCancelled),
49 #[error("{message}")]
54 Routine { sqlstate: String, message: String },
55 #[error("{message}")]
57 Diagnostic {
58 sqlstate: String,
59 message: String,
60 detail: Option<String>,
61 hint: Option<String>,
62 },
63 #[error("internal error: {0}")]
64 Internal(String),
65}
66
67impl SQLError {
68 pub const fn code(&self) -> Option<&'static str> {
70 match self {
71 Self::NotificationRequiresSubscription => Some("NOTIFICATION_REQUIRES_SUBSCRIPTION"),
72 _ => None,
73 }
74 }
75
76 pub fn undefined_function_call(signature: &str) -> Self {
78 Self::Diagnostic {
79 sqlstate: "42883".into(),
80 message: format!("function {signature} does not exist"),
81 detail: None,
82 hint: Some(
83 "No function matches the given name and argument types. You might need to add explicit type casts."
84 .into(),
85 ),
86 }
87 }
88
89 pub fn ambiguous_function_call(signature: &str) -> Self {
91 Self::Diagnostic {
92 sqlstate: "42725".into(),
93 message: format!("function {signature} is not unique"),
94 detail: None,
95 hint: Some(
96 "Could not choose a best candidate function. You might need to add explicit type casts."
97 .into(),
98 ),
99 }
100 }
101
102 pub fn function_call_resolution(sqlstate: &str, signature: &str, suffix: &str) -> Self {
104 match (sqlstate, suffix) {
105 ("42883", "does not exist") => Self::undefined_function_call(signature),
106 ("42725", "is not unique") => Self::ambiguous_function_call(signature),
107 _ => Self::Routine {
108 sqlstate: sqlstate.into(),
109 message: format!("function {signature} {suffix}"),
110 },
111 }
112 }
113
114 pub fn unknown_qualified_column(qualifier: &str, column: &str) -> Self {
115 Self::Routine {
116 sqlstate: "42703".into(),
117 message: format!("column {qualifier}.{column} does not exist"),
118 }
119 }
120
121 pub fn sqlstate(&self) -> Option<&str> {
125 match self {
126 SQLError::Cancelled(cancelled) => Some(cancelled.sqlstate()),
127 SQLError::Parse(_) => Some("42601"), SQLError::ParseDiagnostic(diagnostic) => Some(&diagnostic.sqlstate),
129 SQLError::Unsupported(_) | SQLError::NotificationRequiresSubscription => Some("0A000"), SQLError::UnknownTable(_) => Some("42P01"), SQLError::UnknownColumn(_) => Some("42703"), SQLError::AmbiguousColumn(_) => Some("42702"), SQLError::UnknownFunction(_) => Some("42883"), SQLError::TypeMismatch(_) | SQLError::TextIndexRequired { .. } => Some("42804"), SQLError::BadArity { .. } => Some("42883"), SQLError::MissingParam(_) => Some("S1002"), SQLError::VectorDimMismatch { .. } => Some("22023"), SQLError::Routine { sqlstate, .. } | SQLError::Diagnostic { sqlstate, .. } => {
139 Some(sqlstate)
140 }
141 SQLError::Internal(_) => Some("XX000"), }
143 }
144
145 pub fn detail(&self) -> Option<&str> {
147 match self {
148 SQLError::Diagnostic { detail, .. } => detail.as_deref(),
149 SQLError::ParseDiagnostic(diagnostic) => diagnostic.detail.as_deref(),
150 _ => None,
151 }
152 }
153
154 pub fn hint(&self) -> Option<&str> {
156 match self {
157 SQLError::Diagnostic { hint, .. } => hint.as_deref(),
158 SQLError::ParseDiagnostic(diagnostic) => diagnostic.hint.as_deref(),
159 _ => None,
160 }
161 }
162
163 pub fn position(&self) -> Option<u32> {
165 match self {
166 Self::ParseDiagnostic(diagnostic) => u32::try_from(diagnostic.cursor_position)
167 .ok()
168 .filter(|position| *position != 0),
169 _ => None,
170 }
171 }
172}
173
174pub type Result<T> = std::result::Result<T, SQLError>;
175
176impl From<pg_query::Error> for SQLError {
177 fn from(value: pg_query::Error) -> Self {
178 match value {
179 pg_query::Error::ParseDiagnostic(diagnostic) => Self::ParseDiagnostic(diagnostic),
180 pg_query::Error::Parse(message) => Self::Parse(message),
181 other => Self::Parse(other.to_string()),
182 }
183 }
184}
185
186impl From<uqa_core::memory::MemoryError> for SQLError {
187 fn from(error: uqa_core::memory::MemoryError) -> Self {
188 Self::Routine {
189 sqlstate: "53200".into(),
190 message: error.to_string(),
191 }
192 }
193}
194
195impl From<uqa_core::ValueRetentionError> for SQLError {
196 fn from(error: uqa_core::ValueRetentionError) -> Self {
197 match error {
198 uqa_core::ValueRetentionError::Memory(error) => error.into(),
199 uqa_core::ValueRetentionError::Cancelled(error) => error.into(),
200 error @ uqa_core::ValueRetentionError::Malformed { .. } => Self::Routine {
201 sqlstate: "XX001".into(),
202 message: error.to_string(),
203 },
204 }
205 }
206}