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