blob: 772876e61ea9f51fb62540a5ad607eb43bf49fa3 [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//! Error, status and result types.
use std::os::raw::c_char;
use std::{ffi::NulError, fmt::Display};
use arrow_schema::ArrowError;
use crate::constants;
pub type AdbcStatusCode = u8;
/// Status of an operation.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Status {
/// No error.
Ok,
/// An unknown error occurred.
Unknown,
/// The operation is not implemented or supported.
NotImplemented,
/// A requested resource was not found.
NotFound,
/// A requested resource already exists.
AlreadyExists,
/// The arguments are invalid, likely a programming error.
/// For instance, they may be of the wrong format, or out of range.
InvalidArguments,
/// The preconditions for the operation are not met, likely a programming error.
/// For instance, the object may be uninitialized, or may have not
/// been fully configured.
InvalidState,
/// Invalid data was processed (not a programming error).
/// For instance, a division by zero may have occurred during query
/// execution.
InvalidData,
/// The database's integrity was affected.
/// For instance, a foreign key check may have failed, or a uniqueness
/// constraint may have been violated.
Integrity,
/// An error internal to the driver or database occurred.
Internal,
/// An I/O error occurred.
/// For instance, a remote service may be unavailable.
IO,
/// The operation was cancelled, not due to a timeout.
Cancelled,
/// The operation was cancelled due to a timeout.
Timeout,
/// Authentication failed.
Unauthenticated,
/// The client is not authorized to perform the given operation.
Unauthorized,
}
/// An ADBC error.
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Error {
/// The error message.
pub message: String,
/// The status of the operation.
pub status: Status,
/// A vendor-specific error code, if applicable.
pub vendor_code: i32,
/// A SQLSTATE error code, if provided, as defined by the SQL:2003 standard.
///
/// If not set, it should be set to `"\0\0\0\0\0"` (all zeroes) or `"00000"` (48 ASCII).
pub sqlstate: [c_char; 5], // TODO(alexandreyc): should we move to something else than c_char? (see https://github.com/apache/arrow-adbc/pull/1725#discussion_r1567531539)
/// Additional metadata. Introduced in ADBC 1.1.0.
pub details: Option<Vec<(String, Vec<u8>)>>,
}
/// Result type wrapping [Error].
pub type Result<T> = std::result::Result<T, Error>;
impl Error {
pub fn with_message_and_status(message: impl Into<String>, status: Status) -> Self {
Self {
message: message.into(),
status,
vendor_code: 0,
sqlstate: [0; 5],
details: None,
}
}
}
impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let safe_ascii = |c: c_char| -> char {
if c == 0 {
'0'
} else if (32..=126).contains(&c) {
char::from(c as u8)
} else {
'\u{FFFD}'
}
};
write!(
f,
"{:?}: {} (sqlstate: {}{}{}{}{}, vendor_code: {})",
self.status,
self.message,
safe_ascii(self.sqlstate[0]),
safe_ascii(self.sqlstate[1]),
safe_ascii(self.sqlstate[2]),
safe_ascii(self.sqlstate[3]),
safe_ascii(self.sqlstate[4]),
self.vendor_code
)
}
}
impl std::error::Error for Error {}
impl From<ArrowError> for Error {
fn from(value: ArrowError) -> Self {
Self {
message: value.to_string(),
status: Status::Internal,
vendor_code: 0,
sqlstate: [0; 5],
details: None,
}
}
}
impl From<NulError> for Error {
fn from(value: NulError) -> Self {
Self {
message: format!(
"Interior null byte was found at position {}",
value.nul_position()
),
status: Status::InvalidData,
vendor_code: 0,
sqlstate: [0; 5],
details: None,
}
}
}
impl From<std::str::Utf8Error> for Error {
fn from(value: std::str::Utf8Error) -> Self {
Self {
message: format!("Error while decoding UTF-8: {value}"),
status: Status::Internal,
vendor_code: 0,
sqlstate: [0; 5],
details: None,
}
}
}
impl From<std::ffi::IntoStringError> for Error {
fn from(value: std::ffi::IntoStringError) -> Self {
let error = value.utf8_error();
error.into()
}
}
impl TryFrom<AdbcStatusCode> for Status {
type Error = Error;
fn try_from(value: AdbcStatusCode) -> Result<Self> {
match value {
constants::ADBC_STATUS_OK => Ok(Status::Ok),
constants::ADBC_STATUS_UNKNOWN => Ok(Status::Unknown),
constants::ADBC_STATUS_NOT_IMPLEMENTED => Ok(Status::NotImplemented),
constants::ADBC_STATUS_NOT_FOUND => Ok(Status::NotFound),
constants::ADBC_STATUS_ALREADY_EXISTS => Ok(Status::AlreadyExists),
constants::ADBC_STATUS_INVALID_ARGUMENT => Ok(Status::InvalidArguments),
constants::ADBC_STATUS_INVALID_STATE => Ok(Status::InvalidState),
constants::ADBC_STATUS_INVALID_DATA => Ok(Status::InvalidData),
constants::ADBC_STATUS_INTEGRITY => Ok(Status::Integrity),
constants::ADBC_STATUS_INTERNAL => Ok(Status::Internal),
constants::ADBC_STATUS_IO => Ok(Status::IO),
constants::ADBC_STATUS_CANCELLED => Ok(Status::Cancelled),
constants::ADBC_STATUS_TIMEOUT => Ok(Status::Timeout),
constants::ADBC_STATUS_UNAUTHENTICATED => Ok(Status::Unauthenticated),
constants::ADBC_STATUS_UNAUTHORIZED => Ok(Status::Unauthorized),
v => Err(Error::with_message_and_status(
format!("Unknown status code: {v}"),
Status::InvalidData,
)),
}
}
}
impl From<Status> for AdbcStatusCode {
fn from(value: Status) -> Self {
match value {
Status::Ok => constants::ADBC_STATUS_OK,
Status::Unknown => constants::ADBC_STATUS_UNKNOWN,
Status::NotImplemented => constants::ADBC_STATUS_NOT_IMPLEMENTED,
Status::NotFound => constants::ADBC_STATUS_NOT_FOUND,
Status::AlreadyExists => constants::ADBC_STATUS_ALREADY_EXISTS,
Status::InvalidArguments => constants::ADBC_STATUS_INVALID_ARGUMENT,
Status::InvalidState => constants::ADBC_STATUS_INVALID_STATE,
Status::InvalidData => constants::ADBC_STATUS_INVALID_DATA,
Status::Integrity => constants::ADBC_STATUS_INTEGRITY,
Status::Internal => constants::ADBC_STATUS_INTERNAL,
Status::IO => constants::ADBC_STATUS_IO,
Status::Cancelled => constants::ADBC_STATUS_CANCELLED,
Status::Timeout => constants::ADBC_STATUS_TIMEOUT,
Status::Unauthenticated => constants::ADBC_STATUS_UNAUTHENTICATED,
Status::Unauthorized => constants::ADBC_STATUS_UNAUTHORIZED,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn display_unset_sqlstate() {
let err = Error::with_message_and_status("something failed", Status::Unknown);
let msg = err.to_string();
assert_eq!(
msg,
"Unknown: something failed (sqlstate: 00000, vendor_code: 0)"
);
}
#[test]
fn display_ascii_sqlstate() {
let err = Error {
message: "constraint violation".into(),
status: Status::Integrity,
vendor_code: 42,
sqlstate: [
b'2' as c_char,
b'3' as c_char,
b'5' as c_char,
b'0' as c_char,
b'5' as c_char,
],
details: None,
};
let msg = err.to_string();
assert_eq!(
msg,
"Integrity: constraint violation (sqlstate: 23505, vendor_code: 42)"
);
}
#[test]
fn display_non_printable_sqlstate() {
let err = Error {
message: "bad state".into(),
status: Status::Internal,
vendor_code: 0,
sqlstate: [1, 2, 3, 4, 5],
details: None,
};
let msg = err.to_string();
assert_eq!(msg, "Internal: bad state (sqlstate: �����, vendor_code: 0)");
}
#[test]
fn display_mixed_sqlstate() {
let err = Error {
message: "mixed".into(),
status: Status::InvalidData,
vendor_code: 7,
sqlstate: [b'H' as c_char, b'V' as c_char, 0, 0, 1],
details: None,
};
let msg = err.to_string();
assert_eq!(msg, "InvalidData: mixed (sqlstate: HV00�, vendor_code: 7)");
}
}