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 with_fallback_span(mut self, span: Span) -> Self {
182 if self.0.span.is_none() && !span.is_empty() {
183 self.0.span = Some(span);
184 }
185 self
186 }
187
188 #[must_use]
190 pub fn code(&self) -> ErrorCode {
191 self.0.code
192 }
193
194 #[must_use]
196 pub fn message(&self) -> &str {
197 &self.0.message
198 }
199
200 #[must_use]
202 pub fn span(&self) -> Option<Span> {
203 self.0.span
204 }
205
206 #[must_use]
208 pub fn into_json(mut self) -> Self {
209 let exception_type = self.0.code.json_name();
210 let subtype = self.0.code.json_subtype(&self.0.message);
211 let mut fields = vec![
212 ("exception_type", exception_type.to_string()),
213 ("exception_message", self.0.message.clone()),
214 ];
215 if let Some(span) = self.0.span {
216 fields.push(("location", format!("[{},{}]", span.start, span.len())));
217 fields.push(("position", span.start.to_string()));
218 }
219 if let Some(subtype) = subtype {
220 fields.push(("error_subtype", subtype.to_string()));
221 }
222 self.0.message = json_object(&fields);
223 self.0.message_only = true;
224 self
225 }
226
227 pub fn parser(message: impl Into<String>) -> Self {
229 Self::new(ErrorCode::Parser, message)
230 }
231
232 pub fn syntax(message: impl Into<String>) -> Self {
234 Self::new(ErrorCode::Syntax, message)
235 }
236
237 pub fn binder(message: impl Into<String>) -> Self {
239 Self::new(ErrorCode::Binder, message)
240 }
241
242 pub fn catalog(message: impl Into<String>) -> Self {
244 Self::new(ErrorCode::Catalog, message)
245 }
246
247 pub fn conversion(message: impl Into<String>) -> Self {
249 Self::new(ErrorCode::Conversion, message)
250 }
251
252 pub fn out_of_range(message: impl Into<String>) -> Self {
254 Self::new(ErrorCode::OutOfRange, message)
255 }
256
257 pub fn invalid_input(message: impl Into<String>) -> Self {
259 Self::new(ErrorCode::InvalidInput, message)
260 }
261
262 pub fn out_of_memory(message: impl Into<String>) -> Self {
264 Self::new(ErrorCode::OutOfMemory, message)
265 }
266
267 pub fn io(message: impl Into<String>) -> Self {
269 Self::new(ErrorCode::Io, message)
270 }
271
272 pub fn not_implemented(message: impl Into<String>) -> Self {
274 Self::new(ErrorCode::NotImplemented, message)
275 }
276
277 pub fn constraint(message: impl Into<String>) -> Self {
279 Self::new(ErrorCode::Constraint, message)
280 }
281
282 pub fn transaction(message: impl Into<String>) -> Self {
284 Self::new(ErrorCode::Transaction, message)
285 }
286
287 pub fn settings(message: impl Into<String>) -> Self {
289 Self::new(ErrorCode::Settings, message)
290 }
291
292 pub fn interrupt(message: impl Into<String>) -> Self {
294 Self::new(ErrorCode::Interrupt, message)
295 }
296
297 pub fn internal(message: impl Into<String>) -> Self {
302 Self::new(ErrorCode::Internal, message)
303 }
304}
305
306impl fmt::Display for Error {
307 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
308 if self.0.message_only {
309 return f.write_str(&self.0.message);
310 }
311 write!(f, "{}: {}", self.0.code, self.0.message)
312 }
313}
314
315impl ErrorCode {
316 const fn json_name(self) -> &'static str {
317 match self {
318 Self::Parser => "Parser",
319 Self::Syntax => "Syntax",
320 Self::Binder => "Binder",
321 Self::Catalog => "Catalog",
322 Self::Conversion => "Conversion",
323 Self::OutOfRange => "Out of Range",
324 Self::InvalidInput => "Invalid Input",
325 Self::OutOfMemory => "Out of Memory",
326 Self::Io => "IO",
327 Self::NotImplemented => "Not implemented",
328 Self::Constraint => "Constraint",
329 Self::Transaction => "TransactionContext",
330 Self::Settings => "Settings",
331 Self::Interrupt => "Interrupt",
332 Self::Internal => "INTERNAL",
333 }
334 }
335
336 fn json_subtype(self, message: &str) -> Option<&'static str> {
337 match self {
338 Self::Parser => Some("SYNTAX_ERROR"),
339 Self::Binder if message.starts_with("Referenced column") => Some("COLUMN_NOT_FOUND"),
340 Self::Binder if message.contains("No function matches") => Some("NO_MATCHING_FUNCTION"),
341 Self::Catalog if message.contains("does not exist") => Some("MISSING_ENTRY"),
342 _ => None,
343 }
344 }
345}
346
347fn json_object(fields: &[(&str, String)]) -> String {
348 let mut out = String::from("{");
349 for (index, (name, value)) in fields.iter().enumerate() {
350 if index != 0 {
351 out.push(',');
352 }
353 out.push('"');
354 out.push_str(name);
355 out.push_str("\":\"");
356 for character in value.chars() {
357 match character {
358 '"' => out.push_str("\\\""),
359 '\\' => out.push_str("\\\\"),
360 '\n' => out.push_str("\\n"),
361 '\r' => out.push_str("\\r"),
362 '\t' => out.push_str("\\t"),
363 character if character <= '\u{1f}' => {
364 use std::fmt::Write as _;
365 let _ = write!(out, "\\u{:04x}", character as u32);
366 }
367 character => out.push(character),
368 }
369 }
370 out.push('"');
371 }
372 out.push('}');
373 out
374}
375
376impl std::error::Error for Error {}
377
378impl From<std::io::Error> for Error {
379 fn from(error: std::io::Error) -> Self {
380 Self::io(error.to_string())
381 }
382}
383
384#[cfg(test)]
385mod tests {
386 use super::{Error, ErrorCode, Span};
387
388 #[test]
389 fn an_error_prints_the_way_duckdb_prints_it() {
390 let error = Error::binder("Referenced column \"nope\" not found in FROM clause!");
391 assert_eq!(
392 error.to_string(),
393 "Binder Error: Referenced column \"nope\" not found in FROM clause!"
394 );
395 }
396
397 #[test]
398 fn a_json_error_is_structured_and_has_no_text_prefix() {
399 let error = Error::binder("Referenced column \"nope\" not found\nnext")
400 .with_span(Span::new(7, 11))
401 .into_json();
402 assert_eq!(
403 error.to_string(),
404 "{\"exception_type\":\"Binder\",\"exception_message\":\"Referenced column \\\"nope\\\" not found\\nnext\",\"location\":\"[7,4]\",\"position\":\"7\",\"error_subtype\":\"COLUMN_NOT_FOUND\"}"
405 );
406 assert_eq!(error.code(), ErrorCode::Binder);
407 }
408
409 #[test]
410 fn a_result_is_no_wider_than_the_value_in_it() {
411 assert_eq!(size_of::<Error>(), size_of::<usize>());
414 assert_eq!(size_of::<Result<String, Error>>(), size_of::<String>());
415 }
416
417 #[test]
418 fn a_span_survives_being_attached() {
419 let error = Error::parser("syntax error at or near \"FROM\"").with_span(Span::new(7, 11));
420 assert_eq!(error.span(), Some(Span::new(7, 11)));
421 assert_eq!(error.span().map(Span::len), Some(4));
422 assert_eq!(error.code(), ErrorCode::Parser);
423 }
424
425 #[test]
426 fn a_fallback_span_keeps_the_more_specific_range() {
427 let specific = Error::binder("missing")
428 .with_span(Span::new(7, 14))
429 .with_fallback_span(Span::new(0, 20));
430 assert_eq!(specific.span(), Some(Span::new(7, 14)));
431 let fallback = Error::binder("missing").with_fallback_span(Span::new(0, 20));
432 assert_eq!(fallback.span(), Some(Span::new(0, 20)));
433 assert_eq!(Error::binder("missing").with_fallback_span(Span::new(0, 0)).span(), None);
434 }
435
436 #[test]
437 fn the_fuzzer_can_tell_our_bugs_from_the_query_s_bugs() {
438 assert!(ErrorCode::Binder.is_user_error());
439 assert!(ErrorCode::Conversion.is_user_error());
440 assert!(!ErrorCode::Internal.is_user_error());
441 assert!(!ErrorCode::OutOfMemory.is_user_error());
442 assert!(!ErrorCode::NotImplemented.is_user_error());
445 }
446}