mirror of
https://github.com/isar/rusqlite.git
synced 2025-12-15 16:02:24 +08:00
Merge remote-tracking branch 'origin/master' into sub_type
This commit is contained in:
12
src/busy.rs
12
src/busy.rs
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
67
src/error.rs
67
src/error.rs
@@ -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))
|
||||
|
||||
@@ -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 {
|
||||
|
||||
31
src/lib.rs
31
src/lib.rs
@@ -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)")?;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,6 +87,7 @@ pub struct Transaction<'conn> {
|
||||
/// sp.commit()
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
pub struct Savepoint<'conn> {
|
||||
conn: &'conn Connection,
|
||||
name: String,
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user