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 Dependency,
86 Sequence,
88 Transaction,
90 Settings,
92 Interrupt,
94 ParameterNotAllowed,
97 MismatchType,
99 InvalidType,
101 Internal,
104}
105
106impl ErrorCode {
107 #[must_use]
114 pub const fn duckdb_name(self) -> &'static str {
115 match self {
116 Self::Parser => "Parser Error",
117 Self::Syntax => "Syntax Error",
118 Self::Binder => "Binder Error",
119 Self::Catalog => "Catalog Error",
120 Self::Conversion => "Conversion Error",
121 Self::OutOfRange => "Out of Range Error",
122 Self::InvalidInput => "Invalid Input Error",
123 Self::OutOfMemory => "Out of Memory Error",
124 Self::Io => "IO Error",
125 Self::NotImplemented => "Not implemented Error",
126 Self::Constraint => "Constraint Error",
127 Self::Dependency => "Dependency Error",
128 Self::Sequence => "Sequence Error",
129 Self::Transaction => "TransactionContext Error",
130 Self::Settings => "Settings Error",
131 Self::Interrupt => "Interrupt Error",
132 Self::ParameterNotAllowed => "Parameter Not Allowed Error",
133 Self::MismatchType => "Mismatch Type Error",
134 Self::InvalidType => "Invalid type Error",
135 Self::Internal => "INTERNAL Error",
136 }
137 }
138
139 #[must_use]
144 pub const fn is_user_error(self) -> bool {
145 matches!(
146 self,
147 Self::Parser
148 | Self::Syntax
149 | Self::Binder
150 | Self::Catalog
151 | Self::Conversion
152 | Self::OutOfRange
153 | Self::InvalidInput
154 | Self::Constraint
155 | Self::Dependency
156 | Self::Sequence
157 | Self::Transaction
158 | Self::Settings
159 | Self::ParameterNotAllowed
160 | Self::MismatchType
161 | Self::InvalidType
162 )
163 }
164}
165
166impl fmt::Display for ErrorCode {
167 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
168 f.write_str(self.duckdb_name())
169 }
170}
171
172#[derive(Debug, Clone, PartialEq, Eq)]
178pub struct Error(Box<Payload>);
179
180#[derive(Debug, Clone, PartialEq, Eq)]
181struct Payload {
182 code: ErrorCode,
183 message: String,
184 span: Option<Span>,
185 message_only: bool,
186}
187
188impl Error {
189 pub fn new(code: ErrorCode, message: impl Into<String>) -> Self {
191 Self(Box::new(Payload { code, message: message.into(), span: None, message_only: false }))
192 }
193
194 #[must_use]
196 pub fn with_span(mut self, span: Span) -> Self {
197 self.0.span = Some(span);
198 self
199 }
200
201 #[must_use]
203 pub fn with_fallback_span(mut self, span: Span) -> Self {
204 if self.0.span.is_none() && !span.is_empty() {
205 self.0.span = Some(span);
206 }
207 self
208 }
209
210 #[must_use]
212 pub fn code(&self) -> ErrorCode {
213 self.0.code
214 }
215
216 #[must_use]
218 pub fn message(&self) -> &str {
219 &self.0.message
220 }
221
222 #[must_use]
224 pub fn span(&self) -> Option<Span> {
225 self.0.span
226 }
227
228 #[must_use]
230 pub fn into_json(mut self) -> Self {
231 let exception_type = self.0.code.json_name();
232 let subtype = self.0.code.json_subtype(&self.0.message);
233 let mut fields = vec![
234 ("exception_type", exception_type.to_string()),
235 ("exception_message", self.0.message.clone()),
236 ];
237 if let Some(span) = self.0.span {
238 fields.push(("location", format!("[{},{}]", span.start, span.len())));
239 fields.push(("position", span.start.to_string()));
240 }
241 if let Some(subtype) = subtype {
242 fields.push(("error_subtype", subtype.to_string()));
243 }
244 self.0.message = json_object(&fields);
245 self.0.message_only = true;
246 self
247 }
248
249 pub fn parser(message: impl Into<String>) -> Self {
251 Self::new(ErrorCode::Parser, message)
252 }
253
254 pub fn syntax(message: impl Into<String>) -> Self {
256 Self::new(ErrorCode::Syntax, message)
257 }
258
259 pub fn binder(message: impl Into<String>) -> Self {
261 Self::new(ErrorCode::Binder, message)
262 }
263
264 pub fn catalog(message: impl Into<String>) -> Self {
266 Self::new(ErrorCode::Catalog, message)
267 }
268
269 pub fn conversion(message: impl Into<String>) -> Self {
271 Self::new(ErrorCode::Conversion, message)
272 }
273
274 pub fn out_of_range(message: impl Into<String>) -> Self {
276 Self::new(ErrorCode::OutOfRange, message)
277 }
278
279 pub fn invalid_input(message: impl Into<String>) -> Self {
281 Self::new(ErrorCode::InvalidInput, message)
282 }
283
284 pub fn out_of_memory(message: impl Into<String>) -> Self {
286 Self::new(ErrorCode::OutOfMemory, message)
287 }
288
289 pub fn io(message: impl Into<String>) -> Self {
291 Self::new(ErrorCode::Io, message)
292 }
293
294 pub fn not_implemented(message: impl Into<String>) -> Self {
296 Self::new(ErrorCode::NotImplemented, message)
297 }
298
299 pub fn constraint(message: impl Into<String>) -> Self {
301 Self::new(ErrorCode::Constraint, message)
302 }
303
304 pub fn transaction(message: impl Into<String>) -> Self {
306 Self::new(ErrorCode::Transaction, message)
307 }
308
309 pub fn settings(message: impl Into<String>) -> Self {
311 Self::new(ErrorCode::Settings, message)
312 }
313
314 pub fn parameter_not_allowed(message: impl Into<String>) -> Self {
316 Self::new(ErrorCode::ParameterNotAllowed, message)
317 }
318
319 pub fn dependency(message: impl Into<String>) -> Self {
321 Self::new(ErrorCode::Dependency, message)
322 }
323
324 pub fn sequence(message: impl Into<String>) -> Self {
326 Self::new(ErrorCode::Sequence, message)
327 }
328
329 pub fn mismatch_type(message: impl Into<String>) -> Self {
331 Self::new(ErrorCode::MismatchType, message)
332 }
333
334 pub fn invalid_type(message: impl Into<String>) -> Self {
336 Self::new(ErrorCode::InvalidType, message)
337 }
338
339 pub fn interrupt(message: impl Into<String>) -> Self {
341 Self::new(ErrorCode::Interrupt, message)
342 }
343
344 pub fn internal(message: impl Into<String>) -> Self {
349 Self::new(ErrorCode::Internal, message)
350 }
351}
352
353impl fmt::Display for Error {
354 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
355 if self.0.message_only {
356 return f.write_str(&self.0.message);
357 }
358 write!(f, "{}: {}", self.0.code, self.0.message)
359 }
360}
361
362impl ErrorCode {
363 const fn json_name(self) -> &'static str {
364 match self {
365 Self::Parser => "Parser",
366 Self::Syntax => "Syntax",
367 Self::Binder => "Binder",
368 Self::Catalog => "Catalog",
369 Self::Conversion => "Conversion",
370 Self::OutOfRange => "Out of Range",
371 Self::InvalidInput => "Invalid Input",
372 Self::OutOfMemory => "Out of Memory",
373 Self::Io => "IO",
374 Self::NotImplemented => "Not implemented",
375 Self::Constraint => "Constraint",
376 Self::Dependency => "Dependency",
377 Self::Sequence => "Sequence",
378 Self::Transaction => "TransactionContext",
379 Self::Settings => "Settings",
380 Self::Interrupt => "Interrupt",
381 Self::ParameterNotAllowed => "Parameter Not Allowed",
382 Self::MismatchType => "Mismatch Type",
383 Self::InvalidType => "Invalid type",
384 Self::Internal => "INTERNAL",
385 }
386 }
387
388 fn json_subtype(self, message: &str) -> Option<&'static str> {
389 match self {
390 Self::Parser => Some("SYNTAX_ERROR"),
391 Self::Binder if message.starts_with("Referenced column") => Some("COLUMN_NOT_FOUND"),
392 Self::Binder if message.contains("No function matches") => Some("NO_MATCHING_FUNCTION"),
393 Self::Catalog if message.contains("does not exist") => Some("MISSING_ENTRY"),
394 _ => None,
395 }
396 }
397}
398
399fn json_object(fields: &[(&str, String)]) -> String {
400 let mut out = String::from("{");
401 for (index, (name, value)) in fields.iter().enumerate() {
402 if index != 0 {
403 out.push(',');
404 }
405 out.push('"');
406 out.push_str(name);
407 out.push_str("\":\"");
408 for character in value.chars() {
409 match character {
410 '"' => out.push_str("\\\""),
411 '\\' => out.push_str("\\\\"),
412 '\n' => out.push_str("\\n"),
413 '\r' => out.push_str("\\r"),
414 '\t' => out.push_str("\\t"),
415 character if character <= '\u{1f}' => {
416 use std::fmt::Write as _;
417 let _ = write!(out, "\\u{:04x}", character as u32);
418 }
419 character => out.push(character),
420 }
421 }
422 out.push('"');
423 }
424 out.push('}');
425 out
426}
427
428impl std::error::Error for Error {}
429
430impl From<std::io::Error> for Error {
431 fn from(error: std::io::Error) -> Self {
432 Self::io(error.to_string())
433 }
434}
435
436#[cfg(test)]
437mod tests {
438 use super::{Error, ErrorCode, Span};
439
440 #[test]
441 fn an_error_prints_the_way_duckdb_prints_it() {
442 let error = Error::binder("Referenced column \"nope\" not found in FROM clause!");
443 assert_eq!(
444 error.to_string(),
445 "Binder Error: Referenced column \"nope\" not found in FROM clause!"
446 );
447 }
448
449 #[test]
450 fn a_json_error_is_structured_and_has_no_text_prefix() {
451 let error = Error::binder("Referenced column \"nope\" not found\nnext")
452 .with_span(Span::new(7, 11))
453 .into_json();
454 assert_eq!(
455 error.to_string(),
456 "{\"exception_type\":\"Binder\",\"exception_message\":\"Referenced column \\\"nope\\\" not found\\nnext\",\"location\":\"[7,4]\",\"position\":\"7\",\"error_subtype\":\"COLUMN_NOT_FOUND\"}"
457 );
458 assert_eq!(error.code(), ErrorCode::Binder);
459 }
460
461 #[test]
462 fn a_result_is_no_wider_than_the_value_in_it() {
463 assert_eq!(size_of::<Error>(), size_of::<usize>());
466 assert_eq!(size_of::<Result<String, Error>>(), size_of::<String>());
467 }
468
469 #[test]
470 fn a_span_survives_being_attached() {
471 let error = Error::parser("syntax error at or near \"FROM\"").with_span(Span::new(7, 11));
472 assert_eq!(error.span(), Some(Span::new(7, 11)));
473 assert_eq!(error.span().map(Span::len), Some(4));
474 assert_eq!(error.code(), ErrorCode::Parser);
475 }
476
477 #[test]
478 fn a_fallback_span_keeps_the_more_specific_range() {
479 let specific = Error::binder("missing")
480 .with_span(Span::new(7, 14))
481 .with_fallback_span(Span::new(0, 20));
482 assert_eq!(specific.span(), Some(Span::new(7, 14)));
483 let fallback = Error::binder("missing").with_fallback_span(Span::new(0, 20));
484 assert_eq!(fallback.span(), Some(Span::new(0, 20)));
485 assert_eq!(Error::binder("missing").with_fallback_span(Span::new(0, 0)).span(), None);
486 }
487
488 #[test]
489 fn the_fuzzer_can_tell_our_bugs_from_the_query_s_bugs() {
490 assert!(ErrorCode::Binder.is_user_error());
491 assert!(ErrorCode::Conversion.is_user_error());
492 assert!(!ErrorCode::Internal.is_user_error());
493 assert!(!ErrorCode::OutOfMemory.is_user_error());
494 assert!(!ErrorCode::NotImplemented.is_user_error());
497 }
498}