1use std::fmt;
11
12pub type Result<T> = std::result::Result<T, Error>;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
22pub struct Span {
23 pub start: u32,
25 pub end: u32,
27}
28
29impl Span {
30 #[must_use]
32 pub const fn new(start: u32, end: u32) -> Self {
33 Self { start, end }
34 }
35
36 #[must_use]
38 pub const fn len(self) -> u32 {
39 self.end.saturating_sub(self.start)
40 }
41
42 #[must_use]
44 pub const fn is_empty(self) -> bool {
45 self.len() == 0
46 }
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
55#[non_exhaustive]
56pub enum ErrorCode {
57 Parser,
59 Syntax,
65 Binder,
67 Catalog,
69 Conversion,
71 OutOfRange,
73 InvalidInput,
75 OutOfMemory,
77 Io,
79 NotImplemented,
81 Constraint,
83 Transaction,
85 Settings,
87 Interrupt,
89 Internal,
92}
93
94impl ErrorCode {
95 #[must_use]
102 pub const fn duckdb_name(self) -> &'static str {
103 match self {
104 Self::Parser => "Parser Error",
105 Self::Syntax => "Syntax Error",
106 Self::Binder => "Binder Error",
107 Self::Catalog => "Catalog Error",
108 Self::Conversion => "Conversion Error",
109 Self::OutOfRange => "Out of Range Error",
110 Self::InvalidInput => "Invalid Input Error",
111 Self::OutOfMemory => "Out of Memory Error",
112 Self::Io => "IO Error",
113 Self::NotImplemented => "Not implemented Error",
114 Self::Constraint => "Constraint Error",
115 Self::Transaction => "TransactionContext Error",
116 Self::Settings => "Settings Error",
117 Self::Interrupt => "Interrupt Error",
118 Self::Internal => "INTERNAL Error",
119 }
120 }
121
122 #[must_use]
127 pub const fn is_user_error(self) -> bool {
128 matches!(
129 self,
130 Self::Parser
131 | Self::Syntax
132 | Self::Binder
133 | Self::Catalog
134 | Self::Conversion
135 | Self::OutOfRange
136 | Self::InvalidInput
137 | Self::Constraint
138 | Self::Transaction
139 | Self::Settings
140 )
141 }
142}
143
144impl fmt::Display for ErrorCode {
145 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
146 f.write_str(self.duckdb_name())
147 }
148}
149
150#[derive(Debug, Clone, PartialEq, Eq)]
156pub struct Error(Box<Payload>);
157
158#[derive(Debug, Clone, PartialEq, Eq)]
159struct Payload {
160 code: ErrorCode,
161 message: String,
162 span: Option<Span>,
163 message_only: bool,
164}
165
166impl Error {
167 pub fn new(code: ErrorCode, message: impl Into<String>) -> Self {
169 Self(Box::new(Payload { code, message: message.into(), span: None, message_only: false }))
170 }
171
172 #[must_use]
174 pub fn with_span(mut self, span: Span) -> Self {
175 self.0.span = Some(span);
176 self
177 }
178
179 #[must_use]
181 pub fn code(&self) -> ErrorCode {
182 self.0.code
183 }
184
185 #[must_use]
187 pub fn message(&self) -> &str {
188 &self.0.message
189 }
190
191 #[must_use]
193 pub fn span(&self) -> Option<Span> {
194 self.0.span
195 }
196
197 #[must_use]
199 pub fn into_json(mut self) -> Self {
200 let exception_type = self.0.code.json_name();
201 let subtype = self.0.code.json_subtype(&self.0.message);
202 let mut fields = vec![
203 ("exception_type", exception_type.to_string()),
204 ("exception_message", self.0.message.clone()),
205 ];
206 if let Some(span) = self.0.span {
207 fields.push(("location", format!("[{},{}]", span.start, span.len())));
208 fields.push(("position", span.start.to_string()));
209 }
210 if let Some(subtype) = subtype {
211 fields.push(("error_subtype", subtype.to_string()));
212 }
213 self.0.message = json_object(&fields);
214 self.0.message_only = true;
215 self
216 }
217
218 pub fn parser(message: impl Into<String>) -> Self {
220 Self::new(ErrorCode::Parser, message)
221 }
222
223 pub fn syntax(message: impl Into<String>) -> Self {
225 Self::new(ErrorCode::Syntax, message)
226 }
227
228 pub fn binder(message: impl Into<String>) -> Self {
230 Self::new(ErrorCode::Binder, message)
231 }
232
233 pub fn catalog(message: impl Into<String>) -> Self {
235 Self::new(ErrorCode::Catalog, message)
236 }
237
238 pub fn conversion(message: impl Into<String>) -> Self {
240 Self::new(ErrorCode::Conversion, message)
241 }
242
243 pub fn out_of_range(message: impl Into<String>) -> Self {
245 Self::new(ErrorCode::OutOfRange, message)
246 }
247
248 pub fn invalid_input(message: impl Into<String>) -> Self {
250 Self::new(ErrorCode::InvalidInput, message)
251 }
252
253 pub fn out_of_memory(message: impl Into<String>) -> Self {
255 Self::new(ErrorCode::OutOfMemory, message)
256 }
257
258 pub fn io(message: impl Into<String>) -> Self {
260 Self::new(ErrorCode::Io, message)
261 }
262
263 pub fn not_implemented(message: impl Into<String>) -> Self {
265 Self::new(ErrorCode::NotImplemented, message)
266 }
267
268 pub fn constraint(message: impl Into<String>) -> Self {
270 Self::new(ErrorCode::Constraint, message)
271 }
272
273 pub fn transaction(message: impl Into<String>) -> Self {
275 Self::new(ErrorCode::Transaction, message)
276 }
277
278 pub fn settings(message: impl Into<String>) -> Self {
280 Self::new(ErrorCode::Settings, message)
281 }
282
283 pub fn interrupt(message: impl Into<String>) -> Self {
285 Self::new(ErrorCode::Interrupt, message)
286 }
287
288 pub fn internal(message: impl Into<String>) -> Self {
293 Self::new(ErrorCode::Internal, message)
294 }
295}
296
297impl fmt::Display for Error {
298 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
299 if self.0.message_only {
300 return f.write_str(&self.0.message);
301 }
302 write!(f, "{}: {}", self.0.code, self.0.message)
303 }
304}
305
306impl ErrorCode {
307 const fn json_name(self) -> &'static str {
308 match self {
309 Self::Parser => "Parser",
310 Self::Syntax => "Syntax",
311 Self::Binder => "Binder",
312 Self::Catalog => "Catalog",
313 Self::Conversion => "Conversion",
314 Self::OutOfRange => "Out of Range",
315 Self::InvalidInput => "Invalid Input",
316 Self::OutOfMemory => "Out of Memory",
317 Self::Io => "IO",
318 Self::NotImplemented => "Not implemented",
319 Self::Constraint => "Constraint",
320 Self::Transaction => "TransactionContext",
321 Self::Settings => "Settings",
322 Self::Interrupt => "Interrupt",
323 Self::Internal => "INTERNAL",
324 }
325 }
326
327 fn json_subtype(self, message: &str) -> Option<&'static str> {
328 match self {
329 Self::Parser => Some("SYNTAX_ERROR"),
330 Self::Binder if message.starts_with("Referenced column") => Some("COLUMN_NOT_FOUND"),
331 Self::Binder if message.contains("No function matches") => Some("NO_MATCHING_FUNCTION"),
332 Self::Catalog if message.contains("does not exist") => Some("MISSING_ENTRY"),
333 _ => None,
334 }
335 }
336}
337
338fn json_object(fields: &[(&str, String)]) -> String {
339 let mut out = String::from("{");
340 for (index, (name, value)) in fields.iter().enumerate() {
341 if index != 0 {
342 out.push(',');
343 }
344 out.push('"');
345 out.push_str(name);
346 out.push_str("\":\"");
347 for character in value.chars() {
348 match character {
349 '"' => out.push_str("\\\""),
350 '\\' => out.push_str("\\\\"),
351 '\n' => out.push_str("\\n"),
352 '\r' => out.push_str("\\r"),
353 '\t' => out.push_str("\\t"),
354 character if character <= '\u{1f}' => {
355 use std::fmt::Write as _;
356 let _ = write!(out, "\\u{:04x}", character as u32);
357 }
358 character => out.push(character),
359 }
360 }
361 out.push('"');
362 }
363 out.push('}');
364 out
365}
366
367impl std::error::Error for Error {}
368
369impl From<std::io::Error> for Error {
370 fn from(error: std::io::Error) -> Self {
371 Self::io(error.to_string())
372 }
373}
374
375#[cfg(test)]
376mod tests {
377 use super::{Error, ErrorCode, Span};
378
379 #[test]
380 fn an_error_prints_the_way_duckdb_prints_it() {
381 let error = Error::binder("Referenced column \"nope\" not found in FROM clause!");
382 assert_eq!(
383 error.to_string(),
384 "Binder Error: Referenced column \"nope\" not found in FROM clause!"
385 );
386 }
387
388 #[test]
389 fn a_json_error_is_structured_and_has_no_text_prefix() {
390 let error = Error::binder("Referenced column \"nope\" not found\nnext")
391 .with_span(Span::new(7, 11))
392 .into_json();
393 assert_eq!(
394 error.to_string(),
395 "{\"exception_type\":\"Binder\",\"exception_message\":\"Referenced column \\\"nope\\\" not found\\nnext\",\"location\":\"[7,4]\",\"position\":\"7\",\"error_subtype\":\"COLUMN_NOT_FOUND\"}"
396 );
397 assert_eq!(error.code(), ErrorCode::Binder);
398 }
399
400 #[test]
401 fn a_result_is_no_wider_than_the_value_in_it() {
402 assert_eq!(size_of::<Error>(), size_of::<usize>());
405 assert_eq!(size_of::<Result<String, Error>>(), size_of::<String>());
406 }
407
408 #[test]
409 fn a_span_survives_being_attached() {
410 let error = Error::parser("syntax error at or near \"FROM\"").with_span(Span::new(7, 11));
411 assert_eq!(error.span(), Some(Span::new(7, 11)));
412 assert_eq!(error.span().map(Span::len), Some(4));
413 assert_eq!(error.code(), ErrorCode::Parser);
414 }
415
416 #[test]
417 fn the_fuzzer_can_tell_our_bugs_from_the_query_s_bugs() {
418 assert!(ErrorCode::Binder.is_user_error());
419 assert!(ErrorCode::Conversion.is_user_error());
420 assert!(!ErrorCode::Internal.is_user_error());
421 assert!(!ErrorCode::OutOfMemory.is_user_error());
422 assert!(!ErrorCode::NotImplemented.is_user_error());
425 }
426}