Merge remote-tracking branch 'jgallagher/master' into tail

# Conflicts:
#	src/inner_connection.rs
#	src/lib.rs
This commit is contained in:
gwenn
2019-08-26 20:41:15 +02:00
46 changed files with 12744 additions and 7882 deletions

View File

@@ -167,7 +167,7 @@ pub struct Backup<'a, 'b> {
b: *mut ffi::sqlite3_backup,
}
impl<'a, 'b> Backup<'a, 'b> {
impl Backup<'_, '_> {
/// Attempt to create a new handle that will allow backups from `from` to
/// `to`. Note that `to` is a `&mut` - this is because SQLite forbids any
/// API calls on the destination of a backup while the backup is taking
@@ -177,7 +177,7 @@ impl<'a, 'b> Backup<'a, 'b> {
///
/// Will return `Err` if the underlying `sqlite3_backup_init` call returns
/// `NULL`.
pub fn new(from: &'a Connection, to: &'b mut Connection) -> Result<Backup<'a, 'b>> {
pub fn new<'a, 'b>(from: &'a Connection, to: &'b mut Connection) -> Result<Backup<'a, 'b>> {
Backup::new_with_names(from, DatabaseName::Main, to, DatabaseName::Main)
}
@@ -190,7 +190,7 @@ impl<'a, 'b> Backup<'a, 'b> {
///
/// Will return `Err` if the underlying `sqlite3_backup_init` call returns
/// `NULL`.
pub fn new_with_names(
pub fn new_with_names<'a, 'b>(
from: &'a Connection,
from_name: DatabaseName<'_>,
to: &'b mut Connection,
@@ -294,7 +294,7 @@ impl<'a, 'b> Backup<'a, 'b> {
}
}
impl<'a, 'b> Drop for Backup<'a, 'b> {
impl Drop for Backup<'_, '_> {
fn drop(&mut self) {
unsafe { ffi::sqlite3_backup_finish(self.b) };
}

View File

@@ -16,43 +16,40 @@
//! ```rust
//! use rusqlite::blob::ZeroBlob;
//! use rusqlite::{Connection, DatabaseName, NO_PARAMS};
//! use std::error::Error;
//! use std::io::{Read, Seek, SeekFrom, Write};
//!
//! fn main() {
//! let db = Connection::open_in_memory().unwrap();
//! db.execute_batch("CREATE TABLE test (content BLOB);")
//! .unwrap();
//! fn main() -> Result<(), Box<Error>> {
//! let db = Connection::open_in_memory()?;
//! db.execute_batch("CREATE TABLE test (content BLOB);")?;
//! db.execute(
//! "INSERT INTO test (content) VALUES (ZEROBLOB(10))",
//! NO_PARAMS,
//! )
//! .unwrap();
//! )?;
//!
//! let rowid = db.last_insert_rowid();
//! let mut blob = db
//! .blob_open(DatabaseName::Main, "test", "content", rowid, false)
//! .unwrap();
//! let mut blob = db.blob_open(DatabaseName::Main, "test", "content", rowid, false)?;
//!
//! // Make sure to test that the number of bytes written matches what you expect;
//! // if you try to write too much, the data will be truncated to the size of the
//! // BLOB.
//! let bytes_written = blob.write(b"01234567").unwrap();
//! let bytes_written = blob.write(b"01234567")?;
//! assert_eq!(bytes_written, 8);
//!
//! // Same guidance - make sure you check the number of bytes read!
//! blob.seek(SeekFrom::Start(0)).unwrap();
//! blob.seek(SeekFrom::Start(0))?;
//! let mut buf = [0u8; 20];
//! let bytes_read = blob.read(&mut buf[..]).unwrap();
//! let bytes_read = blob.read(&mut buf[..])?;
//! assert_eq!(bytes_read, 10); // note we read 10 bytes because the blob has size 10
//!
//! db.execute("INSERT INTO test (content) VALUES (?)", &[ZeroBlob(64)])
//! .unwrap();
//! db.execute("INSERT INTO test (content) VALUES (?)", &[ZeroBlob(64)])?;
//!
//! // given a new row ID, we can reopen the blob on that row
//! let rowid = db.last_insert_rowid();
//! blob.reopen(rowid).unwrap();
//! blob.reopen(rowid)?;
//!
//! assert_eq!(blob.size(), 64);
//! Ok(())
//! }
//! ```
use std::cmp::min;
@@ -111,7 +108,7 @@ impl Connection {
}
}
impl<'conn> Blob<'conn> {
impl Blob<'_> {
/// Move a BLOB handle to a new row.
///
/// # Failure
@@ -151,7 +148,7 @@ impl<'conn> Blob<'conn> {
}
}
impl<'conn> io::Read for Blob<'conn> {
impl io::Read for Blob<'_> {
/// Read data from a BLOB incrementally. Will return Ok(0) if the end of
/// the blob has been reached.
///
@@ -175,7 +172,7 @@ impl<'conn> io::Read for Blob<'conn> {
}
}
impl<'conn> io::Write for Blob<'conn> {
impl io::Write for Blob<'_> {
/// Write data into a BLOB incrementally. Will return `Ok(0)` if the end of
/// the blob has been reached; consider using `Write::write_all(buf)`
/// if you want to get an error if the entirety of the buffer cannot be
@@ -208,7 +205,7 @@ impl<'conn> io::Write for Blob<'conn> {
}
}
impl<'conn> io::Seek for Blob<'conn> {
impl io::Seek for Blob<'_> {
/// Seek to an offset, in bytes, in BLOB.
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
let pos = match pos {
@@ -235,7 +232,7 @@ impl<'conn> io::Seek for Blob<'conn> {
}
#[allow(unused_must_use)]
impl<'conn> Drop for Blob<'conn> {
impl Drop for Blob<'_> {
fn drop(&mut self) {
self.close_();
}

View File

@@ -75,14 +75,13 @@ impl InnerConnection {
#[cfg(test)]
mod test {
use self::tempdir::TempDir;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::sync_channel;
use std::thread;
use std::time::Duration;
use tempdir;
use tempdir::TempDir;
use crate::{Connection, Error, ErrorCode, TransactionBehavior, NO_PARAMS};
use crate::{Connection, Error, ErrorCode, Result, TransactionBehavior, NO_PARAMS};
#[test]
fn test_default_busy() {
@@ -94,7 +93,7 @@ mod test {
.transaction_with_behavior(TransactionBehavior::Exclusive)
.unwrap();
let db2 = Connection::open(&path).unwrap();
let r = db2.query_row("PRAGMA schema_version", NO_PARAMS, |_| unreachable!());
let r: Result<()> = db2.query_row("PRAGMA schema_version", NO_PARAMS, |_| unreachable!());
match r.unwrap_err() {
Error::SqliteFailure(err, _) => {
assert_eq!(err.code, ErrorCode::DatabaseBusy);
@@ -127,7 +126,7 @@ mod test {
assert_eq!(tx.recv().unwrap(), 1);
let _ = db2
.query_row("PRAGMA schema_version", NO_PARAMS, |row| {
row.get_checked::<_, i32>(0)
row.get::<_, i32>(0)
})
.expect("unexpected error");
@@ -137,7 +136,7 @@ mod test {
#[test]
#[ignore] // FIXME: unstable
fn test_busy_handler() {
lazy_static! {
lazy_static::lazy_static! {
static ref CALLED: AtomicBool = AtomicBool::new(false);
}
fn busy_handler(_: i32) -> bool {
@@ -166,7 +165,7 @@ mod test {
assert_eq!(tx.recv().unwrap(), 1);
let _ = db2
.query_row("PRAGMA schema_version", NO_PARAMS, |row| {
row.get_checked::<_, i32>(0)
row.get::<_, i32>(0)
})
.expect("unexpected error");
assert_eq!(CALLED.load(Ordering::Relaxed), true);

View File

@@ -79,7 +79,7 @@ impl<'conn> DerefMut for CachedStatement<'conn> {
}
}
impl<'conn> Drop for CachedStatement<'conn> {
impl Drop for CachedStatement<'_> {
#[allow(unused_must_use)]
fn drop(&mut self) {
if let Some(stmt) = self.stmt.take() {
@@ -88,8 +88,8 @@ impl<'conn> Drop for CachedStatement<'conn> {
}
}
impl<'conn> CachedStatement<'conn> {
fn new(stmt: Statement<'conn>, cache: &'conn StatementCache) -> CachedStatement<'conn> {
impl CachedStatement<'_> {
fn new<'conn>(stmt: Statement<'conn>, cache: &'conn StatementCache) -> CachedStatement<'conn> {
CachedStatement {
stmt: Some(stmt),
cache,
@@ -153,6 +153,7 @@ impl StatementCache {
mod test {
use super::StatementCache;
use crate::{Connection, NO_PARAMS};
use fallible_iterator::FallibleIterator;
impl StatementCache {
fn clear(&self) {
@@ -277,12 +278,8 @@ mod test {
{
let mut stmt = db.prepare_cached(sql).unwrap();
assert_eq!(
1i32,
stmt.query_map::<i32, _, _>(NO_PARAMS, |r| r.get(0))
.unwrap()
.next()
.unwrap()
.unwrap()
Ok(Some(1i32)),
stmt.query(NO_PARAMS).unwrap().map(|r| r.get(0)).next()
);
}
@@ -297,12 +294,11 @@ mod test {
{
let mut stmt = db.prepare_cached(sql).unwrap();
assert_eq!(
(1i32, 2i32),
stmt.query_map(NO_PARAMS, |r| (r.get(0), r.get(1)))
Ok(Some((1i32, 2i32))),
stmt.query(NO_PARAMS)
.unwrap()
.map(|r| Ok((r.get(0)?, r.get(1)?)))
.next()
.unwrap()
.unwrap()
);
}
}

207
src/collation.rs Normal file
View File

@@ -0,0 +1,207 @@
//! Add, remove, or modify a collation
use std::cmp::Ordering;
use std::os::raw::{c_char, c_int, c_void};
use std::panic::{catch_unwind, UnwindSafe};
use std::ptr;
use std::slice;
use crate::ffi;
use crate::{str_to_cstring, Connection, InnerConnection, Result};
// FIXME copy/paste from function.rs
unsafe extern "C" fn free_boxed_value<T>(p: *mut c_void) {
drop(Box::from_raw(p as *mut T));
}
impl Connection {
/// Add or modify a collation.
pub fn create_collation<C>(&self, collation_name: &str, x_compare: C) -> Result<()>
where
C: Fn(&str, &str) -> Ordering + Send + UnwindSafe + 'static,
{
self.db
.borrow_mut()
.create_collation(collation_name, x_compare)
}
/// Collation needed callback
pub fn collation_needed(
&self,
x_coll_needed: fn(&Connection, &str) -> Result<()>,
) -> Result<()> {
self.db.borrow_mut().collation_needed(x_coll_needed)
}
/// Remove collation.
pub fn remove_collation(&self, collation_name: &str) -> Result<()> {
self.db.borrow_mut().remove_collation(collation_name)
}
}
impl InnerConnection {
fn create_collation<C>(&mut self, collation_name: &str, x_compare: C) -> Result<()>
where
C: Fn(&str, &str) -> Ordering + Send + UnwindSafe + 'static,
{
unsafe extern "C" fn call_boxed_closure<C>(
arg1: *mut c_void,
arg2: c_int,
arg3: *const c_void,
arg4: c_int,
arg5: *const c_void,
) -> c_int
where
C: Fn(&str, &str) -> Ordering,
{
use std::str;
let r = catch_unwind(|| {
let boxed_f: *mut C = arg1 as *mut C;
assert!(!boxed_f.is_null(), "Internal error - null function pointer");
let s1 = {
let c_slice = slice::from_raw_parts(arg3 as *const u8, arg2 as usize);
str::from_utf8_unchecked(c_slice)
};
let s2 = {
let c_slice = slice::from_raw_parts(arg5 as *const u8, arg4 as usize);
str::from_utf8_unchecked(c_slice)
};
(*boxed_f)(s1, s2)
});
let t = match r {
Err(_) => {
return -1; // FIXME How ?
}
Ok(r) => r,
};
match t {
Ordering::Less => -1,
Ordering::Equal => 0,
Ordering::Greater => 1,
}
}
let boxed_f: *mut C = Box::into_raw(Box::new(x_compare));
let c_name = str_to_cstring(collation_name)?;
let flags = ffi::SQLITE_UTF8;
let r = unsafe {
ffi::sqlite3_create_collation_v2(
self.db(),
c_name.as_ptr(),
flags,
boxed_f as *mut c_void,
Some(call_boxed_closure::<C>),
Some(free_boxed_value::<C>),
)
};
self.decode_result(r)
}
fn collation_needed(
&mut self,
x_coll_needed: fn(&Connection, &str) -> Result<()>,
) -> Result<()> {
use std::mem;
unsafe extern "C" fn collation_needed_callback(
arg1: *mut c_void,
arg2: *mut ffi::sqlite3,
e_text_rep: c_int,
arg3: *const c_char,
) {
use std::ffi::CStr;
use std::str;
if e_text_rep != ffi::SQLITE_UTF8 {
// TODO: validate
return;
}
let callback: fn(&Connection, &str) -> Result<()> = mem::transmute(arg1);
if catch_unwind(|| {
let conn = Connection::from_handle(arg2).unwrap();
let collation_name = {
let c_slice = CStr::from_ptr(arg3).to_bytes();
str::from_utf8_unchecked(c_slice)
};
callback(&conn, collation_name)
}).is_err() {
return; // FIXME How ?
}
}
let r = unsafe {
ffi::sqlite3_collation_needed(
self.db(),
mem::transmute(x_coll_needed),
Some(collation_needed_callback),
)
};
self.decode_result(r)
}
fn remove_collation(&mut self, collation_name: &str) -> Result<()> {
let c_name = str_to_cstring(collation_name)?;
let r = unsafe {
ffi::sqlite3_create_collation_v2(
self.db(),
c_name.as_ptr(),
ffi::SQLITE_UTF8,
ptr::null_mut(),
None,
None,
)
};
self.decode_result(r)
}
}
#[cfg(test)]
mod test {
use crate::{Connection, Result, NO_PARAMS};
use fallible_streaming_iterator::FallibleStreamingIterator;
use std::cmp::Ordering;
use unicase::UniCase;
fn unicase_compare(s1: &str, s2: &str) -> Ordering {
UniCase::new(s1).cmp(&UniCase::new(s2))
}
#[test]
fn test_unicase() {
let db = Connection::open_in_memory().unwrap();
db.create_collation("unicase", unicase_compare).unwrap();
collate(db);
}
fn collate(db: Connection) {
db.execute_batch(
"CREATE TABLE foo (bar);
INSERT INTO foo (bar) VALUES ('Maße');
INSERT INTO foo (bar) VALUES ('MASSE');",
)
.unwrap();
let mut stmt = db
.prepare("SELECT DISTINCT bar COLLATE unicase FROM foo ORDER BY 1")
.unwrap();
let rows = stmt.query(NO_PARAMS).unwrap();
assert_eq!(rows.count().unwrap(), 1);
}
fn collation_needed(db: &Connection, collation_name: &str) -> Result<()> {
if "unicase" == collation_name {
db.create_collation(collation_name, unicase_compare)
} else {
Ok(())
}
}
#[test]
fn test_collation_needed() {
let db = Connection::open_in_memory().unwrap();
db.collation_needed(collation_needed).unwrap();
collate(db);
}
}

171
src/column.rs Normal file
View File

@@ -0,0 +1,171 @@
use std::str;
use crate::{Error, Result, Row, Rows, Statement};
/// Information about a column of a SQLite query.
#[derive(Debug)]
pub struct Column<'stmt> {
name: &'stmt str,
decl_type: Option<&'stmt str>,
}
impl Column<'_> {
/// Returns the name of the column.
pub fn name(&self) -> &str {
self.name
}
/// Returns the type of the column (`None` for expression).
pub fn decl_type(&self) -> Option<&str> {
self.decl_type
}
}
impl Statement<'_> {
/// Get all the column names in the result set of the prepared statement.
pub fn column_names(&self) -> Vec<&str> {
let n = self.column_count();
let mut cols = Vec::with_capacity(n as usize);
for i in 0..n {
let s = self.column_name(i);
cols.push(s);
}
cols
}
/// Return the number of columns in the result set returned by the prepared
/// statement.
pub fn column_count(&self) -> usize {
self.stmt.column_count()
}
pub(crate) fn column_name(&self, col: usize) -> &str {
// Just panic if the bounds are wrong for now, we never call this
// without checking first.
let slice = self.stmt.column_name(col).expect("Column out of bounds");
str::from_utf8(slice.to_bytes()).unwrap()
}
/// Returns the column index in the result set for a given column name.
///
/// If there is no AS clause then the name of the column is unspecified and
/// may change from one release of SQLite to the next.
///
/// # Failure
///
/// Will return an `Error::InvalidColumnName` when there is no column with
/// the specified `name`.
pub fn column_index(&self, name: &str) -> Result<usize> {
let bytes = name.as_bytes();
let n = self.column_count();
for i in 0..n {
// Note: `column_name` is only fallible if `i` is out of bounds,
// which we've already checked.
if bytes.eq_ignore_ascii_case(self.stmt.column_name(i).unwrap().to_bytes()) {
return Ok(i);
}
}
Err(Error::InvalidColumnName(String::from(name)))
}
/// Returns a slice describing the columns of the result of the query.
pub fn columns<'stmt>(&'stmt self) -> Vec<Column<'stmt>> {
let n = self.column_count();
let mut cols = Vec::with_capacity(n as usize);
for i in 0..n {
let name = self.column_name(i);
let slice = self.stmt.column_decltype(i);
let decl_type = slice.map(|s| str::from_utf8(s.to_bytes()).unwrap());
cols.push(Column { name, decl_type });
}
cols
}
}
impl<'stmt> Rows<'stmt> {
/// Get all the column names.
pub fn column_names(&self) -> Option<Vec<&str>> {
self.stmt.map(Statement::column_names)
}
/// Return the number of columns.
pub fn column_count(&self) -> Option<usize> {
self.stmt.map(Statement::column_count)
}
/// Returns a slice describing the columns of the Rows.
pub fn columns(&self) -> Option<Vec<Column<'stmt>>> {
self.stmt.map(Statement::columns)
}
}
impl<'stmt> Row<'stmt> {
/// Return the number of columns in the current row.
pub fn column_count(&self) -> usize {
self.stmt.column_count()
}
/// Returns a slice describing the columns of the Row.
pub fn columns(&self) -> Vec<Column<'stmt>> {
self.stmt.columns()
}
}
#[cfg(test)]
mod test {
use super::Column;
use crate::Connection;
#[test]
fn test_columns() {
let db = Connection::open_in_memory().unwrap();
let query = db.prepare("SELECT * FROM sqlite_master").unwrap();
let columns = query.columns();
let column_names: Vec<&str> = columns.iter().map(Column::name).collect();
assert_eq!(
column_names.as_slice(),
&["type", "name", "tbl_name", "rootpage", "sql"]
);
let column_types: Vec<Option<&str>> = columns.iter().map(Column::decl_type).collect();
assert_eq!(
&column_types[..3],
&[Some("text"), Some("text"), Some("text"),]
);
}
#[test]
fn test_column_name_in_error() {
use crate::{types::Type, Error};
let db = Connection::open_in_memory().unwrap();
db.execute_batch(
"BEGIN;
CREATE TABLE foo(x INTEGER, y TEXT);
INSERT INTO foo VALUES(4, NULL);
END;",
)
.unwrap();
let mut stmt = db.prepare("SELECT x as renamed, y FROM foo").unwrap();
let mut rows = stmt.query(crate::NO_PARAMS).unwrap();
let row = rows.next().unwrap().unwrap();
match row.get::<_, String>(0).unwrap_err() {
Error::InvalidColumnType(idx, name, ty) => {
assert_eq!(idx, 0);
assert_eq!(name, "renamed");
assert_eq!(ty, Type::Integer);
}
e => {
panic!("Unexpected error type: {:?}", e);
}
}
match row.get::<_, String>("y").unwrap_err() {
Error::InvalidColumnType(idx, name, ty) => {
assert_eq!(idx, 1);
assert_eq!(name, "y");
assert_eq!(ty, Type::Null);
}
e => {
panic!("Unexpected error type: {:?}", e);
}
}
}
}

114
src/config.rs Normal file
View File

@@ -0,0 +1,114 @@
//! Configure database connections
use std::os::raw::c_int;
use crate::ffi;
use crate::{Connection, Result};
/// Database Connection Configuration Options
#[repr(i32)]
#[allow(non_snake_case, non_camel_case_types)]
pub enum DbConfig {
//SQLITE_DBCONFIG_MAINDBNAME = 1000, /* const char* */
//SQLITE_DBCONFIG_LOOKASIDE = 1001, /* void* int int */
SQLITE_DBCONFIG_ENABLE_FKEY = 1002,
SQLITE_DBCONFIG_ENABLE_TRIGGER = 1003,
SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER = 1004, // 3.12.0
//SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION = 1005,
SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE = 1006,
SQLITE_DBCONFIG_ENABLE_QPSG = 1007, // 3.20.0
SQLITE_DBCONFIG_TRIGGER_EQP = 1008,
//SQLITE_DBCONFIG_RESET_DATABASE = 1009,
SQLITE_DBCONFIG_DEFENSIVE = 1010,
}
impl Connection {
/// Returns the current value of a `config`.
///
/// - SQLITE_DBCONFIG_ENABLE_FKEY: return `false` or `true` to indicate
/// whether FK enforcement is off or on
/// - SQLITE_DBCONFIG_ENABLE_TRIGGER: return `false` or `true` to indicate
/// whether triggers are disabled or enabled
/// - SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER: return `false` or `true` to
/// indicate whether fts3_tokenizer are disabled or enabled
/// - SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE: return `false` to indicate
/// checkpoints-on-close are not disabled or `true` if they are
/// - SQLITE_DBCONFIG_ENABLE_QPSG: return `false` or `true` to indicate
/// whether the QPSG is disabled or enabled
/// - SQLITE_DBCONFIG_TRIGGER_EQP: return `false` to indicate
/// output-for-trigger are not disabled or `true` if it is
pub fn db_config(&self, config: DbConfig) -> Result<bool> {
let c = self.db.borrow();
unsafe {
let mut val = 0;
check!(ffi::sqlite3_db_config(
c.db(),
config as c_int,
-1,
&mut val
));
Ok(val != 0)
}
}
/// Make configuration changes to a database connection
///
/// - SQLITE_DBCONFIG_ENABLE_FKEY: `false` to disable FK enforcement, `true`
/// to enable FK enforcement
/// - SQLITE_DBCONFIG_ENABLE_TRIGGER: `false` to disable triggers, `true` to
/// enable triggers
/// - SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER: `false` to disable
/// fts3_tokenizer(), `true` to enable fts3_tokenizer()
/// - SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE: `false` (the default) to enable
/// checkpoints-on-close, `true` to disable them
/// - SQLITE_DBCONFIG_ENABLE_QPSG: `false` to disable the QPSG, `true` to
/// enable QPSG
/// - SQLITE_DBCONFIG_TRIGGER_EQP: `false` to disable output for trigger
/// programs, `true` to enable it
pub fn set_db_config(&self, config: DbConfig, new_val: bool) -> Result<bool> {
let c = self.db.borrow_mut();
unsafe {
let mut val = 0;
check!(ffi::sqlite3_db_config(
c.db(),
config as c_int,
if new_val { 1 } else { 0 },
&mut val
));
Ok(val != 0)
}
}
}
#[cfg(test)]
mod test {
use super::DbConfig;
use crate::Connection;
#[test]
fn test_db_config() {
let db = Connection::open_in_memory().unwrap();
let opposite = !db.db_config(DbConfig::SQLITE_DBCONFIG_ENABLE_FKEY).unwrap();
assert_eq!(
db.set_db_config(DbConfig::SQLITE_DBCONFIG_ENABLE_FKEY, opposite),
Ok(opposite)
);
assert_eq!(
db.db_config(DbConfig::SQLITE_DBCONFIG_ENABLE_FKEY),
Ok(opposite)
);
let opposite = !db
.db_config(DbConfig::SQLITE_DBCONFIG_ENABLE_TRIGGER)
.unwrap();
assert_eq!(
db.set_db_config(DbConfig::SQLITE_DBCONFIG_ENABLE_TRIGGER, opposite),
Ok(opposite)
);
assert_eq!(
db.db_config(DbConfig::SQLITE_DBCONFIG_ENABLE_TRIGGER),
Ok(opposite)
);
}
}

View File

@@ -7,7 +7,7 @@ use std::rc::Rc;
use crate::ffi;
use crate::ffi::sqlite3_context;
use crate::str_to_cstring;
use crate::str_for_sqlite;
use crate::types::{ToSqlOutput, ValueRef};
#[cfg(feature = "array")]
use crate::vtab::array::{free_array, ARRAY_TYPE};
@@ -38,25 +38,20 @@ pub(crate) unsafe fn set_result(ctx: *mut sqlite3_context, result: &ToSqlOutput<
ValueRef::Real(r) => ffi::sqlite3_result_double(ctx, r),
ValueRef::Text(s) => {
let length = s.len();
if length > ::std::i32::MAX as usize {
if length > c_int::max_value() as usize {
ffi::sqlite3_result_error_toobig(ctx);
} else {
let c_str = match str_to_cstring(s) {
let (c_str, len, destructor) = match str_for_sqlite(s) {
Ok(c_str) => c_str,
// TODO sqlite3_result_error
Err(_) => return ffi::sqlite3_result_error_code(ctx, ffi::SQLITE_MISUSE),
};
let destructor = if length > 0 {
ffi::SQLITE_TRANSIENT()
} else {
ffi::SQLITE_STATIC()
};
ffi::sqlite3_result_text(ctx, c_str.as_ptr(), length as c_int, destructor);
ffi::sqlite3_result_text(ctx, c_str, len, destructor);
}
}
ValueRef::Blob(b) => {
let length = b.len();
if length > ::std::i32::MAX as usize {
if length > c_int::max_value() as usize {
ffi::sqlite3_result_error_toobig(ctx);
} else if length == 0 {
ffi::sqlite3_result_zeroblob(ctx, 0)

View File

@@ -1,3 +1,4 @@
use crate::types::FromSqlError;
use crate::types::Type;
use crate::{errmsg_to_string, ffi};
use std::error;
@@ -59,7 +60,7 @@ pub enum Error {
/// Error when the value of a particular column is requested, but the type
/// of the result in that column cannot be converted to the requested
/// Rust type.
InvalidColumnType(usize, Type),
InvalidColumnType(usize, String, Type),
/// Error when a query that was expected to insert one row did not insert
/// any or insert many.
@@ -119,8 +120,8 @@ impl PartialEq for Error {
(Error::QueryReturnedNoRows, Error::QueryReturnedNoRows) => true,
(Error::InvalidColumnIndex(i1), Error::InvalidColumnIndex(i2)) => i1 == i2,
(Error::InvalidColumnName(n1), Error::InvalidColumnName(n2)) => n1 == n2,
(Error::InvalidColumnType(i1, t1), Error::InvalidColumnType(i2, t2)) => {
i1 == i2 && t1 == t2
(Error::InvalidColumnType(i1, n1, t1), Error::InvalidColumnType(i2, n2, t2)) => {
i1 == i2 && t1 == t2 && n1 == n2
}
(Error::StatementChangedRows(n1), Error::StatementChangedRows(n2)) => n1 == n2,
#[cfg(feature = "functions")]
@@ -157,6 +158,32 @@ impl From<::std::ffi::NulError> for Error {
}
}
const UNKNOWN_COLUMN: usize = std::usize::MAX;
/// The conversion isn't precise, but it's convenient to have it
/// to allow use of `get_raw(…).as_…()?` in callbacks that take `Error`.
impl From<FromSqlError> for Error {
fn from(err: FromSqlError) -> Error {
// The error type requires index and type fields, but they aren't known in this
// context.
match err {
FromSqlError::OutOfRange(val) => Error::IntegralValueOutOfRange(UNKNOWN_COLUMN, val),
#[cfg(feature = "i128_blob")]
FromSqlError::InvalidI128Size(_) => {
Error::FromSqlConversionFailure(UNKNOWN_COLUMN, Type::Blob, Box::new(err))
}
#[cfg(feature = "uuid")]
FromSqlError::InvalidUuidSize(_) => {
Error::FromSqlConversionFailure(UNKNOWN_COLUMN, Type::Blob, Box::new(err))
}
FromSqlError::Other(source) => {
Error::FromSqlConversionFailure(UNKNOWN_COLUMN, Type::Null, source)
}
_ => Error::FromSqlConversionFailure(UNKNOWN_COLUMN, Type::Null, Box::new(err)),
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
@@ -166,13 +193,23 @@ impl fmt::Display for Error {
f,
"SQLite was compiled or configured for single-threaded use only"
),
Error::FromSqlConversionFailure(i, ref t, ref err) => write!(
f,
"Conversion error from type {} at index: {}, {}",
t, i, err
),
Error::FromSqlConversionFailure(i, ref t, ref err) => {
if i != UNKNOWN_COLUMN {
write!(
f,
"Conversion error from type {} at index: {}, {}",
t, i, err
)
} else {
err.fmt(f)
}
}
Error::IntegralValueOutOfRange(col, val) => {
write!(f, "Integer {} out of range at index {}", val, col)
if col != UNKNOWN_COLUMN {
write!(f, "Integer {} out of range at index {}", val, col)
} else {
write!(f, "Integer {} out of range", val)
}
}
Error::Utf8Error(ref err) => err.fmt(f),
Error::NulError(ref err) => err.fmt(f),
@@ -184,9 +221,11 @@ impl fmt::Display for Error {
Error::QueryReturnedNoRows => write!(f, "Query returned no rows"),
Error::InvalidColumnIndex(i) => write!(f, "Invalid column index: {}", i),
Error::InvalidColumnName(ref name) => write!(f, "Invalid column name: {}", name),
Error::InvalidColumnType(i, ref t) => {
write!(f, "Invalid column type {} at index: {}", t, i)
}
Error::InvalidColumnType(i, ref name, ref t) => write!(
f,
"Invalid column type {} at index: {}, name: {}",
t, i, name
),
Error::StatementChangedRows(i) => write!(f, "Query changed {} rows", i),
#[cfg(feature = "functions")]
@@ -232,7 +271,7 @@ impl error::Error for Error {
Error::QueryReturnedNoRows => "query returned no rows",
Error::InvalidColumnIndex(_) => "invalid column index",
Error::InvalidColumnName(_) => "invalid column name",
Error::InvalidColumnType(_, _) => "invalid column type",
Error::InvalidColumnType(_, _, _) => "invalid column type",
Error::StatementChangedRows(_) => "query inserted zero or more than one row",
#[cfg(feature = "functions")]
@@ -266,7 +305,7 @@ impl error::Error for Error {
| Error::QueryReturnedNoRows
| Error::InvalidColumnIndex(_)
| Error::InvalidColumnName(_)
| Error::InvalidColumnType(_, _)
| Error::InvalidColumnType(_, _, _)
| Error::InvalidPath(_)
| Error::StatementChangedRows(_)
| Error::InvalidQuery
@@ -314,7 +353,7 @@ macro_rules! check {
($funcall:expr) => {{
let rc = $funcall;
if rc != crate::ffi::SQLITE_OK {
Err(crate::error::error_from_sqlite_code(rc, None))?;
return Err(crate::error::error_from_sqlite_code(rc, None).into());
}
}};
}

View File

@@ -11,42 +11,54 @@
//! ```rust
//! use regex::Regex;
//! use rusqlite::{Connection, Error, Result, NO_PARAMS};
//! use std::collections::HashMap;
//!
//! fn add_regexp_function(db: &Connection) -> Result<()> {
//! let mut cached_regexes = HashMap::new();
//! db.create_scalar_function("regexp", 2, true, move |ctx| {
//! let regex_s = ctx.get::<String>(0)?;
//! let entry = cached_regexes.entry(regex_s.clone());
//! let regex = {
//! use std::collections::hash_map::Entry::{Occupied, Vacant};
//! match entry {
//! Occupied(occ) => occ.into_mut(),
//! Vacant(vac) => match Regex::new(&regex_s) {
//! Ok(r) => vac.insert(r),
//! assert_eq!(ctx.len(), 2, "called with unexpected number of arguments");
//!
//! let saved_re: Option<&Regex> = ctx.get_aux(0)?;
//! let new_re = match saved_re {
//! None => {
//! let s = ctx.get::<String>(0)?;
//! match Regex::new(&s) {
//! Ok(r) => Some(r),
//! Err(err) => return Err(Error::UserFunctionError(Box::new(err))),
//! },
//! }
//! }
//! Some(_) => None,
//! };
//!
//! let text = ctx.get::<String>(1)?;
//! Ok(regex.is_match(&text))
//! let is_match = {
//! let re = saved_re.unwrap_or_else(|| new_re.as_ref().unwrap());
//!
//! let text = ctx
//! .get_raw(1)
//! .as_str()
//! .map_err(|e| Error::UserFunctionError(e.into()))?;
//!
//! re.is_match(text)
//! };
//!
//! if let Some(re) = new_re {
//! ctx.set_aux(0, re);
//! }
//!
//! Ok(is_match)
//! })
//! }
//!
//! fn main() {
//! let db = Connection::open_in_memory().unwrap();
//! add_regexp_function(&db).unwrap();
//! fn main() -> Result<()> {
//! let db = Connection::open_in_memory()?;
//! add_regexp_function(&db)?;
//!
//! let is_match: bool = db
//! .query_row(
//! "SELECT regexp('[aeiou]*', 'aaaaeeeiii')",
//! NO_PARAMS,
//! |row| row.get(0),
//! )
//! .unwrap();
//! let is_match: bool = db.query_row(
//! "SELECT regexp('[aeiou]*', 'aaaaeeeiii')",
//! NO_PARAMS,
//! |row| row.get(0),
//! )?;
//!
//! assert!(is_match);
//! Ok(())
//! }
//! ```
use std::error::Error as StdError;
@@ -104,7 +116,7 @@ pub struct Context<'a> {
args: &'a [*mut sqlite3_value],
}
impl<'a> Context<'a> {
impl Context<'_> {
/// Returns the number of arguments to the function.
pub fn len(&self) -> usize {
self.args.len()
@@ -138,6 +150,10 @@ impl<'a> Context<'a> {
FromSqlError::InvalidI128Size(_) => {
Error::FromSqlConversionFailure(idx, value.data_type(), Box::new(err))
}
#[cfg(feature = "uuid")]
FromSqlError::InvalidUuidSize(_) => {
Error::FromSqlConversionFailure(idx, value.data_type(), Box::new(err))
}
})
}
@@ -146,7 +162,7 @@ impl<'a> Context<'a> {
/// # Failure
///
/// Will panic if `idx` is greater than or equal to `self.len()`.
pub fn get_raw(&self, idx: usize) -> ValueRef<'a> {
pub fn get_raw(&self, idx: usize) -> ValueRef<'_> {
let arg = self.args[idx];
unsafe { ValueRef::from_value(arg) }
}
@@ -210,6 +226,22 @@ where
fn finalize(&self, _: Option<A>) -> Result<T>;
}
/// WindowAggregate is the callback interface for user-defined aggregate window
/// function.
#[cfg(feature = "window")]
pub trait WindowAggregate<A, T>: Aggregate<A, T>
where
A: RefUnwindSafe + UnwindSafe,
T: ToSql,
{
/// Returns the current value of the aggregate. Unlike xFinal, the
/// implementation should not delete any context.
fn value(&self, _: Option<&A>) -> Result<T>;
/// Removes a row from the current window.
fn inverse(&self, _: &mut Context<'_>, _: &mut A) -> Result<()>;
}
impl Connection {
/// Attach a user-defined scalar function to this database connection.
///
@@ -278,6 +310,24 @@ impl Connection {
.create_aggregate_function(fn_name, n_arg, deterministic, aggr)
}
#[cfg(feature = "window")]
pub fn create_window_function<A, W, T>(
&self,
fn_name: &str,
n_arg: c_int,
deterministic: bool,
aggr: W,
) -> Result<()>
where
A: RefUnwindSafe + UnwindSafe,
W: WindowAggregate<A, T>,
T: ToSql,
{
self.db
.borrow_mut()
.create_window_function(fn_name, n_arg, deterministic, aggr)
}
/// Removes a user-defined function from this database connection.
///
/// `fn_name` and `n_arg` should match the name and number of arguments
@@ -370,105 +420,6 @@ impl InnerConnection {
D: Aggregate<A, T>,
T: ToSql,
{
unsafe fn aggregate_context<A>(
ctx: *mut sqlite3_context,
bytes: usize,
) -> Option<*mut *mut A> {
let pac = ffi::sqlite3_aggregate_context(ctx, bytes as c_int) as *mut *mut A;
if pac.is_null() {
return None;
}
Some(pac)
}
unsafe extern "C" fn call_boxed_step<A, D, T>(
ctx: *mut sqlite3_context,
argc: c_int,
argv: *mut *mut sqlite3_value,
) where
A: RefUnwindSafe + UnwindSafe,
D: Aggregate<A, T>,
T: ToSql,
{
let pac = match aggregate_context(ctx, ::std::mem::size_of::<*mut A>()) {
Some(pac) => pac,
None => {
ffi::sqlite3_result_error_nomem(ctx);
return;
}
};
let r = catch_unwind(|| {
let boxed_aggr: *mut D = ffi::sqlite3_user_data(ctx) as *mut D;
assert!(
!boxed_aggr.is_null(),
"Internal error - null aggregate pointer"
);
if (*pac as *mut A).is_null() {
*pac = Box::into_raw(Box::new((*boxed_aggr).init()));
}
let mut ctx = Context {
ctx,
args: slice::from_raw_parts(argv, argc as usize),
};
(*boxed_aggr).step(&mut ctx, &mut **pac)
});
let r = match r {
Err(_) => {
report_error(ctx, &Error::UnwindingPanic);
return;
}
Ok(r) => r,
};
match r {
Ok(_) => {}
Err(err) => report_error(ctx, &err),
};
}
unsafe extern "C" fn call_boxed_final<A, D, T>(ctx: *mut sqlite3_context)
where
A: RefUnwindSafe + UnwindSafe,
D: Aggregate<A, T>,
T: ToSql,
{
// Within the xFinal callback, it is customary to set N=0 in calls to
// sqlite3_aggregate_context(C,N) so that no pointless memory allocations occur.
let a: Option<A> = match aggregate_context(ctx, 0) {
Some(pac) => {
if (*pac as *mut A).is_null() {
None
} else {
let a = Box::from_raw(*pac);
Some(*a)
}
}
None => None,
};
let r = catch_unwind(|| {
let boxed_aggr: *mut D = ffi::sqlite3_user_data(ctx) as *mut D;
assert!(
!boxed_aggr.is_null(),
"Internal error - null aggregate pointer"
);
(*boxed_aggr).finalize(a)
});
let t = match r {
Err(_) => {
report_error(ctx, &Error::UnwindingPanic);
return;
}
Ok(r) => r,
};
let t = t.as_ref().map(|t| ToSql::to_sql(t));
match t {
Ok(Ok(ref value)) => set_result(ctx, value),
Ok(Err(err)) => report_error(ctx, &err),
Err(err) => report_error(ctx, err),
}
}
let boxed_aggr: *mut D = Box::into_raw(Box::new(aggr));
let c_name = str_to_cstring(fn_name)?;
let mut flags = ffi::SQLITE_UTF8;
@@ -491,6 +442,42 @@ impl InnerConnection {
self.decode_result(r)
}
#[cfg(feature = "window")]
fn create_window_function<A, W, T>(
&mut self,
fn_name: &str,
n_arg: c_int,
deterministic: bool,
aggr: W,
) -> Result<()>
where
A: RefUnwindSafe + UnwindSafe,
W: WindowAggregate<A, T>,
T: ToSql,
{
let boxed_aggr: *mut W = Box::into_raw(Box::new(aggr));
let c_name = str_to_cstring(fn_name)?;
let mut flags = ffi::SQLITE_UTF8;
if deterministic {
flags |= ffi::SQLITE_DETERMINISTIC;
}
let r = unsafe {
ffi::sqlite3_create_window_function(
self.db(),
c_name.as_ptr(),
n_arg,
flags,
boxed_aggr as *mut c_void,
Some(call_boxed_step::<A, W, T>),
Some(call_boxed_final::<A, W, T>),
Some(call_boxed_value::<A, W, T>),
Some(call_boxed_inverse::<A, W, T>),
Some(free_boxed_value::<W>),
)
};
self.decode_result(r)
}
fn remove_function(&mut self, fn_name: &str, n_arg: c_int) -> Result<()> {
let c_name = str_to_cstring(fn_name)?;
let r = unsafe {
@@ -510,15 +497,197 @@ impl InnerConnection {
}
}
unsafe fn aggregate_context<A>(ctx: *mut sqlite3_context, bytes: usize) -> Option<*mut *mut A> {
let pac = ffi::sqlite3_aggregate_context(ctx, bytes as c_int) as *mut *mut A;
if pac.is_null() {
return None;
}
Some(pac)
}
unsafe extern "C" fn call_boxed_step<A, D, T>(
ctx: *mut sqlite3_context,
argc: c_int,
argv: *mut *mut sqlite3_value,
) where
A: RefUnwindSafe + UnwindSafe,
D: Aggregate<A, T>,
T: ToSql,
{
let pac = match aggregate_context(ctx, ::std::mem::size_of::<*mut A>()) {
Some(pac) => pac,
None => {
ffi::sqlite3_result_error_nomem(ctx);
return;
}
};
let r = catch_unwind(|| {
let boxed_aggr: *mut D = ffi::sqlite3_user_data(ctx) as *mut D;
assert!(
!boxed_aggr.is_null(),
"Internal error - null aggregate pointer"
);
if (*pac as *mut A).is_null() {
*pac = Box::into_raw(Box::new((*boxed_aggr).init()));
}
let mut ctx = Context {
ctx,
args: slice::from_raw_parts(argv, argc as usize),
};
(*boxed_aggr).step(&mut ctx, &mut **pac)
});
let r = match r {
Err(_) => {
report_error(ctx, &Error::UnwindingPanic);
return;
}
Ok(r) => r,
};
match r {
Ok(_) => {}
Err(err) => report_error(ctx, &err),
};
}
#[cfg(feature = "window")]
unsafe extern "C" fn call_boxed_inverse<A, W, T>(
ctx: *mut sqlite3_context,
argc: c_int,
argv: *mut *mut sqlite3_value,
) where
A: RefUnwindSafe + UnwindSafe,
W: WindowAggregate<A, T>,
T: ToSql,
{
let pac = match aggregate_context(ctx, ::std::mem::size_of::<*mut A>()) {
Some(pac) => pac,
None => {
ffi::sqlite3_result_error_nomem(ctx);
return;
}
};
let r = catch_unwind(|| {
let boxed_aggr: *mut W = ffi::sqlite3_user_data(ctx) as *mut W;
assert!(
!boxed_aggr.is_null(),
"Internal error - null aggregate pointer"
);
let mut ctx = Context {
ctx,
args: slice::from_raw_parts(argv, argc as usize),
};
(*boxed_aggr).inverse(&mut ctx, &mut **pac)
});
let r = match r {
Err(_) => {
report_error(ctx, &Error::UnwindingPanic);
return;
}
Ok(r) => r,
};
match r {
Ok(_) => {}
Err(err) => report_error(ctx, &err),
};
}
unsafe extern "C" fn call_boxed_final<A, D, T>(ctx: *mut sqlite3_context)
where
A: RefUnwindSafe + UnwindSafe,
D: Aggregate<A, T>,
T: ToSql,
{
// Within the xFinal callback, it is customary to set N=0 in calls to
// sqlite3_aggregate_context(C,N) so that no pointless memory allocations occur.
let a: Option<A> = match aggregate_context(ctx, 0) {
Some(pac) => {
if (*pac as *mut A).is_null() {
None
} else {
let a = Box::from_raw(*pac);
Some(*a)
}
}
None => None,
};
let r = catch_unwind(|| {
let boxed_aggr: *mut D = ffi::sqlite3_user_data(ctx) as *mut D;
assert!(
!boxed_aggr.is_null(),
"Internal error - null aggregate pointer"
);
(*boxed_aggr).finalize(a)
});
let t = match r {
Err(_) => {
report_error(ctx, &Error::UnwindingPanic);
return;
}
Ok(r) => r,
};
let t = t.as_ref().map(|t| ToSql::to_sql(t));
match t {
Ok(Ok(ref value)) => set_result(ctx, value),
Ok(Err(err)) => report_error(ctx, &err),
Err(err) => report_error(ctx, err),
}
}
#[cfg(feature = "window")]
unsafe extern "C" fn call_boxed_value<A, W, T>(ctx: *mut sqlite3_context)
where
A: RefUnwindSafe + UnwindSafe,
W: WindowAggregate<A, T>,
T: ToSql,
{
// Within the xValue callback, it is customary to set N=0 in calls to
// sqlite3_aggregate_context(C,N) so that no pointless memory allocations occur.
let a: Option<&A> = match aggregate_context(ctx, 0) {
Some(pac) => {
if (*pac as *mut A).is_null() {
None
} else {
let a = &**pac;
Some(a)
}
}
None => None,
};
let r = catch_unwind(|| {
let boxed_aggr: *mut W = ffi::sqlite3_user_data(ctx) as *mut W;
assert!(
!boxed_aggr.is_null(),
"Internal error - null aggregate pointer"
);
(*boxed_aggr).value(a)
});
let t = match r {
Err(_) => {
report_error(ctx, &Error::UnwindingPanic);
return;
}
Ok(r) => r,
};
let t = t.as_ref().map(|t| ToSql::to_sql(t));
match t {
Ok(Ok(ref value)) => set_result(ctx, value),
Ok(Err(err)) => report_error(ctx, &err),
Err(err) => report_error(ctx, err),
}
}
#[cfg(test)]
mod test {
use regex;
use self::regex::Regex;
use std::collections::HashMap;
use regex::Regex;
use std::f64::EPSILON;
use std::os::raw::c_double;
#[cfg(feature = "window")]
use crate::functions::WindowAggregate;
use crate::functions::{Aggregate, Context};
use crate::{Connection, Error, Result, NO_PARAMS};
@@ -616,60 +785,6 @@ mod test {
assert_eq!(2, result.unwrap());
}
#[test]
fn test_function_regexp_with_hashmap_cache() {
let db = Connection::open_in_memory().unwrap();
db.execute_batch(
"BEGIN;
CREATE TABLE foo (x string);
INSERT INTO foo VALUES ('lisa');
INSERT INTO foo VALUES ('lXsi');
INSERT INTO foo VALUES ('lisX');
END;",
)
.unwrap();
// This implementation of a regexp scalar function uses a captured HashMap
// to keep cached regular expressions around (even across multiple queries)
// until the function is removed.
let mut cached_regexes = HashMap::new();
db.create_scalar_function("regexp", 2, true, move |ctx| {
assert_eq!(ctx.len(), 2, "called with unexpected number of arguments");
let regex_s = ctx.get::<String>(0)?;
let entry = cached_regexes.entry(regex_s.clone());
let regex = {
use std::collections::hash_map::Entry::{Occupied, Vacant};
match entry {
Occupied(occ) => occ.into_mut(),
Vacant(vac) => match Regex::new(&regex_s) {
Ok(r) => vac.insert(r),
Err(err) => return Err(Error::UserFunctionError(Box::new(err))),
},
}
};
let text = ctx.get::<String>(1)?;
Ok(regex.is_match(&text))
})
.unwrap();
let result: Result<bool> =
db.query_row("SELECT regexp('l.s[aeiouy]', 'lisa')", NO_PARAMS, |r| {
r.get(0)
});
assert_eq!(true, result.unwrap());
let result: Result<i64> = db.query_row(
"SELECT COUNT(*) FROM foo WHERE regexp('l.s[aeiouy]', x) == 1",
NO_PARAMS,
|r| r.get(0),
);
assert_eq!(2, result.unwrap());
}
#[test]
fn test_varargs_function() {
let db = Connection::open_in_memory().unwrap();
@@ -771,7 +886,7 @@ mod test {
let dual_sum = "SELECT my_sum(i), my_sum(j) FROM (SELECT 2 AS i, 1 AS j UNION ALL SELECT \
2, 1)";
let result: (i64, i64) = db
.query_row(dual_sum, NO_PARAMS, |r| (r.get(0), r.get(1)))
.query_row(dual_sum, NO_PARAMS, |r| Ok((r.get(0)?, r.get(1)?)))
.unwrap();
assert_eq!((4, 2), result);
}
@@ -791,4 +906,58 @@ mod test {
let result: i64 = db.query_row(single_sum, NO_PARAMS, |r| r.get(0)).unwrap();
assert_eq!(2, result);
}
#[cfg(feature = "window")]
impl WindowAggregate<i64, Option<i64>> for Sum {
fn inverse(&self, ctx: &mut Context<'_>, sum: &mut i64) -> Result<()> {
*sum -= ctx.get::<i64>(0)?;
Ok(())
}
fn value(&self, sum: Option<&i64>) -> Result<Option<i64>> {
Ok(sum.copied())
}
}
#[test]
#[cfg(feature = "window")]
fn test_window() {
use fallible_iterator::FallibleIterator;
let db = Connection::open_in_memory().unwrap();
db.create_window_function("sumint", 1, true, Sum).unwrap();
db.execute_batch(
"CREATE TABLE t3(x, y);
INSERT INTO t3 VALUES('a', 4),
('b', 5),
('c', 3),
('d', 8),
('e', 1);",
)
.unwrap();
let mut stmt = db
.prepare(
"SELECT x, sumint(y) OVER (
ORDER BY x ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING
) AS sum_y
FROM t3 ORDER BY x;",
)
.unwrap();
let results: Vec<(String, i64)> = stmt
.query(NO_PARAMS)
.unwrap()
.map(|row| Ok((row.get("x")?, row.get("sum_y")?)))
.collect()
.unwrap();
let expected = vec![
("a".to_owned(), 9),
("b".to_owned(), 12),
("c".to_owned(), 16),
("d".to_owned(), 12),
("e".to_owned(), 9),
];
assert_eq!(expected, results);
}
}

View File

@@ -236,6 +236,7 @@ fn free_boxed_hook<F>(p: *mut c_void) {
mod test {
use super::Action;
use crate::Connection;
use lazy_static::lazy_static;
use std::sync::atomic::{AtomicBool, Ordering};
#[test]

View File

@@ -1,15 +1,15 @@
use std::ffi::CString;
use std::mem;
use std::mem::MaybeUninit;
use std::os::raw::c_int;
#[cfg(feature = "load_extension")]
use std::path::Path;
use std::ptr;
use std::str;
use std::sync::atomic::{AtomicBool, Ordering, ATOMIC_BOOL_INIT};
use std::sync::{Arc, Mutex, Once, ONCE_INIT};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex, Once};
use super::ffi;
use super::str_to_cstring;
use super::{str_for_sqlite, str_to_cstring};
use super::{Connection, InterruptHandle, OpenFlags, Result};
use crate::error::{error_from_handle, error_from_sqlite_code, Error};
use crate::raw_statement::RawStatement;
@@ -78,8 +78,10 @@ impl InnerConnection {
}
unsafe {
let mut db: *mut ffi::sqlite3 = mem::uninitialized();
let r = ffi::sqlite3_open_v2(c_path.as_ptr(), &mut db, flags.bits(), ptr::null());
let mut db = MaybeUninit::uninit();
let r =
ffi::sqlite3_open_v2(c_path.as_ptr(), db.as_mut_ptr(), flags.bits(), ptr::null());
let db: *mut ffi::sqlite3 = db.assume_init();
if r != ffi::SQLITE_OK {
let e = if db.is_null() {
error_from_sqlite_code(r, None)
@@ -180,21 +182,27 @@ impl InnerConnection {
let dylib_str = super::path_to_cstring(dylib_path)?;
unsafe {
let mut errmsg: *mut c_char = mem::uninitialized();
let mut errmsg = MaybeUninit::uninit();
let r = if let Some(entry_point) = entry_point {
let c_entry = str_to_cstring(entry_point)?;
ffi::sqlite3_load_extension(
self.db,
dylib_str.as_ptr(),
c_entry.as_ptr(),
&mut errmsg,
errmsg.as_mut_ptr(),
)
} else {
ffi::sqlite3_load_extension(self.db, dylib_str.as_ptr(), ptr::null(), &mut errmsg)
ffi::sqlite3_load_extension(
self.db,
dylib_str.as_ptr(),
ptr::null(),
errmsg.as_mut_ptr(),
)
};
if r == ffi::SQLITE_OK {
Ok(())
} else {
let errmsg: *mut c_char = errmsg.assume_init();
let message = super::errmsg_to_string(&*errmsg);
ffi::sqlite3_free(errmsg as *mut ::std::os::raw::c_void);
Err(error_from_sqlite_code(r, Some(message)))
@@ -207,12 +215,8 @@ impl InnerConnection {
}
pub fn prepare<'a>(&mut self, conn: &'a Connection, sql: &str) -> Result<Statement<'a>> {
if sql.len() >= ::std::i32::MAX as usize {
return Err(error_from_sqlite_code(ffi::SQLITE_TOOBIG, None));
}
let mut c_stmt: *mut ffi::sqlite3_stmt = unsafe { mem::uninitialized() };
let c_sql = str_to_cstring(sql)?;
let len_with_nul = (sql.len() + 1) as c_int;
let mut c_stmt = MaybeUninit::uninit();
let (c_sql, len, _) = str_for_sqlite(sql.as_bytes())?;
let mut c_tail = ptr::null();
let r = unsafe {
if cfg!(feature = "unlock_notify") {
@@ -220,9 +224,9 @@ impl InnerConnection {
loop {
rc = ffi::sqlite3_prepare_v2(
self.db(),
c_sql.as_ptr(),
len_with_nul,
&mut c_stmt,
c_sql,
len,
c_stmt.as_mut_ptr(),
&mut c_tail,
);
if !unlock_notify::is_locked(self.db, rc) {
@@ -235,15 +239,10 @@ impl InnerConnection {
}
rc
} else {
ffi::sqlite3_prepare_v2(
self.db(),
c_sql.as_ptr(),
len_with_nul,
&mut c_stmt,
&mut c_tail,
)
ffi::sqlite3_prepare_v2(self.db(), c_sql, len, c_stmt.as_mut_ptr(), &mut c_tail)
}
};
let c_stmt: *mut ffi::sqlite3_stmt = unsafe { c_stmt.assume_init() };
if !c_tail.is_null() && unsafe { *c_tail == 0 } {
// '\0' when there is no ';' at the end
c_tail = ptr::null(); // TODO ignore spaces, comments, ... at the end
@@ -295,9 +294,9 @@ impl Drop for InnerConnection {
}
#[cfg(not(feature = "bundled"))]
static SQLITE_VERSION_CHECK: Once = ONCE_INIT;
static SQLITE_VERSION_CHECK: Once = Once::new();
#[cfg(not(feature = "bundled"))]
pub static BYPASS_VERSION_CHECK: AtomicBool = ATOMIC_BOOL_INIT;
pub static BYPASS_VERSION_CHECK: AtomicBool = AtomicBool::new(false);
#[cfg(not(feature = "bundled"))]
fn ensure_valid_sqlite_version() {
@@ -343,8 +342,8 @@ rusqlite was built against SQLite {} but the runtime SQLite version is {}. To fi
});
}
static SQLITE_INIT: Once = ONCE_INIT;
pub static BYPASS_SQLITE_INIT: AtomicBool = ATOMIC_BOOL_INIT;
static SQLITE_INIT: Once = Once::new();
pub static BYPASS_SQLITE_INIT: AtomicBool = AtomicBool::new(false);
fn ensure_safe_sqlite_threading_mode() -> Result<()> {
// Ensure SQLite was compiled in thredsafe mode.

View File

@@ -3,7 +3,7 @@
//!
//! ```rust
//! use rusqlite::types::ToSql;
//! use rusqlite::{params, Connection};
//! use rusqlite::{params, Connection, Result};
//! use time::Timespec;
//!
//! #[derive(Debug)]
@@ -14,8 +14,8 @@
//! data: Option<Vec<u8>>,
//! }
//!
//! fn main() {
//! let conn = Connection::open_in_memory().unwrap();
//! fn main() -> Result<()> {
//! let conn = Connection::open_in_memory()?;
//!
//! conn.execute(
//! "CREATE TABLE person (
@@ -25,8 +25,7 @@
//! data BLOB
//! )",
//! params![],
//! )
//! .unwrap();
//! )?;
//! let me = Person {
//! id: 0,
//! name: "Steven".to_string(),
@@ -37,36 +36,28 @@
//! "INSERT INTO person (name, time_created, data)
//! VALUES (?1, ?2, ?3)",
//! params![me.name, me.time_created, me.data],
//! )
//! .unwrap();
//! )?;
//!
//! let mut stmt = conn
//! .prepare("SELECT id, name, time_created, data FROM person")
//! .unwrap();
//! let person_iter = stmt
//! .query_map(params![], |row| Person {
//! id: row.get(0),
//! name: row.get(1),
//! time_created: row.get(2),
//! data: row.get(3),
//! let mut stmt = conn.prepare("SELECT id, name, time_created, data FROM person")?;
//! let person_iter = stmt.query_map(params![], |row| {
//! Ok(Person {
//! id: row.get(0)?,
//! name: row.get(1)?,
//! time_created: row.get(2)?,
//! data: row.get(3)?,
//! })
//! .unwrap();
//! })?;
//!
//! for person in person_iter {
//! println!("Found person {:?}", person.unwrap());
//! }
//! Ok(())
//! }
//! ```
#![allow(unknown_lints)]
pub use libsqlite3_sys as ffi;
#[macro_use]
extern crate bitflags;
#[cfg(any(test, feature = "vtab"))]
#[macro_use]
extern crate lazy_static;
use std::cell::RefCell;
use std::convert;
use std::default::Default;
@@ -86,10 +77,11 @@ use crate::raw_statement::RawStatement;
use crate::types::ValueRef;
pub use crate::cache::CachedStatement;
pub use crate::column::Column;
pub use crate::error::Error;
pub use crate::ffi::ErrorCode;
#[cfg(feature = "hooks")]
pub use crate::hooks::*;
pub use crate::hooks::Action;
#[cfg(feature = "load_extension")]
pub use crate::load_extension_guard::LoadExtensionGuard;
pub use crate::row::{AndThenRows, MappedRows, Row, RowIndex, Rows};
@@ -98,16 +90,21 @@ pub use crate::transaction::{DropBehavior, Savepoint, Transaction, TransactionBe
pub use crate::types::ToSql;
pub use crate::version::*;
#[macro_use]
mod error;
#[cfg(feature = "backup")]
pub mod backup;
#[cfg(feature = "blob")]
pub mod blob;
mod busy;
mod cache;
#[cfg(feature = "collation")]
mod collation;
mod column;
pub mod config;
#[cfg(any(feature = "functions", feature = "vtab"))]
mod context;
#[macro_use]
mod error;
#[cfg(feature = "functions")]
pub mod functions;
#[cfg(feature = "hooks")]
@@ -117,6 +114,7 @@ mod inner_connection;
pub mod limits;
#[cfg(feature = "load_extension")]
mod load_extension_guard;
mod pragma;
mod raw_statement;
mod row;
#[cfg(feature = "session")]
@@ -238,6 +236,42 @@ fn str_to_cstring(s: &str) -> Result<CString> {
Ok(CString::new(s)?)
}
/// Returns `Ok((string ptr, len as c_int, SQLITE_STATIC | SQLITE_TRANSIENT))`
/// normally.
/// Returns errors if the string has embedded nuls or is too large for sqlite.
/// The `sqlite3_destructor_type` item is always `SQLITE_TRANSIENT` unless
/// the string was empty (in which case it's `SQLITE_STATIC`, and the ptr is
/// static).
fn str_for_sqlite(s: &[u8]) -> Result<(*const c_char, c_int, ffi::sqlite3_destructor_type)> {
let len = len_as_c_int(s.len())?;
if memchr::memchr(0, s).is_none() {
let (ptr, dtor_info) = if len != 0 {
(s.as_ptr() as *const c_char, ffi::SQLITE_TRANSIENT())
} else {
// Return a pointer guaranteed to live forever
("".as_ptr() as *const c_char, ffi::SQLITE_STATIC())
};
Ok((ptr, len, dtor_info))
} else {
// There's an embedded nul, so we fabricate a NulError.
let e = CString::new(s);
Err(Error::NulError(e.unwrap_err()))
}
}
// Helper to cast to c_int safely, returning the correct error type if the cast
// failed.
fn len_as_c_int(len: usize) -> Result<c_int> {
if len >= (c_int::max_value() as usize) {
Err(Error::SqliteFailure(
ffi::Error::new(ffi::SQLITE_TOOBIG),
None,
))
} else {
Ok(len as c_int)
}
}
fn path_to_cstring(p: &Path) -> Result<CString> {
let s = p.to_str().ok_or_else(|| Error::InvalidPath(p.to_owned()))?;
str_to_cstring(s)
@@ -264,7 +298,7 @@ pub enum DatabaseName<'a> {
feature = "session",
feature = "bundled"
))]
impl<'a> DatabaseName<'a> {
impl DatabaseName<'_> {
fn to_cstring(&self) -> Result<CString> {
use self::DatabaseName::{Attached, Main, Temp};
match *self {
@@ -298,6 +332,16 @@ impl Connection {
/// OpenFlags::SQLITE_OPEN_READ_WRITE |
/// OpenFlags::SQLITE_OPEN_CREATE)`.
///
/// ```rust,no_run
/// # use rusqlite::{Connection, Result};
/// fn open_my_db() -> Result<()> {
/// let path = "./my_db.db3";
/// let db = Connection::open(&path)?;
/// println!("{}", db.is_autocommit());
/// Ok(())
/// }
/// ```
///
/// # Failure
///
/// Will return `Err` if `path` cannot be converted to a C-compatible
@@ -477,7 +521,7 @@ impl Connection {
where
P: IntoIterator,
P::Item: ToSql,
F: FnOnce(&Row<'_, '_>) -> T,
F: FnOnce(&Row<'_>) -> Result<T>,
{
let mut stmt = self.prepare(sql)?;
stmt.check_no_tail()?;
@@ -500,13 +544,11 @@ impl Connection {
/// or if the underlying SQLite call fails.
pub fn query_row_named<T, F>(&self, sql: &str, params: &[(&str, &dyn ToSql)], f: F) -> Result<T>
where
F: FnOnce(&Row<'_, '_>) -> T,
F: FnOnce(&Row<'_>) -> Result<T>,
{
let mut stmt = self.prepare(sql)?;
stmt.check_no_tail()?;
let mut rows = stmt.query_named(params)?;
rows.get_expected_row().map(|r| f(&r))
stmt.query_row_named(params, f)
}
/// Convenience method to execute a query that is expected to return a
@@ -522,7 +564,7 @@ impl Connection {
/// conn.query_row_and_then(
/// "SELECT value FROM preferences WHERE name='locale'",
/// NO_PARAMS,
/// |row| row.get_checked(0),
/// |row| row.get(0),
/// )
/// }
/// ```
@@ -538,7 +580,7 @@ impl Connection {
where
P: IntoIterator,
P::Item: ToSql,
F: FnOnce(&Row<'_, '_>) -> result::Result<T, E>,
F: FnOnce(&Row<'_>) -> result::Result<T, E>,
E: convert::From<Error>,
{
let mut stmt = self.prepare(sql)?;
@@ -694,7 +736,7 @@ impl Connection {
/// Return the number of rows modified, inserted or deleted by the most
/// recently completed INSERT, UPDATE or DELETE statement on the database
/// connection.
pub fn changes(&self) -> usize {
fn changes(&self) -> usize {
self.db.borrow_mut().changes()
}
@@ -719,7 +761,7 @@ impl fmt::Debug for Connection {
}
}
bitflags! {
bitflags::bitflags! {
#[doc = "Flags for opening SQLite database connections."]
#[doc = "See [sqlite3_open_v2](http://www.sqlite.org/c3ref/open.html) for details."]
#[repr(C)]
@@ -814,12 +856,12 @@ unsafe fn db_filename(_: *mut ffi::sqlite3) -> Option<PathBuf> {
#[cfg(test)]
mod test {
use self::tempdir::TempDir;
pub use super::*;
use super::*;
use crate::ffi;
pub use std::error::Error as StdError;
pub use std::fmt;
use tempdir;
use fallible_iterator::FallibleIterator;
use std::error::Error as StdError;
use std::fmt;
use tempdir::TempDir;
// this function is never called, but is still type checked; in
// particular, calls with specific instantiations will require
@@ -854,8 +896,8 @@ mod test {
)
.expect("create temp db");
let mut db1 = Connection::open(&path).unwrap();
let mut db2 = Connection::open(&path).unwrap();
let mut db1 = Connection::open_with_flags(&path, OpenFlags::SQLITE_OPEN_READ_WRITE).unwrap();
let mut db2 = Connection::open_with_flags(&path, OpenFlags::SQLITE_OPEN_READ_ONLY).unwrap();
db1.busy_timeout(Duration::from_millis(0)).unwrap();
db2.busy_timeout(Duration::from_millis(0)).unwrap();
@@ -865,9 +907,9 @@ mod test {
let tx2 = db2.transaction().unwrap();
// SELECT first makes sqlite lock with a shared lock
tx1.query_row("SELECT x FROM foo LIMIT 1", NO_PARAMS, |_| ())
tx1.query_row("SELECT x FROM foo LIMIT 1", NO_PARAMS, |_| Ok(()))
.unwrap();
tx2.query_row("SELECT x FROM foo LIMIT 1", NO_PARAMS, |_| ())
tx2.query_row("SELECT x FROM foo LIMIT 1", NO_PARAMS, |_| Ok(()))
.unwrap();
tx1.execute("INSERT INTO foo VALUES(?1)", &[1]).unwrap();
@@ -923,23 +965,24 @@ mod test {
// statement first.
let raw_stmt = {
use super::str_to_cstring;
use std::mem;
use std::mem::MaybeUninit;
use std::os::raw::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 = unsafe { mem::uninitialized() };
let mut raw_stmt = MaybeUninit::uninit();
let rc = unsafe {
ffi::sqlite3_prepare_v2(
raw_db,
str_to_cstring(sql).unwrap().as_ptr(),
(sql.len() + 1) as c_int,
&mut raw_stmt,
raw_stmt.as_mut_ptr(),
ptr::null_mut(),
)
};
assert_eq!(rc, ffi::SQLITE_OK);
let raw_stmt: *mut ffi::sqlite3_stmt = unsafe { raw_stmt.assume_init() };
raw_stmt
};
@@ -1081,8 +1124,8 @@ mod test {
let mut rows = query.query(&[4i32]).unwrap();
let mut v = Vec::<i32>::new();
while let Some(row) = rows.next() {
v.push(row.unwrap().get(0));
while let Some(row) = rows.next().unwrap() {
v.push(row.get(0).unwrap());
}
assert_eq!(v, [3i32, 2, 1]);
@@ -1092,8 +1135,8 @@ mod test {
let mut rows = query.query(&[3i32]).unwrap();
let mut v = Vec::<i32>::new();
while let Some(row) = rows.next() {
v.push(row.unwrap().get(0));
while let Some(row) = rows.next().unwrap() {
v.push(row.get(0).unwrap());
}
assert_eq!(v, [2i32, 1]);
@@ -1114,8 +1157,9 @@ mod test {
let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap();
let results: Result<Vec<String>> = query
.query_map(NO_PARAMS, |row| row.get(1))
.query(NO_PARAMS)
.unwrap()
.map(|row| row.get(1))
.collect();
assert_eq!(results.unwrap().concat(), "hello, world!");
@@ -1146,7 +1190,7 @@ mod test {
err => panic!("Unexpected error {}", err),
}
let bad_query_result = db.query_row("NOT A PROPER QUERY; test123", NO_PARAMS, |_| ());
let bad_query_result = db.query_row("NOT A PROPER QUERY; test123", NO_PARAMS, |_| Ok(()));
assert!(bad_query_result.is_err());
}
@@ -1235,7 +1279,7 @@ mod test {
{
let mut rows = stmt.query(NO_PARAMS).unwrap();
assert!(!db.is_busy());
let row = rows.next();
let row = rows.next().unwrap();
assert!(db.is_busy());
assert!(row.is_some());
}
@@ -1304,12 +1348,11 @@ mod test {
.prepare("SELECT interrupt() FROM (SELECT 1 UNION SELECT 2 UNION SELECT 3)")
.unwrap();
let result: Result<Vec<i32>> = stmt.query_map(NO_PARAMS, |r| r.get(0)).unwrap().collect();
let result: Result<Vec<i32>> = stmt.query(NO_PARAMS).unwrap().map(|r| r.get(0)).collect();
match result.unwrap_err() {
Error::SqliteFailure(err, _) => {
assert_eq!(err.code, ErrorCode::OperationInterrupted);
return;
}
err => {
panic!("Unexpected error {}", err);
@@ -1347,8 +1390,7 @@ mod test {
let mut query = db.prepare("SELECT i, x FROM foo").unwrap();
let mut rows = query.query(NO_PARAMS).unwrap();
while let Some(res) = rows.next() {
let row = res.unwrap();
while let Some(row) = rows.next().unwrap() {
let i = row.get_raw(0).as_i64().unwrap();
let expect = vals[i as usize];
let x = row.get_raw("x").as_str().unwrap();
@@ -1421,7 +1463,7 @@ mod test {
let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap();
let results: Result<Vec<String>> = query
.query_and_then(NO_PARAMS, |row| row.get_checked(1))
.query_and_then(NO_PARAMS, |row| row.get(1))
.unwrap()
.collect();
@@ -1442,17 +1484,17 @@ mod test {
let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap();
let bad_type: Result<Vec<f64>> = query
.query_and_then(NO_PARAMS, |row| row.get_checked(1))
.query_and_then(NO_PARAMS, |row| row.get(1))
.unwrap()
.collect();
match bad_type.unwrap_err() {
Error::InvalidColumnType(_, _) => (),
Error::InvalidColumnType(_, _, _) => (),
err => panic!("Unexpected error {}", err),
}
let bad_idx: Result<Vec<String>> = query
.query_and_then(NO_PARAMS, |row| row.get_checked(3))
.query_and_then(NO_PARAMS, |row| row.get(3))
.unwrap()
.collect();
@@ -1476,9 +1518,7 @@ mod test {
let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap();
let results: CustomResult<Vec<String>> = query
.query_and_then(NO_PARAMS, |row| {
row.get_checked(1).map_err(CustomError::Sqlite)
})
.query_and_then(NO_PARAMS, |row| row.get(1).map_err(CustomError::Sqlite))
.unwrap()
.collect();
@@ -1499,21 +1539,17 @@ mod test {
let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap();
let bad_type: CustomResult<Vec<f64>> = query
.query_and_then(NO_PARAMS, |row| {
row.get_checked(1).map_err(CustomError::Sqlite)
})
.query_and_then(NO_PARAMS, |row| row.get(1).map_err(CustomError::Sqlite))
.unwrap()
.collect();
match bad_type.unwrap_err() {
CustomError::Sqlite(Error::InvalidColumnType(_, _)) => (),
CustomError::Sqlite(Error::InvalidColumnType(_, _, _)) => (),
err => panic!("Unexpected error {}", err),
}
let bad_idx: CustomResult<Vec<String>> = query
.query_and_then(NO_PARAMS, |row| {
row.get_checked(3).map_err(CustomError::Sqlite)
})
.query_and_then(NO_PARAMS, |row| row.get(3).map_err(CustomError::Sqlite))
.unwrap()
.collect();
@@ -1544,7 +1580,7 @@ mod test {
let query = "SELECT x, y FROM foo ORDER BY x DESC";
let results: CustomResult<String> = db.query_row_and_then(query, NO_PARAMS, |row| {
row.get_checked(1).map_err(CustomError::Sqlite)
row.get(1).map_err(CustomError::Sqlite)
});
assert_eq!(results.unwrap(), "hello");
@@ -1561,16 +1597,16 @@ mod test {
let query = "SELECT x, y FROM foo ORDER BY x DESC";
let bad_type: CustomResult<f64> = db.query_row_and_then(query, NO_PARAMS, |row| {
row.get_checked(1).map_err(CustomError::Sqlite)
row.get(1).map_err(CustomError::Sqlite)
});
match bad_type.unwrap_err() {
CustomError::Sqlite(Error::InvalidColumnType(_, _)) => (),
CustomError::Sqlite(Error::InvalidColumnType(_, _, _)) => (),
err => panic!("Unexpected error {}", err),
}
let bad_idx: CustomResult<String> = db.query_row_and_then(query, NO_PARAMS, |row| {
row.get_checked(3).map_err(CustomError::Sqlite)
row.get(3).map_err(CustomError::Sqlite)
});
match bad_idx.unwrap_err() {
@@ -1597,7 +1633,20 @@ mod test {
db.execute_batch(sql).unwrap();
db.query_row("SELECT * FROM foo", params![], |r| {
assert_eq!(2, r.column_count())
assert_eq!(2, r.column_count());
Ok(())
})
.unwrap();
}
#[test]
fn test_dyn_box() {
let db = checked_memory_handle();
db.execute_batch("CREATE TABLE foo(x INTEGER);").unwrap();
let b: Box<dyn ToSql> = Box::new(5);
db.execute("INSERT INTO foo VALUES(?)", &[b]).unwrap();
db.query_row("SELECT x FROM foo", params![], |r| {
assert_eq!(5, r.get_unwrap::<_, i32>(0));
Ok(())
})
.unwrap();
}

View File

@@ -17,7 +17,7 @@ pub struct LoadExtensionGuard<'conn> {
conn: &'conn Connection,
}
impl<'conn> LoadExtensionGuard<'conn> {
impl LoadExtensionGuard<'_> {
/// Attempt to enable loading extensions. Loading extensions will be
/// disabled when this guard goes out of scope. Cannot be meaningfully
/// nested.
@@ -28,7 +28,7 @@ impl<'conn> LoadExtensionGuard<'conn> {
}
#[allow(unused_must_use)]
impl<'conn> Drop for LoadExtensionGuard<'conn> {
impl Drop for LoadExtensionGuard<'_> {
fn drop(&mut self) {
self.conn.load_extension_disable();
}

433
src/pragma.rs Normal file
View File

@@ -0,0 +1,433 @@
//! Pragma helpers
use std::ops::Deref;
use crate::error::Error;
use crate::ffi;
use crate::types::{ToSql, ToSqlOutput, ValueRef};
use crate::{Connection, DatabaseName, Result, Row, NO_PARAMS};
pub struct Sql {
buf: String,
}
impl Sql {
pub fn new() -> Sql {
Sql { buf: String::new() }
}
pub fn push_pragma(
&mut self,
schema_name: Option<DatabaseName<'_>>,
pragma_name: &str,
) -> Result<()> {
self.push_keyword("PRAGMA")?;
self.push_space();
if let Some(schema_name) = schema_name {
self.push_schema_name(schema_name);
self.push_dot();
}
self.push_keyword(pragma_name)
}
pub fn push_keyword(&mut self, keyword: &str) -> Result<()> {
if !keyword.is_empty() && is_identifier(keyword) {
self.buf.push_str(keyword);
Ok(())
} else {
Err(Error::SqliteFailure(
ffi::Error::new(ffi::SQLITE_MISUSE),
Some(format!("Invalid keyword \"{}\"", keyword)),
))
}
}
pub fn push_schema_name(&mut self, schema_name: DatabaseName<'_>) {
match schema_name {
DatabaseName::Main => self.buf.push_str("main"),
DatabaseName::Temp => self.buf.push_str("temp"),
DatabaseName::Attached(s) => self.push_identifier(s),
};
}
pub fn push_identifier(&mut self, s: &str) {
if is_identifier(s) {
self.buf.push_str(s);
} else {
self.wrap_and_escape(s, '"');
}
}
pub fn push_value(&mut self, value: &dyn ToSql) -> Result<()> {
let value = value.to_sql()?;
let value = match value {
ToSqlOutput::Borrowed(v) => v,
ToSqlOutput::Owned(ref v) => ValueRef::from(v),
#[cfg(feature = "blob")]
ToSqlOutput::ZeroBlob(_) => {
return Err(Error::SqliteFailure(
ffi::Error::new(ffi::SQLITE_MISUSE),
Some(format!("Unsupported value \"{:?}\"", value)),
));
}
#[cfg(feature = "array")]
ToSqlOutput::Array(_) => {
return Err(Error::SqliteFailure(
ffi::Error::new(ffi::SQLITE_MISUSE),
Some(format!("Unsupported value \"{:?}\"", value)),
));
}
};
match value {
ValueRef::Integer(i) => {
self.push_int(i);
}
ValueRef::Real(r) => {
self.push_real(r);
}
ValueRef::Text(s) => {
let s = std::str::from_utf8(s)?;
self.push_string_literal(s);
}
_ => {
return Err(Error::SqliteFailure(
ffi::Error::new(ffi::SQLITE_MISUSE),
Some(format!("Unsupported value \"{:?}\"", value)),
));
}
};
Ok(())
}
pub fn push_string_literal(&mut self, s: &str) {
self.wrap_and_escape(s, '\'');
}
pub fn push_int(&mut self, i: i64) {
self.buf.push_str(&i.to_string());
}
pub fn push_real(&mut self, f: f64) {
self.buf.push_str(&f.to_string());
}
pub fn push_space(&mut self) {
self.buf.push(' ');
}
pub fn push_dot(&mut self) {
self.buf.push('.');
}
pub fn push_equal_sign(&mut self) {
self.buf.push('=');
}
pub fn open_brace(&mut self) {
self.buf.push('(');
}
pub fn close_brace(&mut self) {
self.buf.push(')');
}
pub fn as_str(&self) -> &str {
&self.buf
}
fn wrap_and_escape(&mut self, s: &str, quote: char) {
self.buf.push(quote);
let chars = s.chars();
for ch in chars {
// escape `quote` by doubling it
if ch == quote {
self.buf.push(ch);
}
self.buf.push(ch)
}
self.buf.push(quote);
}
}
impl Deref for Sql {
type Target = str;
fn deref(&self) -> &str {
self.as_str()
}
}
impl Connection {
/// Query the current value of `pragma_name`.
///
/// Some pragmas will return multiple rows/values which cannot be retrieved
/// with this method.
///
/// Prefer [PRAGMA function](https://sqlite.org/pragma.html#pragfunc) introduced in SQLite 3.20:
/// `SELECT user_version FROM pragma_user_version;`
pub fn pragma_query_value<T, F>(
&self,
schema_name: Option<DatabaseName<'_>>,
pragma_name: &str,
f: F,
) -> Result<T>
where
F: FnOnce(&Row<'_>) -> Result<T>,
{
let mut query = Sql::new();
query.push_pragma(schema_name, pragma_name)?;
self.query_row(&query, NO_PARAMS, f)
}
/// Query the current rows/values of `pragma_name`.
///
/// Prefer [PRAGMA function](https://sqlite.org/pragma.html#pragfunc) introduced in SQLite 3.20:
/// `SELECT * FROM pragma_collation_list;`
pub fn pragma_query<F>(
&self,
schema_name: Option<DatabaseName<'_>>,
pragma_name: &str,
mut f: F,
) -> Result<()>
where
F: FnMut(&Row<'_>) -> Result<()>,
{
let mut query = Sql::new();
query.push_pragma(schema_name, pragma_name)?;
let mut stmt = self.prepare(&query)?;
let mut rows = stmt.query(NO_PARAMS)?;
while let Some(result_row) = rows.next()? {
let row = result_row;
f(&row)?;
}
Ok(())
}
/// Query the current value(s) of `pragma_name` associated to
/// `pragma_value`.
///
/// This method can be used with query-only pragmas which need an argument
/// (e.g. `table_info('one_tbl')`) or pragmas which returns value(s)
/// (e.g. `integrity_check`).
///
/// Prefer [PRAGMA function](https://sqlite.org/pragma.html#pragfunc) introduced in SQLite 3.20:
/// `SELECT * FROM pragma_table_info(?);`
pub fn pragma<F>(
&self,
schema_name: Option<DatabaseName<'_>>,
pragma_name: &str,
pragma_value: &dyn ToSql,
mut f: F,
) -> Result<()>
where
F: FnMut(&Row<'_>) -> Result<()>,
{
let mut sql = Sql::new();
sql.push_pragma(schema_name, pragma_name)?;
// The argument may be either in parentheses
// or it may be separated from the pragma name by an equal sign.
// The two syntaxes yield identical results.
sql.open_brace();
sql.push_value(pragma_value)?;
sql.close_brace();
let mut stmt = self.prepare(&sql)?;
let mut rows = stmt.query(NO_PARAMS)?;
while let Some(result_row) = rows.next()? {
let row = result_row;
f(&row)?;
}
Ok(())
}
/// Set a new value to `pragma_name`.
///
/// Some pragmas will return the updated value which cannot be retrieved
/// with this method.
pub fn pragma_update(
&self,
schema_name: Option<DatabaseName<'_>>,
pragma_name: &str,
pragma_value: &dyn ToSql,
) -> Result<()> {
let mut sql = Sql::new();
sql.push_pragma(schema_name, pragma_name)?;
// The argument may be either in parentheses
// or it may be separated from the pragma name by an equal sign.
// The two syntaxes yield identical results.
sql.push_equal_sign();
sql.push_value(pragma_value)?;
self.execute_batch(&sql)
}
/// Set a new value to `pragma_name` and return the updated value.
///
/// Only few pragmas automatically return the updated value.
pub fn pragma_update_and_check<F, T>(
&self,
schema_name: Option<DatabaseName<'_>>,
pragma_name: &str,
pragma_value: &dyn ToSql,
f: F,
) -> Result<T>
where
F: FnOnce(&Row<'_>) -> Result<T>,
{
let mut sql = Sql::new();
sql.push_pragma(schema_name, pragma_name)?;
// The argument may be either in parentheses
// or it may be separated from the pragma name by an equal sign.
// The two syntaxes yield identical results.
sql.push_equal_sign();
sql.push_value(pragma_value)?;
self.query_row(&sql, NO_PARAMS, f)
}
}
fn is_identifier(s: &str) -> bool {
let chars = s.char_indices();
for (i, ch) in chars {
if i == 0 {
if !is_identifier_start(ch) {
return false;
}
} else if !is_identifier_continue(ch) {
return false;
}
}
true
}
fn is_identifier_start(c: char) -> bool {
(c >= 'A' && c <= 'Z') || c == '_' || (c >= 'a' && c <= 'z') || c > '\x7F'
}
fn is_identifier_continue(c: char) -> bool {
c == '$'
|| (c >= '0' && c <= '9')
|| (c >= 'A' && c <= 'Z')
|| c == '_'
|| (c >= 'a' && c <= 'z')
|| c > '\x7F'
}
#[cfg(test)]
mod test {
use super::Sql;
use crate::pragma;
use crate::{Connection, DatabaseName};
#[test]
fn pragma_query_value() {
let db = Connection::open_in_memory().unwrap();
let user_version: i32 = db
.pragma_query_value(None, "user_version", |row| row.get(0))
.unwrap();
assert_eq!(0, user_version);
}
#[test]
#[cfg(feature = "bundled")]
fn pragma_func_query_value() {
use crate::NO_PARAMS;
let db = Connection::open_in_memory().unwrap();
let user_version: i32 = db
.query_row(
"SELECT user_version FROM pragma_user_version",
NO_PARAMS,
|row| row.get(0),
)
.unwrap();
assert_eq!(0, user_version);
}
#[test]
fn pragma_query_no_schema() {
let db = Connection::open_in_memory().unwrap();
let mut user_version = -1;
db.pragma_query(None, "user_version", |row| {
user_version = row.get(0)?;
Ok(())
})
.unwrap();
assert_eq!(0, user_version);
}
#[test]
fn pragma_query_with_schema() {
let db = Connection::open_in_memory().unwrap();
let mut user_version = -1;
db.pragma_query(Some(DatabaseName::Main), "user_version", |row| {
user_version = row.get(0)?;
Ok(())
})
.unwrap();
assert_eq!(0, user_version);
}
#[test]
fn pragma() {
let db = Connection::open_in_memory().unwrap();
let mut columns = Vec::new();
db.pragma(None, "table_info", &"sqlite_master", |row| {
let column: String = row.get(1)?;
columns.push(column);
Ok(())
})
.unwrap();
assert_eq!(5, columns.len());
}
#[test]
#[cfg(feature = "bundled")]
fn pragma_func() {
let db = Connection::open_in_memory().unwrap();
let mut table_info = db.prepare("SELECT * FROM pragma_table_info(?)").unwrap();
let mut columns = Vec::new();
let mut rows = table_info.query(&["sqlite_master"]).unwrap();
while let Some(row) = rows.next().unwrap() {
let row = row;
let column: String = row.get(1).unwrap();
columns.push(column);
}
assert_eq!(5, columns.len());
}
#[test]
fn pragma_update() {
let db = Connection::open_in_memory().unwrap();
db.pragma_update(None, "user_version", &1).unwrap();
}
#[test]
fn pragma_update_and_check() {
let db = Connection::open_in_memory().unwrap();
let journal_mode: String = db
.pragma_update_and_check(None, "journal_mode", &"OFF", |row| row.get(0))
.unwrap();
assert_eq!("off", &journal_mode);
}
#[test]
fn is_identifier() {
assert!(pragma::is_identifier("full"));
assert!(pragma::is_identifier("r2d2"));
assert!(!pragma::is_identifier("sp ce"));
assert!(!pragma::is_identifier("semi;colon"));
}
#[test]
fn double_quote() {
let mut sql = Sql::new();
sql.push_schema_name(DatabaseName::Attached(r#"schema";--"#));
assert_eq!(r#""schema"";--""#, sql.as_str());
}
#[test]
fn wrap_and_escape() {
let mut sql = Sql::new();
sql.push_string_literal("value'; --");
assert_eq!("'value''; --'", sql.as_str());
}
}

View File

@@ -26,8 +26,32 @@ impl RawStatement {
unsafe { ffi::sqlite3_column_type(self.0, idx as c_int) }
}
pub fn column_name(&self, idx: usize) -> &CStr {
unsafe { CStr::from_ptr(ffi::sqlite3_column_name(self.0, idx as c_int)) }
pub fn column_decltype(&self, idx: usize) -> Option<&CStr> {
unsafe {
let decltype = ffi::sqlite3_column_decltype(self.0, idx as c_int);
if decltype.is_null() {
None
} else {
Some(CStr::from_ptr(decltype))
}
}
}
pub fn column_name(&self, idx: usize) -> Option<&CStr> {
let idx = idx as c_int;
if idx < 0 || idx >= self.column_count() as c_int {
return None;
}
unsafe {
let ptr = ffi::sqlite3_column_name(self.0, idx);
// If ptr is null here, it's an OOM, so there's probably nothing
// meaningful we can do. Just assert instead of returning None.
assert!(
!ptr.is_null(),
"Null pointer from sqlite3_column_name: Out of memory?"
);
Some(CStr::from_ptr(ptr))
}
}
pub fn step(&self) -> c_int {
@@ -90,15 +114,14 @@ impl RawStatement {
unsafe { ffi::sqlite3_stmt_readonly(self.0) != 0 }
}
/// `CStr` must be freed
#[cfg(feature = "bundled")]
pub fn expanded_sql(&self) -> Option<&CStr> {
unsafe {
let ptr = ffi::sqlite3_expanded_sql(self.0);
if ptr.is_null() {
None
} else {
Some(CStr::from_ptr(ptr))
}
pub unsafe fn expanded_sql(&self) -> Option<&CStr> {
let ptr = ffi::sqlite3_expanded_sql(self.0);
if ptr.is_null() {
None
} else {
Some(CStr::from_ptr(ptr))
}
}

View File

@@ -1,4 +1,5 @@
use std::marker::PhantomData;
use fallible_iterator::FallibleIterator;
use fallible_streaming_iterator::FallibleStreamingIterator;
use std::{convert, result};
use super::{Error, Result, Statement};
@@ -6,7 +7,8 @@ use crate::types::{FromSql, FromSqlError, ValueRef};
/// An handle for the resulting rows of a query.
pub struct Rows<'stmt> {
stmt: Option<&'stmt Statement<'stmt>>,
pub(crate) stmt: Option<&'stmt Statement<'stmt>>,
row: Option<Row<'stmt>>,
}
impl<'stmt> Rows<'stmt> {
@@ -16,55 +18,73 @@ impl<'stmt> Rows<'stmt> {
}
}
/// Attempt to get the next row from the query. Returns `Some(Ok(Row))` if
/// there is another row, `Some(Err(...))` if there was an error
/// getting the next row, and `None` if all rows have been retrieved.
/// Attempt to get the next row from the query. Returns `Ok(Some(Row))` if
/// there is another row, `Err(...)` if there was an error
/// getting the next row, and `Ok(None)` if all rows have been retrieved.
///
/// ## Note
///
/// This interface is not compatible with Rust's `Iterator` trait, because
/// the lifetime of the returned row is tied to the lifetime of `self`.
/// This is a "streaming iterator". For a more natural interface,
/// This is a fallible "streaming iterator". For a more natural interface,
/// consider using `query_map` or `query_and_then` instead, which
/// return types that implement `Iterator`.
#[allow(clippy::should_implement_trait)] // cannot implement Iterator
pub fn next<'a>(&'a mut self) -> Option<Result<Row<'a, 'stmt>>> {
self.stmt.and_then(|stmt| match stmt.step() {
Ok(true) => Some(Ok(Row {
stmt,
phantom: PhantomData,
})),
Ok(false) => {
self.reset();
None
}
Err(err) => {
self.reset();
Some(Err(err))
}
})
pub fn next(&mut self) -> Result<Option<&Row<'stmt>>> {
self.advance()?;
Ok((*self).get())
}
pub fn map<F, B>(self, f: F) -> Map<'stmt, F>
where
F: FnMut(&Row<'_>) -> Result<B>,
{
Map { rows: self, f }
}
}
impl<'stmt> Rows<'stmt> {
pub(crate) fn new(stmt: &'stmt Statement<'stmt>) -> Rows<'stmt> {
Rows { stmt: Some(stmt) }
Rows {
stmt: Some(stmt),
row: None,
}
}
pub(crate) fn get_expected_row<'a>(&'a mut self) -> Result<Row<'a, 'stmt>> {
match self.next() {
Some(row) => row,
pub(crate) fn get_expected_row(&mut self) -> Result<&Row<'stmt>> {
match self.next()? {
Some(row) => Ok(row),
None => Err(Error::QueryReturnedNoRows),
}
}
}
impl<'stmt> Drop for Rows<'stmt> {
impl Drop for Rows<'_> {
fn drop(&mut self) {
self.reset();
}
}
pub struct Map<'stmt, F> {
rows: Rows<'stmt>,
f: F,
}
impl<F, B> FallibleIterator for Map<'_, F>
where
F: FnMut(&Row<'_>) -> Result<B>,
{
type Error = Error;
type Item = B;
fn next(&mut self) -> Result<Option<B>> {
match self.rows.next()? {
Some(v) => Ok(Some((self.f)(v)?)),
None => Ok(None),
}
}
}
/// An iterator over the mapped resulting rows of a query.
pub struct MappedRows<'stmt, F> {
rows: Rows<'stmt>,
@@ -73,16 +93,16 @@ pub struct MappedRows<'stmt, F> {
impl<'stmt, T, F> MappedRows<'stmt, F>
where
F: FnMut(&Row<'_, '_>) -> T,
F: FnMut(&Row<'_>) -> Result<T>,
{
pub(crate) fn new(rows: Rows<'stmt>, f: F) -> MappedRows<'stmt, F> {
MappedRows { rows, map: f }
}
}
impl<'conn, T, F> Iterator for MappedRows<'conn, F>
impl<T, F> Iterator for MappedRows<'_, F>
where
F: FnMut(&Row<'_, '_>) -> T,
F: FnMut(&Row<'_>) -> Result<T>,
{
type Item = Result<T>;
@@ -90,7 +110,8 @@ where
let map = &mut self.map;
self.rows
.next()
.map(|row_result| row_result.map(|row| (map)(&row)))
.transpose()
.map(|row_result| row_result.and_then(|row| (map)(&row)))
}
}
@@ -103,17 +124,17 @@ pub struct AndThenRows<'stmt, F> {
impl<'stmt, T, E, F> AndThenRows<'stmt, F>
where
F: FnMut(&Row<'_, '_>) -> result::Result<T, E>,
F: FnMut(&Row<'_>) -> result::Result<T, E>,
{
pub(crate) fn new(rows: Rows<'stmt>, f: F) -> AndThenRows<'stmt, F> {
AndThenRows { rows, map: f }
}
}
impl<'stmt, T, E, F> Iterator for AndThenRows<'stmt, F>
impl<T, E, F> Iterator for AndThenRows<'_, F>
where
E: convert::From<Error>,
F: FnMut(&Row<'_, '_>) -> result::Result<T, E>,
F: FnMut(&Row<'_>) -> result::Result<T, E>,
{
type Item = result::Result<T, E>;
@@ -121,31 +142,65 @@ where
let map = &mut self.map;
self.rows
.next()
.transpose()
.map(|row_result| row_result.map_err(E::from).and_then(|row| (map)(&row)))
}
}
/// A single result row of a query.
pub struct Row<'a, 'stmt: 'a> {
stmt: &'stmt Statement<'stmt>,
phantom: PhantomData<&'a ()>,
impl<'stmt> FallibleStreamingIterator for Rows<'stmt> {
type Error = Error;
type Item = Row<'stmt>;
fn advance(&mut self) -> Result<()> {
match self.stmt {
Some(ref stmt) => match stmt.step() {
Ok(true) => {
self.row = Some(Row { stmt });
Ok(())
}
Ok(false) => {
self.reset();
self.row = None;
Ok(())
}
Err(e) => {
self.reset();
self.row = None;
Err(e)
}
},
None => {
self.row = None;
Ok(())
}
}
}
fn get(&self) -> Option<&Row<'stmt>> {
self.row.as_ref()
}
}
impl<'a, 'stmt> Row<'a, 'stmt> {
/// A single result row of a query.
pub struct Row<'stmt> {
pub(crate) stmt: &'stmt Statement<'stmt>,
}
impl<'stmt> Row<'stmt> {
/// Get the value of a particular column of the result row.
///
/// ## Failure
///
/// Panics if calling `row.get_checked(idx)` would return an error,
/// Panics if calling `row.get(idx)` would return an error,
/// including:
///
/// * If the underlying SQLite column type is not a valid type as a
/// source for `T`
/// * If the underlying SQLite integral value is outside the range
/// representable by `T`
/// * If `idx` is outside the range of columns in the returned query
pub fn get<I: RowIndex, T: FromSql>(&self, idx: I) -> T {
self.get_checked(idx).unwrap()
/// * If the underlying SQLite column type is not a valid type as a source
/// for `T`
/// * If the underlying SQLite integral value is outside the range
/// representable by `T`
/// * If `idx` is outside the range of columns in the returned query
pub fn get_unwrap<I: RowIndex, T: FromSql>(&self, idx: I) -> T {
self.get(idx).unwrap()
}
/// Get the value of a particular column of the result row.
@@ -164,17 +219,25 @@ impl<'a, 'stmt> Row<'a, 'stmt> {
/// If the result type is i128 (which requires the `i128_blob` feature to be
/// enabled), and the underlying SQLite column is a blob whose size is not
/// 16 bytes, `Error::InvalidColumnType` will also be returned.
pub fn get_checked<I: RowIndex, T: FromSql>(&self, idx: I) -> Result<T> {
pub fn get<I: RowIndex, T: FromSql>(&self, idx: I) -> Result<T> {
let idx = idx.idx(self.stmt)?;
let value = self.stmt.value_ref(idx);
FromSql::column_result(value).map_err(|err| match err {
FromSqlError::InvalidType => Error::InvalidColumnType(idx, value.data_type()),
FromSqlError::InvalidType => {
Error::InvalidColumnType(idx, self.stmt.column_name(idx).into(), value.data_type())
}
FromSqlError::OutOfRange(i) => Error::IntegralValueOutOfRange(idx, i),
FromSqlError::Other(err) => {
Error::FromSqlConversionFailure(idx as usize, value.data_type(), err)
}
#[cfg(feature = "i128_blob")]
FromSqlError::InvalidI128Size(_) => Error::InvalidColumnType(idx, value.data_type()),
FromSqlError::InvalidI128Size(_) => {
Error::InvalidColumnType(idx, self.stmt.column_name(idx).into(), value.data_type())
}
#[cfg(feature = "uuid")]
FromSqlError::InvalidUuidSize(_) => {
Error::InvalidColumnType(idx, self.stmt.column_name(idx).into(), value.data_type())
}
})
}
@@ -184,7 +247,7 @@ impl<'a, 'stmt> Row<'a, 'stmt> {
/// This `ValueRef` is valid only as long as this Row, which is enforced by
/// it's lifetime. This means that while this method is completely safe,
/// it can be somewhat difficult to use, and most callers will be better
/// served by `get` or `get_checked`.
/// served by `get` or `get`.
///
/// ## Failure
///
@@ -193,7 +256,7 @@ impl<'a, 'stmt> Row<'a, 'stmt> {
///
/// Returns an `Error::InvalidColumnName` if `idx` is not a valid column
/// name for this row.
pub fn get_raw_checked<I: RowIndex>(&self, idx: I) -> Result<ValueRef<'a>> {
pub fn get_raw_checked<I: RowIndex>(&self, idx: I) -> Result<ValueRef<'_>> {
let idx = idx.idx(self.stmt)?;
// Narrowing from `ValueRef<'stmt>` (which `self.stmt.value_ref(idx)`
// returns) to `ValueRef<'a>` is needed because it's only valid until
@@ -208,23 +271,18 @@ impl<'a, 'stmt> Row<'a, 'stmt> {
/// This `ValueRef` is valid only as long as this Row, which is enforced by
/// it's lifetime. This means that while this method is completely safe,
/// it can be difficult to use, and most callers will be better served by
/// `get` or `get_checked`.
/// `get` or `get`.
///
/// ## Failure
///
/// Panics if calling `row.get_raw_checked(idx)` would return an error,
/// including:
///
/// * If `idx` is outside the range of columns in the returned query.
/// * If `idx` is not a valid column name for this row.
pub fn get_raw<I: RowIndex>(&self, idx: I) -> ValueRef<'a> {
/// * If `idx` is outside the range of columns in the returned query.
/// * If `idx` is not a valid column name for this row.
pub fn get_raw<I: RowIndex>(&self, idx: I) -> ValueRef<'_> {
self.get_raw_checked(idx).unwrap()
}
/// Return the number of columns in the current row.
pub fn column_count(&self) -> usize {
self.stmt.column_count()
}
}
/// A trait implemented by types that can index into columns of a row.
@@ -245,7 +303,7 @@ impl RowIndex for usize {
}
}
impl<'a> RowIndex for &'a str {
impl RowIndex for &'_ str {
#[inline]
fn idx(&self, stmt: &Statement<'_>) -> Result<usize> {
stmt.column_index(*self)

View File

@@ -4,7 +4,7 @@
use std::ffi::CStr;
use std::io::{Read, Write};
use std::marker::PhantomData;
use std::mem;
use std::mem::MaybeUninit;
use std::os::raw::{c_char, c_int, c_uchar, c_void};
use std::panic::{catch_unwind, RefUnwindSafe};
use std::ptr;
@@ -28,20 +28,24 @@ pub struct Session<'conn> {
filter: Option<Box<dyn Fn(&str) -> bool>>,
}
impl<'conn> Session<'conn> {
impl Session<'_> {
/// Create a new session object
pub fn new(db: &'conn Connection) -> Result<Session<'conn>> {
pub fn new<'conn>(db: &'conn Connection) -> Result<Session<'conn>> {
Session::new_with_name(db, DatabaseName::Main)
}
/// Create a new session object
pub fn new_with_name(db: &'conn Connection, name: DatabaseName<'_>) -> Result<Session<'conn>> {
pub fn new_with_name<'conn>(
db: &'conn Connection,
name: DatabaseName<'_>,
) -> Result<Session<'conn>> {
let name = name.to_cstring()?;
let db = db.db.borrow_mut().db;
let mut s: *mut ffi::sqlite3_session = unsafe { mem::uninitialized() };
check!(unsafe { ffi::sqlite3session_create(db, name.as_ptr(), &mut s) });
let mut s = MaybeUninit::uninit();
check!(unsafe { ffi::sqlite3session_create(db, name.as_ptr(), s.as_mut_ptr()) });
let s: *mut ffi::sqlite3_session = unsafe { s.assume_init() };
Ok(Session {
phantom: PhantomData,
@@ -62,7 +66,6 @@ impl<'conn> Session<'conn> {
where
F: Fn(&str) -> bool + RefUnwindSafe,
{
use std::ffi::CStr;
use std::str;
let boxed_filter: *mut F = p_arg as *mut F;
@@ -110,8 +113,9 @@ impl<'conn> Session<'conn> {
/// Generate a Changeset
pub fn changeset(&mut self) -> Result<Changeset> {
let mut n = 0;
let mut cs: *mut c_void = unsafe { mem::uninitialized() };
check!(unsafe { ffi::sqlite3session_changeset(self.s, &mut n, &mut cs) });
let mut cs = MaybeUninit::uninit();
check!(unsafe { ffi::sqlite3session_changeset(self.s, &mut n, cs.as_mut_ptr()) });
let cs: *mut c_void = unsafe { cs.assume_init() };
Ok(Changeset { cs, n })
}
@@ -131,8 +135,9 @@ impl<'conn> Session<'conn> {
/// Generate a Patchset
pub fn patchset(&mut self) -> Result<Changeset> {
let mut n = 0;
let mut ps: *mut c_void = unsafe { mem::uninitialized() };
check!(unsafe { ffi::sqlite3session_patchset(self.s, &mut n, &mut ps) });
let mut ps = MaybeUninit::uninit();
check!(unsafe { ffi::sqlite3session_patchset(self.s, &mut n, ps.as_mut_ptr()) });
let ps: *mut c_void = unsafe { ps.assume_init() };
// TODO Validate: same struct
Ok(Changeset { cs: ps, n })
}
@@ -155,9 +160,10 @@ impl<'conn> Session<'conn> {
let from = from.to_cstring()?;
let table = str_to_cstring(table)?.as_ptr();
unsafe {
let mut errmsg: *mut c_char = mem::uninitialized();
let r = ffi::sqlite3session_diff(self.s, from.as_ptr(), table, &mut errmsg);
let mut errmsg = MaybeUninit::uninit();
let r = ffi::sqlite3session_diff(self.s, from.as_ptr(), table, errmsg.as_mut_ptr());
if r != ffi::SQLITE_OK {
let errmsg: *mut c_char = errmsg.assume_init();
let message = errmsg_to_string(&*errmsg);
ffi::sqlite3_free(errmsg as *mut ::std::os::raw::c_void);
return Err(error_from_sqlite_code(r, Some(message)));
@@ -196,7 +202,7 @@ impl<'conn> Session<'conn> {
}
}
impl<'conn> Drop for Session<'conn> {
impl Drop for Session<'_> {
fn drop(&mut self) {
if self.filter.is_some() {
self.table_filter(None::<fn(&str) -> bool>);
@@ -252,15 +258,17 @@ impl Changeset {
/// Invert a changeset
pub fn invert(&self) -> Result<Changeset> {
let mut n = 0;
let mut cs: *mut c_void = unsafe { mem::uninitialized() };
check!(unsafe { ffi::sqlite3changeset_invert(self.n, self.cs, &mut n, &mut cs) });
let mut cs = MaybeUninit::uninit();
check!(unsafe { ffi::sqlite3changeset_invert(self.n, self.cs, &mut n, cs.as_mut_ptr()) });
let cs: *mut c_void = unsafe { cs.assume_init() };
Ok(Changeset { cs, n })
}
/// Create an iterator to traverse a changeset
pub fn iter(&self) -> Result<ChangesetIter<'_>> {
let mut it: *mut ffi::sqlite3_changeset_iter = unsafe { mem::uninitialized() };
check!(unsafe { ffi::sqlite3changeset_start(&mut it, self.n, self.cs) });
let mut it = MaybeUninit::uninit();
check!(unsafe { ffi::sqlite3changeset_start(it.as_mut_ptr(), self.n, self.cs) });
let it: *mut ffi::sqlite3_changeset_iter = unsafe { it.assume_init() };
Ok(ChangesetIter {
phantom: PhantomData,
it,
@@ -271,8 +279,11 @@ impl Changeset {
/// Concatenate two changeset objects
pub fn concat(a: &Changeset, b: &Changeset) -> Result<Changeset> {
let mut n = 0;
let mut cs: *mut c_void = unsafe { mem::uninitialized() };
check!(unsafe { ffi::sqlite3changeset_concat(a.n, a.cs, b.n, b.cs, &mut n, &mut cs) });
let mut cs = MaybeUninit::uninit();
check!(unsafe {
ffi::sqlite3changeset_concat(a.n, a.cs, b.n, b.cs, &mut n, cs.as_mut_ptr())
});
let cs: *mut c_void = unsafe { cs.assume_init() };
Ok(Changeset { cs, n })
}
}
@@ -292,18 +303,19 @@ pub struct ChangesetIter<'changeset> {
item: Option<ChangesetItem>,
}
impl<'changeset> ChangesetIter<'changeset> {
impl ChangesetIter<'_> {
/// Create an iterator on `input`
pub fn start_strm<'input>(input: &'input mut dyn Read) -> Result<ChangesetIter<'input>> {
let input_ref = &input;
let mut it: *mut ffi::sqlite3_changeset_iter = unsafe { mem::uninitialized() };
let mut it = MaybeUninit::uninit();
check!(unsafe {
ffi::sqlite3changeset_start_strm(
&mut it,
it.as_mut_ptr(),
Some(x_input),
input_ref as *const &mut dyn Read as *mut c_void,
)
});
let it: *mut ffi::sqlite3_changeset_iter = unsafe { it.assume_init() };
Ok(ChangesetIter {
phantom: PhantomData,
it,
@@ -312,7 +324,7 @@ impl<'changeset> ChangesetIter<'changeset> {
}
}
impl<'changeset> FallibleStreamingIterator for ChangesetIter<'changeset> {
impl FallibleStreamingIterator for ChangesetIter<'_> {
type Error = crate::error::Error;
type Item = ChangesetItem;
@@ -343,7 +355,7 @@ pub struct Operation<'item> {
indirect: bool,
}
impl<'item> Operation<'item> {
impl Operation<'_> {
pub fn table_name(&self) -> &str {
self.table_name
}
@@ -361,7 +373,7 @@ impl<'item> Operation<'item> {
}
}
impl<'changeset> Drop for ChangesetIter<'changeset> {
impl Drop for ChangesetIter<'_> {
fn drop(&mut self) {
unsafe {
ffi::sqlite3changeset_finalize(self.it);
@@ -383,12 +395,13 @@ impl ChangesetItem {
/// `SQLITE_CHANGESET_CONFLICT` conflict handler callback.
pub fn conflict(&self, col: usize) -> Result<ValueRef<'_>> {
unsafe {
let mut p_value: *mut ffi::sqlite3_value = mem::uninitialized();
let mut p_value = MaybeUninit::uninit();
check!(ffi::sqlite3changeset_conflict(
self.it,
col as i32,
&mut p_value
p_value.as_mut_ptr()
));
let p_value: *mut ffi::sqlite3_value = p_value.assume_init();
Ok(ValueRef::from_value(p_value))
}
}
@@ -411,8 +424,13 @@ impl ChangesetItem {
/// `SQLITE_INSERT`.
pub fn new_value(&self, col: usize) -> Result<ValueRef<'_>> {
unsafe {
let mut p_value: *mut ffi::sqlite3_value = mem::uninitialized();
check!(ffi::sqlite3changeset_new(self.it, col as i32, &mut p_value));
let mut p_value = MaybeUninit::uninit();
check!(ffi::sqlite3changeset_new(
self.it,
col as i32,
p_value.as_mut_ptr()
));
let p_value: *mut ffi::sqlite3_value = p_value.assume_init();
Ok(ValueRef::from_value(p_value))
}
}
@@ -423,8 +441,13 @@ impl ChangesetItem {
/// `SQLITE_UPDATE`.
pub fn old_value(&self, col: usize) -> Result<ValueRef<'_>> {
unsafe {
let mut p_value: *mut ffi::sqlite3_value = mem::uninitialized();
check!(ffi::sqlite3changeset_old(self.it, col as i32, &mut p_value));
let mut p_value = MaybeUninit::uninit();
check!(ffi::sqlite3changeset_old(
self.it,
col as i32,
p_value.as_mut_ptr()
));
let p_value: *mut ffi::sqlite3_value = p_value.assume_init();
Ok(ValueRef::from_value(p_value))
}
}
@@ -435,14 +458,15 @@ impl ChangesetItem {
let mut code = 0;
let mut indirect = 0;
let tab = unsafe {
let mut pz_tab: *const c_char = mem::uninitialized();
let mut pz_tab = MaybeUninit::uninit();
check!(ffi::sqlite3changeset_op(
self.it,
&mut pz_tab,
pz_tab.as_mut_ptr(),
&mut number_of_columns,
&mut code,
&mut indirect
));
let pz_tab: *const c_char = pz_tab.assume_init();
CStr::from_ptr(pz_tab)
};
let table_name = tab.to_str()?;
@@ -458,12 +482,13 @@ impl ChangesetItem {
pub fn pk(&self) -> Result<&[u8]> {
let mut number_of_columns = 0;
unsafe {
let mut pks: *mut c_uchar = mem::uninitialized();
let mut pks = MaybeUninit::uninit();
check!(ffi::sqlite3changeset_pk(
self.it,
&mut pks,
pks.as_mut_ptr(),
&mut number_of_columns
));
let pks: *mut c_uchar = pks.assume_init();
Ok(from_raw_parts(pks, number_of_columns as usize))
}
}
@@ -477,8 +502,9 @@ pub struct Changegroup {
impl Changegroup {
pub fn new() -> Result<Self> {
let mut cg: *mut ffi::sqlite3_changegroup = unsafe { mem::uninitialized() };
check!(unsafe { ffi::sqlite3changegroup_new(&mut cg) });
let mut cg = MaybeUninit::uninit();
check!(unsafe { ffi::sqlite3changegroup_new(cg.as_mut_ptr()) });
let cg: *mut ffi::sqlite3_changegroup = unsafe { cg.assume_init() };
Ok(Changegroup { cg })
}
@@ -504,8 +530,9 @@ impl Changegroup {
/// Obtain a composite Changeset
pub fn output(&mut self) -> Result<Changeset> {
let mut n = 0;
let mut output: *mut c_void = unsafe { mem::uninitialized() };
check!(unsafe { ffi::sqlite3changegroup_output(self.cg, &mut n, &mut output) });
let mut output = MaybeUninit::uninit();
check!(unsafe { ffi::sqlite3changegroup_output(self.cg, &mut n, output.as_mut_ptr()) });
let output: *mut c_void = unsafe { output.assume_init() };
Ok(Changeset { cs: output, n })
}
@@ -645,7 +672,6 @@ where
F: Fn(&str) -> bool + Send + RefUnwindSafe + 'static,
C: Fn(ConflictType, ChangesetItem) -> ConflictAction + Send + RefUnwindSafe + 'static,
{
use std::ffi::CStr;
use std::str;
let tuple: *mut (Option<F>, C) = p_ctx as *mut (Option<F>, C);
@@ -796,7 +822,7 @@ mod test {
db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);")
.unwrap();
lazy_static! {
lazy_static::lazy_static! {
static ref CALLED: AtomicBool = AtomicBool::new(false);
}
db.apply(

View File

@@ -7,7 +7,7 @@ use std::slice::from_raw_parts;
use std::{convert, fmt, mem, ptr, result, str};
use super::ffi;
use super::str_to_cstring;
use super::{len_as_c_int, str_for_sqlite, str_to_cstring};
use super::{
AndThenRows, Connection, Error, MappedRows, RawStatement, Result, Row, Rows, ValueRef,
};
@@ -18,48 +18,10 @@ use crate::vtab::array::{free_array, ARRAY_TYPE};
/// A prepared statement.
pub struct Statement<'conn> {
conn: &'conn Connection,
stmt: RawStatement,
pub(crate) stmt: RawStatement,
}
impl<'conn> Statement<'conn> {
/// Get all the column names in the result set of the prepared statement.
pub fn column_names(&self) -> Vec<&str> {
let n = self.column_count();
let mut cols = Vec::with_capacity(n as usize);
for i in 0..n {
let slice = self.stmt.column_name(i);
let s = str::from_utf8(slice.to_bytes()).unwrap();
cols.push(s);
}
cols
}
/// Return the number of columns in the result set returned by the prepared
/// statement.
pub fn column_count(&self) -> usize {
self.stmt.column_count()
}
/// Returns the column index in the result set for a given column name.
///
/// If there is no AS clause then the name of the column is unspecified and
/// may change from one release of SQLite to the next.
///
/// # Failure
///
/// Will return an `Error::InvalidColumnName` when there is no column with
/// the specified `name`.
pub fn column_index(&self, name: &str) -> Result<usize> {
let bytes = name.as_bytes();
let n = self.column_count();
for i in 0..n {
if bytes.eq_ignore_ascii_case(self.stmt.column_name(i).to_bytes()) {
return Ok(i);
}
}
Err(Error::InvalidColumnName(String::from(name)))
}
impl Statement<'_> {
/// Execute the prepared statement.
///
/// On success, returns the number of rows that were changed or inserted or
@@ -174,9 +136,8 @@ impl<'conn> Statement<'conn> {
/// let mut rows = stmt.query(NO_PARAMS)?;
///
/// let mut names = Vec::new();
/// while let Some(result_row) = rows.next() {
/// let row = result_row?;
/// names.push(row.get(0));
/// while let Some(row) = rows.next()? {
/// names.push(row.get(0)?);
/// }
///
/// Ok(names)
@@ -209,7 +170,7 @@ impl<'conn> Statement<'conn> {
/// fn query(conn: &Connection) -> Result<()> {
/// let mut stmt = conn.prepare("SELECT * FROM test where name = :name")?;
/// let mut rows = stmt.query_named(&[(":name", &"one")])?;
/// while let Some(row) = rows.next() {
/// while let Some(row) = rows.next()? {
/// // ...
/// }
/// Ok(())
@@ -224,7 +185,7 @@ impl<'conn> Statement<'conn> {
/// fn query(conn: &Connection) -> Result<()> {
/// let mut stmt = conn.prepare("SELECT * FROM test where name = :name")?;
/// let mut rows = stmt.query_named(named_params!{ ":name": "one" })?;
/// while let Some(row) = rows.next() {
/// while let Some(row) = rows.next()? {
/// // ...
/// }
/// Ok(())
@@ -234,7 +195,7 @@ impl<'conn> Statement<'conn> {
/// # Failure
///
/// Will return `Err` if binding parameters fails.
pub fn query_named<'a>(&'a mut self, params: &[(&str, &dyn ToSql)]) -> Result<Rows<'a>> {
pub fn query_named(&mut self, params: &[(&str, &dyn ToSql)]) -> Result<Rows<'_>> {
self.check_readonly()?;
self.bind_parameters_named(params)?;
Ok(Rows::new(self))
@@ -267,7 +228,7 @@ impl<'conn> Statement<'conn> {
where
P: IntoIterator,
P::Item: ToSql,
F: FnMut(&Row<'_, '_>) -> T,
F: FnMut(&Row<'_>) -> Result<T>,
{
let rows = self.query(params)?;
Ok(MappedRows::new(rows, f))
@@ -300,13 +261,13 @@ impl<'conn> Statement<'conn> {
/// ## Failure
///
/// Will return `Err` if binding parameters fails.
pub fn query_map_named<'a, T, F>(
&'a mut self,
pub fn query_map_named<T, F>(
&mut self,
params: &[(&str, &dyn ToSql)],
f: F,
) -> Result<MappedRows<'a, F>>
) -> Result<MappedRows<'_, F>>
where
F: FnMut(&Row<'_, '_>) -> T,
F: FnMut(&Row<'_>) -> Result<T>,
{
let rows = self.query_named(params)?;
Ok(MappedRows::new(rows, f))
@@ -324,7 +285,7 @@ impl<'conn> Statement<'conn> {
P: IntoIterator,
P::Item: ToSql,
E: convert::From<Error>,
F: FnMut(&Row<'_, '_>) -> result::Result<T, E>,
F: FnMut(&Row<'_>) -> result::Result<T, E>,
{
let rows = self.query(params)?;
Ok(AndThenRows::new(rows, f))
@@ -354,7 +315,7 @@ impl<'conn> Statement<'conn> {
/// fn get_names(conn: &Connection) -> Result<Vec<Person>> {
/// let mut stmt = conn.prepare("SELECT name FROM people WHERE id = :id")?;
/// let rows =
/// stmt.query_and_then_named(&[(":id", &"one")], |row| name_to_person(row.get(0)))?;
/// stmt.query_and_then_named(&[(":id", &"one")], |row| name_to_person(row.get(0)?))?;
///
/// let mut persons = Vec::new();
/// for person_result in rows {
@@ -368,14 +329,14 @@ impl<'conn> Statement<'conn> {
/// ## Failure
///
/// Will return `Err` if binding parameters fails.
pub fn query_and_then_named<'a, T, E, F>(
&'a mut self,
pub fn query_and_then_named<T, E, F>(
&mut self,
params: &[(&str, &dyn ToSql)],
f: F,
) -> Result<AndThenRows<'a, F>>
) -> Result<AndThenRows<'_, F>>
where
E: convert::From<Error>,
F: FnMut(&Row<'_, '_>) -> result::Result<T, E>,
F: FnMut(&Row<'_>) -> result::Result<T, E>,
{
let rows = self.query_named(params)?;
Ok(AndThenRows::new(rows, f))
@@ -389,7 +350,7 @@ impl<'conn> Statement<'conn> {
P::Item: ToSql,
{
let mut rows = self.query(params)?;
let exists = rows.next().is_some();
let exists = rows.next()?.is_some();
Ok(exists)
}
@@ -410,11 +371,34 @@ impl<'conn> Statement<'conn> {
where
P: IntoIterator,
P::Item: ToSql,
F: FnOnce(&Row<'_, '_>) -> T,
F: FnOnce(&Row<'_>) -> Result<T>,
{
let mut rows = self.query(params)?;
rows.get_expected_row().map(|r| f(&r))
rows.get_expected_row().and_then(|r| f(&r))
}
/// Convenience method to execute a query with named parameter(s) that is
/// expected to return a single row.
///
/// If the query returns more than one row, all rows except the first are
/// ignored.
///
/// Returns `Err(QueryReturnedNoRows)` if no results are returned. If the
/// query truly is optional, you can call `.optional()` on the result of
/// this to get a `Result<Option<T>>`.
///
/// # Failure
///
/// Will return `Err` if `sql` cannot be converted to a C-compatible string
/// or if the underlying SQLite call fails.
pub fn query_row_named<T, F>(&mut self, params: &[(&str, &dyn ToSql)], f: F) -> Result<T>
where
F: FnOnce(&Row<'_>) -> Result<T>,
{
let mut rows = self.query_named(params)?;
rows.get_expected_row().and_then(|r| f(&r))
}
/// Consumes the statement.
@@ -506,37 +490,19 @@ impl<'conn> Statement<'conn> {
ValueRef::Integer(i) => unsafe { ffi::sqlite3_bind_int64(ptr, col as c_int, i) },
ValueRef::Real(r) => unsafe { ffi::sqlite3_bind_double(ptr, col as c_int, r) },
ValueRef::Text(s) => unsafe {
let length = s.len();
if length > ::std::i32::MAX as usize {
ffi::SQLITE_TOOBIG
} else {
let c_str = str_to_cstring(s)?;
let destructor = if length > 0 {
ffi::SQLITE_TRANSIENT()
} else {
ffi::SQLITE_STATIC()
};
ffi::sqlite3_bind_text(
ptr,
col as c_int,
c_str.as_ptr(),
length as c_int,
destructor,
)
}
let (c_str, len, destructor) = str_for_sqlite(s)?;
ffi::sqlite3_bind_text(ptr, col as c_int, c_str, len, destructor)
},
ValueRef::Blob(b) => unsafe {
let length = b.len();
if length > ::std::i32::MAX as usize {
ffi::SQLITE_TOOBIG
} else if length == 0 {
let length = len_as_c_int(b.len())?;
if length == 0 {
ffi::sqlite3_bind_zeroblob(ptr, col as c_int, 0)
} else {
ffi::sqlite3_bind_blob(
ptr,
col as c_int,
b.as_ptr() as *const c_void,
length as c_int,
length,
ffi::SQLITE_TRANSIENT(),
)
}
@@ -584,11 +550,16 @@ impl<'conn> Statement<'conn> {
/// Returns a string containing the SQL text of prepared statement with
/// bound parameters expanded.
#[cfg(feature = "bundled")]
pub fn expanded_sql(&self) -> Option<&str> {
pub fn expanded_sql(&self) -> Option<String> {
unsafe {
self.stmt
.expanded_sql()
.map(|s| str::from_utf8_unchecked(s.to_bytes()))
match self.stmt.expanded_sql() {
Some(s) => {
let sql = str::from_utf8_unchecked(s.to_bytes()).to_owned();
ffi::sqlite3_free(s.as_ptr() as *mut _);
Some(sql)
}
_ => None,
}
}
}
@@ -612,7 +583,7 @@ impl<'conn> Statement<'conn> {
}
}
impl<'conn> Into<RawStatement> for Statement<'conn> {
impl Into<RawStatement> for Statement<'_> {
fn into(mut self) -> RawStatement {
let mut stmt = RawStatement::new(ptr::null_mut(), ptr::null());
mem::swap(&mut stmt, &mut self.stmt);
@@ -620,7 +591,7 @@ impl<'conn> Into<RawStatement> for Statement<'conn> {
}
}
impl<'conn> fmt::Debug for Statement<'conn> {
impl fmt::Debug for Statement<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let sql = str::from_utf8(self.stmt.sql().to_bytes());
f.debug_struct("Statement")
@@ -631,14 +602,14 @@ impl<'conn> fmt::Debug for Statement<'conn> {
}
}
impl<'conn> Drop for Statement<'conn> {
impl Drop for Statement<'_> {
#[allow(unused_must_use)]
fn drop(&mut self) {
self.finalize_();
}
}
impl<'conn> Statement<'conn> {
impl Statement<'_> {
pub(crate) fn new(conn: &Connection, stmt: RawStatement) -> Statement<'_> {
Statement { conn, stmt }
}
@@ -664,10 +635,7 @@ impl<'conn> Statement<'conn> {
CStr::from_ptr(text as *const c_char)
};
// sqlite3_column_text returns UTF8 data, so our unwrap here should be fine.
let s = s
.to_str()
.expect("sqlite3_column_text returned invalid UTF-8");
let s = s.to_bytes();
ValueRef::Text(s)
}
ffi::SQLITE_BLOB => {
@@ -781,14 +749,13 @@ mod test {
.unwrap();
stmt.execute_named(&[(":name", &"one")]).unwrap();
let mut stmt = db
.prepare("SELECT COUNT(*) FROM test WHERE name = :name")
.unwrap();
assert_eq!(
1i32,
db.query_row_named::<i32, _>(
"SELECT COUNT(*) FROM test WHERE name = :name",
&[(":name", &"one")],
|r| r.get(0)
)
.unwrap()
stmt.query_row_named::<i32, _>(&[(":name", &"one")], |r| r.get(0))
.unwrap()
);
}
@@ -806,8 +773,8 @@ mod test {
.unwrap();
let mut rows = stmt.query_named(&[(":name", &"one")]).unwrap();
let id: i32 = rows.next().unwrap().unwrap().get(0);
assert_eq!(1, id);
let id: Result<i32> = rows.next().unwrap().unwrap().get(0);
assert_eq!(Ok(1), id);
}
#[test]
@@ -824,8 +791,8 @@ mod test {
.unwrap();
let mut rows = stmt
.query_map_named(&[(":name", &"one")], |row| {
let id: i32 = row.get(0);
2 * id
let id: Result<i32> = row.get(0);
id.map(|i| 2 * i)
})
.unwrap();
@@ -848,7 +815,7 @@ mod test {
.unwrap();
let mut rows = stmt
.query_and_then_named(&[(":name", &"one")], |row| {
let id: i32 = row.get(0);
let id: i32 = row.get(0)?;
if id == 1 {
Ok(id)
} else {
@@ -1021,7 +988,7 @@ mod test {
let db = Connection::open_in_memory().unwrap();
let stmt = db.prepare("SELECT ?").unwrap();
stmt.bind_parameter(&1, 1).unwrap();
assert_eq!(Some("SELECT 1"), stmt.expanded_sql());
assert_eq!(Some("SELECT 1".to_owned()), stmt.expanded_sql());
}
#[test]

View File

@@ -122,6 +122,7 @@ impl Connection {
#[cfg(test)]
mod test {
use lazy_static::lazy_static;
use std::sync::Mutex;
use std::time::Duration;
@@ -140,13 +141,13 @@ mod test {
let mut db = Connection::open_in_memory().unwrap();
db.trace(Some(tracer));
{
let _ = db.query_row("SELECT ?", &[1i32], |_| {});
let _ = db.query_row("SELECT ?", &["hello"], |_| {});
let _ = db.query_row("SELECT ?", &[1i32], |_| Ok(()));
let _ = db.query_row("SELECT ?", &["hello"], |_| Ok(()));
}
db.trace(None);
{
let _ = db.query_row("SELECT ?", &[2i32], |_| {});
let _ = db.query_row("SELECT ?", &["goodbye"], |_| {});
let _ = db.query_row("SELECT ?", &[2i32], |_| Ok(()));
let _ = db.query_row("SELECT ?", &["goodbye"], |_| Ok(()));
}
let traced_stmts = TRACED_STMTS.lock().unwrap();

View File

@@ -87,7 +87,7 @@ pub struct Savepoint<'conn> {
committed: bool,
}
impl<'conn> Transaction<'conn> {
impl Transaction<'_> {
/// Begin a new transaction. Cannot be nested; see `savepoint` for nested
/// transactions.
/// Even though we don't mutate the connection, we take a `&mut Connection`
@@ -195,7 +195,7 @@ impl<'conn> Transaction<'conn> {
}
}
impl<'conn> Deref for Transaction<'conn> {
impl Deref for Transaction<'_> {
type Target = Connection;
fn deref(&self) -> &Connection {
@@ -204,13 +204,13 @@ impl<'conn> Deref for Transaction<'conn> {
}
#[allow(unused_must_use)]
impl<'conn> Drop for Transaction<'conn> {
impl Drop for Transaction<'_> {
fn drop(&mut self) {
self.finish_();
}
}
impl<'conn> Savepoint<'conn> {
impl Savepoint<'_> {
fn with_depth_and_name<T: Into<String>>(
conn: &Connection,
depth: u32,
@@ -308,7 +308,7 @@ impl<'conn> Savepoint<'conn> {
}
}
impl<'conn> Deref for Savepoint<'conn> {
impl Deref for Savepoint<'_> {
type Target = Connection;
fn deref(&self) -> &Connection {
@@ -317,7 +317,7 @@ impl<'conn> Deref for Savepoint<'conn> {
}
#[allow(unused_must_use)]
impl<'conn> Drop for Savepoint<'conn> {
impl Drop for Savepoint<'_> {
fn drop(&mut self) {
self.finish_();
}

View File

@@ -1,9 +1,8 @@
//! Convert most of the [Time Strings](http://sqlite.org/lang_datefunc.html) to chrono types.
use chrono;
use std::borrow::Cow;
use self::chrono::{DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
use chrono::{DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
use crate::Result;
@@ -54,7 +53,7 @@ impl FromSql for NaiveTime {
}
/// ISO 8601 combined date and time without timezone =>
/// "YYYY-MM-DD HH:MM:SS.SSS"
/// "YYYY-MM-DDTHH:MM:SS.SSS"
impl ToSql for NaiveDateTime {
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
let date_str = self.format("%Y-%m-%dT%H:%M:%S%.f").to_string();
@@ -129,10 +128,8 @@ impl FromSql for DateTime<Local> {
#[cfg(test)]
mod test {
use super::chrono::{
DateTime, Duration, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc,
};
use crate::{Connection, Result, NO_PARAMS};
use chrono::{DateTime, Duration, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
fn checked_memory_handle() -> Connection {
let db = Connection::open_in_memory().unwrap();

View File

@@ -18,6 +18,11 @@ pub enum FromSqlError {
#[cfg(feature = "i128_blob")]
InvalidI128Size(usize),
/// Error returned when reading a `uuid` from a blob with a size
/// other than 16. Only available when the `uuid` feature is enabled.
#[cfg(feature = "uuid")]
InvalidUuidSize(usize),
/// An error case available for implementors of the `FromSql` trait.
Other(Box<dyn Error + Send + Sync>),
}
@@ -29,6 +34,8 @@ impl PartialEq for FromSqlError {
(FromSqlError::OutOfRange(n1), FromSqlError::OutOfRange(n2)) => n1 == n2,
#[cfg(feature = "i128_blob")]
(FromSqlError::InvalidI128Size(s1), FromSqlError::InvalidI128Size(s2)) => s1 == s2,
#[cfg(feature = "uuid")]
(FromSqlError::InvalidUuidSize(s1), FromSqlError::InvalidUuidSize(s2)) => s1 == s2,
(_, _) => false,
}
}
@@ -43,6 +50,10 @@ impl fmt::Display for FromSqlError {
FromSqlError::InvalidI128Size(s) => {
write!(f, "Cannot read 128bit value out of {} byte blob", s)
}
#[cfg(feature = "uuid")]
FromSqlError::InvalidUuidSize(s) => {
write!(f, "Cannot read UUID value out of {} byte blob", s)
}
FromSqlError::Other(ref err) => err.fmt(f),
}
}
@@ -55,6 +66,8 @@ impl Error for FromSqlError {
FromSqlError::OutOfRange(_) => "value out of range",
#[cfg(feature = "i128_blob")]
FromSqlError::InvalidI128Size(_) => "unexpected blob size for 128bit value",
#[cfg(feature = "uuid")]
FromSqlError::InvalidUuidSize(_) => "unexpected blob size for UUID value",
FromSqlError::Other(ref err) => err.description(),
}
}
@@ -64,9 +77,7 @@ impl Error for FromSqlError {
fn cause(&self) -> Option<&dyn Error> {
match *self {
FromSqlError::Other(ref err) => err.cause(),
FromSqlError::InvalidType | FromSqlError::OutOfRange(_) => None,
#[cfg(feature = "i128_blob")]
FromSqlError::InvalidI128Size(_) => None,
_ => None,
}
}
}
@@ -151,7 +162,7 @@ impl FromSql for bool {
impl FromSql for String {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
value.as_str().map(|s| s.to_string())
value.as_str().map(ToString::to_string)
}
}
@@ -176,6 +187,19 @@ impl FromSql for i128 {
}
}
#[cfg(feature = "uuid")]
impl FromSql for uuid::Uuid {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
value
.as_blob()
.and_then(|bytes| {
uuid::Builder::from_slice(bytes)
.map_err(|_| FromSqlError::InvalidUuidSize(bytes.len()))
})
.map(|mut builder| builder.build())
}
}
impl<T: FromSql> FromSql for Option<T> {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
match value {
@@ -210,8 +234,7 @@ mod test {
{
for n in out_of_range {
let err = db
.query_row("SELECT ?", &[n], |r| r.get_checked::<_, T>(0))
.unwrap()
.query_row("SELECT ?", &[n], |r| r.get::<_, T>(0))
.unwrap_err();
match err {
Error::IntegralValueOutOfRange(_, value) => assert_eq!(*n, value),

View File

@@ -66,6 +66,8 @@ mod from_sql;
mod serde_json;
mod time;
mod to_sql;
#[cfg(feature = "url")]
mod url;
mod value;
mod value_ref;
@@ -207,27 +209,27 @@ mod test {
{
let row1 = rows.next().unwrap().unwrap();
let s1: Option<String> = row1.get(0);
let b1: Option<Vec<u8>> = row1.get(1);
let s1: Option<String> = row1.get_unwrap(0);
let b1: Option<Vec<u8>> = row1.get_unwrap(1);
assert_eq!(s.unwrap(), s1.unwrap());
assert!(b1.is_none());
}
{
let row2 = rows.next().unwrap().unwrap();
let s2: Option<String> = row2.get(0);
let b2: Option<Vec<u8>> = row2.get(1);
let s2: Option<String> = row2.get_unwrap(0);
let b2: Option<Vec<u8>> = row2.get_unwrap(1);
assert!(s2.is_none());
assert_eq!(b, b2);
}
}
#[test]
#[allow(clippy::cyclomatic_complexity)]
#[allow(clippy::cognitive_complexity)]
fn test_mismatched_types() {
fn is_invalid_column_type(err: Error) -> bool {
match err {
Error::InvalidColumnType(_, _) => true,
Error::InvalidColumnType(_, _, _) => true,
_ => false,
}
}
@@ -246,102 +248,94 @@ mod test {
let row = rows.next().unwrap().unwrap();
// check the correct types come back as expected
assert_eq!(vec![1, 2], row.get_checked::<_, Vec<u8>>(0).unwrap());
assert_eq!("text", row.get_checked::<_, String>(1).unwrap());
assert_eq!(1, row.get_checked::<_, c_int>(2).unwrap());
assert!((1.5 - row.get_checked::<_, c_double>(3).unwrap()).abs() < EPSILON);
assert!(row.get_checked::<_, Option<c_int>>(4).unwrap().is_none());
assert!(row.get_checked::<_, Option<c_double>>(4).unwrap().is_none());
assert!(row.get_checked::<_, Option<String>>(4).unwrap().is_none());
assert_eq!(vec![1, 2], row.get::<_, Vec<u8>>(0).unwrap());
assert_eq!("text", row.get::<_, String>(1).unwrap());
assert_eq!(1, row.get::<_, c_int>(2).unwrap());
assert!((1.5 - row.get::<_, c_double>(3).unwrap()).abs() < EPSILON);
assert!(row.get::<_, Option<c_int>>(4).unwrap().is_none());
assert!(row.get::<_, Option<c_double>>(4).unwrap().is_none());
assert!(row.get::<_, Option<String>>(4).unwrap().is_none());
// check some invalid types
// 0 is actually a blob (Vec<u8>)
assert!(is_invalid_column_type(
row.get_checked::<_, c_int>(0).err().unwrap()
row.get::<_, c_int>(0).err().unwrap()
));
assert!(is_invalid_column_type(
row.get_checked::<_, c_int>(0).err().unwrap()
row.get::<_, c_int>(0).err().unwrap()
));
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_checked::<_, i64>(0).err().unwrap()
row.get::<_, String>(0).err().unwrap()
));
assert!(is_invalid_column_type(
row.get_checked::<_, c_double>(0).err().unwrap()
row.get::<_, time::Timespec>(0).err().unwrap()
));
assert!(is_invalid_column_type(
row.get_checked::<_, String>(0).err().unwrap()
));
assert!(is_invalid_column_type(
row.get_checked::<_, time::Timespec>(0).err().unwrap()
));
assert!(is_invalid_column_type(
row.get_checked::<_, Option<c_int>>(0).err().unwrap()
row.get::<_, Option<c_int>>(0).err().unwrap()
));
// 1 is actually a text (String)
assert!(is_invalid_column_type(
row.get_checked::<_, c_int>(1).err().unwrap()
row.get::<_, c_int>(1).err().unwrap()
));
assert!(is_invalid_column_type(row.get::<_, i64>(1).err().unwrap()));
assert!(is_invalid_column_type(
row.get::<_, c_double>(1).err().unwrap()
));
assert!(is_invalid_column_type(
row.get_checked::<_, i64>(1).err().unwrap()
row.get::<_, Vec<u8>>(1).err().unwrap()
));
assert!(is_invalid_column_type(
row.get_checked::<_, c_double>(1).err().unwrap()
));
assert!(is_invalid_column_type(
row.get_checked::<_, Vec<u8>>(1).err().unwrap()
));
assert!(is_invalid_column_type(
row.get_checked::<_, Option<c_int>>(1).err().unwrap()
row.get::<_, Option<c_int>>(1).err().unwrap()
));
// 2 is actually an integer
assert!(is_invalid_column_type(
row.get_checked::<_, String>(2).err().unwrap()
row.get::<_, String>(2).err().unwrap()
));
assert!(is_invalid_column_type(
row.get_checked::<_, Vec<u8>>(2).err().unwrap()
row.get::<_, Vec<u8>>(2).err().unwrap()
));
assert!(is_invalid_column_type(
row.get_checked::<_, Option<String>>(2).err().unwrap()
row.get::<_, Option<String>>(2).err().unwrap()
));
// 3 is actually a float (c_double)
assert!(is_invalid_column_type(
row.get_checked::<_, c_int>(3).err().unwrap()
row.get::<_, c_int>(3).err().unwrap()
));
assert!(is_invalid_column_type(row.get::<_, i64>(3).err().unwrap()));
assert!(is_invalid_column_type(
row.get::<_, String>(3).err().unwrap()
));
assert!(is_invalid_column_type(
row.get_checked::<_, i64>(3).err().unwrap()
row.get::<_, Vec<u8>>(3).err().unwrap()
));
assert!(is_invalid_column_type(
row.get_checked::<_, String>(3).err().unwrap()
));
assert!(is_invalid_column_type(
row.get_checked::<_, Vec<u8>>(3).err().unwrap()
));
assert!(is_invalid_column_type(
row.get_checked::<_, Option<c_int>>(3).err().unwrap()
row.get::<_, Option<c_int>>(3).err().unwrap()
));
// 4 is actually NULL
assert!(is_invalid_column_type(
row.get_checked::<_, c_int>(4).err().unwrap()
row.get::<_, c_int>(4).err().unwrap()
));
assert!(is_invalid_column_type(row.get::<_, i64>(4).err().unwrap()));
assert!(is_invalid_column_type(
row.get::<_, c_double>(4).err().unwrap()
));
assert!(is_invalid_column_type(
row.get_checked::<_, i64>(4).err().unwrap()
row.get::<_, String>(4).err().unwrap()
));
assert!(is_invalid_column_type(
row.get_checked::<_, c_double>(4).err().unwrap()
row.get::<_, Vec<u8>>(4).err().unwrap()
));
assert!(is_invalid_column_type(
row.get_checked::<_, String>(4).err().unwrap()
));
assert!(is_invalid_column_type(
row.get_checked::<_, Vec<u8>>(4).err().unwrap()
));
assert!(is_invalid_column_type(
row.get_checked::<_, time::Timespec>(4).err().unwrap()
row.get::<_, time::Timespec>(4).err().unwrap()
));
}
@@ -360,19 +354,16 @@ mod test {
let mut rows = stmt.query(NO_PARAMS).unwrap();
let row = rows.next().unwrap().unwrap();
assert_eq!(
Value::Blob(vec![1, 2]),
row.get_checked::<_, Value>(0).unwrap()
);
assert_eq!(Value::Blob(vec![1, 2]), row.get::<_, Value>(0).unwrap());
assert_eq!(
Value::Text(String::from("text")),
row.get_checked::<_, Value>(1).unwrap()
row.get::<_, Value>(1).unwrap()
);
assert_eq!(Value::Integer(1), row.get_checked::<_, Value>(2).unwrap());
match row.get_checked::<_, Value>(3).unwrap() {
assert_eq!(Value::Integer(1), row.get::<_, Value>(2).unwrap());
match row.get::<_, Value>(3).unwrap() {
Value::Real(val) => assert!((1.5 - val).abs() < EPSILON),
x => panic!("Invalid Value {:?}", x),
}
assert_eq!(Value::Null, row.get_checked::<_, Value>(4).unwrap());
assert_eq!(Value::Null, row.get::<_, Value>(4).unwrap());
}
}

View File

@@ -1,7 +1,6 @@
//! `ToSql` and `FromSql` implementation for JSON `Value`.
use serde_json;
use self::serde_json::Value;
use serde_json::Value;
use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
use crate::Result;
@@ -17,7 +16,7 @@ impl ToSql for Value {
impl FromSql for Value {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
match value {
ValueRef::Text(s) => serde_json::from_str(s),
ValueRef::Text(s) => serde_json::from_slice(s),
ValueRef::Blob(b) => serde_json::from_slice(b),
_ => return Err(FromSqlError::InvalidType),
}
@@ -27,9 +26,9 @@ impl FromSql for Value {
#[cfg(test)]
mod test {
use super::serde_json;
use crate::types::ToSql;
use crate::{Connection, NO_PARAMS};
use serde_json;
fn checked_memory_handle() -> Connection {
let db = Connection::open_in_memory().unwrap();

View File

@@ -36,8 +36,8 @@ impl FromSql for time::Timespec {
#[cfg(test)]
mod test {
use super::time;
use crate::{Connection, Result, NO_PARAMS};
use time;
fn checked_memory_handle() -> Connection {
let db = Connection::open_in_memory().unwrap();

View File

@@ -40,7 +40,7 @@ where
// be converted into Values.
macro_rules! from_value(
($t:ty) => (
impl<'a> From<$t> for ToSqlOutput<'a> {
impl From<$t> for ToSqlOutput<'_> {
fn from(t: $t) -> Self { ToSqlOutput::Owned(t.into())}
}
)
@@ -65,7 +65,10 @@ from_value!(Vec<u8>);
#[cfg(feature = "i128_blob")]
from_value!(i128);
impl<'a> ToSql for ToSqlOutput<'a> {
#[cfg(feature = "uuid")]
from_value!(uuid::Uuid);
impl ToSql for ToSqlOutput<'_> {
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
Ok(match *self {
ToSqlOutput::Borrowed(v) => ToSqlOutput::Borrowed(v),
@@ -84,6 +87,13 @@ pub trait ToSql {
fn to_sql(&self) -> Result<ToSqlOutput<'_>>;
}
impl ToSql for Box<dyn ToSql> {
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
let derefed: &dyn ToSql = &**self;
derefed.to_sql()
}
}
// We should be able to use a generic impl like this:
//
// impl<T: Copy> ToSql for T where T: Into<Value> {
@@ -121,7 +131,10 @@ to_sql_self!(f64);
#[cfg(feature = "i128_blob")]
to_sql_self!(i128);
impl<'a, T: ?Sized> ToSql for &'a T
#[cfg(feature = "uuid")]
to_sql_self!(uuid::Uuid);
impl<T: ?Sized> ToSql for &'_ T
where
T: ToSql,
{
@@ -169,7 +182,7 @@ impl<T: ToSql> ToSql for Option<T> {
}
}
impl<'a> ToSql for Cow<'a, str> {
impl ToSql for Cow<'_, str> {
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
Ok(ToSqlOutput::from(self.as_ref()))
}
@@ -229,7 +242,7 @@ mod test {
let res = stmt
.query_map(NO_PARAMS, |row| {
(row.get::<_, i128>(0), row.get::<_, String>(1))
Ok((row.get::<_, i128>(0)?, row.get::<_, String>(1)?))
})
.unwrap()
.collect::<Result<Vec<_>, _>>()
@@ -248,4 +261,36 @@ mod test {
]
);
}
#[cfg(feature = "uuid")]
#[test]
fn test_uuid() {
use crate::{params, Connection};
use uuid::Uuid;
let db = Connection::open_in_memory().unwrap();
db.execute_batch("CREATE TABLE foo (id BLOB CHECK(length(id) = 16), label TEXT);")
.unwrap();
let id = Uuid::new_v4();
db.execute(
"INSERT INTO foo (id, label) VALUES (?, ?)",
params![id, "target"],
)
.unwrap();
let mut stmt = db
.prepare("SELECT id, label FROM foo WHERE id = ?")
.unwrap();
let mut rows = stmt.query(params![id]).unwrap();
let row = rows.next().unwrap().unwrap();
let found_id: Uuid = row.get_unwrap(0);
let found_label: String = row.get_unwrap(1);
assert_eq!(found_id, id);
assert_eq!(found_label, "target");
}
}

81
src/types/url.rs Normal file
View File

@@ -0,0 +1,81 @@
//! `ToSql` and `FromSql` implementation for [`url::Url`].
use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
use crate::Result;
use url::Url;
/// Serialize `Url` to text.
impl ToSql for Url {
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
Ok(ToSqlOutput::from(self.as_str()))
}
}
/// Deserialize text to `Url`.
impl FromSql for Url {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
match value {
ValueRef::Text(s) => {
let s = std::str::from_utf8(s).map_err(|e| FromSqlError::Other(Box::new(e)))?;
Url::parse(s).map_err(|e| FromSqlError::Other(Box::new(e)))
}
_ => Err(FromSqlError::InvalidType),
}
}
}
#[cfg(test)]
mod test {
use crate::{params, Connection, Error, Result};
use url::{ParseError, Url};
fn checked_memory_handle() -> Connection {
let db = Connection::open_in_memory().unwrap();
db.execute_batch("CREATE TABLE urls (i INTEGER, v TEXT)")
.unwrap();
db
}
fn get_url(db: &Connection, id: i64) -> Result<Url> {
db.query_row("SELECT v FROM urls WHERE i = ?", params![id], |r| r.get(0))
}
#[test]
fn test_sql_url() {
let db = &checked_memory_handle();
let url0 = Url::parse("http://www.example1.com").unwrap();
let url1 = Url::parse("http://www.example1.com/👌").unwrap();
let url2 = "http://www.example2.com/👌";
db.execute(
"INSERT INTO urls (i, v) VALUES (0, ?), (1, ?), (2, ?), (3, ?)",
// also insert a non-hex encoded url (which might be present if it was
// inserted separately)
params![url0, url1, url2, "illegal"],
)
.unwrap();
assert_eq!(get_url(db, 0).unwrap(), url0);
assert_eq!(get_url(db, 1).unwrap(), url1);
// Should successfully read it, even though it wasn't inserted as an
// escaped url.
let out_url2: Url = get_url(db, 2).unwrap();
assert_eq!(out_url2, Url::parse(url2).unwrap());
// Make sure the conversion error comes through correctly.
let err = get_url(db, 3).unwrap_err();
match err {
Error::FromSqlConversionFailure(_, _, e) => {
assert_eq!(
*e.downcast::<ParseError>().unwrap(),
ParseError::RelativeUrlWithoutBase,
);
}
e => {
panic!("Expected conversion failure, got {}", e);
}
}
}
}

View File

@@ -48,6 +48,13 @@ impl From<i128> for Value {
}
}
#[cfg(feature = "uuid")]
impl From<uuid::Uuid> for Value {
fn from(id: uuid::Uuid) -> Value {
Value::Blob(id.as_bytes().to_vec())
}
}
macro_rules! from_i64(
($t:ty) => (
impl From<$t> for Value {

View File

@@ -14,12 +14,12 @@ pub enum ValueRef<'a> {
/// The value is a floating point number.
Real(f64),
/// The value is a text string.
Text(&'a str),
Text(&'a [u8]),
/// The value is a blob of data
Blob(&'a [u8]),
}
impl<'a> ValueRef<'a> {
impl ValueRef<'_> {
pub fn data_type(&self) -> Type {
match *self {
ValueRef::Null => Type::Null,
@@ -54,7 +54,9 @@ impl<'a> ValueRef<'a> {
/// `Err(Error::InvalidColumnType)`.
pub fn as_str(&self) -> FromSqlResult<&'a str> {
match *self {
ValueRef::Text(t) => Ok(t),
ValueRef::Text(t) => {
std::str::from_utf8(t).map_err(|e| FromSqlError::Other(Box::new(e)))
}
_ => Err(FromSqlError::InvalidType),
}
}
@@ -69,13 +71,16 @@ impl<'a> ValueRef<'a> {
}
}
impl<'a> From<ValueRef<'a>> for Value {
impl From<ValueRef<'_>> for Value {
fn from(borrowed: ValueRef<'_>) -> Value {
match borrowed {
ValueRef::Null => Value::Null,
ValueRef::Integer(i) => Value::Integer(i),
ValueRef::Real(r) => Value::Real(r),
ValueRef::Text(s) => Value::Text(s.to_string()),
ValueRef::Text(s) => {
let s = std::str::from_utf8(s).expect("invalid UTF-8");
Value::Text(s.to_string())
}
ValueRef::Blob(b) => Value::Blob(b.to_vec()),
}
}
@@ -83,7 +88,7 @@ impl<'a> From<ValueRef<'a>> for Value {
impl<'a> From<&'a str> for ValueRef<'a> {
fn from(s: &str) -> ValueRef<'_> {
ValueRef::Text(s)
ValueRef::Text(s.as_bytes())
}
}
@@ -99,7 +104,7 @@ impl<'a> From<&'a Value> for ValueRef<'a> {
Value::Null => ValueRef::Null,
Value::Integer(i) => ValueRef::Integer(i),
Value::Real(r) => ValueRef::Real(r),
Value::Text(ref s) => ValueRef::Text(s),
Value::Text(ref s) => ValueRef::Text(s.as_bytes()),
Value::Blob(ref b) => ValueRef::Blob(b),
}
}
@@ -125,10 +130,7 @@ impl<'a> ValueRef<'a> {
);
let s = CStr::from_ptr(text as *const c_char);
// sqlite3_value_text returns UTF8 data, so our unwrap here should be fine.
let s = s
.to_str()
.expect("sqlite3_value_text returned invalid UTF-8");
let s = s.to_bytes();
ValueRef::Text(s)
}
ffi::SQLITE_BLOB => {

View File

@@ -35,7 +35,7 @@ pub fn load_module(conn: &Connection) -> Result<()> {
conn.create_module("rarray", &ARRAY_MODULE, aux)
}
lazy_static! {
lazy_static::lazy_static! {
static ref ARRAY_MODULE: Module<ArrayTab> = eponymous_only_module::<ArrayTab>(1);
}

View File

@@ -32,7 +32,7 @@ pub fn load_module(conn: &Connection) -> Result<()> {
conn.create_module("csv", &CSV_MODULE, aux)
}
lazy_static! {
lazy_static::lazy_static! {
static ref CSV_MODULE: Module<CSVTab> = read_only_module::<CSVTab>(1);
}
@@ -347,6 +347,7 @@ impl From<csv::Error> for Error {
mod test {
use crate::vtab::csvtab;
use crate::{Connection, Result, NO_PARAMS};
use fallible_iterator::FallibleIterator;
#[test]
fn test_csv_module() {
@@ -363,8 +364,9 @@ mod test {
}
let ids: Result<Vec<i32>> = s
.query_map(NO_PARAMS, |row| row.get::<_, i32>(0))
.query(NO_PARAMS)
.unwrap()
.map(|row| row.get::<_, i32>(0))
.collect();
let sum = ids.unwrap().iter().sum::<i32>();
assert_eq!(sum, 15);
@@ -389,7 +391,7 @@ mod test {
let mut rows = s.query(NO_PARAMS).unwrap();
let row = rows.next().unwrap().unwrap();
assert_eq!(row.get::<_, i32>(0), 2);
assert_eq!(row.get_unwrap::<_, i32>(0), 2);
}
db.execute_batch("DROP TABLE vtab").unwrap();
}

View File

@@ -67,6 +67,7 @@ pub struct Module<T: VTab> {
phantom: PhantomData<T>,
}
unsafe impl<T: VTab> Send for Module<T> {}
unsafe impl<T: VTab> Sync for Module<T> {}
/// Create a read-only virtual table implementation.
@@ -233,7 +234,7 @@ pub trait CreateVTab: VTab {
}
}
bitflags! {
bitflags::bitflags! {
#[doc = "Index constraint operator."]
#[repr(C)]
pub struct IndexConstraintOp: ::std::os::raw::c_uchar {
@@ -337,7 +338,7 @@ impl<'a> Iterator for IndexConstraintIter<'a> {
/// WHERE clause constraint
pub struct IndexConstraint<'a>(&'a ffi::sqlite3_index_constraint);
impl<'a> IndexConstraint<'a> {
impl IndexConstraint<'_> {
/// Column constrained. -1 for ROWID
pub fn column(&self) -> c_int {
self.0.iColumn
@@ -357,7 +358,7 @@ impl<'a> IndexConstraint<'a> {
/// Information about what parameters to pass to `VTabCursor.filter`.
pub struct IndexConstraintUsage<'a>(&'a mut ffi::sqlite3_index_constraint_usage);
impl<'a> IndexConstraintUsage<'a> {
impl IndexConstraintUsage<'_> {
/// if `argv_index` > 0, constraint is part of argv to `VTabCursor.filter`
pub fn set_argv_index(&mut self, argv_index: c_int) {
self.0.argvIndex = argv_index;
@@ -388,7 +389,7 @@ impl<'a> Iterator for OrderByIter<'a> {
/// A column of the ORDER BY clause.
pub struct OrderBy<'a>(&'a ffi::sqlite3_index_info_sqlite3_index_orderby);
impl<'a> OrderBy<'a> {
impl OrderBy<'_> {
/// Column number
pub fn column(&self) -> c_int {
self.0.iColumn
@@ -453,7 +454,7 @@ pub struct Values<'a> {
args: &'a [*mut ffi::sqlite3_value],
}
impl<'a> Values<'a> {
impl Values<'_> {
pub fn len(&self) -> usize {
self.args.len()
}
@@ -472,7 +473,13 @@ impl<'a> Values<'a> {
}
FromSqlError::OutOfRange(i) => Error::IntegralValueOutOfRange(idx, i),
#[cfg(feature = "i128_blob")]
FromSqlError::InvalidI128Size(_) => Error::InvalidColumnType(idx, value.data_type()),
FromSqlError::InvalidI128Size(_) => {
Error::InvalidColumnType(idx, idx.to_string(), value.data_type())
}
#[cfg(feature = "uuid")]
FromSqlError::InvalidUuidSize(_) => {
Error::FromSqlConversionFailure(idx, value.data_type(), Box::new(err))
}
})
}
@@ -641,7 +648,6 @@ where
{
use std::error::Error as StdError;
use std::ffi::CStr;
use std::slice;
let mut conn = VTabConnection(db);
let aux = aux as *mut T::Aux;
@@ -695,7 +701,6 @@ where
{
use std::error::Error as StdError;
use std::ffi::CStr;
use std::slice;
let mut conn = VTabConnection(db);
let aux = aux as *mut T::Aux;
@@ -848,7 +853,6 @@ where
C: VTabCursor,
{
use std::ffi::CStr;
use std::slice;
use std::str;
let idx_name = if idx_str.is_null() {
None
@@ -981,7 +985,7 @@ fn mprintf(err_msg: &str) -> *mut c_char {
pub mod array;
#[cfg(feature = "csvtab")]
pub mod csvtab;
#[cfg(feature = "bundled")]
#[cfg(feature = "series")]
pub mod series; // SQLite >= 3.9.0
#[cfg(test)]

View File

@@ -18,7 +18,7 @@ pub fn load_module(conn: &Connection) -> Result<()> {
conn.create_module("generate_series", &SERIES_MODULE, aux)
}
lazy_static! {
lazy_static::lazy_static! {
static ref SERIES_MODULE: Module<SeriesTab> = eponymous_only_module::<SeriesTab>(1);
}
@@ -28,7 +28,7 @@ const SERIES_COLUMN_START: c_int = 1;
const SERIES_COLUMN_STOP: c_int = 2;
const SERIES_COLUMN_STEP: c_int = 3;
bitflags! {
bitflags::bitflags! {
#[repr(C)]
struct QueryPlanFlags: ::std::os::raw::c_int {
// start = $value -- constraint exists