sqlite: produce connection specific errors

postgres, mysql: use derive for Debug for error types
This commit is contained in:
Ryan Leckey 2020-03-15 02:14:23 -07:00
parent f667910cbf
commit 68d4a0d258
8 changed files with 65 additions and 33 deletions

View file

@ -223,19 +223,6 @@ macro_rules! tls_err {
#[allow(unused_macros)]
macro_rules! impl_fmt_error {
($err:ty) => {
impl std::fmt::Debug for $err {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.debug_struct("DatabaseError")
.field("message", &self.message())
.field("details", &self.details())
.field("hint", &self.hint())
.field("table_name", &self.table_name())
.field("column_name", &self.column_name())
.field("constraint_name", &self.constraint_name())
.finish()
}
}
impl std::fmt::Display for $err {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.pad(self.message())

View file

@ -1,12 +1,19 @@
use std::fmt::{self, Display};
use crate::error::DatabaseError;
use crate::mysql::protocol::ErrPacket;
#[derive(Debug)]
pub struct MySqlError(pub(super) ErrPacket);
impl Display for MySqlError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.pad(self.message())
}
}
impl DatabaseError for MySqlError {
fn message(&self) -> &str {
&*self.0.error_message
}
}
impl_fmt_error!(MySqlError);

View file

@ -1,6 +1,9 @@
use std::fmt::{self, Display};
use crate::error::DatabaseError;
use crate::postgres::protocol::Response;
#[derive(Debug)]
pub struct PgError(pub(super) Response);
impl DatabaseError for PgError {
@ -29,4 +32,8 @@ impl DatabaseError for PgError {
}
}
impl_fmt_error!(PgError);
impl Display for PgError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.pad(self.message())
}
}

View file

@ -125,7 +125,7 @@ impl SqliteArgumentValue {
};
if status != SQLITE_OK {
return Err(SqliteError::new(status).into());
return Err(SqliteError::from_connection(statement.connection.0.as_ptr()).into());
}
Ok(())

View file

@ -20,11 +20,11 @@ use crate::url::Url;
/// Thin wrapper around [sqlite3] to impl `Send`.
#[derive(Clone, Copy)]
pub(super) struct SqliteConnectionHandle(NonNull<sqlite3>);
pub(super) struct SqliteConnectionHandle(pub(super) NonNull<sqlite3>);
/// A connection to a [SQLite][super::Sqlite] database.
pub struct SqliteConnection {
handle: SqliteConnectionHandle,
pub(super) handle: SqliteConnectionHandle,
pub(super) worker: Worker,
// Storage of the most recently prepared, non-persistent statement
pub(super) statement: Option<SqliteStatement>,
@ -74,7 +74,7 @@ async fn establish(url: crate::Result<Url>) -> crate::Result<SqliteConnection> {
let status = unsafe { sqlite3_open_v2(filename.as_ptr(), &mut handle, flags, null()) };
if status != SQLITE_OK {
return Err(SqliteError::new(status).into());
return Err(SqliteError::from_connection(handle).into());
}
// Enable extended result codes

View file

@ -1,35 +1,48 @@
use crate::error::DatabaseError;
use libsqlite3_sys::sqlite3_errstr;
use bitflags::_core::str::from_utf8_unchecked;
use libsqlite3_sys::{sqlite3, sqlite3_errmsg, sqlite3_extended_errcode};
use std::ffi::CStr;
use std::fmt::{self, Display};
use std::os::raw::c_int;
#[derive(Debug)]
pub struct SqliteError {
#[allow(dead_code)]
code: c_int,
message: String,
}
// Error Codes And Messages
// https://www.sqlite.org/c3ref/errcode.html
impl SqliteError {
pub(crate) fn new(code: c_int) -> Self {
pub(super) fn from_connection(conn: *mut sqlite3) -> Self {
#[allow(unsafe_code)]
let code: c_int = unsafe { sqlite3_extended_errcode(conn) };
#[allow(unsafe_code)]
let message = unsafe {
let err = sqlite3_errstr(code);
let err = sqlite3_errmsg(conn);
debug_assert!(!err.is_null());
CStr::from_ptr(err)
from_utf8_unchecked(CStr::from_ptr(err).to_bytes())
};
Self {
code,
message: message.to_string_lossy().into_owned(),
message: message.to_owned(),
}
}
}
impl Display for SqliteError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.pad(self.message())
}
}
impl DatabaseError for SqliteError {
fn message(&self) -> &str {
&self.message
}
}
impl_fmt_error!(SqliteError);

View file

@ -11,6 +11,7 @@ use libsqlite3_sys::{
SQLITE_PREPARE_NO_VTAB, SQLITE_PREPARE_PERSISTENT, SQLITE_ROW,
};
use crate::sqlite::connection::SqliteConnectionHandle;
use crate::sqlite::worker::Worker;
use crate::sqlite::SqliteError;
use crate::sqlite::{SqliteArguments, SqliteConnection};
@ -34,6 +35,7 @@ pub(super) struct SqliteStatementHandle(NonNull<sqlite3_stmt>);
/// The statement is finalized ( `sqlite3_finalize` ) on drop.
pub(super) struct SqliteStatement {
handle: SqliteStatementHandle,
pub(super) connection: SqliteConnectionHandle,
pub(super) worker: Worker,
pub(super) tail: usize,
pub(super) columns: HashMap<String, usize>,
@ -80,17 +82,18 @@ impl SqliteStatement {
)
};
if status != SQLITE_OK {
return Err(SqliteError::from_connection(conn.handle()).into());
}
// If pzTail is not NULL then *pzTail is made to point to the first byte
// past the end of the first SQL statement in zSql.
let tail = (tail as usize) - (query_ptr as usize);
*query = &query[tail..].trim();
if status != SQLITE_OK {
return Err(SqliteError::new(status).into());
}
let mut self_ = Self {
worker: conn.worker.clone(),
connection: conn.handle,
handle: SqliteStatementHandle(NonNull::new(statement_handle).unwrap()),
columns: HashMap::new(),
tail,
@ -215,8 +218,8 @@ impl SqliteStatement {
SQLITE_ROW => Ok(Step::Row),
status => {
return Err(SqliteError::new(status).into());
_ => {
return Err(SqliteError::from_connection(self.connection.0.as_ptr()).into());
}
}
}

View file

@ -22,6 +22,21 @@ async fn it_fails_to_connect() -> anyhow::Result<()> {
Ok(())
}
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
async fn it_fails_to_parse() -> anyhow::Result<()> {
let mut conn = new::<Sqlite>().await?;
let res = conn.execute("SEELCT 1").await;
assert!(res.is_err());
let err = res.unwrap_err().to_string();
assert_eq!("near \"SEELCT\": syntax error", err);
Ok(())
}
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
async fn it_executes() -> anyhow::Result<()> {