From 69a40526d5bc5c48b08ee3c3ffc2659e0e0985a8 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 13 Mar 2022 17:31:07 +0100 Subject: [PATCH 1/3] 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(()) + } } From 8370970b0b503e9f0ba4065932dbdad2ca6b4d12 Mon Sep 17 00:00:00 2001 From: gwenn Date: Thu, 21 Apr 2022 15:14:08 +0200 Subject: [PATCH 2/3] Keep track of SQL input --- src/error.rs | 28 ++++++++++++++++++++-------- src/inner_connection.rs | 2 +- src/statement.rs | 2 +- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/error.rs b/src/error.rs index 6764157..fd2dae5 100644 --- a/src/error.rs +++ b/src/error.rs @@ -136,6 +136,8 @@ pub enum Error { error: ffi::Error, /// error message msg: String, + /// SQL input + sql: String, /// byte offset of the start of invalid token offset: c_int, }, @@ -187,15 +189,17 @@ impl PartialEq for Error { ( Error::SqlInputError { error: e1, - msg: s1, + msg: m1, + sql: s1, offset: o1, }, Error::SqlInputError { error: e2, - msg: s2, + msg: m2, + sql: s2, offset: o2, }, - ) => e1 == e2 && s1 == s2 && o1 == o2, + ) => e1 == e2 && m1 == m2 && s1 == s2 && o1 == o2, (..) => false, } } @@ -309,8 +313,11 @@ impl fmt::Display for Error { Error::BlobSizeError => "Blob size is insufficient".fmt(f), #[cfg(feature = "modern_sqlite")] Error::SqlInputError { - ref msg, offset, .. - } => write!(f, "{} at offset {}", msg, offset), + ref msg, + offset, + ref sql, + .. + } => write!(f, "{} in {} at offset {}", msg, sql, offset), } } } @@ -402,13 +409,13 @@ pub unsafe fn error_from_handle(db: *mut ffi::sqlite3, code: c_int) -> Error { #[cold] #[cfg(not(feature = "modern_sqlite"))] -pub unsafe fn error_with_offset(db: *mut ffi::sqlite3, code: c_int) -> Error { +pub unsafe fn error_with_offset(db: *mut ffi::sqlite3, code: c_int, sql: &str) -> 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 { +pub unsafe fn error_with_offset(db: *mut ffi::sqlite3, code: c_int, sql: &str) -> Error { if db.is_null() { error_from_sqlite_code(code, None) } else { @@ -417,7 +424,12 @@ pub unsafe fn error_with_offset(db: *mut ffi::sqlite3, code: c_int) -> Error { if ffi::ErrorCode::Unknown == error.code { let offset = ffi::sqlite3_error_offset(db); if offset >= 0 { - return Error::SqlInputError { error, msg, offset }; + return Error::SqlInputError { + error, + msg, + sql: sql.to_owned(), + offset, + }; } } Error::SqliteFailure(error, Some(msg)) diff --git a/src/inner_connection.rs b/src/inner_connection.rs index f239e48..08ce18f 100644 --- a/src/inner_connection.rs +++ b/src/inner_connection.rs @@ -257,7 +257,7 @@ impl InnerConnection { }; // If there is an error, *ppStmt is set to NULL. if r != ffi::SQLITE_OK { - return Err(unsafe { error_with_offset(self.db, r) }); + return Err(unsafe { error_with_offset(self.db, r, sql) }); } // If the input text contains no SQL (if the input is an empty string or a // comment) then *ppStmt is set to NULL. diff --git a/src/statement.rs b/src/statement.rs index d44816f..f147868 100644 --- a/src/statement.rs +++ b/src/statement.rs @@ -1537,7 +1537,7 @@ mod test { } #[test] - #[cfg(feature = "modern_sqlite")] + #[cfg(all(feature = "modern_sqlite", not(feature = "bundled-sqlcipher")))] // SQLite >= 3.38.0 fn test_error_offset() -> Result<()> { use crate::ffi::ErrorCode; let db = Connection::open_in_memory()?; From 8f40fd1cf3fd88ac12362a8c9b35493c0b02411c Mon Sep 17 00:00:00 2001 From: gwenn Date: Thu, 21 Apr 2022 15:26:46 +0200 Subject: [PATCH 3/3] Fix build errors with SQLCipher --- src/error.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/error.rs b/src/error.rs index fd2dae5..7401b7a 100644 --- a/src/error.rs +++ b/src/error.rs @@ -408,13 +408,13 @@ pub unsafe fn error_from_handle(db: *mut ffi::sqlite3, code: c_int) -> Error { } #[cold] -#[cfg(not(feature = "modern_sqlite"))] -pub unsafe fn error_with_offset(db: *mut ffi::sqlite3, code: c_int, sql: &str) -> Error { +#[cfg(not(all(feature = "modern_sqlite", not(feature = "bundled-sqlcipher"))))] // SQLite >= 3.38.0 +pub unsafe fn error_with_offset(db: *mut ffi::sqlite3, code: c_int, _sql: &str) -> Error { error_from_handle(db, code) } #[cold] -#[cfg(feature = "modern_sqlite")] // 3.38.0 +#[cfg(all(feature = "modern_sqlite", not(feature = "bundled-sqlcipher")))] // SQLite >= 3.38.0 pub unsafe fn error_with_offset(db: *mut ffi::sqlite3, code: c_int, sql: &str) -> Error { if db.is_null() { error_from_sqlite_code(code, None)