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#[derive(Debug)]
12#[allow(clippy::enum_variant_names)]
13#[non_exhaustive]
14pub enum Error {
15 DuckDBFailure(ffi::Error, Option<String>),
17
18 FromSqlConversionFailure(usize, Type, Box<dyn error::Error + Send + Sync + 'static>),
21
22 IntegralValueOutOfRange(usize, i128),
27
28 Utf8Error(str::Utf8Error),
30
31 NulError(::std::ffi::NulError),
34
35 InvalidParameterName(String),
38
39 InvalidPath(PathBuf),
41
42 ExecuteReturnedResults,
45
46 QueryReturnedNoRows,
49
50 QueryReturnedMoreThanOneRow,
53
54 InvalidColumnIndex(usize),
57
58 InvalidColumnName(String),
61
62 InvalidColumnType(usize, String, Type),
66
67 ArrowTypeToDuckdbType(String, DataType),
69
70 StatementChangedRows(usize),
73
74 ToSqlConversionFailure(Box<dyn error::Error + Send + Sync + 'static>),
77
78 InvalidQuery,
80
81 MultipleStatement,
83
84 InvalidParameterCount(usize, usize),
88
89 InvalidParameterIndex(usize),
92
93 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
138impl From<FromSqlError> for Error {
141 #[cold]
142 fn from(err: FromSqlError) -> Self {
143 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#[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}