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 the value of a particular column is requested, but the index
51    /// is out of range for the statement.
52    InvalidColumnIndex(usize),
53
54    /// Error when the value of a named column is requested, but no column
55    /// matches the name for the statement.
56    InvalidColumnName(String),
57
58    /// Error when the value of a particular column is requested, but the type
59    /// of the result in that column cannot be converted to the requested
60    /// Rust type.
61    InvalidColumnType(usize, String, Type),
62
63    /// Error when datatype to duckdb type
64    ArrowTypeToDuckdbType(String, DataType),
65
66    /// Error when a query that was expected to insert one row did not insert
67    /// any or insert many.
68    StatementChangedRows(usize),
69
70    /// Error available for the implementors of the
71    /// [`ToSql`](crate::types::ToSql) trait.
72    ToSqlConversionFailure(Box<dyn error::Error + Send + Sync + 'static>),
73
74    /// Error when the SQL is not a `SELECT`, is not read-only.
75    InvalidQuery,
76
77    /// Error when the SQL contains multiple statements.
78    MultipleStatement,
79    /// Error when the number of bound parameters does not match the number of
80    /// parameters in the query. The first `usize` is how many parameters were
81    /// given, the 2nd is how many were expected.
82    InvalidParameterCount(usize, usize),
83
84    /// Append Error
85    AppendError,
86}
87
88impl PartialEq for Error {
89    fn eq(&self, other: &Error) -> bool {
90        match (self, other) {
91            (Error::DuckDBFailure(e1, s1), Error::DuckDBFailure(e2, s2)) => e1 == e2 && s1 == s2,
92            (Error::IntegralValueOutOfRange(i1, n1), Error::IntegralValueOutOfRange(i2, n2)) => i1 == i2 && n1 == n2,
93            (Error::Utf8Error(e1), Error::Utf8Error(e2)) => e1 == e2,
94            (Error::NulError(e1), Error::NulError(e2)) => e1 == e2,
95            (Error::InvalidParameterName(n1), Error::InvalidParameterName(n2)) => n1 == n2,
96            (Error::InvalidPath(p1), Error::InvalidPath(p2)) => p1 == p2,
97            (Error::ExecuteReturnedResults, Error::ExecuteReturnedResults) => true,
98            (Error::QueryReturnedNoRows, Error::QueryReturnedNoRows) => true,
99            (Error::InvalidColumnIndex(i1), Error::InvalidColumnIndex(i2)) => i1 == i2,
100            (Error::InvalidColumnName(n1), Error::InvalidColumnName(n2)) => n1 == n2,
101            (Error::InvalidColumnType(i1, n1, t1), Error::InvalidColumnType(i2, n2, t2)) => {
102                i1 == i2 && t1 == t2 && n1 == n2
103            }
104            (Error::StatementChangedRows(n1), Error::StatementChangedRows(n2)) => n1 == n2,
105            (Error::InvalidParameterCount(i1, n1), Error::InvalidParameterCount(i2, n2)) => i1 == i2 && n1 == n2,
106            (..) => false,
107        }
108    }
109}
110
111impl From<str::Utf8Error> for Error {
112    #[cold]
113    fn from(err: str::Utf8Error) -> Error {
114        Error::Utf8Error(err)
115    }
116}
117
118impl From<::std::ffi::NulError> for Error {
119    #[cold]
120    fn from(err: ::std::ffi::NulError) -> Error {
121        Error::NulError(err)
122    }
123}
124
125const UNKNOWN_COLUMN: usize = usize::MAX;
126
127/// The conversion isn't precise, but it's convenient to have it
128/// to allow use of `get_raw(…).as_…()?` in callbacks that take `Error`.
129impl From<FromSqlError> for Error {
130    #[cold]
131    fn from(err: FromSqlError) -> Error {
132        // The error type requires index and type fields, but they aren't known in this
133        // context.
134        match err {
135            FromSqlError::OutOfRange(val) => Error::IntegralValueOutOfRange(UNKNOWN_COLUMN, val),
136            #[cfg(feature = "uuid")]
137            FromSqlError::InvalidUuidSize(_) => {
138                Error::FromSqlConversionFailure(UNKNOWN_COLUMN, Type::Blob, Box::new(err))
139            }
140            FromSqlError::Other(source) => Error::FromSqlConversionFailure(UNKNOWN_COLUMN, Type::Null, source),
141            _ => Error::FromSqlConversionFailure(UNKNOWN_COLUMN, Type::Null, Box::new(err)),
142        }
143    }
144}
145
146impl fmt::Display for Error {
147    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
148        match *self {
149            Error::DuckDBFailure(ref err, None) => err.fmt(f),
150            Error::DuckDBFailure(_, Some(ref s)) => write!(f, "{s}"),
151            Error::FromSqlConversionFailure(i, ref t, ref err) => {
152                if i != UNKNOWN_COLUMN {
153                    write!(f, "Conversion error from type {t} at index: {i}, {err}")
154                } else {
155                    err.fmt(f)
156                }
157            }
158            Error::IntegralValueOutOfRange(col, val) => {
159                if col != UNKNOWN_COLUMN {
160                    write!(f, "Integer {val} out of range at index {col}")
161                } else {
162                    write!(f, "Integer {val} out of range")
163                }
164            }
165            Error::Utf8Error(ref err) => err.fmt(f),
166            Error::NulError(ref err) => err.fmt(f),
167            Error::InvalidParameterName(ref name) => write!(f, "Invalid parameter name: {name}"),
168            Error::InvalidPath(ref p) => write!(f, "Invalid path: {}", p.to_string_lossy()),
169            Error::ExecuteReturnedResults => {
170                write!(f, "Execute returned results - did you mean to call query?")
171            }
172            Error::QueryReturnedNoRows => write!(f, "Query returned no rows"),
173            Error::InvalidColumnIndex(i) => write!(f, "Invalid column index: {i}"),
174            Error::InvalidColumnName(ref name) => write!(f, "Invalid column name: {name}"),
175            Error::InvalidColumnType(i, ref name, ref t) => {
176                write!(f, "Invalid column type {t} at index: {i}, name: {name}")
177            }
178            Error::ArrowTypeToDuckdbType(ref name, ref t) => {
179                write!(f, "Invalid column type {t} , name: {name}")
180            }
181            Error::InvalidParameterCount(i1, n1) => {
182                write!(f, "Wrong number of parameters passed to query. Got {i1}, needed {n1}")
183            }
184            Error::StatementChangedRows(i) => write!(f, "Query changed {i} rows"),
185            Error::ToSqlConversionFailure(ref err) => err.fmt(f),
186            Error::InvalidQuery => write!(f, "Query is not read-only"),
187            Error::MultipleStatement => write!(f, "Multiple statements provided"),
188            Error::AppendError => write!(f, "Append error"),
189        }
190    }
191}
192
193impl error::Error for Error {
194    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
195        match *self {
196            Error::DuckDBFailure(ref err, _) => Some(err),
197            Error::Utf8Error(ref err) => Some(err),
198            Error::NulError(ref err) => Some(err),
199
200            Error::IntegralValueOutOfRange(..)
201            | Error::InvalidParameterName(_)
202            | Error::ExecuteReturnedResults
203            | Error::QueryReturnedNoRows
204            | Error::InvalidColumnIndex(_)
205            | Error::InvalidColumnName(_)
206            | Error::InvalidColumnType(..)
207            | Error::InvalidPath(_)
208            | Error::InvalidParameterCount(..)
209            | Error::StatementChangedRows(_)
210            | Error::InvalidQuery
211            | Error::AppendError
212            | Error::ArrowTypeToDuckdbType(..)
213            | Error::MultipleStatement => None,
214            Error::FromSqlConversionFailure(_, _, ref err) | Error::ToSqlConversionFailure(ref err) => Some(&**err),
215        }
216    }
217}
218
219// These are public but not re-exported by lib.rs, so only visible within crate.
220
221#[inline]
222pub(crate) fn error_from_duckdb_code(code: ffi::duckdb_state, message: Option<String>) -> Result<()> {
223    Err(Error::DuckDBFailure(ffi::Error::new(code), message))
224}
225
226#[cold]
227#[inline]
228pub fn result_from_duckdb_appender(code: ffi::duckdb_state, appender: *mut ffi::duckdb_appender) -> Result<()> {
229    if code == ffi::DuckDBSuccess {
230        return Ok(());
231    }
232    unsafe {
233        let message = if (*appender).is_null() {
234            Some("appender is null".to_string())
235        } else {
236            let c_err = ffi::duckdb_appender_error(*appender);
237            let message = Some(CStr::from_ptr(c_err).to_string_lossy().to_string());
238            ffi::duckdb_appender_destroy(appender);
239            message
240        };
241        error_from_duckdb_code(code, message)
242    }
243}
244
245#[cold]
246#[inline]
247pub fn result_from_duckdb_prepare(code: ffi::duckdb_state, mut prepare: ffi::duckdb_prepared_statement) -> Result<()> {
248    if code == ffi::DuckDBSuccess {
249        return Ok(());
250    }
251    unsafe {
252        let message = if prepare.is_null() {
253            Some("prepare is null".to_string())
254        } else {
255            let c_err = ffi::duckdb_prepare_error(prepare);
256            let message = Some(CStr::from_ptr(c_err).to_string_lossy().to_string());
257            ffi::duckdb_destroy_prepare(&mut prepare);
258            message
259        };
260        error_from_duckdb_code(code, message)
261    }
262}
263
264#[cold]
265#[inline]
266pub fn result_from_duckdb_arrow(code: ffi::duckdb_state, mut out: ffi::duckdb_arrow) -> Result<()> {
267    if code == ffi::DuckDBSuccess {
268        return Ok(());
269    }
270    unsafe {
271        let message = if out.is_null() {
272            Some("out is null".to_string())
273        } else {
274            let c_err = ffi::duckdb_query_arrow_error(out);
275            let message = Some(CStr::from_ptr(c_err).to_string_lossy().to_string());
276            ffi::duckdb_destroy_arrow(&mut out);
277            message
278        };
279        error_from_duckdb_code(code, message)
280    }
281}