use super::Result;
use crate::ffi;
use crate::types::FromSqlError;
use crate::types::Type;
use std::error;
use std::ffi::CStr;
use std::fmt;
use std::path::PathBuf;
use std::str;
#[derive(Debug)]
#[allow(clippy::enum_variant_names)]
#[non_exhaustive]
pub enum Error {
DuckDBFailure(ffi::Error, Option<String>),
FromSqlConversionFailure(usize, Type, Box<dyn error::Error + Send + Sync + 'static>),
IntegralValueOutOfRange(usize, i128),
Utf8Error(str::Utf8Error),
NulError(::std::ffi::NulError),
InvalidParameterName(String),
InvalidPath(PathBuf),
ExecuteReturnedResults,
QueryReturnedNoRows,
InvalidColumnIndex(usize),
InvalidColumnName(String),
InvalidColumnType(usize, String, Type),
StatementChangedRows(usize),
ToSqlConversionFailure(Box<dyn error::Error + Send + Sync + 'static>),
InvalidQuery,
MultipleStatement,
InvalidParameterCount(usize, usize),
AppendError,
}
impl PartialEq for Error {
fn eq(&self, other: &Error) -> bool {
match (self, other) {
(Error::DuckDBFailure(e1, s1), Error::DuckDBFailure(e2, s2)) => e1 == e2 && s1 == s2,
(Error::IntegralValueOutOfRange(i1, n1), Error::IntegralValueOutOfRange(i2, n2)) => i1 == i2 && n1 == n2,
(Error::Utf8Error(e1), Error::Utf8Error(e2)) => e1 == e2,
(Error::NulError(e1), Error::NulError(e2)) => e1 == e2,
(Error::InvalidParameterName(n1), Error::InvalidParameterName(n2)) => n1 == n2,
(Error::InvalidPath(p1), Error::InvalidPath(p2)) => p1 == p2,
(Error::ExecuteReturnedResults, Error::ExecuteReturnedResults) => true,
(Error::QueryReturnedNoRows, Error::QueryReturnedNoRows) => true,
(Error::InvalidColumnIndex(i1), Error::InvalidColumnIndex(i2)) => i1 == i2,
(Error::InvalidColumnName(n1), Error::InvalidColumnName(n2)) => n1 == n2,
(Error::InvalidColumnType(i1, n1, t1), Error::InvalidColumnType(i2, n2, t2)) => {
i1 == i2 && t1 == t2 && n1 == n2
}
(Error::StatementChangedRows(n1), Error::StatementChangedRows(n2)) => n1 == n2,
(Error::InvalidParameterCount(i1, n1), Error::InvalidParameterCount(i2, n2)) => i1 == i2 && n1 == n2,
(..) => false,
}
}
}
impl From<str::Utf8Error> for Error {
#[cold]
fn from(err: str::Utf8Error) -> Error {
Error::Utf8Error(err)
}
}
impl From<::std::ffi::NulError> for Error {
#[cold]
fn from(err: ::std::ffi::NulError) -> Error {
Error::NulError(err)
}
}
const UNKNOWN_COLUMN: usize = std::usize::MAX;
impl From<FromSqlError> for Error {
#[cold]
fn from(err: FromSqlError) -> Error {
match err {
FromSqlError::OutOfRange(val) => Error::IntegralValueOutOfRange(UNKNOWN_COLUMN, val),
#[cfg(feature = "uuid")]
FromSqlError::InvalidUuidSize(_) => {
Error::FromSqlConversionFailure(UNKNOWN_COLUMN, Type::Blob, Box::new(err))
}
FromSqlError::Other(source) => Error::FromSqlConversionFailure(UNKNOWN_COLUMN, Type::Null, source),
_ => Error::FromSqlConversionFailure(UNKNOWN_COLUMN, Type::Null, Box::new(err)),
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Error::DuckDBFailure(ref err, None) => err.fmt(f),
Error::DuckDBFailure(_, Some(ref s)) => write!(f, "{}", s),
Error::FromSqlConversionFailure(i, ref t, ref err) => {
if i != UNKNOWN_COLUMN {
write!(f, "Conversion error from type {} at index: {}, {}", t, i, err)
} else {
err.fmt(f)
}
}
Error::IntegralValueOutOfRange(col, val) => {
if col != UNKNOWN_COLUMN {
write!(f, "Integer {} out of range at index {}", val, col)
} else {
write!(f, "Integer {} out of range", val)
}
}
Error::Utf8Error(ref err) => err.fmt(f),
Error::NulError(ref err) => err.fmt(f),
Error::InvalidParameterName(ref name) => write!(f, "Invalid parameter name: {}", name),
Error::InvalidPath(ref p) => write!(f, "Invalid path: {}", p.to_string_lossy()),
Error::ExecuteReturnedResults => {
write!(f, "Execute returned results - did you mean to call query?")
}
Error::QueryReturnedNoRows => write!(f, "Query returned no rows"),
Error::InvalidColumnIndex(i) => write!(f, "Invalid column index: {}", i),
Error::InvalidColumnName(ref name) => write!(f, "Invalid column name: {}", name),
Error::InvalidColumnType(i, ref name, ref t) => {
write!(f, "Invalid column type {} at index: {}, name: {}", t, i, name)
}
Error::InvalidParameterCount(i1, n1) => write!(
f,
"Wrong number of parameters passed to query. Got {}, needed {}",
i1, n1
),
Error::StatementChangedRows(i) => write!(f, "Query changed {} rows", i),
Error::ToSqlConversionFailure(ref err) => err.fmt(f),
Error::InvalidQuery => write!(f, "Query is not read-only"),
Error::MultipleStatement => write!(f, "Multiple statements provided"),
Error::AppendError => write!(f, "Append error"),
}
}
}
impl error::Error for Error {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match *self {
Error::DuckDBFailure(ref err, _) => Some(err),
Error::Utf8Error(ref err) => Some(err),
Error::NulError(ref err) => Some(err),
Error::IntegralValueOutOfRange(..)
| Error::InvalidParameterName(_)
| Error::ExecuteReturnedResults
| Error::QueryReturnedNoRows
| Error::InvalidColumnIndex(_)
| Error::InvalidColumnName(_)
| Error::InvalidColumnType(..)
| Error::InvalidPath(_)
| Error::InvalidParameterCount(..)
| Error::StatementChangedRows(_)
| Error::InvalidQuery
| Error::AppendError
| Error::MultipleStatement => None,
Error::FromSqlConversionFailure(_, _, ref err) | Error::ToSqlConversionFailure(ref err) => Some(&**err),
}
}
}
#[inline]
fn error_from_duckdb_code(code: ffi::duckdb_state, message: Option<String>) -> Result<()> {
Err(Error::DuckDBFailure(ffi::Error::new(code), message))
}
#[cold]
#[inline]
pub fn result_from_duckdb_appender(code: ffi::duckdb_state, mut appender: ffi::duckdb_appender) -> Result<()> {
if code == ffi::DuckDBSuccess {
return Ok(());
}
unsafe {
let message = if appender.is_null() {
Some("appender is null".to_string())
} else {
let c_err = ffi::duckdb_appender_error(appender);
let message = Some(CStr::from_ptr(c_err).to_string_lossy().to_string());
ffi::duckdb_appender_destroy(&mut appender);
message
};
error_from_duckdb_code(code, message)
}
}
#[cold]
#[inline]
pub fn result_from_duckdb_prepare(code: ffi::duckdb_state, mut prepare: ffi::duckdb_prepared_statement) -> Result<()> {
if code == ffi::DuckDBSuccess {
return Ok(());
}
unsafe {
let message = if prepare.is_null() {
Some("prepare is null".to_string())
} else {
let c_err = ffi::duckdb_prepare_error(prepare);
let message = Some(CStr::from_ptr(c_err).to_string_lossy().to_string());
ffi::duckdb_destroy_prepare(&mut prepare);
message
};
error_from_duckdb_code(code, message)
}
}
#[cold]
#[inline]
pub fn result_from_duckdb_arrow(code: ffi::duckdb_state, mut out: ffi::duckdb_arrow) -> Result<()> {
if code == ffi::DuckDBSuccess {
return Ok(());
}
unsafe {
let message = if out.is_null() {
Some("out is null".to_string())
} else {
let c_err = ffi::duckdb_query_arrow_error(out);
let message = Some(CStr::from_ptr(c_err).to_string_lossy().to_string());
ffi::duckdb_destroy_arrow(&mut out);
message
};
error_from_duckdb_code(code, message)
}
}