Merge remote-tracking branch 'origin/master' into sub_type

This commit is contained in:
gwenn
2022-05-26 15:42:02 +02:00
14 changed files with 251 additions and 109 deletions

View File

@@ -90,7 +90,7 @@ mod test {
use std::thread;
use std::time::Duration;
use crate::{Connection, Error, ErrorCode, Result, TransactionBehavior};
use crate::{Connection, ErrorCode, Result, TransactionBehavior};
#[test]
fn test_default_busy() -> Result<()> {
@@ -101,12 +101,10 @@ mod test {
let tx1 = db1.transaction_with_behavior(TransactionBehavior::Exclusive)?;
let db2 = Connection::open(&path)?;
let r: Result<()> = db2.query_row("PRAGMA schema_version", [], |_| unreachable!());
match r.unwrap_err() {
Error::SqliteFailure(err, _) => {
assert_eq!(err.code, ErrorCode::DatabaseBusy);
}
err => panic!("Unexpected error {}", err),
}
assert_eq!(
r.unwrap_err().sqlite_error_code(),
Some(ErrorCode::DatabaseBusy)
);
tx1.rollback()
}

View File

@@ -128,6 +128,19 @@ 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,
/// SQL input
sql: String,
/// byte offset of the start of invalid token
offset: c_int,
},
}
impl PartialEq for Error {
@@ -172,6 +185,21 @@ impl PartialEq for Error {
}
#[cfg(feature = "blob")]
(Error::BlobSizeError, Error::BlobSizeError) => true,
#[cfg(feature = "modern_sqlite")]
(
Error::SqlInputError {
error: e1,
msg: m1,
sql: s1,
offset: o1,
},
Error::SqlInputError {
error: e2,
msg: m2,
sql: s2,
offset: o2,
},
) => e1 == e2 && m1 == m2 && s1 == s2 && o1 == o2,
(..) => false,
}
}
@@ -281,9 +309,15 @@ 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,
ref sql,
..
} => write!(f, "{} in {} at offset {}", msg, sql, offset),
}
}
}
@@ -331,6 +365,8 @@ impl error::Error for Error {
#[cfg(feature = "blob")]
Error::BlobSizeError => None,
#[cfg(feature = "modern_sqlite")]
Error::SqlInputError { ref error, .. } => Some(error),
}
}
}
@@ -371,6 +407,35 @@ pub unsafe fn error_from_handle(db: *mut ffi::sqlite3, code: c_int) -> Error {
error_from_sqlite_code(code, message)
}
#[cold]
#[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(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)
} 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,
sql: sql.to_owned(),
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))

View File

