#![warn(missing_docs)]
#![cfg_attr(docsrs, feature(doc_cfg))]
pub use fallible_iterator;
pub use fallible_streaming_iterator;
pub use libsqlite3_sys as ffi;
use std::cell::RefCell;
use std::default::Default;
use std::ffi::{c_char, c_int, c_uint, CStr, CString};
use std::fmt;
use std::path::Path;
use std::result;
use std::str;
use std::sync::{Arc, Mutex};
use crate::cache::StatementCache;
use crate::inner_connection::InnerConnection;
use crate::raw_statement::RawStatement;
use crate::types::ValueRef;
pub use crate::bind::BindIndex;
pub use crate::cache::CachedStatement;
#[cfg(feature = "column_decltype")]
pub use crate::column::Column;
pub use crate::error::{to_sqlite_error, Error};
pub use crate::ffi::ErrorCode;
#[cfg(feature = "load_extension")]
pub use crate::load_extension_guard::LoadExtensionGuard;
pub use crate::params::{params_from_iter, Params, ParamsFromIter};
pub use crate::row::{AndThenRows, Map, MappedRows, Row, RowIndex, Rows};
pub use crate::statement::{Statement, StatementStatus};
#[cfg(feature = "modern_sqlite")]
pub use crate::transaction::TransactionState;
pub use crate::transaction::{DropBehavior, Savepoint, Transaction, TransactionBehavior};
pub use crate::types::ToSql;
pub use crate::version::*;
#[cfg(feature = "rusqlite-macros")]
#[doc(hidden)]
pub use rusqlite_macros::__bind;
#[macro_use]
mod error;
#[cfg(not(feature = "loadable_extension"))]
pub mod auto_extension;
#[cfg(feature = "backup")]
#[cfg_attr(docsrs, doc(cfg(feature = "backup")))]
pub mod backup;
mod bind;
#[cfg(feature = "blob")]
#[cfg_attr(docsrs, doc(cfg(feature = "blob")))]
pub mod blob;
mod busy;
mod cache;
#[cfg(feature = "collation")]
#[cfg_attr(docsrs, doc(cfg(feature = "collation")))]
mod collation;
mod column;
pub mod config;
#[cfg(any(feature = "functions", feature = "vtab"))]
mod context;
#[cfg(feature = "functions")]
#[cfg_attr(docsrs, doc(cfg(feature = "functions")))]
pub mod functions;
#[cfg(feature = "hooks")]
#[cfg_attr(docsrs, doc(cfg(feature = "hooks")))]
pub mod hooks;
mod inner_connection;
#[cfg(feature = "limits")]
#[cfg_attr(docsrs, doc(cfg(feature = "limits")))]
pub mod limits;
#[cfg(feature = "load_extension")]
mod load_extension_guard;
mod params;
mod pragma;
mod raw_statement;
mod row;
#[cfg(feature = "serialize")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
pub mod serialize;
#[cfg(feature = "session")]
#[cfg_attr(docsrs, doc(cfg(feature = "session")))]
pub mod session;
mod statement;
#[cfg(feature = "trace")]
#[cfg_attr(docsrs, doc(cfg(feature = "trace")))]
pub mod trace;
mod transaction;
pub mod types;
#[cfg(feature = "unlock_notify")]
mod unlock_notify;
mod version;
#[cfg(feature = "vtab")]
#[cfg_attr(docsrs, doc(cfg(feature = "vtab")))]
pub mod vtab;
pub(crate) mod util;
pub(crate) use util::SmallCString;
const STATEMENT_CACHE_DEFAULT_CAPACITY: usize = 16;
#[macro_export]
macro_rules! params {
() => {
&[] as &[&dyn $crate::ToSql]
};
($($param:expr),+ $(,)?) => {
&[$(&$param as &dyn $crate::ToSql),+] as &[&dyn $crate::ToSql]
};
}
#[macro_export]
macro_rules! named_params {
() => {
&[] as &[(&str, &dyn $crate::ToSql)]
};
($($param_name:literal: $param_val:expr),+ $(,)?) => {
&[$(($param_name, &$param_val as &dyn $crate::ToSql)),+] as &[(&str, &dyn $crate::ToSql)]
};
}
#[cfg(feature = "rusqlite-macros")]
#[cfg_attr(docsrs, doc(cfg(feature = "rusqlite-macros")))]
#[macro_export]
macro_rules! prepare_and_bind {
($conn:expr, $sql:literal) => {{
let mut stmt = $conn.prepare($sql)?;
$crate::__bind!(stmt $sql);
stmt
}};
}
#[cfg(feature = "rusqlite-macros")]
#[cfg_attr(docsrs, doc(cfg(feature = "rusqlite-macros")))]
#[macro_export]
macro_rules! prepare_cached_and_bind {
($conn:expr, $sql:literal) => {{
let mut stmt = $conn.prepare_cached($sql)?;
$crate::__bind!(stmt $sql);
stmt
}};
}
pub type Result<T, E = Error> = result::Result<T, E>;
pub trait OptionalExtension<T> {
fn optional(self) -> Result<Option<T>>;
}
impl<T> OptionalExtension<T> for Result<T> {
fn optional(self) -> Result<Option<T>> {
match self {
Ok(value) => Ok(Some(value)),
Err(Error::QueryReturnedNoRows) => Ok(None),
Err(e) => Err(e),
}
}
}
unsafe fn errmsg_to_string(errmsg: *const c_char) -> String {
CStr::from_ptr(errmsg).to_string_lossy().into_owned()
}
fn str_to_cstring(s: &str) -> Result<SmallCString> {
Ok(SmallCString::new(s)?)
}
fn str_for_sqlite(s: &[u8]) -> Result<(*const c_char, c_int, ffi::sqlite3_destructor_type)> {
let len = len_as_c_int(s.len())?;
let (ptr, dtor_info) = if len != 0 {
(s.as_ptr().cast::<c_char>(), ffi::SQLITE_TRANSIENT())
} else {
("".as_ptr().cast::<c_char>(), ffi::SQLITE_STATIC())
};
Ok((ptr, len, dtor_info))
}
fn len_as_c_int(len: usize) -> Result<c_int> {
if len >= (c_int::MAX as usize) {
Err(err!(ffi::SQLITE_TOOBIG))
} else {
Ok(len as c_int)
}
}
#[cfg(unix)]
fn path_to_cstring(p: &Path) -> Result<CString> {
use std::os::unix::ffi::OsStrExt;
Ok(CString::new(p.as_os_str().as_bytes())?)
}
#[cfg(not(unix))]
fn path_to_cstring(p: &Path) -> Result<CString> {
let s = p.to_str().ok_or_else(|| Error::InvalidPath(p.to_owned()))?;
Ok(CString::new(s)?)
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum DatabaseName<'a> {
Main,
Temp,
Attached(&'a str),
C(&'a CStr),
}
pub const MAIN_DB: DatabaseName<'static> = DatabaseName::Main;
pub const TEMP_DB: DatabaseName<'static> = DatabaseName::Temp;
impl DatabaseName<'_> {
#[inline]
fn as_cstr(&self) -> Result<std::borrow::Cow<'_, CStr>> {
Ok(match *self {
DatabaseName::Main => std::borrow::Cow::Borrowed(c"main"),
DatabaseName::Temp => std::borrow::Cow::Borrowed(c"temp"),
DatabaseName::Attached(s) => std::borrow::Cow::Owned(CString::new(s)?),
DatabaseName::C(s) => std::borrow::Cow::Borrowed(s),
})
}
#[cfg(feature = "hooks")]
pub(crate) fn from_cstr(cs: &CStr) -> DatabaseName<'_> {
if cs == c"main" {
DatabaseName::Main
} else if cs == c"temp" {
DatabaseName::Temp
} else {
DatabaseName::C(cs)
}
}
}
pub struct Connection {
db: RefCell<InnerConnection>,
cache: StatementCache,
transaction_behavior: TransactionBehavior,
}
unsafe impl Send for Connection {}
impl Drop for Connection {
#[inline]
fn drop(&mut self) {
self.flush_prepared_statement_cache();
}
}
impl Connection {
#[inline]
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
let flags = OpenFlags::default();
Self::open_with_flags(path, flags)
}
#[inline]
pub fn open_in_memory() -> Result<Self> {
let flags = OpenFlags::default();
Self::open_in_memory_with_flags(flags)
}
#[inline]
pub fn open_with_flags<P: AsRef<Path>>(path: P, flags: OpenFlags) -> Result<Self> {
let c_path = path_to_cstring(path.as_ref())?;
InnerConnection::open_with_flags(&c_path, flags, None).map(|db| Self {
db: RefCell::new(db),
cache: StatementCache::with_capacity(STATEMENT_CACHE_DEFAULT_CAPACITY),
transaction_behavior: TransactionBehavior::Deferred,
})
}
#[inline]
pub fn open_with_flags_and_vfs<P: AsRef<Path>>(
path: P,
flags: OpenFlags,
vfs: &str,
) -> Result<Self> {
let c_path = path_to_cstring(path.as_ref())?;
let c_vfs = str_to_cstring(vfs)?;
InnerConnection::open_with_flags(&c_path, flags, Some(&c_vfs)).map(|db| Self {
db: RefCell::new(db),
cache: StatementCache::with_capacity(STATEMENT_CACHE_DEFAULT_CAPACITY),
transaction_behavior: TransactionBehavior::Deferred,
})
}
#[inline]
pub fn open_in_memory_with_flags(flags: OpenFlags) -> Result<Self> {
Self::open_with_flags(":memory:", flags)
}
#[inline]
pub fn open_in_memory_with_flags_and_vfs(flags: OpenFlags, vfs: &str) -> Result<Self> {
Self::open_with_flags_and_vfs(":memory:", flags, vfs)
}
pub fn execute_batch(&self, sql: &str) -> Result<()> {
let mut sql = sql;
while !sql.is_empty() {
let stmt = self.prepare(sql)?;
if !stmt.stmt.is_null() && stmt.step()? && cfg!(feature = "extra_check") {
return Err(Error::ExecuteReturnedResults);
}
let tail = stmt.stmt.tail();
if tail == 0 || tail >= sql.len() {
break;
}
sql = &sql[tail..];
}
Ok(())
}
#[inline]
pub fn execute<P: Params>(&self, sql: &str, params: P) -> Result<usize> {
self.prepare(sql)
.and_then(|mut stmt| stmt.check_no_tail().and_then(|()| stmt.execute(params)))
}
#[inline]
pub fn path(&self) -> Option<&str> {
unsafe { crate::inner_connection::db_filename(self.handle(), DatabaseName::Main) }
}
#[inline]
pub fn release_memory(&self) -> Result<()> {
self.db.borrow_mut().release_memory()
}
#[inline]
pub fn last_insert_rowid(&self) -> i64 {
self.db.borrow_mut().last_insert_rowid()
}
#[inline]
pub fn query_row<T, P, F>(&self, sql: &str, params: P, f: F) -> Result<T>
where
P: Params,
F: FnOnce(&Row<'_>) -> Result<T>,
{
let mut stmt = self.prepare(sql)?;
stmt.check_no_tail()?;
stmt.query_row(params, f)
}
#[cfg(test)]
pub(crate) fn one_column<T: types::FromSql>(&self, sql: &str) -> Result<T> {
self.query_row(sql, [], |r| r.get(0))
}
#[inline]
pub fn query_row_and_then<T, E, P, F>(&self, sql: &str, params: P, f: F) -> Result<T, E>
where
P: Params,
F: FnOnce(&Row<'_>) -> Result<T, E>,
E: From<Error>,
{
let mut stmt = self.prepare(sql)?;
stmt.check_no_tail()?;
let mut rows = stmt.query(params)?;
rows.get_expected_row().map_err(E::from).and_then(f)
}
#[inline]
pub fn prepare(&self, sql: &str) -> Result<Statement<'_>> {
self.prepare_with_flags(sql, PrepFlags::default())
}
#[inline]
pub fn prepare_with_flags(&self, sql: &str, flags: PrepFlags) -> Result<Statement<'_>> {
self.db.borrow_mut().prepare(self, sql, flags)
}
#[inline]
pub fn close(self) -> Result<(), (Self, Error)> {
self.flush_prepared_statement_cache();
let r = self.db.borrow_mut().close();
r.map_err(move |err| (self, err))
}
#[cfg(feature = "load_extension")]
#[cfg_attr(docsrs, doc(cfg(feature = "load_extension")))]
#[inline]
pub unsafe fn load_extension_enable(&self) -> Result<()> {
self.db.borrow_mut().enable_load_extension(1)
}
#[cfg(feature = "load_extension")]
#[cfg_attr(docsrs, doc(cfg(feature = "load_extension")))]
#[inline]
pub fn load_extension_disable(&self) -> Result<()> {
unsafe { self.db.borrow_mut().enable_load_extension(0) }
}
#[cfg(feature = "load_extension")]
#[cfg_attr(docsrs, doc(cfg(feature = "load_extension")))]
#[inline]
pub unsafe fn load_extension<P: AsRef<Path>>(
&self,
dylib_path: P,
entry_point: Option<&str>,
) -> Result<()> {
self.db
.borrow_mut()
.load_extension(dylib_path.as_ref(), entry_point)
}
#[inline]
pub unsafe fn handle(&self) -> *mut ffi::sqlite3 {
self.db.borrow().db()
}
#[inline]
pub unsafe fn from_handle(db: *mut ffi::sqlite3) -> Result<Self> {
let db = InnerConnection::new(db, false);
Ok(Self {
db: RefCell::new(db),
cache: StatementCache::with_capacity(STATEMENT_CACHE_DEFAULT_CAPACITY),
transaction_behavior: TransactionBehavior::Deferred,
})
}
#[cfg(feature = "loadable_extension")]
#[cfg_attr(docsrs, doc(cfg(feature = "loadable_extension")))]
pub unsafe fn extension_init2(
db: *mut ffi::sqlite3,
pz_err_msg: *mut *mut c_char,
p_api: *mut ffi::sqlite3_api_routines,
init: fn(Self) -> Result<bool>,
) -> c_int {
if p_api.is_null() {
return ffi::SQLITE_ERROR;
}
match ffi::rusqlite_extension_init2(p_api)
.map_err(Error::from)
.and(Self::from_handle(db))
.and_then(init)
{
Err(err) => to_sqlite_error(&err, pz_err_msg),
Ok(true) => ffi::SQLITE_OK_LOAD_PERMANENTLY,
_ => ffi::SQLITE_OK,
}
}
#[inline]
pub unsafe fn from_handle_owned(db: *mut ffi::sqlite3) -> Result<Self> {
let db = InnerConnection::new(db, true);
Ok(Self {
db: RefCell::new(db),
cache: StatementCache::with_capacity(STATEMENT_CACHE_DEFAULT_CAPACITY),
transaction_behavior: TransactionBehavior::Deferred,
})
}
#[inline]
pub fn get_interrupt_handle(&self) -> InterruptHandle {
self.db.borrow().get_interrupt_handle()
}
#[inline]
fn decode_result(&self, code: c_int) -> Result<()> {
self.db.borrow().decode_result(code)
}
#[inline]
pub fn changes(&self) -> u64 {
self.db.borrow().changes()
}
#[inline]
pub fn total_changes(&self) -> u64 {
self.db.borrow().total_changes()
}
#[inline]
pub fn is_autocommit(&self) -> bool {
self.db.borrow().is_autocommit()
}
#[inline]
pub fn is_busy(&self) -> bool {
self.db.borrow().is_busy()
}
pub fn cache_flush(&self) -> Result<()> {
self.db.borrow_mut().cache_flush()
}
pub fn is_readonly(&self, db_name: DatabaseName<'_>) -> Result<bool> {
self.db.borrow().db_readonly(db_name)
}
#[cfg(feature = "modern_sqlite")] #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))]
pub fn db_name(&self, index: usize) -> Result<String> {
unsafe {
let db = self.handle();
let name = ffi::sqlite3_db_name(db, index as c_int);
if name.is_null() {
Err(Error::InvalidDatabaseIndex(index))
} else {
Ok(CStr::from_ptr(name).to_str()?.to_owned())
}
}
}
#[cfg(feature = "modern_sqlite")] #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))]
pub fn is_interrupted(&self) -> bool {
self.db.borrow().is_interrupted()
}
}
impl fmt::Debug for Connection {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Connection")
.field("path", &self.path())
.finish()
}
}
#[derive(Debug)]
pub struct Batch<'conn, 'sql> {
conn: &'conn Connection,
sql: &'sql str,
tail: usize,
}
impl<'conn, 'sql> Batch<'conn, 'sql> {
pub fn new(conn: &'conn Connection, sql: &'sql str) -> Self {
Batch { conn, sql, tail: 0 }
}
}
impl<'conn> fallible_iterator::FallibleIterator for Batch<'conn, '_> {
type Error = Error;
type Item = Statement<'conn>;
fn next(&mut self) -> Result<Option<Statement<'conn>>> {
while self.tail < self.sql.len() {
let sql = &self.sql[self.tail..];
let next = self.conn.prepare(sql)?;
let tail = next.stmt.tail();
if tail == 0 {
self.tail = self.sql.len();
} else {
self.tail += tail;
}
if next.stmt.is_null() {
continue;
}
return Ok(Some(next));
}
Ok(None)
}
}
bitflags::bitflags! {
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[repr(C)]
pub struct OpenFlags: c_int {
const SQLITE_OPEN_READ_ONLY = ffi::SQLITE_OPEN_READONLY;
const SQLITE_OPEN_READ_WRITE = ffi::SQLITE_OPEN_READWRITE;
const SQLITE_OPEN_CREATE = ffi::SQLITE_OPEN_CREATE;
const SQLITE_OPEN_URI = ffi::SQLITE_OPEN_URI;
const SQLITE_OPEN_MEMORY = ffi::SQLITE_OPEN_MEMORY;
const SQLITE_OPEN_NO_MUTEX = ffi::SQLITE_OPEN_NOMUTEX;
const SQLITE_OPEN_FULL_MUTEX = ffi::SQLITE_OPEN_FULLMUTEX;
const SQLITE_OPEN_SHARED_CACHE = 0x0002_0000;
const SQLITE_OPEN_PRIVATE_CACHE = 0x0004_0000;
const SQLITE_OPEN_NOFOLLOW = 0x0100_0000;
const SQLITE_OPEN_EXRESCODE = 0x0200_0000;
}
}
impl Default for OpenFlags {
#[inline]
fn default() -> Self {
Self::SQLITE_OPEN_READ_WRITE
| Self::SQLITE_OPEN_CREATE
| Self::SQLITE_OPEN_NO_MUTEX
| Self::SQLITE_OPEN_URI
}
}
bitflags::bitflags! {
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
#[repr(C)]
pub struct PrepFlags: c_uint {
const SQLITE_PREPARE_PERSISTENT = 0x01;
const SQLITE_PREPARE_NO_VTAB = 0x04;
const SQLITE_PREPARE_DONT_LOG = 0x10;
}
}
pub struct InterruptHandle {
db_lock: Arc<Mutex<*mut ffi::sqlite3>>,
}
unsafe impl Send for InterruptHandle {}
unsafe impl Sync for InterruptHandle {}
impl InterruptHandle {
pub fn interrupt(&self) {
let db_handle = self.db_lock.lock().unwrap();
if !db_handle.is_null() {
unsafe { ffi::sqlite3_interrupt(*db_handle) }
}
}
}
#[cfg(doctest)]
doc_comment::doctest!("../README.md");
#[cfg(test)]
mod test {
use super::*;
use fallible_iterator::FallibleIterator;
use std::error::Error as StdError;
use std::fmt;
#[allow(dead_code)]
#[expect(unconditional_recursion, clippy::extra_unused_type_parameters)]
fn ensure_send<T: Send>() {
ensure_send::<Connection>();
ensure_send::<InterruptHandle>();
}
#[allow(dead_code)]
#[expect(unconditional_recursion, clippy::extra_unused_type_parameters)]
fn ensure_sync<T: Sync>() {
ensure_sync::<InterruptHandle>();
}
fn checked_memory_handle() -> Connection {
Connection::open_in_memory().unwrap()
}
#[test]
fn test_concurrent_transactions_busy_commit() -> Result<()> {
use std::time::Duration;
let tmp = tempfile::tempdir().unwrap();
let path = tmp.path().join("transactions.db3");
Connection::open(&path)?.execute_batch(
"
BEGIN; CREATE TABLE foo(x INTEGER);
INSERT INTO foo VALUES(42); END;",
)?;
let mut db1 = Connection::open_with_flags(&path, OpenFlags::SQLITE_OPEN_READ_WRITE)?;
let mut db2 = Connection::open_with_flags(&path, OpenFlags::SQLITE_OPEN_READ_ONLY)?;
db1.busy_timeout(Duration::from_millis(0))?;
db2.busy_timeout(Duration::from_millis(0))?;
{
let tx1 = db1.transaction()?;
let tx2 = db2.transaction()?;
tx1.query_row("SELECT x FROM foo LIMIT 1", [], |_| Ok(()))?;
tx2.query_row("SELECT x FROM foo LIMIT 1", [], |_| Ok(()))?;
tx1.execute("INSERT INTO foo VALUES(?1)", [1])?;
let _ = tx2.execute("INSERT INTO foo VALUES(?1)", [2]);
let _ = tx1.commit();
let _ = tx2.commit();
}
let _ = db1
.transaction()
.expect("commit should have closed transaction");
let _ = db2
.transaction()
.expect("commit should have closed transaction");
Ok(())
}
#[test]
fn test_persistence() -> Result<()> {
let temp_dir = tempfile::tempdir().unwrap();
let path = temp_dir.path().join("test.db3");
{
let db = Connection::open(&path)?;
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER);
INSERT INTO foo VALUES(42);
END;";
db.execute_batch(sql)?;
}
let path_string = path.to_str().unwrap();
let db = Connection::open(path_string)?;
let the_answer: i64 = db.one_column("SELECT x FROM foo")?;
assert_eq!(42i64, the_answer);
Ok(())
}
#[test]
fn test_open() {
Connection::open_in_memory().unwrap();
let db = checked_memory_handle();
db.close().unwrap();
}
#[test]
fn test_path() -> Result<()> {
let tmp = tempfile::tempdir().unwrap();
let db = Connection::open("")?;
assert_eq!(Some(""), db.path());
let db = Connection::open_in_memory()?;
assert_eq!(Some(""), db.path());
let db = Connection::open("file:dummy.db?mode=memory&cache=shared")?;
assert_eq!(Some(""), db.path());
let path = tmp.path().join("file.db");
let db = Connection::open(path)?;
assert!(db.path().is_some_and(|p| p.ends_with("file.db")));
Ok(())
}
#[test]
fn test_open_failure() {
let filename = "no_such_file.db";
let result = Connection::open_with_flags(filename, OpenFlags::SQLITE_OPEN_READ_ONLY);
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);
assert!(
msg.contains(filename),
"error message '{msg}' does not contain '{filename}'"
);
} else {
panic!("SqliteFailure expected");
}
}
#[cfg(unix)]
#[test]
fn test_invalid_unicode_file_names() -> Result<()> {
use std::ffi::OsStr;
use std::fs::File;
use std::os::unix::ffi::OsStrExt;
let temp_dir = tempfile::tempdir().unwrap();
let path = temp_dir.path();
if File::create(path.join(OsStr::from_bytes(&[0xFE]))).is_err() {
return Ok(());
}
let db_path = path.join(OsStr::from_bytes(&[0xFF]));
{
let db = Connection::open(&db_path)?;
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER);
INSERT INTO foo VALUES(42);
END;";
db.execute_batch(sql)?;
}
let db = Connection::open(&db_path)?;
let the_answer: i64 = db.one_column("SELECT x FROM foo")?;
assert_eq!(42i64, the_answer);
Ok(())
}
#[test]
fn test_close_retry() -> Result<()> {
let db = Connection::open_in_memory()?;
let raw_stmt = {
use super::str_to_cstring;
use std::ffi::c_int;
use std::ptr;
let raw_db = db.db.borrow_mut().db;
let sql = "SELECT 1";
let mut raw_stmt: *mut ffi::sqlite3_stmt = ptr::null_mut();
let cstring = str_to_cstring(sql)?;
let rc = unsafe {
ffi::sqlite3_prepare_v2(
raw_db,
cstring.as_ptr(),
(sql.len() + 1) as c_int,
&mut raw_stmt,
ptr::null_mut(),
)
};
assert_eq!(rc, ffi::SQLITE_OK);
raw_stmt
};
let (db, _) = db.close().unwrap_err();
let (db, _) = db.close().unwrap_err();
let (db, _) = db.close().unwrap_err();
assert_eq!(ffi::SQLITE_OK, unsafe { ffi::sqlite3_finalize(raw_stmt) });
db.close().unwrap();
Ok(())
}
#[test]
fn test_open_with_flags() {
for bad_flags in &[
OpenFlags::empty(),
OpenFlags::SQLITE_OPEN_READ_ONLY | OpenFlags::SQLITE_OPEN_READ_WRITE,
OpenFlags::SQLITE_OPEN_READ_ONLY | OpenFlags::SQLITE_OPEN_CREATE,
] {
Connection::open_in_memory_with_flags(*bad_flags).unwrap_err();
}
}
#[test]
fn test_execute_batch() -> Result<()> {
let db = Connection::open_in_memory()?;
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER);
INSERT INTO foo VALUES(1);
INSERT INTO foo VALUES(2);
INSERT INTO foo VALUES(3);
INSERT INTO foo VALUES(4);
END;";
db.execute_batch(sql)?;
db.execute_batch("UPDATE foo SET x = 3 WHERE x < 3")?;
db.execute_batch("INVALID SQL").unwrap_err();
Ok(())
}
#[test]
fn test_execute() -> Result<()> {
let db = Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE foo(x INTEGER)")?;
assert_eq!(1, db.execute("INSERT INTO foo(x) VALUES (?1)", [1i32])?);
assert_eq!(1, db.execute("INSERT INTO foo(x) VALUES (?1)", [2i32])?);
assert_eq!(3i32, db.one_column::<i32>("SELECT SUM(x) FROM foo")?);
Ok(())
}
#[test]
#[cfg(feature = "extra_check")]
fn test_execute_select_with_no_row() {
let db = checked_memory_handle();
let err = db.execute("SELECT 1 WHERE 1 < ?1", [1i32]).unwrap_err();
assert_eq!(
err,
Error::ExecuteReturnedResults,
"Unexpected error: {err}"
);
}
#[test]
fn test_execute_select_with_row() {
let db = checked_memory_handle();
let err = db.execute("SELECT 1", []).unwrap_err();
assert_eq!(err, Error::ExecuteReturnedResults);
}
#[test]
#[cfg(feature = "extra_check")]
fn test_execute_multiple() {
let db = checked_memory_handle();
let err = db
.execute(
"CREATE TABLE foo(x INTEGER); CREATE TABLE foo(x INTEGER)",
[],
)
.unwrap_err();
match err {
Error::MultipleStatement => (),
_ => panic!("Unexpected error: {err}"),
}
}
#[test]
fn test_prepare_column_names() -> Result<()> {
let db = Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE foo(x INTEGER);")?;
let stmt = db.prepare("SELECT * FROM foo")?;
assert_eq!(stmt.column_count(), 1);
assert_eq!(stmt.column_names(), vec!["x"]);
let stmt = db.prepare("SELECT x AS a, x AS b FROM foo")?;
assert_eq!(stmt.column_count(), 2);
assert_eq!(stmt.column_names(), vec!["a", "b"]);
Ok(())
}
#[test]
fn test_prepare_execute() -> Result<()> {
let db = Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE foo(x INTEGER);")?;
let mut insert_stmt = db.prepare("INSERT INTO foo(x) VALUES(?1)")?;
assert_eq!(insert_stmt.execute([1i32])?, 1);
assert_eq!(insert_stmt.execute([2i32])?, 1);
assert_eq!(insert_stmt.execute([3i32])?, 1);
assert_eq!(insert_stmt.execute(["hello"])?, 1);
assert_eq!(insert_stmt.execute(["goodbye"])?, 1);
assert_eq!(insert_stmt.execute([types::Null])?, 1);
let mut update_stmt = db.prepare("UPDATE foo SET x=?1 WHERE x<?2")?;
assert_eq!(update_stmt.execute([3i32, 3i32])?, 2);
assert_eq!(update_stmt.execute([3i32, 3i32])?, 0);
assert_eq!(update_stmt.execute([8i32, 8i32])?, 3);
Ok(())
}
#[test]
fn test_prepare_query() -> Result<()> {
let db = Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE foo(x INTEGER);")?;
let mut insert_stmt = db.prepare("INSERT INTO foo(x) VALUES(?1)")?;
assert_eq!(insert_stmt.execute([1i32])?, 1);
assert_eq!(insert_stmt.execute([2i32])?, 1);
assert_eq!(insert_stmt.execute([3i32])?, 1);
let mut query = db.prepare("SELECT x FROM foo WHERE x < ?1 ORDER BY x DESC")?;
{
let mut rows = query.query([4i32])?;
let mut v = Vec::<i32>::new();
while let Some(row) = rows.next()? {
v.push(row.get(0)?);
}
assert_eq!(v, [3i32, 2, 1]);
}
{
let mut rows = query.query([3i32])?;
let mut v = Vec::<i32>::new();
while let Some(row) = rows.next()? {
v.push(row.get(0)?);
}
assert_eq!(v, [2i32, 1]);
}
Ok(())
}
#[test]
fn test_query_map() -> Result<()> {
let db = Connection::open_in_memory()?;
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER, y TEXT);
INSERT INTO foo VALUES(4, \"hello\");
INSERT INTO foo VALUES(3, \", \");
INSERT INTO foo VALUES(2, \"world\");
INSERT INTO foo VALUES(1, \"!\");
END;";
db.execute_batch(sql)?;
let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC")?;
let results: Result<Vec<String>> = query.query([])?.map(|row| row.get(1)).collect();
assert_eq!(results?.concat(), "hello, world!");
Ok(())
}
#[test]
fn test_query_row() -> Result<()> {
let db = Connection::open_in_memory()?;
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER);
INSERT INTO foo VALUES(1);
INSERT INTO foo VALUES(2);
INSERT INTO foo VALUES(3);
INSERT INTO foo VALUES(4);
END;";
db.execute_batch(sql)?;
assert_eq!(10i64, db.one_column::<i64>("SELECT SUM(x) FROM foo")?);
let result: Result<i64> = db.one_column("SELECT x FROM foo WHERE x > 5");
match result.unwrap_err() {
Error::QueryReturnedNoRows => (),
err => panic!("Unexpected error {err}"),
}
let bad_query_result = db.query_row("NOT A PROPER QUERY; test123", [], |_| Ok(()));
bad_query_result.unwrap_err();
Ok(())
}
#[test]
fn test_optional() -> Result<()> {
let db = Connection::open_in_memory()?;
let result: Result<i64> = db.one_column("SELECT 1 WHERE 0 <> 0");
let result = result.optional();
match result? {
None => (),
_ => panic!("Unexpected result"),
}
let result: Result<i64> = db.one_column("SELECT 1 WHERE 0 == 0");
let result = result.optional();
match result? {
Some(1) => (),
_ => panic!("Unexpected result"),
}
let bad_query_result: Result<i64> = db.one_column("NOT A PROPER QUERY");
let bad_query_result = bad_query_result.optional();
bad_query_result.unwrap_err();
Ok(())
}
#[test]
fn test_pragma_query_row() -> Result<()> {
let db = Connection::open_in_memory()?;
assert_eq!("memory", db.one_column::<String>("PRAGMA journal_mode")?);
let mode = db.one_column::<String>("PRAGMA journal_mode=off")?;
if cfg!(feature = "bundled") {
assert_eq!(mode, "off");
} else {
assert!(mode == "memory" || mode == "off", "Got mode {mode:?}");
}
Ok(())
}
#[test]
fn test_prepare_failures() -> Result<()> {
let db = Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE foo(x INTEGER);")?;
let err = db.prepare("SELECT * FROM does_not_exist").unwrap_err();
assert!(format!("{err}").contains("does_not_exist"));
Ok(())
}
#[test]
fn test_last_insert_rowid() -> Result<()> {
let db = Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE foo(x INTEGER PRIMARY KEY)")?;
db.execute_batch("INSERT INTO foo DEFAULT VALUES")?;
assert_eq!(db.last_insert_rowid(), 1);
let mut stmt = db.prepare("INSERT INTO foo DEFAULT VALUES")?;
for _ in 0i32..9 {
stmt.execute([])?;
}
assert_eq!(db.last_insert_rowid(), 10);
Ok(())
}
#[test]
fn test_total_changes() -> Result<()> {
let db = Connection::open_in_memory()?;
let sql = "CREATE TABLE foo(x INTEGER PRIMARY KEY, value TEXT default '' NOT NULL,
desc TEXT default '');
CREATE VIEW foo_bar AS SELECT x, desc FROM foo WHERE value = 'bar';
CREATE TRIGGER INSERT_FOOBAR
INSTEAD OF INSERT
ON foo_bar
BEGIN
INSERT INTO foo VALUES(new.x, 'bar', new.desc);
END;";
db.execute_batch(sql)?;
let total_changes_before = db.total_changes();
let changes = db
.prepare("INSERT INTO foo_bar VALUES(null, 'baz');")?
.execute([])?;
let total_changes_after = db.total_changes();
assert_eq!(changes, 0);
assert_eq!(total_changes_after - total_changes_before, 1);
Ok(())
}
#[test]
fn test_is_autocommit() -> Result<()> {
let db = Connection::open_in_memory()?;
assert!(
db.is_autocommit(),
"autocommit expected to be active by default"
);
Ok(())
}
#[test]
fn test_is_busy() -> Result<()> {
let db = Connection::open_in_memory()?;
assert!(!db.is_busy());
let mut stmt = db.prepare("PRAGMA schema_version")?;
assert!(!db.is_busy());
{
let mut rows = stmt.query([])?;
assert!(!db.is_busy());
let row = rows.next()?;
assert!(db.is_busy());
assert!(row.is_some());
}
assert!(!db.is_busy());
Ok(())
}
#[test]
fn test_statement_debugging() -> Result<()> {
let db = Connection::open_in_memory()?;
let query = "SELECT 12345";
let stmt = db.prepare(query)?;
assert!(format!("{stmt:?}").contains(query));
Ok(())
}
#[test]
fn test_notnull_constraint_error() -> Result<()> {
let db = Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE foo(x NOT NULL)")?;
let result = db.execute("INSERT INTO foo (x) VALUES (NULL)", []);
match result.unwrap_err() {
Error::SqliteFailure(err, _) => {
assert_eq!(err.code, ErrorCode::ConstraintViolation);
assert_eq!(err.extended_code, ffi::SQLITE_CONSTRAINT_NOTNULL);
}
err => panic!("Unexpected error {err}"),
}
Ok(())
}
#[test]
fn test_version_string() {
let n = version_number();
let major = n / 1_000_000;
let minor = (n % 1_000_000) / 1_000;
let patch = n % 1_000;
assert!(version().contains(&format!("{major}.{minor}.{patch}")));
}
#[test]
#[cfg(feature = "functions")]
fn test_interrupt() -> Result<()> {
let db = Connection::open_in_memory()?;
let interrupt_handle = db.get_interrupt_handle();
db.create_scalar_function(
"interrupt",
0,
functions::FunctionFlags::default(),
move |_| {
interrupt_handle.interrupt();
Ok(0)
},
)?;
let mut stmt =
db.prepare("SELECT interrupt() FROM (SELECT 1 UNION SELECT 2 UNION SELECT 3)")?;
let result: Result<Vec<i32>> = stmt.query([])?.map(|r| r.get(0)).collect();
assert_eq!(
result.unwrap_err().sqlite_error_code(),
Some(ErrorCode::OperationInterrupted)
);
Ok(())
}
#[test]
fn test_interrupt_close() {
let db = checked_memory_handle();
let handle = db.get_interrupt_handle();
handle.interrupt();
db.close().unwrap();
handle.interrupt();
let db_guard = handle.db_lock.lock().unwrap();
assert!(db_guard.is_null());
}
#[test]
fn test_get_raw() -> Result<()> {
let db = Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE foo(i, x);")?;
let vals = ["foobar", "1234", "qwerty"];
let mut insert_stmt = db.prepare("INSERT INTO foo(i, x) VALUES(?1, ?2)")?;
for (i, v) in vals.iter().enumerate() {
let i_to_insert = i as i64;
assert_eq!(insert_stmt.execute(params![i_to_insert, v])?, 1);
}
let mut query = db.prepare("SELECT i, x FROM foo")?;
let mut rows = query.query([])?;
while let Some(row) = rows.next()? {
let i = row.get_ref(0)?.as_i64()?;
let expect = vals[i as usize];
let x = row.get_ref("x")?.as_str()?;
assert_eq!(x, expect);
}
let mut query = db.prepare("SELECT x FROM foo")?;
let rows = query.query_map([], |row| {
let x = row.get_ref(0)?.as_str()?; Ok(x[..].to_owned())
})?;
for (i, row) in rows.enumerate() {
assert_eq!(row?, vals[i]);
}
Ok(())
}
#[test]
fn test_from_handle() -> Result<()> {
let db = Connection::open_in_memory()?;
let handle = unsafe { db.handle() };
{
let db = unsafe { Connection::from_handle(handle) }?;
db.execute_batch("PRAGMA VACUUM")?;
}
db.close().unwrap();
Ok(())
}
#[test]
fn test_from_handle_owned() -> Result<()> {
let mut handle: *mut ffi::sqlite3 = std::ptr::null_mut();
let r = unsafe { ffi::sqlite3_open(c":memory:".as_ptr(), &mut handle) };
assert_eq!(r, ffi::SQLITE_OK);
let db = unsafe { Connection::from_handle_owned(handle) }?;
db.execute_batch("PRAGMA VACUUM")?;
Ok(())
}
mod query_and_then_tests {
use super::*;
#[derive(Debug)]
enum CustomError {
SomeError,
Sqlite(Error),
}
impl fmt::Display for CustomError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
match *self {
Self::SomeError => write!(f, "my custom error"),
Self::Sqlite(ref se) => write!(f, "my custom error: {se}"),
}
}
}
impl StdError for CustomError {
fn description(&self) -> &str {
"my custom error"
}
fn cause(&self) -> Option<&dyn StdError> {
match *self {
Self::SomeError => None,
Self::Sqlite(ref se) => Some(se),
}
}
}
impl From<Error> for CustomError {
fn from(se: Error) -> Self {
Self::Sqlite(se)
}
}
type CustomResult<T> = Result<T, CustomError>;
#[test]
fn test_query_and_then() -> Result<()> {
let db = Connection::open_in_memory()?;
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER, y TEXT);
INSERT INTO foo VALUES(4, \"hello\");
INSERT INTO foo VALUES(3, \", \");
INSERT INTO foo VALUES(2, \"world\");
INSERT INTO foo VALUES(1, \"!\");
END;";
db.execute_batch(sql)?;
let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC")?;
let results: Result<Vec<String>> =
query.query_and_then([], |row| row.get(1))?.collect();
assert_eq!(results?.concat(), "hello, world!");
Ok(())
}
#[test]
fn test_query_and_then_fails() -> Result<()> {
let db = Connection::open_in_memory()?;
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER, y TEXT);
INSERT INTO foo VALUES(4, \"hello\");
INSERT INTO foo VALUES(3, \", \");
INSERT INTO foo VALUES(2, \"world\");
INSERT INTO foo VALUES(1, \"!\");
END;";
db.execute_batch(sql)?;
let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC")?;
let bad_type: Result<Vec<f64>> = query.query_and_then([], |row| row.get(1))?.collect();
match bad_type.unwrap_err() {
Error::InvalidColumnType(..) => (),
err => panic!("Unexpected error {err}"),
}
let bad_idx: Result<Vec<String>> =
query.query_and_then([], |row| row.get(3))?.collect();
match bad_idx.unwrap_err() {
Error::InvalidColumnIndex(_) => (),
err => panic!("Unexpected error {err}"),
}
Ok(())
}
#[test]
fn test_query_and_then_custom_error() -> CustomResult<()> {
let db = Connection::open_in_memory()?;
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER, y TEXT);
INSERT INTO foo VALUES(4, \"hello\");
INSERT INTO foo VALUES(3, \", \");
INSERT INTO foo VALUES(2, \"world\");
INSERT INTO foo VALUES(1, \"!\");
END;";
db.execute_batch(sql)?;
let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC")?;
let results: CustomResult<Vec<String>> = query
.query_and_then([], |row| row.get(1).map_err(CustomError::Sqlite))?
.collect();
assert_eq!(results?.concat(), "hello, world!");
Ok(())
}
#[test]
fn test_query_and_then_custom_error_fails() -> Result<()> {
let db = Connection::open_in_memory()?;
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER, y TEXT);
INSERT INTO foo VALUES(4, \"hello\");
INSERT INTO foo VALUES(3, \", \");
INSERT INTO foo VALUES(2, \"world\");
INSERT INTO foo VALUES(1, \"!\");
END;";
db.execute_batch(sql)?;
let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC")?;
let bad_type: CustomResult<Vec<f64>> = query
.query_and_then([], |row| row.get(1).map_err(CustomError::Sqlite))?
.collect();
match bad_type.unwrap_err() {
CustomError::Sqlite(Error::InvalidColumnType(..)) => (),
err => panic!("Unexpected error {err}"),
}
let bad_idx: CustomResult<Vec<String>> = query
.query_and_then([], |row| row.get(3).map_err(CustomError::Sqlite))?
.collect();
match bad_idx.unwrap_err() {
CustomError::Sqlite(Error::InvalidColumnIndex(_)) => (),
err => panic!("Unexpected error {err}"),
}
let non_sqlite_err: CustomResult<Vec<String>> = query
.query_and_then([], |_| Err(CustomError::SomeError))?
.collect();
match non_sqlite_err.unwrap_err() {
CustomError::SomeError => (),
err => panic!("Unexpected error {err}"),
}
Ok(())
}
#[test]
fn test_query_row_and_then_custom_error() -> CustomResult<()> {
let db = Connection::open_in_memory()?;
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER, y TEXT);
INSERT INTO foo VALUES(4, \"hello\");
END;";
db.execute_batch(sql)?;
let query = "SELECT x, y FROM foo ORDER BY x DESC";
let results: CustomResult<String> =
db.query_row_and_then(query, [], |row| row.get(1).map_err(CustomError::Sqlite));
assert_eq!(results?, "hello");
Ok(())
}
#[test]
fn test_query_row_and_then_custom_error_fails() -> Result<()> {
let db = Connection::open_in_memory()?;
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER, y TEXT);
INSERT INTO foo VALUES(4, \"hello\");
END;";
db.execute_batch(sql)?;
let query = "SELECT x, y FROM foo ORDER BY x DESC";
let bad_type: CustomResult<f64> =
db.query_row_and_then(query, [], |row| row.get(1).map_err(CustomError::Sqlite));
match bad_type.unwrap_err() {
CustomError::Sqlite(Error::InvalidColumnType(..)) => (),
err => panic!("Unexpected error {err}"),
}
let bad_idx: CustomResult<String> =
db.query_row_and_then(query, [], |row| row.get(3).map_err(CustomError::Sqlite));
match bad_idx.unwrap_err() {
CustomError::Sqlite(Error::InvalidColumnIndex(_)) => (),
err => panic!("Unexpected error {err}"),
}
let non_sqlite_err: CustomResult<String> =
db.query_row_and_then(query, [], |_| Err(CustomError::SomeError));
match non_sqlite_err.unwrap_err() {
CustomError::SomeError => (),
err => panic!("Unexpected error {err}"),
}
Ok(())
}
}
#[test]
fn test_dynamic() -> Result<()> {
let db = Connection::open_in_memory()?;
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER, y TEXT);
INSERT INTO foo VALUES(4, \"hello\");
END;";
db.execute_batch(sql)?;
db.query_row("SELECT * FROM foo", [], |r| {
assert_eq!(2, r.as_ref().column_count());
Ok(())
})
}
#[test]
fn test_dyn_box() -> Result<()> {
let db = Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE foo(x INTEGER);")?;
let b: Box<dyn ToSql> = Box::new(5);
db.execute("INSERT INTO foo VALUES(?1)", [b])?;
db.query_row("SELECT x FROM foo", [], |r| {
assert_eq!(5, r.get_unwrap::<_, i32>(0));
Ok(())
})
}
#[test]
fn test_params() -> Result<()> {
let db = Connection::open_in_memory()?;
db.query_row(
"SELECT
?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, ?28, ?29, ?30,
?31, ?32, ?33, ?34;",
params![
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1,
],
|r| {
assert_eq!(1, r.get_unwrap::<_, i32>(0));
Ok(())
},
)
}
#[test]
#[cfg(not(feature = "extra_check"))]
fn test_alter_table() -> Result<()> {
let db = Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE x(t);")?;
db.execute("ALTER TABLE x RENAME TO y;", [])?;
Ok(())
}
#[test]
fn test_batch() -> Result<()> {
let db = Connection::open_in_memory()?;
let sql = r"
CREATE TABLE tbl1 (col);
CREATE TABLE tbl2 (col);
";
let mut batch = Batch::new(&db, sql);
while let Some(mut stmt) = batch.next()? {
stmt.execute([])?;
}
Ok(())
}
#[test]
fn test_invalid_batch() -> Result<()> {
let db = Connection::open_in_memory()?;
let sql = r"
PRAGMA test1;
PRAGMA test2=?;
PRAGMA test3;
";
let mut batch = Batch::new(&db, sql);
assert!(batch.next().is_ok());
assert!(batch.next().is_err());
assert!(batch.next().is_err());
assert!(Batch::new(&db, sql).count().is_err());
Ok(())
}
#[test]
#[cfg(feature = "modern_sqlite")]
fn test_returning() -> Result<()> {
let db = Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE foo(x INTEGER PRIMARY KEY)")?;
let row_id = db.one_column::<i64>("INSERT INTO foo DEFAULT VALUES RETURNING ROWID")?;
assert_eq!(row_id, 1);
Ok(())
}
#[test]
fn test_cache_flush() -> Result<()> {
let db = Connection::open_in_memory()?;
db.cache_flush()
}
#[test]
fn db_readonly() -> Result<()> {
let db = Connection::open_in_memory()?;
assert!(!db.is_readonly(MAIN_DB)?);
Ok(())
}
#[test]
#[cfg(feature = "rusqlite-macros")]
fn prepare_and_bind() -> Result<()> {
let db = Connection::open_in_memory()?;
let name = "Lisa";
let age = 8;
let mut stmt = prepare_and_bind!(db, "SELECT $name, $age;");
let (v1, v2) = stmt
.raw_query()
.next()
.and_then(|o| o.ok_or(Error::QueryReturnedNoRows))
.and_then(|r| Ok((r.get::<_, String>(0)?, r.get::<_, i64>(1)?)))?;
assert_eq!((v1.as_str(), v2), (name, age));
Ok(())
}
#[test]
#[cfg(feature = "modern_sqlite")]
fn test_db_name() -> Result<()> {
let db = Connection::open_in_memory()?;
assert_eq!(db.db_name(0)?, "main");
assert_eq!(db.db_name(1)?, "temp");
assert_eq!(db.db_name(2), Err(Error::InvalidDatabaseIndex(2)));
db.execute_batch("ATTACH DATABASE ':memory:' AS xyz;")?;
assert_eq!(db.db_name(2)?, "xyz");
Ok(())
}
#[test]
#[cfg(feature = "modern_sqlite")]
fn test_is_interrupted() -> Result<()> {
let db = Connection::open_in_memory()?;
assert!(!db.is_interrupted());
db.get_interrupt_handle().interrupt();
assert!(db.is_interrupted());
Ok(())
}
#[test]
fn release_memory() -> Result<()> {
let db = Connection::open_in_memory()?;
db.release_memory()
}
}