Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions crates/duckdb/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,23 @@ pub fn result_from_duckdb_arrow(code: ffi::duckdb_state, mut out: ffi::duckdb_ar
}
}

#[cold]
#[inline]
pub unsafe fn result_from_duckdb_result(result: *mut ffi::duckdb_result) -> crate::Error {
unsafe {
let ffi_error = ffi::Error::from_result(result);
let msg = {
let c_err = ffi::duckdb_result_error(result);
if (c_err).is_null() {
None
} else {
Some(CStr::from_ptr(c_err).to_string_lossy().into_owned())
}
};
crate::Error::DuckDBFailure(ffi_error, msg)
}
}

#[cold]
#[inline]
pub fn result_from_duckdb_extract(
Expand Down
12 changes: 2 additions & 10 deletions crates/duckdb/src/inner_connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use super::{Appender, Config, Connection, Result, ffi};
use crate::{
error::{
Error, result_from_duckdb_appender, result_from_duckdb_arrow, result_from_duckdb_extract,
result_from_duckdb_prepare,
result_from_duckdb_prepare, result_from_duckdb_result,
},
raw_statement::RawStatement,
statement::Statement,
Expand Down Expand Up @@ -177,15 +177,7 @@ impl InnerConnection {
let rc = unsafe { ffi::duckdb_execute_prepared(stmt, &mut result) };

let error = if rc != ffi::DuckDBSuccess {
unsafe {
let c_err = ffi::duckdb_result_error(&mut result as *mut _);
let msg = if c_err.is_null() {
None
} else {
Some(CStr::from_ptr(c_err).to_string_lossy().to_string())
};
Some(Error::DuckDBFailure(ffi::Error::new(rc), msg))
}
Some(unsafe { result_from_duckdb_result(&mut result as *mut _)})
} else {
None
};
Expand Down
63 changes: 62 additions & 1 deletion crates/duckdb/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1102,7 +1102,9 @@ mod test {

match result.unwrap_err() {
Error::DuckDBFailure(err, _) => {
// TODO(wangfenjin): Update errorcode
// TODO: ErrorCode is Unknown here because execute() uses duckdb_execute_prepared_arrow
// which does not expose error types. Fixing this requires migrating execute() off the
// deprecated arrow execution path.
assert_eq!(err.code, ErrorCode::Unknown);
}
err => panic!("Unexpected error {err}"),
Expand Down Expand Up @@ -1903,4 +1905,63 @@ mod test {
assert_eq!(results, vec![Some("CA".into()), None, Some("NY".into())]);
Ok(())
}

// #[test]
// fn test_http_error_code() -> Result<()> {
// let db = checked_memory_handle();

// // httpfs may not be available in all builds, skip gracefully if not
// if db.execute_batch("LOAD httpfs;").is_err() {
// return Ok(());
// }

// let result = db.execute_batch(
// "SELECT * FROM read_parquet('https://localhost:19999/nonexistent.parquet');"
// );

// match result {
// Err(Error::DuckDBFailure(ref e, _)) => {
// assert_eq!(
// e.code,
// ErrorCode::Http,
// "expected ErrorCode::Http for a failed HTTP fetch, got {:?}",
// e.code
// );
// }
// Err(other) => panic!("expected DuckDBFailure with Http code, got: {other}"),
// Ok(()) => panic!("expected an error but query succeeded"),
// }

// Ok(())
// }

#[test]
fn test_http_error_code() -> Result<()> {
let db = checked_memory_handle();

if db.execute_batch("LOAD httpfs;").is_err() {
return Ok(());
}

let result = db.query_row(
"SELECT * FROM read_parquet('https://localhost:19999/nonexistent.parquet');",
[],
|row| row.get::<_, i32>(0),
);

match result {
Err(Error::DuckDBFailure(ref e, _)) => {
assert_eq!(
e.code,
ErrorCode::Http,
"expected ErrorCode::Http for a failed HTTP fetch, got {:?}",
e.code
);
}
Err(other) => panic!("expected DuckDBFailure with Http code, got: {other}"),
Ok(_) => panic!("expected an error but query succeeded"),
}

Ok(())
}
}
13 changes: 3 additions & 10 deletions crates/duckdb/src/raw_statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use arrow::{
};

use super::{Result, ffi};
use crate::{Error, core::LogicalTypeHandle, error::result_from_duckdb_arrow};
use crate::{Error, core::LogicalTypeHandle, error::result_from_duckdb_arrow, error::result_from_duckdb_result};
#[cfg(feature = "polars")]
use polars_core::utils::arrow as polars_arrow;

Expand Down Expand Up @@ -323,16 +323,9 @@ impl RawStatement {

let rc = ffi::duckdb_execute_prepared_streaming(self.ptr, &mut out);
if rc != ffi::DuckDBSuccess {
let msg = {
let c_err = ffi::duckdb_result_error(&mut out);
if c_err.is_null() {
None
} else {
Some(CStr::from_ptr(c_err).to_string_lossy().to_string())
}
};
let err = result_from_duckdb_result(&mut out as *mut _);
ffi::duckdb_destroy_result(&mut out);
return Err(Error::DuckDBFailure(ffi::Error::new(rc), msg));
return Err(err);
}

// Check if the result is truly streaming or materialized
Expand Down
61 changes: 61 additions & 0 deletions crates/libduckdb-sys/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ pub enum ErrorCode {
NotADatabase,
/// SQL error or missing database
Unknown,
/// HTTP error
Http,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
Expand All @@ -68,6 +70,65 @@ impl Error {
extended_code: result_code,
}
}

pub unsafe fn from_result(result: *mut crate::duckdb_result) -> Self {
let raw_type = unsafe { crate::duckdb_result_error_type(result) };
Self {
code: ErrorCode::from_duckdb_error_type(raw_type),
extended_code: crate::duckdb_state_DuckDBError,
}
}
}

impl ErrorCode {
pub fn from_duckdb_error_type(raw: crate::duckdb_error_type) -> Self {
match raw {
crate::duckdb_error_type_DUCKDB_ERROR_INVALID => ErrorCode::Unknown,
crate::duckdb_error_type_DUCKDB_ERROR_OUT_OF_RANGE => ErrorCode::ParameterOutOfRange,
crate::duckdb_error_type_DUCKDB_ERROR_CONVERSION => ErrorCode::TypeMismatch,
crate::duckdb_error_type_DUCKDB_ERROR_UNKNOWN_TYPE => ErrorCode::Unknown,
crate::duckdb_error_type_DUCKDB_ERROR_DECIMAL => ErrorCode::TypeMismatch,
crate::duckdb_error_type_DUCKDB_ERROR_MISMATCH_TYPE => ErrorCode::TypeMismatch,
crate::duckdb_error_type_DUCKDB_ERROR_DIVIDE_BY_ZERO => ErrorCode::OperationAborted,
crate::duckdb_error_type_DUCKDB_ERROR_OBJECT_SIZE => ErrorCode::TooBig,
crate::duckdb_error_type_DUCKDB_ERROR_INVALID_TYPE => ErrorCode::TypeMismatch,
crate::duckdb_error_type_DUCKDB_ERROR_SERIALIZATION => ErrorCode::DatabaseCorrupt,
crate::duckdb_error_type_DUCKDB_ERROR_TRANSACTION => ErrorCode::OperationAborted,
crate::duckdb_error_type_DUCKDB_ERROR_NOT_IMPLEMENTED => ErrorCode::ApiMisuse,
crate::duckdb_error_type_DUCKDB_ERROR_EXPRESSION => ErrorCode::ApiMisuse,
crate::duckdb_error_type_DUCKDB_ERROR_CATALOG => ErrorCode::NotFound,
crate::duckdb_error_type_DUCKDB_ERROR_PARSER => ErrorCode::ApiMisuse,
crate::duckdb_error_type_DUCKDB_ERROR_PLANNER => ErrorCode::ApiMisuse,
crate::duckdb_error_type_DUCKDB_ERROR_SCHEDULER => ErrorCode::OperationAborted,
crate::duckdb_error_type_DUCKDB_ERROR_EXECUTOR => ErrorCode::OperationAborted,
crate::duckdb_error_type_DUCKDB_ERROR_CONSTRAINT => ErrorCode::ConstraintViolation,
crate::duckdb_error_type_DUCKDB_ERROR_INDEX => ErrorCode::ApiMisuse,
crate::duckdb_error_type_DUCKDB_ERROR_STAT => ErrorCode::SystemIoFailure,
crate::duckdb_error_type_DUCKDB_ERROR_CONNECTION => ErrorCode::CannotOpen,
crate::duckdb_error_type_DUCKDB_ERROR_SYNTAX => ErrorCode::ApiMisuse,
crate::duckdb_error_type_DUCKDB_ERROR_SETTINGS => ErrorCode::ApiMisuse,
crate::duckdb_error_type_DUCKDB_ERROR_BINDER => ErrorCode::ApiMisuse,
crate::duckdb_error_type_DUCKDB_ERROR_NETWORK => ErrorCode::SystemIoFailure,
crate::duckdb_error_type_DUCKDB_ERROR_OPTIMIZER => ErrorCode::InternalMalfunction,
crate::duckdb_error_type_DUCKDB_ERROR_NULL_POINTER => ErrorCode::InternalMalfunction,
crate::duckdb_error_type_DUCKDB_ERROR_IO => ErrorCode::SystemIoFailure,
crate::duckdb_error_type_DUCKDB_ERROR_INTERRUPT => ErrorCode::OperationInterrupted,
crate::duckdb_error_type_DUCKDB_ERROR_FATAL => ErrorCode::InternalMalfunction,
crate::duckdb_error_type_DUCKDB_ERROR_INTERNAL => ErrorCode::InternalMalfunction,
crate::duckdb_error_type_DUCKDB_ERROR_INVALID_INPUT => ErrorCode::ApiMisuse,
crate::duckdb_error_type_DUCKDB_ERROR_OUT_OF_MEMORY => ErrorCode::OutOfMemory,
crate::duckdb_error_type_DUCKDB_ERROR_PERMISSION => ErrorCode::PermissionDenied,
crate::duckdb_error_type_DUCKDB_ERROR_PARAMETER_NOT_RESOLVED => ErrorCode::ApiMisuse,
crate::duckdb_error_type_DUCKDB_ERROR_PARAMETER_NOT_ALLOWED => ErrorCode::ApiMisuse,
crate::duckdb_error_type_DUCKDB_ERROR_DEPENDENCY => ErrorCode::ConstraintViolation,
crate::duckdb_error_type_DUCKDB_ERROR_HTTP => ErrorCode::Http,
crate::duckdb_error_type_DUCKDB_ERROR_MISSING_EXTENSION => ErrorCode::NotFound,
crate::duckdb_error_type_DUCKDB_ERROR_AUTOLOAD => ErrorCode::NotFound,
crate::duckdb_error_type_DUCKDB_ERROR_SEQUENCE => ErrorCode::OperationAborted,
crate::duckdb_error_type_DUCKDB_INVALID_CONFIGURATION => ErrorCode::ApiMisuse,
_ => ErrorCode::Unknown,
}
}
}

impl fmt::Display for Error {
Expand Down
23 changes: 23 additions & 0 deletions crates/libduckdb-sys/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,4 +202,27 @@ mod tests {
duckdb_close(&mut db);
}
}

#[test]
fn test_result_error_type() {
unsafe {
let mut db: duckdb_database = ptr::null_mut();
let mut con: duckdb_connection = ptr::null_mut();
duckdb_open(ptr::null_mut(), &mut db);
duckdb_connect(db, &mut con);

let mut result: duckdb_result = mem::zeroed();
let sql = CString::new("SELECT * FROM nonexistent_table").unwrap();
let rc = duckdb_query(con, sql.as_ptr() as *const c_char, &mut result);

assert_eq!(rc, duckdb_state_DuckDBError);

let error_type = duckdb_result_error_type(&mut result);
assert_eq!(error_type, duckdb_error_type_DUCKDB_ERROR_CATALOG);

duckdb_destroy_result(&mut result);
duckdb_disconnect(&mut con);
duckdb_close(&mut db);
}
}
}