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