@@ -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;
@@ -256,7 +256,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, sql) });
}
// 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;
@@ -360,6 +362,12 @@ impl InnerConnection {
)),
}
}
#[inline]
#[cfg(feature = "release_memory")]
pub fn release_memory(&self) -> Result<()> {
self.decode_result(unsafe { ffi::sqlite3_db_release_memory(self.db) })
}
}
impl Drop for InnerConnection {

View File

@@ -384,9 +384,8 @@ impl Connection {
///
/// - Open the database for both reading or writing.
/// - Create the database if one does not exist at the path.
/// - Allow the filename to be interpreted as a URI (see
/// <https://www.sqlite.org/uri.html#uri_filenames_in_sqlite> for
/// details).
/// - Allow the filename to be interpreted as a URI (see <https://www.sqlite.org/uri.html#uri_filenames_in_sqlite>
/// for details).
/// - Disables the use of a per-connection mutex.
///
/// Rusqlite enforces thread-safety at compile time, so additional
@@ -596,6 +595,16 @@ impl Connection {
self.path.as_deref()
}
/// Attempts to free as much heap memory as possible from the database
/// connection.
///
/// This calls [`sqlite3_db_release_memory`](https://www.sqlite.org/c3ref/db_release_memory.html).
#[inline]
#[cfg(feature = "release_memory")]
pub fn release_memory(&self) -> Result<()> {
self.db.borrow_mut().release_memory()
}
/// Convenience method to prepare and execute a single SQL statement with
/// named parameter(s).
///
@@ -1289,7 +1298,7 @@ mod test {
let filename = "no_such_file.db";
let result = Connection::open_with_flags(filename, OpenFlags::SQLITE_OPEN_READ_ONLY);
assert!(result.is_err());
let err = result.err().unwrap();
let err = result.unwrap_err();
if let Error::SqliteFailure(e, Some(msg)) = err {
assert_eq!(ErrorCode::CannotOpen, e.code);
assert_eq!(ffi::SQLITE_CANTOPEN, e.extended_code);
@@ -1742,14 +1751,10 @@ mod test {
let result: Result<Vec<i32>> = stmt.query([])?.map(|r| r.get(0)).collect();
match result.unwrap_err() {
Error::SqliteFailure(err, _) => {
assert_eq!(err.code, ErrorCode::OperationInterrupted);
}
err => {
panic!("Unexpected error {}", err);
}
}
assert_eq!(
result.unwrap_err().sqlite_error_code(),
Some(ErrorCode::OperationInterrupted)
);
Ok(())
}
@@ -2093,7 +2098,7 @@ mod test {
}
#[test]
#[cfg(all(feature = "bundled", not(feature = "bundled-sqlcipher")))] // SQLite >= 3.35.0
#[cfg(feature = "modern_sqlite")]
fn test_returning() -> Result<()> {
let db = Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE foo(x INTEGER PRIMARY KEY)")?;

View File

@@ -327,7 +327,7 @@ macro_rules! impl_for_array_ref {
// don't really think it matters -- users who hit that can use `params!` anyway.
impl_for_array_ref!(
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
18 19 20 21 22 23 24 25 26 27 29 30 31 32
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
);
/// Adapter type which allows any iterator over [`ToSql`] values to implement

View File

@@ -1535,4 +1535,21 @@ mod test {
assert_eq!(0, stmt.is_explain());
Ok(())
}
#[test]
#[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()?;
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(())
}
}

View File

@@ -87,6 +87,7 @@ pub struct Transaction<'conn> {
/// sp.commit()
/// }
/// ```
#[derive(Debug)]
pub struct Savepoint<'conn> {
conn: &'conn Connection,
name: String,

View File

@@ -110,7 +110,7 @@ pub struct Null;
/// SQLite data types.
/// See [Fundamental Datatypes](https://sqlite.org/c3ref/c_blob.html).
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Type {
/// NULL
Null,
@@ -272,85 +272,67 @@ mod test {
// check some invalid types
// 0 is actually a blob (Vec<u8>)
assert!(is_invalid_column_type(
row.get::<_, c_int>(0).err().unwrap()
));
assert!(is_invalid_column_type(
row.get::<_, c_int>(0).err().unwrap()
));
assert!(is_invalid_column_type(row.get::<_, c_int>(0).unwrap_err()));
assert!(is_invalid_column_type(row.get::<_, c_int>(0).unwrap_err()));
assert!(is_invalid_column_type(row.get::<_, i64>(0).err().unwrap()));
assert!(is_invalid_column_type(
row.get::<_, c_double>(0).err().unwrap()
));
assert!(is_invalid_column_type(
row.get::<_, String>(0).err().unwrap()
row.get::<_, c_double>(0).unwrap_err()
));
assert!(is_invalid_column_type(row.get::<_, String>(0).unwrap_err()));
#[cfg(feature = "time")]
assert!(is_invalid_column_type(
row.get::<_, time::OffsetDateTime>(0).err().unwrap()
row.get::<_, time::OffsetDateTime>(0).unwrap_err()
));
assert!(is_invalid_column_type(
row.get::<_, Option<c_int>>(0).err().unwrap()
row.get::<_, Option<c_int>>(0).unwrap_err()
));
// 1 is actually a text (String)
assert!(is_invalid_column_type(
row.get::<_, c_int>(1).err().unwrap()
));
assert!(is_invalid_column_type(row.get::<_, c_int>(1).unwrap_err()));
assert!(is_invalid_column_type(row.get::<_, i64>(1).err().unwrap()));
assert!(is_invalid_column_type(
row.get::<_, c_double>(1).err().unwrap()
row.get::<_, c_double>(1).unwrap_err()
));
assert!(is_invalid_column_type(
row.get::<_, Vec<u8>>(1).err().unwrap()
row.get::<_, Vec<u8>>(1).unwrap_err()
));
assert!(is_invalid_column_type(
row.get::<_, Option<c_int>>(1).err().unwrap()
row.get::<_, Option<c_int>>(1).unwrap_err()
));
// 2 is actually an integer
assert!(is_invalid_column_type(row.get::<_, String>(2).unwrap_err()));
assert!(is_invalid_column_type(
row.get::<_, String>(2).err().unwrap()
row.get::<_, Vec<u8>>(2).unwrap_err()
));
assert!(is_invalid_column_type(
row.get::<_, Vec<u8>>(2).err().unwrap()
));
assert!(is_invalid_column_type(
row.get::<_, Option<String>>(2).err().unwrap()
row.get::<_, Option<String>>(2).unwrap_err()
));
// 3 is actually a float (c_double)
assert!(is_invalid_column_type(
row.get::<_, c_int>(3).err().unwrap()
));
assert!(is_invalid_column_type(row.get::<_, c_int>(3).unwrap_err()));
assert!(is_invalid_column_type(row.get::<_, i64>(3).err().unwrap()));
assert!(is_invalid_column_type(row.get::<_, String>(3).unwrap_err()));
assert!(is_invalid_column_type(
row.get::<_, String>(3).err().unwrap()
row.get::<_, Vec<u8>>(3).unwrap_err()
));
assert!(is_invalid_column_type(
row.get::<_, Vec<u8>>(3).err().unwrap()
));
assert!(is_invalid_column_type(
row.get::<_, Option<c_int>>(3).err().unwrap()
row.get::<_, Option<c_int>>(3).unwrap_err()
));
// 4 is actually NULL
assert!(is_invalid_column_type(
row.get::<_, c_int>(4).err().unwrap()
));
assert!(is_invalid_column_type(row.get::<_, c_int>(4).unwrap_err()));
assert!(is_invalid_column_type(row.get::<_, i64>(4).err().unwrap()));
assert!(is_invalid_column_type(
row.get::<_, c_double>(4).err().unwrap()
row.get::<_, c_double>(4).unwrap_err()
));
assert!(is_invalid_column_type(row.get::<_, String>(4).unwrap_err()));
assert!(is_invalid_column_type(
row.get::<_, String>(4).err().unwrap()
));
assert!(is_invalid_column_type(
row.get::<_, Vec<u8>>(4).err().unwrap()
row.get::<_, Vec<u8>>(4).unwrap_err()
));
#[cfg(feature = "time")]
assert!(is_invalid_column_type(
row.get::<_, time::OffsetDateTime>(4).err().unwrap()
row.get::<_, time::OffsetDateTime>(4).unwrap_err()
));
Ok(())
}