From 69a40526d5bc5c48b08ee3c3ffc2659e0e0985a8 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 13 Mar 2022 17:31:07 +0100 Subject: [PATCH] Introduce SqlInputError with offset --- src/error.rs | 55 ++++++++++++++++++++++++++++++++++++++++- src/inner_connection.rs | 6 +++-- src/statement.rs | 17 +++++++++++++ 3 files changed, 75 insertions(+), 3 deletions(-) diff --git a/src/error.rs b/src/error.rs index 129f697..9964274 100644 --- a/src/error.rs +++ b/src/error.rs @@ -128,6 +128,17 @@ pub enum Error { #[cfg(feature = "blob")] #[cfg_attr(docsrs, doc(cfg(feature = "blob")))] BlobSizeError, + /// Error referencing a specific token in the input SQL + #[cfg(feature = "modern_sqlite")] // 3.38.0 + #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))] + SqlInputError { + /// error code + error: ffi::Error, + /// error message + msg: String, + /// byte offset of the start of invalid token + offset: c_int, + }, } impl PartialEq for Error { @@ -172,6 +183,19 @@ impl PartialEq for Error { } #[cfg(feature = "blob")] (Error::BlobSizeError, Error::BlobSizeError) => true, + #[cfg(feature = "modern_sqlite")] + ( + Error::SqlInputError { + error: e1, + msg: s1, + offset: o1, + }, + Error::SqlInputError { + error: e2, + msg: s2, + offset: o2, + }, + ) => e1 == e2 && s1 == s2 && o1 == o2, (..) => false, } } @@ -281,9 +305,12 @@ impl fmt::Display for Error { #[cfg(feature = "functions")] Error::GetAuxWrongType => write!(f, "get_aux called with wrong type"), Error::MultipleStatement => write!(f, "Multiple statements provided"), - #[cfg(feature = "blob")] Error::BlobSizeError => "Blob size is insufficient".fmt(f), + #[cfg(feature = "modern_sqlite")] + Error::SqlInputError { + ref msg, offset, .. + } => write!(f, "{} at offset {}", msg, offset), } } } @@ -331,6 +358,8 @@ impl error::Error for Error { #[cfg(feature = "blob")] Error::BlobSizeError => None, + #[cfg(feature = "modern_sqlite")] + Error::SqlInputError { ref error, .. } => Some(error), } } } @@ -352,6 +381,30 @@ pub unsafe fn error_from_handle(db: *mut ffi::sqlite3, code: c_int) -> Error { error_from_sqlite_code(code, message) } +#[cold] +#[cfg(not(feature = "modern_sqlite"))] +pub unsafe fn error_with_offset(db: *mut ffi::sqlite3, code: c_int) -> Error { + error_from_handle(db, code) +} + +#[cold] +#[cfg(feature = "modern_sqlite")] // 3.38.0 +pub unsafe fn error_with_offset(db: *mut ffi::sqlite3, code: c_int) -> Error { + if db.is_null() { + error_from_sqlite_code(code, None) + } else { + let error = ffi::Error::new(code); + let msg = errmsg_to_string(ffi::sqlite3_errmsg(db)); + if ffi::ErrorCode::Unknown == error.code { + let offset = ffi::sqlite3_error_offset(db); + if offset >= 0 { + return Error::SqlInputError { error, msg, offset }; + } + } + Error::SqliteFailure(error, Some(msg)) + } +} + pub fn check(code: c_int) -> Result<()> { if code != crate::ffi::SQLITE_OK { Err(crate::error::error_from_sqlite_code(code, None)) diff --git a/src/inner_connection.rs b/src/inner_connection.rs index 0ea630e..daf4100 100644 --- a/src/inner_connection.rs +++ b/src/inner_connection.rs @@ -10,7 +10,7 @@ use std::sync::{Arc, Mutex}; use super::ffi; use super::str_for_sqlite; use super::{Connection, InterruptHandle, OpenFlags, Result}; -use crate::error::{error_from_handle, error_from_sqlite_code, Error}; +use crate::error::{error_from_handle, error_from_sqlite_code, error_with_offset, Error}; use crate::raw_statement::RawStatement; use crate::statement::Statement; use crate::version::version_number; @@ -255,7 +255,9 @@ impl InnerConnection { rc }; // If there is an error, *ppStmt is set to NULL. - self.decode_result(r)?; + if r != ffi::SQLITE_OK { + return Err(unsafe { error_with_offset(self.db, r) }); + } // If the input text contains no SQL (if the input is an empty string or a // comment) then *ppStmt is set to NULL. let c_stmt: *mut ffi::sqlite3_stmt = c_stmt; diff --git a/src/statement.rs b/src/statement.rs index 9a84cbd..7c5fe2f 100644 --- a/src/statement.rs +++ b/src/statement.rs @@ -1508,4 +1508,21 @@ mod test { assert_eq!(expected, actual); Ok(()) } + + #[test] + #[cfg(feature = "modern_sqlite")] + fn test_error_offset() -> Result<()> { + use crate::ffi::ErrorCode; + let db = Connection::open_in_memory()?; + let r = db.execute_batch("SELECT CURRENT_TIMESTANP;"); + assert!(r.is_err()); + match r.unwrap_err() { + Error::SqlInputError { error, offset, .. } => { + assert_eq!(error.code, ErrorCode::Unknown); + assert_eq!(offset, 7); + } + err => panic!("Unexpected error {}", err), + } + Ok(()) + } }