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 ParameterNotAllowed,
92 Internal,
95}
96
97impl ErrorCode {
98 #[must_use]
105 pub const fn duckdb_name(self) -> &'static str {
106 match self {
107 Self::Parser => "Parser Error",
108 Self::Syntax => "Syntax Error",
109 Self::Binder => "Binder Error",
110 Self::Catalog => "Catalog Error",
111 Self::Conversion => "Conversion Error",
112 Self::OutOfRange => "Out of Range Error",
113 Self::InvalidInput => "Invalid Input Error",
114 Self::OutOfMemory => "Out of Memory Error",
115 Self::Io => "IO Error",
116 Self::NotImplemented => "Not implemented Error",
117 Self::Constraint => "Constraint Error",
118 Self::Transaction => "TransactionContext Error",
119 Self::Settings => "Settings Error",
120 Self::Interrupt => "Interrupt Error",
121 Self::ParameterNotAllowed => "Parameter Not Allowed Error",
122 Self::Internal => "INTERNAL Error",
123 }
124 }
125
126 #[must_use]
131 pub const fn is_user_error(self) -> bool {
132 matches!(
133 self,
134 Self::Parser
135 | Self::Syntax
136 | Self::Binder
137 | Self::Catalog
138 | Self::Conversion
139 | Self::OutOfRange
140 | Self::InvalidInput
141 | Self::Constraint
142 | Self::Transaction
143 | Self::Settings
144 | Self::ParameterNotAllowed
145 )
146 }
147}
148
149impl fmt::Display for ErrorCode {
150 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151 f.write_str(self.duckdb_name())
152 }
153}
154
155#[derive(Debug, Clone, PartialEq, Eq)]
161pub struct Error(Box<Payload>);
162
163#[derive(Debug, Clone, PartialEq, Eq)]
164struct Payload {
165 code: ErrorCode,
166 message: String,
167 span: Option<Span>,
168 message_only: bool,
169}
170
171impl Error {
172 pub fn new(code: ErrorCode, message: impl Into<String>) -> Self {
174 Self(Box::new(Payload { code, message: message.into(), span: None, message_only: false }))
175 }
176
177 #[must_use]
179 pub fn with_span(mut self, span: Span) -> Self {
180 self.0.span = Some(span);
181 self
182 }
183
184 #[must_use]
186 pub fn with_fallback_span(mut self, span: Span) -> Self {
187 if self.0.span.is_none() && !span.is_empty() {
188 self.0.span = Some(span);
189 }
190 self
191 }
192
193 #[must_use]
195 pub fn code(&self) -> ErrorCode {
196 self.0.code
197 }
198
199 #[must_use]
201 pub fn message(&self) -> &str {
202 &self.0.message
203 }
204
205 #[must_use]
207 pub fn span(&self) -> Option<Span> {
208 self.0.span
209 }
210
211 #[must_use]
213 pub fn into_json(mut self) -> Self {
214 let exception_type = self.0.code.json_name();
215 let subtype = self.0.code.json_subtype(&self.0.message);
216 let mut fields = vec![
217 ("exception_type", exception_type.to_string()),
218 ("exception_message", self.0.message.clone()),
219 ];
220 if let Some(span) = self.0.span {
221 fields.push(("location", format!("[{},{}]", span.start, span.len())));
222 fields.push(("position", span.start.to_string()));
223 }
224 if let Some(subtype) = subtype {
225 fields.push(("error_subtype", subtype.to_string()));
226 }
227 self.0.message = json_object(&fields);
228 self.0.message_only = true;
229 self
230 }
231
232 pub fn parser(message: impl Into<String>) -> Self {
234 Self::new(ErrorCode::Parser, message)
235 }
236
237 pub fn syntax(message: impl Into<String>) -> Self {
239 Self::new(ErrorCode::Syntax, message)
240 }
241
242 pub fn binder(message: impl Into<String>) -> Self {
244 Self::new(ErrorCode::Binder, message)
245 }
246
247 pub fn catalog(message: impl Into<String>) -> Self {
249 Self::new(ErrorCode::Catalog, message)
250 }
251
252 pub fn conversion(message: impl Into<String>) -> Self {
254 Self::new(ErrorCode::Conversion, message)
255 }
256
257 pub fn out_of_range(message: impl Into<String>) -> Self {
259 Self::new(ErrorCode::OutOfRange, message)
260 }
261
262 pub fn invalid_input(message: impl Into<String>) -> Self {
264 Self::new(ErrorCode::InvalidInput, message)
265 }
266
267 pub fn out_of_memory(message: impl Into<String>) -> Self {
269 Self::new(ErrorCode::OutOfMemory, message)
270 }
271
272 pub fn io(message: impl Into<String>) -> Self {
274 Self::new(ErrorCode::Io, message)
275 }
276
277 pub fn not_implemented(message: impl Into<String>) -> Self {
279 Self::new(ErrorCode::NotImplemented, message)
280 }
281
282 pub fn constraint(message: impl Into<String>) -> Self {
284 Self::new(ErrorCode::Constraint, message)
285 }
286
287 pub fn transaction(message: impl Into<String>) -> Self {
289 Self::new(ErrorCode::Transaction, message)
290 }
291
292 pub fn settings(message: impl Into<String>) -> Self {
294 Self::new(ErrorCode::Settings, message)
295 }
296
297 pub fn parameter_not_allowed(message: impl Into<String>) -> Self {
299 Self::new(ErrorCode::ParameterNotAllowed, message)
300 }
301
302 pub fn interrupt(message: impl Into<String>) -> Self {
304 Self::new(ErrorCode::Interrupt, message)
305 }
306
307 pub fn internal(message: impl Into<String>) -> Self {
312 Self::new(ErrorCode::Internal, message)
313 }
314}
315
316impl fmt::Display for Error {
317 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
318 if self.0.message_only {
319 return f.write_str(&self.0.message);
320 }
321 write!(f, "{}: {}", self.0.code, self.0.message)
322 }
323}
324
325impl ErrorCode {
326 const fn json_name(self) -> &'static str {
327 match self {
328 Self::Parser => "Parser",
329 Self::Syntax => "Syntax",
330 Self::Binder => "Binder",
331 Self::Catalog => "Catalog",
332 Self::Conversion => "Conversion",
333 Self::OutOfRange => "Out of Range",
334 Self::InvalidInput => "Invalid Input",
335 Self::OutOfMemory => "Out of Memory",
336 Self::Io => "IO",
337 Self::NotImplemented => "Not implemented",
338 Self::Constraint => "Constraint",
339 Self::Transaction => "TransactionContext",
340 Self::Settings => "Settings",
341 Self::Interrupt => "Interrupt",
342 Self::ParameterNotAllowed => "Parameter Not Allowed",
343 Self::Internal => "INTERNAL",
344 }
345 }
346
347 fn json_subtype(self, message: &str) -> Option<&'static str> {
348 match self {
349 Self::Parser => Some("SYNTAX_ERROR"),
350 Self::Binder if message.starts_with("Referenced column") => Some("COLUMN_NOT_FOUND"),
351 Self::Binder if message.contains("No function matches") => Some("NO_MATCHING_FUNCTION"),
352 Self::Catalog if message.contains("does not exist") => Some("MISSING_ENTRY"),
353 _ => None,
354 }
355 }
356}
357
358fn json_object(fields: &[(&str, String)]) -> String {
359 let mut out = String::from("{");
360 for (index, (name, value)) in fields.iter().enumerate() {
361 if index != 0 {
362 out.push(',');
363 }
364 out.push('"');
365 out.push_str(name);
366 out.push_str("\":\"");
367 for character in value.chars() {
368 match character {
369 '"' => out.push_str("\\\""),
370 '\\' => out.push_str("\\\\"),
371 '\n' => out.push_str("\\n"),
372 '\r' => out.push_str("\\r"),
373 '\t' => out.push_str("\\t"),
374 character if character <= '\u{1f}' => {
375 use std::fmt::Write as _;
376 let _ = write!(out, "\\u{:04x}", character as u32);
377 }
378 character => out.push(character),
379 }
380 }
381 out.push('"');
382 }
383 out.push('}');
384 out
385}
386
387impl std::error::Error for Error {}
388
389impl From<std::io::Error> for Error {
390 fn from(error: std::io::Error) -> Self {
391 Self::io(error.to_string())
392 }
393}
394
395#[cfg(test)]
396mod tests {
397 use super::{Error, ErrorCode, Span};
398
399 #[test]
400 fn an_error_prints_the_way_duckdb_prints_it() {
401 let error = Error::binder("Referenced column \"nope\" not found in FROM clause!");
402 assert_eq!(
403 error.to_string(),
404 "Binder Error: Referenced column \"nope\" not found in FROM clause!"
405 );
406 }
407
408 #[test]
409 fn a_json_error_is_structured_and_has_no_text_prefix() {
410 let error = Error::binder("Referenced column \"nope\" not found\nnext")
411 .with_span(Span::new(7, 11))
412 .into_json();
413 assert_eq!(
414 error.to_string(),
415 "{\"exception_type\":\"Binder\",\"exception_message\":\"Referenced column \\\"nope\\\" not found\\nnext\",\"location\":\"[7,4]\",\"position\":\"7\",\"error_subtype\":\"COLUMN_NOT_FOUND\"}"
416 );
417 assert_eq!(error.code(), ErrorCode::Binder);
418 }
419
420 #[test]
421 fn a_result_is_no_wider_than_the_value_in_it() {
422 assert_eq!(size_of::<Error>(), size_of::<usize>());
425 assert_eq!(size_of::<Result<String, Error>>(), size_of::<String>());
426 }
427
428 #[test]
429 fn a_span_survives_being_attached() {
430 let error = Error::parser("syntax error at or near \"FROM\"").with_span(Span::new(7, 11));
431 assert_eq!(error.span(), Some(Span::new(7, 11)));
432 assert_eq!(error.span().map(Span::len), Some(4));
433 assert_eq!(error.code(), ErrorCode::Parser);
434 }
435
436 #[test]
437 fn a_fallback_span_keeps_the_more_specific_range() {
438 let specific = Error::binder("missing")
439 .with_span(Span::new(7, 14))
440 .with_fallback_span(Span::new(0, 20));
441 assert_eq!(specific.span(), Some(Span::new(7, 14)));
442 let fallback = Error::binder("missing").with_fallback_span(Span::new(0, 20));
443 assert_eq!(fallback.span(), Some(Span::new(0, 20)));
444 assert_eq!(Error::binder("missing").with_fallback_span(Span::new(0, 0)).span(), None);
445 }
446
447 #[test]
448 fn the_fuzzer_can_tell_our_bugs_from_the_query_s_bugs() {
449 assert!(ErrorCode::Binder.is_user_error());
450 assert!(ErrorCode::Conversion.is_user_error());
451 assert!(!ErrorCode::Internal.is_user_error());
452 assert!(!ErrorCode::OutOfMemory.is_user_error());
453 assert!(!ErrorCode::NotImplemented.is_user_error());
456 }
457}