duckdb/
error.rs

1use arrow::datatypes::DataType;
2
3use super::Result;
4use crate::{
5    ffi,
6    types::{FromSqlError, Type},
7};
8use std::{error, ffi::CStr, fmt, path::PathBuf, str};
9
10/// Enum listing possible errors from duckdb.
11#[derive(Debug)]
12#[allow(clippy::enum_variant_names)]
13#[non_exhaustive]
14pub enum Error {
15    /// An error from an underlying DuckDB call.
16    DuckDBFailure(ffi::Error, Option<String>),
17
18    /// Error when the value of a particular column is requested, but it cannot
19    /// be converted to the requested Rust type.
20    FromSqlConversionFailure(usize, Type, Box<dyn error::Error + Send + Sync + 'static>),
21
22    /// Error when DuckDB gives us an integral value outside the range of the
23    /// requested type (e.g., trying to get the value 1000 into a `u8`).
24    /// The associated `usize` is the column index,
25    /// and the associated `i64` is the value returned by SQLite.
26    IntegralValueOutOfRange(usize, i128),
27
28    /// Error converting a string to UTF-8.
29    Utf8Error(str::Utf8Error),
30
31    /// Error converting a string to a C-compatible string because it contained
32    /// an embedded nul.
33    NulError(::std::ffi::NulError),
34
35    /// Error when using SQL named parameters and passing a parameter name not
36    /// present in the SQL.
37    InvalidParameterName(String),
38
39    /// Error converting a file path to a string.
40    InvalidPath(PathBuf),
41
42    /// Error returned when an [`execute`](crate::Connection::execute) call
43    /// returns rows.
44    ExecuteReturnedResults,
45
46    /// Error when a query that was expected to return at least one row (e.g.,
47    /// for [`query_row`](crate::Connection::query_row)) did not return any.
48    QueryReturnedNoRows,
49
50    /// Error when a query that was expected to return only one row (e.g.,
51    /// for [`query_one`](crate::Connection::query_one)) did return more than one.
52    QueryReturnedMoreThanOneRow,
53
54    /// Error when the value of a particular column is requested, but the index
55    /// is out of range for the statement.
56    InvalidColumnIndex(usize),
57
58    /// Error when the value of a named column is requested, but no column
59    /// matches the name for the statement.
60    InvalidColumnName(String),
61
62    /// Error when the value of a particular column is requested, but the type
63    /// of the result in that column cannot be converted to the requested
64    /// Rust type.
65    InvalidColumnType(usize, String, Type),
66
67    /// Error when datatype to duckdb type
68    ArrowTypeToDuckdbType(String, DataType),
69
70    /// Error when a query that was expected to insert one row did not insert
71    /// any or insert many.
72    StatementChangedRows(usize),
73
74    /// Error available for the implementors of the
75    /// [`ToSql`](crate::types::ToSql) trait.
76    ToSqlConversionFailure(Box<dyn error::Error + Send + Sync + 'static>),
77
78    /// Error when the SQL is not a `SELECT`, is not read-only.
79    InvalidQuery,
80
81    /// Error when the SQL contains multiple statements.
82    MultipleStatement,
83
84    /// Error when the number of bound parameters does not match the number of
85    /// parameters in the query. The first `usize` is how many parameters were
86    /// given, the 2nd is how many were expected.
87    InvalidParameterCount(usize, usize),
88
89    /// Error when a parameter is requested, but the index is out of range
90    /// for the statement.
91    InvalidParameterIndex(usize),
92
93    /// Append Error
94    AppendError,
95}
96
97impl PartialEq for Error {
98    fn eq(&self, other: &Self) -> bool {
99        match (self, other) {
100            (Self::DuckDBFailure(e1, s1), Self::DuckDBFailure(e2, s2)) => e1 == e2 && s1 == s2,
101            (Self::IntegralValueOutOfRange(i1, n1), Self::IntegralValueOutOfRange(i2, n2)) => i1 == i2 && n1 == n2,
102            (Self::Utf8Error(e1), Self::Utf8Error(e2)) => e1 == e2,
103            (Self::NulError(e1), Self::NulError(e2)) => e1 == e2,
104            (Self::InvalidParameterName(n1), Self::InvalidParameterName(n2)) => n1 == n2,
105            (Self::InvalidPath(p1), Self::InvalidPath(p2)) => p1 == p2,
106            (Self::ExecuteReturnedResults, Self::ExecuteReturnedResults) => true,
107            (Self::QueryReturnedNoRows, Self::QueryReturnedNoRows) => true,
108            (Self::QueryReturnedMoreThanOneRow, Self::QueryReturnedMoreThanOneRow) => true,
109            (Self::InvalidColumnIndex(i1), Self::InvalidColumnIndex(i2)) => i1 == i2,
110            (Self::InvalidColumnName(n1), Self::InvalidColumnName(n2)) => n1 == n2,
111            (Self::InvalidColumnType(i1, n1, t1), Self::InvalidColumnType(i2, n2, t2)) => {
112                i1 == i2 && t1 == t2 && n1 == n2
113            }
114            (Self::StatementChangedRows(n1), Self::StatementChangedRows(n2)) => n1 == n2,
115            (Self::InvalidParameterCount(i1, n1), Self::InvalidParameterCount(i2, n2)) => i1 == i2 && n1 == n2,
116            (Self::InvalidParameterIndex(i1), Self::InvalidParameterIndex(i2)) => i1 == i2,
117            (..) => false,
118        }
119    }
120}
121
122impl From<str::Utf8Error> for Error {
123    #[cold]
124    fn from(err: str::Utf8Error) -> Self {
125        Self::Utf8Error(err)
126    }
127}
128
129impl From<::std::ffi::NulError> for Error {
130    #[cold]
131    fn from(err: ::std::ffi::NulError) -> Self {
132        Self::NulError(err)
133    }
134}
135
136const UNKNOWN_COLUMN: usize = usize::MAX;
137
138/// The conversion isn't precise, but it's convenient to have it
139/// to allow use of `get_raw(…).as_…()?` in callbacks that take `Error`.
140impl From<FromSqlError> for Error {
141    #[cold]
142    fn from(err: FromSqlError) -> Self {
143        // The error type requires index and type fields, but they aren't known in this
144        // context.
145        match err {
146            FromSqlError::OutOfRange(val) => Self::IntegralValueOutOfRange(UNKNOWN_COLUMN, val),
147            #[cfg(feature = "uuid")]
148            FromSqlError::InvalidUuidSize(_) => {
149                Self::FromSqlConversionFailure(UNKNOWN_COLUMN, Type::Blob, Box::new(err))
150            }
151            FromSqlError::Other(source) => Self::FromSqlConversionFailure(UNKNOWN_COLUMN, Type::Null, source),
152            _ => Self::FromSqlConversionFailure(UNKNOWN_COLUMN, Type::Null, Box::new(err)),
153        }
154    }
155}
156
157impl fmt::Display for Error {
158    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
159        match *self {
160            Self::DuckDBFailure(ref err, None) => err.fmt(f),
161            Self::DuckDBFailure(_, Some(ref s)) => write!(f, "{s}"),
162            Self::FromSqlConversionFailure(i, ref t, ref err) => {
163                if i != UNKNOWN_COLUMN {
164                    write!(f, "Conversion error from type {t} at index: {i}, {err}")
165                } else {
166                    err.fmt(f)
167                }
168            }
169            Self::IntegralValueOutOfRange(col, val) => {
170                if col != UNKNOWN_COLUMN {
171                    write!(f, "Integer {val} out of range at index {col}")
172                } else {
173                    write!(f, "Integer {val} out of range")
174                }
175            }
176            Self::Utf8Error(ref err) => err.fmt(f),
177            Self::NulError(ref err) => err.fmt(f),
178            Self::InvalidParameterName(ref name) => write!(f, "Invalid parameter name: {name}"),
179            Self::InvalidPath(ref p) => write!(f, "Invalid path: {}", p.to_string_lossy()),
180            Self::ExecuteReturnedResults => {
181                write!(f, "Execute returned results - did you mean to call query?")
182            }
183            Self::QueryReturnedNoRows => write!(f, "Query returned no rows"),
184            Self::QueryReturnedMoreThanOneRow => write!(f, "Query returned more than one row"),
185            Self::InvalidColumnIndex(i) => write!(f, "Invalid column index: {i}"),
186            Self::InvalidColumnName(ref name) => write!(f, "Invalid column name: {name}"),
187            Self::InvalidColumnType(i, ref name, ref t) => {
188                write!(f, "Invalid column type {t} at index: {i}, name: {name}")
189            }
190            Self::ArrowTypeToDuckdbType(ref name, ref t) => {
191                write!(f, "Invalid column type {t} , name: {name}")
192            }
193            Self::InvalidParameterCount(i1, n1) => {
194                write!(f, "Wrong number of parameters passed to query. Got {i1}, needed {n1}")
195            }
196            Self::InvalidParameterIndex(i) => write!(f, "Invalid parameter index: {i}"),
197            Self::StatementChangedRows(i) => write!(f, "Query changed {i} rows"),
198            Self::ToSqlConversionFailure(ref err) => err.fmt(f),
199            Self::InvalidQuery => write!(f, "Query is not read-only"),
200            Self::MultipleStatement => write!(f, "Multiple statements provided"),
201            Self::AppendError => write!(f, "Append error"),
202        }
203    }
204}
205
206impl error::Error for Error {
207    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
208        match *self {
209            Self::DuckDBFailure(ref err, _) => Some(err),
210            Self::Utf8Error(ref err) => Some(err),
211            Self::NulError(ref err) => Some(err),
212
213            Self::IntegralValueOutOfRange(..)
214            | Self::InvalidParameterName(_)
215            | Self::ExecuteReturnedResults
216            | Self::QueryReturnedNoRows
217            | Self::QueryReturnedMoreThanOneRow
218            | Self::InvalidColumnIndex(_)
219            | Self::InvalidColumnName(_)
220            | Self::InvalidColumnType(..)
221            | Self::InvalidPath(_)
222            | Self::InvalidParameterCount(..)
223            | Self::InvalidParameterIndex(_)
224            | Self::StatementChangedRows(_)
225            | Self::InvalidQuery
226            | Self::AppendError
227            | Self::ArrowTypeToDuckdbType(..)
228            | Self::MultipleStatement => None,
229            Self::FromSqlConversionFailure(_, _, ref err) | Self::ToSqlConversionFailure(ref err) => Some(&**err),
230        }
231    }
232}
233
234// These are public but not re-exported by lib.rs, so only visible within crate.
235
236#[inline]
237fn error_from_duckdb_code(code: ffi::duckdb_state, message: Option<String>) -> Result<()> {
238    Err(Error::DuckDBFailure(ffi::Error::new(code), message))
239}
240
241#[cold]
242#[inline]
243pub fn result_from_duckdb_appender(code: ffi::duckdb_state, appender: *mut ffi::duckdb_appender) -> Result<()> {
244    if code == ffi::DuckDBSuccess {
245        return Ok(());
246    }
247    unsafe {
248        let message = if (*appender).is_null() {
249            Some("appender is null".to_string())
250        } else {
251            let c_err = ffi::duckdb_appender_error(*appender);
252            let message = Some(CStr::from_ptr(c_err).to_string_lossy().to_string());
253            ffi::duckdb_appender_destroy(appender);
254            message
255        };
256        error_from_duckdb_code(code, message)
257    }
258}
259
260#[cold]
261#[inline]
262pub fn result_from_duckdb_prepare(code: ffi::duckdb_state, mut prepare: ffi::duckdb_prepared_statement) -> Result<()> {
263    if code == ffi::DuckDBSuccess {
264        return Ok(());
265    }
266    unsafe {
267        let message = if prepare.is_null() {
268            Some("prepare is null".to_string())
269        } else {
270            let c_err = ffi::duckdb_prepare_error(prepare);
271            let message = Some(CStr::from_ptr(c_err).to_string_lossy().to_string());
272            ffi::duckdb_destroy_prepare(&mut prepare);
273            message
274        };
275        error_from_duckdb_code(code, message)
276    }
277}
278
279#[cold]
280#[inline]
281pub fn result_from_duckdb_arrow(code: ffi::duckdb_state, mut out: ffi::duckdb_arrow) -> Result<()> {
282    if code == ffi::DuckDBSuccess {
283        return Ok(());
284    }
285    unsafe {
286        let message = if out.is_null() {
287            Some("out is null".to_string())
288        } else {
289            let c_err = ffi::duckdb_query_arrow_error(out);
290            let message = Some(CStr::from_ptr(c_err).to_string_lossy().to_string());
291            ffi::duckdb_destroy_arrow(&mut out);
292            message
293        };
294        error_from_duckdb_code(code, message)
295    }
296}
297
298#[cold]
299#[inline]
300pub fn result_from_duckdb_extract(
301    num_statements: ffi::idx_t,
302    mut extracted: ffi::duckdb_extracted_statements,
303) -> Result<()> {
304    if num_statements > 0 {
305        return Ok(());
306    }
307    unsafe {
308        let message = if extracted.is_null() {
309            Some("extracted statements are null".to_string())
310        } else {
311            let c_err = ffi::duckdb_extract_statements_error(extracted);
312            let message = if c_err.is_null() {
313                None
314            } else {
315                Some(CStr::from_ptr(c_err).to_string_lossy().to_string())
316            };
317            ffi::duckdb_destroy_extracted(&mut extracted);
318            message
319        };
320        error_from_duckdb_code(ffi::DuckDBError, message)
321    }
322}