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