This commit is contained in:
gwenn 2018-08-11 12:48:21 +02:00
parent 57df0ac3d5
commit c925d1aa97
26 changed files with 1214 additions and 970 deletions

View File

@ -5,8 +5,8 @@ fn main() {
#[cfg(feature = "bundled")] #[cfg(feature = "bundled")]
mod build { mod build {
extern crate cc; extern crate cc;
use std::{env, fs};
use std::path::Path; use std::path::Path;
use std::{env, fs};
pub fn main() { pub fn main() {
if cfg!(feature = "sqlcipher") { if cfg!(feature = "sqlcipher") {
@ -68,8 +68,10 @@ mod build {
match header { match header {
HeaderLocation::FromEnvironment => { HeaderLocation::FromEnvironment => {
let prefix = env_prefix(); let prefix = env_prefix();
let mut header = env::var(format!("{}_INCLUDE_DIR", prefix)) let mut header = env::var(format!("{}_INCLUDE_DIR", prefix)).expect(&format!(
.expect(&format!("{}_INCLUDE_DIR must be set if {}_LIB_DIR is set", prefix, prefix)); "{}_INCLUDE_DIR must be set if {}_LIB_DIR is set",
prefix, prefix
));
header.push_str("/sqlite3.h"); header.push_str("/sqlite3.h");
header header
} }
@ -90,7 +92,7 @@ mod build {
println!("cargo:rerun-if-env-changed={}_INCLUDE_DIR", env_prefix()); println!("cargo:rerun-if-env-changed={}_INCLUDE_DIR", env_prefix());
println!("cargo:rerun-if-env-changed={}_LIB_DIR", env_prefix()); println!("cargo:rerun-if-env-changed={}_LIB_DIR", env_prefix());
if cfg!(target_os="windows") { if cfg!(target_os = "windows") {
println!("cargo:rerun-if-env-changed=PATH"); println!("cargo:rerun-if-env-changed=PATH");
} }
// Allow users to specify where to find SQLite. // Allow users to specify where to find SQLite.
@ -105,7 +107,10 @@ mod build {
} }
// See if pkg-config can do everything for us. // See if pkg-config can do everything for us.
match pkg_config::Config::new().print_system_libs(false).probe(link_lib) { match pkg_config::Config::new()
.print_system_libs(false)
.probe(link_lib)
{
Ok(mut lib) => { Ok(mut lib) => {
if let Some(mut header) = lib.include_paths.pop() { if let Some(mut header) = lib.include_paths.pop() {
header.push("sqlite3.h"); header.push("sqlite3.h");
@ -162,10 +167,9 @@ mod build {
mod bindings { mod bindings {
use super::HeaderLocation; use super::HeaderLocation;
use std::{env, fs};
use std::path::Path; use std::path::Path;
use std::{env, fs};
#[cfg_attr(rustfmt, rustfmt_skip)]
static PREBUILT_BINDGEN_PATHS: &'static [&'static str] = &[ static PREBUILT_BINDGEN_PATHS: &'static [&'static str] = &[
"bindgen-bindings/bindgen_3.6.8.rs", "bindgen-bindings/bindgen_3.6.8.rs",
@ -197,12 +201,12 @@ mod build {
mod bindings { mod bindings {
extern crate bindgen; extern crate bindgen;
use self::bindgen::callbacks::{ParseCallbacks, IntKind}; use self::bindgen::callbacks::{IntKind, ParseCallbacks};
use super::HeaderLocation; use super::HeaderLocation;
use std::env; use std::env;
use std::io::Write;
use std::fs::OpenOptions; use std::fs::OpenOptions;
use std::io::Write;
use std::path::Path; use std::path::Path;
#[derive(Debug)] #[derive(Debug)]
@ -249,7 +253,8 @@ mod build {
.open(path.clone()) .open(path.clone())
.expect(&format!("Could not write to {:?}", path)); .expect(&format!("Could not write to {:?}", path));
file.write_all(output.as_bytes()).expect(&format!("Could not write to {:?}", path)); file.write_all(output.as_bytes())
.expect(&format!("Could not write to {:?}", path));
} }
} }
} }

View File

@ -1,6 +1,6 @@
use std::os::raw::c_int;
use std::error; use std::error;
use std::fmt; use std::fmt;
use std::os::raw::c_int;
/// Error Codes /// Error Codes
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
@ -64,30 +64,30 @@ pub struct Error {
impl Error { impl Error {
pub fn new(result_code: c_int) -> Error { pub fn new(result_code: c_int) -> Error {
let code = match result_code & 0xff { let code = match result_code & 0xff {
super::SQLITE_INTERNAL => ErrorCode::InternalMalfunction, super::SQLITE_INTERNAL => ErrorCode::InternalMalfunction,
super::SQLITE_PERM => ErrorCode::PermissionDenied, super::SQLITE_PERM => ErrorCode::PermissionDenied,
super::SQLITE_ABORT => ErrorCode::OperationAborted, super::SQLITE_ABORT => ErrorCode::OperationAborted,
super::SQLITE_BUSY => ErrorCode::DatabaseBusy, super::SQLITE_BUSY => ErrorCode::DatabaseBusy,
super::SQLITE_LOCKED => ErrorCode::DatabaseLocked, super::SQLITE_LOCKED => ErrorCode::DatabaseLocked,
super::SQLITE_NOMEM => ErrorCode::OutOfMemory, super::SQLITE_NOMEM => ErrorCode::OutOfMemory,
super::SQLITE_READONLY => ErrorCode::ReadOnly, super::SQLITE_READONLY => ErrorCode::ReadOnly,
super::SQLITE_INTERRUPT => ErrorCode::OperationInterrupted, super::SQLITE_INTERRUPT => ErrorCode::OperationInterrupted,
super::SQLITE_IOERR => ErrorCode::SystemIOFailure, super::SQLITE_IOERR => ErrorCode::SystemIOFailure,
super::SQLITE_CORRUPT => ErrorCode::DatabaseCorrupt, super::SQLITE_CORRUPT => ErrorCode::DatabaseCorrupt,
super::SQLITE_NOTFOUND => ErrorCode::NotFound, super::SQLITE_NOTFOUND => ErrorCode::NotFound,
super::SQLITE_FULL => ErrorCode::DiskFull, super::SQLITE_FULL => ErrorCode::DiskFull,
super::SQLITE_CANTOPEN => ErrorCode::CannotOpen, super::SQLITE_CANTOPEN => ErrorCode::CannotOpen,
super::SQLITE_PROTOCOL => ErrorCode::FileLockingProtocolFailed, super::SQLITE_PROTOCOL => ErrorCode::FileLockingProtocolFailed,
super::SQLITE_SCHEMA => ErrorCode::SchemaChanged, super::SQLITE_SCHEMA => ErrorCode::SchemaChanged,
super::SQLITE_TOOBIG => ErrorCode::TooBig, super::SQLITE_TOOBIG => ErrorCode::TooBig,
super::SQLITE_CONSTRAINT=> ErrorCode::ConstraintViolation, super::SQLITE_CONSTRAINT => ErrorCode::ConstraintViolation,
super::SQLITE_MISMATCH => ErrorCode::TypeMismatch, super::SQLITE_MISMATCH => ErrorCode::TypeMismatch,
super::SQLITE_MISUSE => ErrorCode::APIMisuse, super::SQLITE_MISUSE => ErrorCode::APIMisuse,
super::SQLITE_NOLFS => ErrorCode::NoLargeFileSupport, super::SQLITE_NOLFS => ErrorCode::NoLargeFileSupport,
super::SQLITE_AUTH => ErrorCode::AuthorizationForStatementDenied, super::SQLITE_AUTH => ErrorCode::AuthorizationForStatementDenied,
super::SQLITE_RANGE => ErrorCode::ParameterOutOfRange, super::SQLITE_RANGE => ErrorCode::ParameterOutOfRange,
super::SQLITE_NOTADB => ErrorCode::NotADatabase, super::SQLITE_NOTADB => ErrorCode::NotADatabase,
_ => ErrorCode::Unknown, _ => ErrorCode::Unknown,
}; };
Error { Error {
@ -99,7 +99,12 @@ impl Error {
impl fmt::Display for Error { impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Error code {}: {}", self.extended_code, code_to_str(self.extended_code)) write!(
f,
"Error code {}: {}",
self.extended_code,
code_to_str(self.extended_code)
)
} }
} }
@ -114,48 +119,48 @@ impl error::Error for Error {
// in the current version of SQLite. We repeat them here so we don't have to worry about which // in the current version of SQLite. We repeat them here so we don't have to worry about which
// version of SQLite added which constants, and we only use them to implement code_to_str below. // version of SQLite added which constants, and we only use them to implement code_to_str below.
const SQLITE_NOTICE : c_int = 27; const SQLITE_NOTICE: c_int = 27;
const SQLITE_WARNING : c_int = 28; const SQLITE_WARNING: c_int = 28;
// Extended result codes. // Extended result codes.
const SQLITE_IOERR_SHMOPEN : c_int = (super::SQLITE_IOERR | (18<<8)); const SQLITE_IOERR_SHMOPEN: c_int = (super::SQLITE_IOERR | (18 << 8));
const SQLITE_IOERR_SHMSIZE : c_int = (super::SQLITE_IOERR | (19<<8)); const SQLITE_IOERR_SHMSIZE: c_int = (super::SQLITE_IOERR | (19 << 8));
const SQLITE_IOERR_SHMLOCK : c_int = (super::SQLITE_IOERR | (20<<8)); const SQLITE_IOERR_SHMLOCK: c_int = (super::SQLITE_IOERR | (20 << 8));
const SQLITE_IOERR_SHMMAP : c_int = (super::SQLITE_IOERR | (21<<8)); const SQLITE_IOERR_SHMMAP: c_int = (super::SQLITE_IOERR | (21 << 8));
const SQLITE_IOERR_SEEK : c_int = (super::SQLITE_IOERR | (22<<8)); const SQLITE_IOERR_SEEK: c_int = (super::SQLITE_IOERR | (22 << 8));
const SQLITE_IOERR_DELETE_NOENT : c_int = (super::SQLITE_IOERR | (23<<8)); const SQLITE_IOERR_DELETE_NOENT: c_int = (super::SQLITE_IOERR | (23 << 8));
const SQLITE_IOERR_MMAP : c_int = (super::SQLITE_IOERR | (24<<8)); const SQLITE_IOERR_MMAP: c_int = (super::SQLITE_IOERR | (24 << 8));
const SQLITE_IOERR_GETTEMPPATH : c_int = (super::SQLITE_IOERR | (25<<8)); const SQLITE_IOERR_GETTEMPPATH: c_int = (super::SQLITE_IOERR | (25 << 8));
const SQLITE_IOERR_CONVPATH : c_int = (super::SQLITE_IOERR | (26<<8)); const SQLITE_IOERR_CONVPATH: c_int = (super::SQLITE_IOERR | (26 << 8));
const SQLITE_IOERR_VNODE : c_int = (super::SQLITE_IOERR | (27<<8)); const SQLITE_IOERR_VNODE: c_int = (super::SQLITE_IOERR | (27 << 8));
const SQLITE_LOCKED_SHAREDCACHE : c_int = (super::SQLITE_LOCKED | (1<<8)); const SQLITE_LOCKED_SHAREDCACHE: c_int = (super::SQLITE_LOCKED | (1 << 8));
const SQLITE_BUSY_RECOVERY : c_int = (super::SQLITE_BUSY | (1<<8)); const SQLITE_BUSY_RECOVERY: c_int = (super::SQLITE_BUSY | (1 << 8));
const SQLITE_BUSY_SNAPSHOT : c_int = (super::SQLITE_BUSY | (2<<8)); const SQLITE_BUSY_SNAPSHOT: c_int = (super::SQLITE_BUSY | (2 << 8));
const SQLITE_CANTOPEN_NOTEMPDIR : c_int = (super::SQLITE_CANTOPEN | (1<<8)); const SQLITE_CANTOPEN_NOTEMPDIR: c_int = (super::SQLITE_CANTOPEN | (1 << 8));
const SQLITE_CANTOPEN_ISDIR : c_int = (super::SQLITE_CANTOPEN | (2<<8)); const SQLITE_CANTOPEN_ISDIR: c_int = (super::SQLITE_CANTOPEN | (2 << 8));
const SQLITE_CANTOPEN_FULLPATH : c_int = (super::SQLITE_CANTOPEN | (3<<8)); const SQLITE_CANTOPEN_FULLPATH: c_int = (super::SQLITE_CANTOPEN | (3 << 8));
const SQLITE_CANTOPEN_CONVPATH : c_int = (super::SQLITE_CANTOPEN | (4<<8)); const SQLITE_CANTOPEN_CONVPATH: c_int = (super::SQLITE_CANTOPEN | (4 << 8));
const SQLITE_CORRUPT_VTAB : c_int = (super::SQLITE_CORRUPT | (1<<8)); const SQLITE_CORRUPT_VTAB: c_int = (super::SQLITE_CORRUPT | (1 << 8));
const SQLITE_READONLY_RECOVERY : c_int = (super::SQLITE_READONLY | (1<<8)); const SQLITE_READONLY_RECOVERY: c_int = (super::SQLITE_READONLY | (1 << 8));
const SQLITE_READONLY_CANTLOCK : c_int = (super::SQLITE_READONLY | (2<<8)); const SQLITE_READONLY_CANTLOCK: c_int = (super::SQLITE_READONLY | (2 << 8));
const SQLITE_READONLY_ROLLBACK : c_int = (super::SQLITE_READONLY | (3<<8)); const SQLITE_READONLY_ROLLBACK: c_int = (super::SQLITE_READONLY | (3 << 8));
const SQLITE_READONLY_DBMOVED : c_int = (super::SQLITE_READONLY | (4<<8)); const SQLITE_READONLY_DBMOVED: c_int = (super::SQLITE_READONLY | (4 << 8));
const SQLITE_ABORT_ROLLBACK : c_int = (super::SQLITE_ABORT | (2<<8)); const SQLITE_ABORT_ROLLBACK: c_int = (super::SQLITE_ABORT | (2 << 8));
const SQLITE_CONSTRAINT_CHECK : c_int = (super::SQLITE_CONSTRAINT | (1<<8)); const SQLITE_CONSTRAINT_CHECK: c_int = (super::SQLITE_CONSTRAINT | (1 << 8));
const SQLITE_CONSTRAINT_COMMITHOOK : c_int = (super::SQLITE_CONSTRAINT | (2<<8)); const SQLITE_CONSTRAINT_COMMITHOOK: c_int = (super::SQLITE_CONSTRAINT | (2 << 8));
const SQLITE_CONSTRAINT_FOREIGNKEY : c_int = (super::SQLITE_CONSTRAINT | (3<<8)); const SQLITE_CONSTRAINT_FOREIGNKEY: c_int = (super::SQLITE_CONSTRAINT | (3 << 8));
const SQLITE_CONSTRAINT_FUNCTION : c_int = (super::SQLITE_CONSTRAINT | (4<<8)); const SQLITE_CONSTRAINT_FUNCTION: c_int = (super::SQLITE_CONSTRAINT | (4 << 8));
const SQLITE_CONSTRAINT_NOTNULL : c_int = (super::SQLITE_CONSTRAINT | (5<<8)); const SQLITE_CONSTRAINT_NOTNULL: c_int = (super::SQLITE_CONSTRAINT | (5 << 8));
const SQLITE_CONSTRAINT_PRIMARYKEY : c_int = (super::SQLITE_CONSTRAINT | (6<<8)); const SQLITE_CONSTRAINT_PRIMARYKEY: c_int = (super::SQLITE_CONSTRAINT | (6 << 8));
const SQLITE_CONSTRAINT_TRIGGER : c_int = (super::SQLITE_CONSTRAINT | (7<<8)); const SQLITE_CONSTRAINT_TRIGGER: c_int = (super::SQLITE_CONSTRAINT | (7 << 8));
const SQLITE_CONSTRAINT_UNIQUE : c_int = (super::SQLITE_CONSTRAINT | (8<<8)); const SQLITE_CONSTRAINT_UNIQUE: c_int = (super::SQLITE_CONSTRAINT | (8 << 8));
const SQLITE_CONSTRAINT_VTAB : c_int = (super::SQLITE_CONSTRAINT | (9<<8)); const SQLITE_CONSTRAINT_VTAB: c_int = (super::SQLITE_CONSTRAINT | (9 << 8));
const SQLITE_CONSTRAINT_ROWID : c_int = (super::SQLITE_CONSTRAINT |(10<<8)); const SQLITE_CONSTRAINT_ROWID: c_int = (super::SQLITE_CONSTRAINT | (10 << 8));
const SQLITE_NOTICE_RECOVER_WAL : c_int = (SQLITE_NOTICE | (1<<8)); const SQLITE_NOTICE_RECOVER_WAL: c_int = (SQLITE_NOTICE | (1 << 8));
const SQLITE_NOTICE_RECOVER_ROLLBACK : c_int = (SQLITE_NOTICE | (2<<8)); const SQLITE_NOTICE_RECOVER_ROLLBACK: c_int = (SQLITE_NOTICE | (2 << 8));
const SQLITE_WARNING_AUTOINDEX : c_int = (SQLITE_WARNING | (1<<8)); const SQLITE_WARNING_AUTOINDEX: c_int = (SQLITE_WARNING | (1 << 8));
const SQLITE_AUTH_USER : c_int = (super::SQLITE_AUTH | (1<<8)); const SQLITE_AUTH_USER: c_int = (super::SQLITE_AUTH | (1 << 8));
pub fn code_to_str(code: c_int) -> &'static str { pub fn code_to_str(code: c_int) -> &'static str {
match code { match code {

View File

@ -36,8 +36,8 @@ use std::time::Duration;
use ffi; use ffi;
use {DatabaseName, Connection, Result}; use error::{error_from_handle, error_from_sqlite_code};
use error::{error_from_sqlite_code, error_from_handle}; use {Connection, DatabaseName, Result};
impl Connection { impl Connection {
/// Back up the `name` database to the given destination path. /// Back up the `name` database to the given destination path.
@ -52,14 +52,20 @@ impl Connection {
/// ///
/// Will return `Err` if the destination path cannot be opened /// Will return `Err` if the destination path cannot be opened
/// or if the backup fails. /// or if the backup fails.
pub fn backup<P: AsRef<Path>>(&self, pub fn backup<P: AsRef<Path>>(
name: DatabaseName, &self,
dst_path: P, name: DatabaseName,
progress: Option<fn(Progress)>) dst_path: P,
-> Result<()> { progress: Option<fn(Progress)>,
use self::StepResult::{More, Done, Busy, Locked}; ) -> Result<()> {
use self::StepResult::{Busy, Done, Locked, More};
let mut dst = try!(Connection::open(dst_path)); let mut dst = try!(Connection::open(dst_path));
let backup = try!(Backup::new_with_names(self, name, &mut dst, DatabaseName::Main)); let backup = try!(Backup::new_with_names(
self,
name,
&mut dst,
DatabaseName::Main
));
let mut r = More; let mut r = More;
while r == More { while r == More {
@ -89,12 +95,13 @@ impl Connection {
/// ///
/// Will return `Err` if the destination path cannot be opened /// Will return `Err` if the destination path cannot be opened
/// or if the restore fails. /// or if the restore fails.
pub fn restore<P: AsRef<Path>>(&mut self, pub fn restore<P: AsRef<Path>>(
name: DatabaseName, &mut self,
src_path: P, name: DatabaseName,
progress: Option<fn(Progress)>) src_path: P,
-> Result<()> { progress: Option<fn(Progress)>,
use self::StepResult::{More, Done, Busy, Locked}; ) -> Result<()> {
use self::StepResult::{Busy, Done, Locked, More};
let src = try!(Connection::open(src_path)); let src = try!(Connection::open(src_path));
let restore = try!(Backup::new_with_names(&src, DatabaseName::Main, self, name)); let restore = try!(Backup::new_with_names(&src, DatabaseName::Main, self, name));
@ -124,7 +131,7 @@ impl Connection {
} }
/// Possible successful results of calling `Backup::step`. /// Possible successful results of calling `Backup::step`.
#[derive(Copy,Clone,Debug,PartialEq,Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum StepResult { pub enum StepResult {
/// The backup is complete. /// The backup is complete.
Done, Done,
@ -146,7 +153,7 @@ pub enum StepResult {
/// backup is as of the last call to `step` - if the source database is /// backup is as of the last call to `step` - if the source database is
/// modified after a call to `step`, the progress value will become outdated /// modified after a call to `step`, the progress value will become outdated
/// and potentially incorrect. /// and potentially incorrect.
#[derive(Copy,Clone,Debug)] #[derive(Copy, Clone, Debug)]
pub struct Progress { pub struct Progress {
/// Number of pages in the source database that still need to be backed up. /// Number of pages in the source database that still need to be backed up.
pub remaining: c_int, pub remaining: c_int,
@ -184,21 +191,24 @@ impl<'a, 'b> Backup<'a, 'b> {
/// ///
/// Will return `Err` if the underlying `sqlite3_backup_init` call returns /// Will return `Err` if the underlying `sqlite3_backup_init` call returns
/// `NULL`. /// `NULL`.
pub fn new_with_names(from: &'a Connection, pub fn new_with_names(
from_name: DatabaseName, from: &'a Connection,
to: &'b mut Connection, from_name: DatabaseName,
to_name: DatabaseName) to: &'b mut Connection,
-> Result<Backup<'a, 'b>> { to_name: DatabaseName,
) -> Result<Backup<'a, 'b>> {
let to_name = try!(to_name.to_cstring()); let to_name = try!(to_name.to_cstring());
let from_name = try!(from_name.to_cstring()); let from_name = try!(from_name.to_cstring());
let to_db = to.db.borrow_mut().db; let to_db = to.db.borrow_mut().db;
let b = unsafe { let b = unsafe {
let b = ffi::sqlite3_backup_init(to_db, let b = ffi::sqlite3_backup_init(
to_name.as_ptr(), to_db,
from.db.borrow_mut().db, to_name.as_ptr(),
from_name.as_ptr()); from.db.borrow_mut().db,
from_name.as_ptr(),
);
if b.is_null() { if b.is_null() {
return Err(error_from_handle(to_db, ffi::sqlite3_errcode(to_db))); return Err(error_from_handle(to_db, ffi::sqlite3_errcode(to_db)));
} }
@ -206,10 +216,10 @@ impl<'a, 'b> Backup<'a, 'b> {
}; };
Ok(Backup { Ok(Backup {
phantom_from: PhantomData, phantom_from: PhantomData,
phantom_to: PhantomData, phantom_to: PhantomData,
b, b,
}) })
} }
/// Gets the progress of the backup as of the last call to `step`. /// Gets the progress of the backup as of the last call to `step`.
@ -235,7 +245,7 @@ impl<'a, 'b> Backup<'a, 'b> {
/// `LOCKED` are transient errors and are therefore returned as possible /// `LOCKED` are transient errors and are therefore returned as possible
/// `Ok` values. /// `Ok` values.
pub fn step(&self, num_pages: c_int) -> Result<StepResult> { pub fn step(&self, num_pages: c_int) -> Result<StepResult> {
use self::StepResult::{Done, More, Busy, Locked}; use self::StepResult::{Busy, Done, Locked, More};
let rc = unsafe { ffi::sqlite3_backup_step(self.b, num_pages) }; let rc = unsafe { ffi::sqlite3_backup_step(self.b, num_pages) };
match rc { match rc {
@ -262,12 +272,13 @@ impl<'a, 'b> Backup<'a, 'b> {
/// # Failure /// # Failure
/// ///
/// Will return `Err` if any of the calls to `step` return `Err`. /// Will return `Err` if any of the calls to `step` return `Err`.
pub fn run_to_completion(&self, pub fn run_to_completion(
pages_per_step: c_int, &self,
pause_between_pages: Duration, pages_per_step: c_int,
progress: Option<fn(Progress)>) pause_between_pages: Duration,
-> Result<()> { progress: Option<fn(Progress)>,
use self::StepResult::{Done, More, Busy, Locked}; ) -> Result<()> {
use self::StepResult::{Busy, Done, Locked, More};
assert!(pages_per_step > 0, "pages_per_step must be positive"); assert!(pages_per_step > 0, "pages_per_step must be positive");
@ -292,12 +303,11 @@ impl<'a, 'b> Drop for Backup<'a, 'b> {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use {Connection, DatabaseName};
use std::time::Duration;
use super::Backup; use super::Backup;
use std::time::Duration;
use {Connection, DatabaseName};
#[test] #[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_backup() { fn test_backup() {
let src = Connection::open_in_memory().unwrap(); let src = Connection::open_in_memory().unwrap();
let sql = "BEGIN; let sql = "BEGIN;
@ -313,22 +323,27 @@ mod test {
backup.step(-1).unwrap(); backup.step(-1).unwrap();
} }
let the_answer: i64 = dst.query_row("SELECT x FROM foo", &[], |r| r.get(0)).unwrap(); let the_answer: i64 = dst
.query_row("SELECT x FROM foo", &[], |r| r.get(0))
.unwrap();
assert_eq!(42, the_answer); assert_eq!(42, the_answer);
src.execute_batch("INSERT INTO foo VALUES(43)").unwrap(); src.execute_batch("INSERT INTO foo VALUES(43)").unwrap();
{ {
let backup = Backup::new(&src, &mut dst).unwrap(); let backup = Backup::new(&src, &mut dst).unwrap();
backup.run_to_completion(5, Duration::from_millis(250), None).unwrap(); backup
.run_to_completion(5, Duration::from_millis(250), None)
.unwrap();
} }
let the_answer: i64 = dst.query_row("SELECT SUM(x) FROM foo", &[], |r| r.get(0)).unwrap(); let the_answer: i64 = dst
.query_row("SELECT SUM(x) FROM foo", &[], |r| r.get(0))
.unwrap();
assert_eq!(42 + 43, the_answer); assert_eq!(42 + 43, the_answer);
} }
#[test] #[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_backup_temp() { fn test_backup_temp() {
let src = Connection::open_in_memory().unwrap(); let src = Connection::open_in_memory().unwrap();
let sql = "BEGIN; let sql = "BEGIN;
@ -340,34 +355,35 @@ mod test {
let mut dst = Connection::open_in_memory().unwrap(); let mut dst = Connection::open_in_memory().unwrap();
{ {
let backup = Backup::new_with_names(&src, let backup =
DatabaseName::Temp, Backup::new_with_names(&src, DatabaseName::Temp, &mut dst, DatabaseName::Main)
&mut dst, .unwrap();
DatabaseName::Main)
.unwrap();
backup.step(-1).unwrap(); backup.step(-1).unwrap();
} }
let the_answer: i64 = dst.query_row("SELECT x FROM foo", &[], |r| r.get(0)).unwrap(); let the_answer: i64 = dst
.query_row("SELECT x FROM foo", &[], |r| r.get(0))
.unwrap();
assert_eq!(42, the_answer); assert_eq!(42, the_answer);
src.execute_batch("INSERT INTO foo VALUES(43)").unwrap(); src.execute_batch("INSERT INTO foo VALUES(43)").unwrap();
{ {
let backup = Backup::new_with_names(&src, let backup =
DatabaseName::Temp, Backup::new_with_names(&src, DatabaseName::Temp, &mut dst, DatabaseName::Main)
&mut dst, .unwrap();
DatabaseName::Main) backup
.unwrap(); .run_to_completion(5, Duration::from_millis(250), None)
backup.run_to_completion(5, Duration::from_millis(250), None).unwrap(); .unwrap();
} }
let the_answer: i64 = dst.query_row("SELECT SUM(x) FROM foo", &[], |r| r.get(0)).unwrap(); let the_answer: i64 = dst
.query_row("SELECT SUM(x) FROM foo", &[], |r| r.get(0))
.unwrap();
assert_eq!(42 + 43, the_answer); assert_eq!(42 + 43, the_answer);
} }
#[test] #[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_backup_attached() { fn test_backup_attached() {
let src = Connection::open_in_memory().unwrap(); let src = Connection::open_in_memory().unwrap();
let sql = "ATTACH DATABASE ':memory:' AS my_attached; let sql = "ATTACH DATABASE ':memory:' AS my_attached;
@ -380,29 +396,37 @@ mod test {
let mut dst = Connection::open_in_memory().unwrap(); let mut dst = Connection::open_in_memory().unwrap();
{ {
let backup = Backup::new_with_names(&src, let backup = Backup::new_with_names(
DatabaseName::Attached("my_attached"), &src,
&mut dst, DatabaseName::Attached("my_attached"),
DatabaseName::Main) &mut dst,
.unwrap(); DatabaseName::Main,
).unwrap();
backup.step(-1).unwrap(); backup.step(-1).unwrap();
} }
let the_answer: i64 = dst.query_row("SELECT x FROM foo", &[], |r| r.get(0)).unwrap(); let the_answer: i64 = dst
.query_row("SELECT x FROM foo", &[], |r| r.get(0))
.unwrap();
assert_eq!(42, the_answer); assert_eq!(42, the_answer);
src.execute_batch("INSERT INTO foo VALUES(43)").unwrap(); src.execute_batch("INSERT INTO foo VALUES(43)").unwrap();
{ {
let backup = Backup::new_with_names(&src, let backup = Backup::new_with_names(
DatabaseName::Attached("my_attached"), &src,
&mut dst, DatabaseName::Attached("my_attached"),
DatabaseName::Main) &mut dst,
.unwrap(); DatabaseName::Main,
backup.run_to_completion(5, Duration::from_millis(250), None).unwrap(); ).unwrap();
backup
.run_to_completion(5, Duration::from_millis(250), None)
.unwrap();
} }
let the_answer: i64 = dst.query_row("SELECT SUM(x) FROM foo", &[], |r| r.get(0)).unwrap(); let the_answer: i64 = dst
.query_row("SELECT SUM(x) FROM foo", &[], |r| r.get(0))
.unwrap();
assert_eq!(42 + 43, the_answer); assert_eq!(42 + 43, the_answer);
} }
} }

View File

@ -48,13 +48,13 @@
//! assert_eq!(blob.size(), 64); //! assert_eq!(blob.size(), 64);
//! } //! }
//! ``` //! ```
use std::io;
use std::cmp::min; use std::cmp::min;
use std::io;
use std::ptr; use std::ptr;
use super::ffi; use super::ffi;
use super::types::{ToSql, ToSqlOutput}; use super::types::{ToSql, ToSqlOutput};
use {Result, Connection, DatabaseName}; use {Connection, DatabaseName, Result};
/// Handle to an open BLOB. /// Handle to an open BLOB.
pub struct Blob<'conn> { pub struct Blob<'conn> {
@ -70,35 +70,35 @@ impl Connection {
/// ///
/// Will return `Err` if `db`/`table`/`column` cannot be converted to a C-compatible string /// Will return `Err` if `db`/`table`/`column` cannot be converted to a C-compatible string
/// or if the underlying SQLite BLOB open call fails. /// or if the underlying SQLite BLOB open call fails.
pub fn blob_open<'a>(&'a self, pub fn blob_open<'a>(
db: DatabaseName, &'a self,
table: &str, db: DatabaseName,
column: &str, table: &str,
row_id: i64, column: &str,
read_only: bool) row_id: i64,
-> Result<Blob<'a>> { read_only: bool,
) -> Result<Blob<'a>> {
let mut c = self.db.borrow_mut(); let mut c = self.db.borrow_mut();
let mut blob = ptr::null_mut(); let mut blob = ptr::null_mut();
let db = try!(db.to_cstring()); let db = try!(db.to_cstring());
let table = try!(super::str_to_cstring(table)); let table = try!(super::str_to_cstring(table));
let column = try!(super::str_to_cstring(column)); let column = try!(super::str_to_cstring(column));
let rc = unsafe { let rc = unsafe {
ffi::sqlite3_blob_open(c.db(), ffi::sqlite3_blob_open(
db.as_ptr(), c.db(),
table.as_ptr(), db.as_ptr(),
column.as_ptr(), table.as_ptr(),
row_id, column.as_ptr(),
if read_only { 0 } else { 1 }, row_id,
&mut blob) if read_only { 0 } else { 1 },
&mut blob,
)
}; };
c.decode_result(rc) c.decode_result(rc).map(|_| Blob {
.map(|_| { conn: self,
Blob { blob,
conn: self, pos: 0,
blob, })
pos: 0,
}
})
} }
} }
@ -154,15 +154,13 @@ impl<'conn> io::Read for Blob<'conn> {
if n <= 0 { if n <= 0 {
return Ok(0); return Ok(0);
} }
let rc = let rc = unsafe { ffi::sqlite3_blob_read(self.blob, buf.as_ptr() as *mut _, n, self.pos) };
unsafe { ffi::sqlite3_blob_read(self.blob, buf.as_ptr() as *mut _, n, self.pos) };
self.conn self.conn
.decode_result(rc) .decode_result(rc)
.map(|_| { .map(|_| {
self.pos += n; self.pos += n;
n as usize n as usize
}) }).map_err(|err| io::Error::new(io::ErrorKind::Other, err))
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))
} }
} }
@ -183,16 +181,13 @@ impl<'conn> io::Write for Blob<'conn> {
if n <= 0 { if n <= 0 {
return Ok(0); return Ok(0);
} }
let rc = unsafe { let rc = unsafe { ffi::sqlite3_blob_write(self.blob, buf.as_ptr() as *mut _, n, self.pos) };
ffi::sqlite3_blob_write(self.blob, buf.as_ptr() as *mut _, n, self.pos)
};
self.conn self.conn
.decode_result(rc) .decode_result(rc)
.map(|_| { .map(|_| {
self.pos += n; self.pos += n;
n as usize n as usize
}) }).map_err(|err| io::Error::new(io::ErrorKind::Other, err))
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))
} }
fn flush(&mut self) -> io::Result<()> { fn flush(&mut self) -> io::Result<()> {
@ -210,11 +205,15 @@ impl<'conn> io::Seek for Blob<'conn> {
}; };
if pos < 0 { if pos < 0 {
Err(io::Error::new(io::ErrorKind::InvalidInput, Err(io::Error::new(
"invalid seek to negative position")) io::ErrorKind::InvalidInput,
"invalid seek to negative position",
))
} else if pos > i64::from(self.size()) { } else if pos > i64::from(self.size()) {
Err(io::Error::new(io::ErrorKind::InvalidInput, Err(io::Error::new(
"invalid seek to position past end of blob")) io::ErrorKind::InvalidInput,
"invalid seek to position past end of blob",
))
} else { } else {
self.pos = pos as i32; self.pos = pos as i32;
Ok(pos as u64) Ok(pos as u64)
@ -235,7 +234,7 @@ impl<'conn> Drop for Blob<'conn> {
/// incremental BLOB I/O routines. /// incremental BLOB I/O routines.
/// ///
/// A negative value for the zeroblob results in a zero-length BLOB. /// A negative value for the zeroblob results in a zero-length BLOB.
#[derive(Copy,Clone)] #[derive(Copy, Clone)]
pub struct ZeroBlob(pub i32); pub struct ZeroBlob(pub i32);
impl ToSql for ZeroBlob { impl ToSql for ZeroBlob {
@ -247,10 +246,9 @@ impl ToSql for ZeroBlob {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use std::io::{BufReader, BufRead, BufWriter, Read, Write, Seek, SeekFrom}; use std::io::{BufRead, BufReader, BufWriter, Read, Seek, SeekFrom, Write};
use {Connection, DatabaseName, Result}; use {Connection, DatabaseName, Result};
#[cfg_attr(rustfmt, rustfmt_skip)]
fn db_with_test_blob() -> Result<(Connection, i64)> { fn db_with_test_blob() -> Result<(Connection, i64)> {
let db = try!(Connection::open_in_memory()); let db = try!(Connection::open_in_memory());
let sql = "BEGIN; let sql = "BEGIN;
@ -266,7 +264,8 @@ mod test {
fn test_blob() { fn test_blob() {
let (db, rowid) = db_with_test_blob().unwrap(); let (db, rowid) = db_with_test_blob().unwrap();
let mut blob = db.blob_open(DatabaseName::Main, "test", "content", rowid, false) let mut blob = db
.blob_open(DatabaseName::Main, "test", "content", rowid, false)
.unwrap(); .unwrap();
assert_eq!(4, blob.write(b"Clob").unwrap()); assert_eq!(4, blob.write(b"Clob").unwrap());
assert_eq!(6, blob.write(b"567890xxxxxx").unwrap()); // cannot write past 10 assert_eq!(6, blob.write(b"567890xxxxxx").unwrap()); // cannot write past 10
@ -275,7 +274,8 @@ mod test {
blob.reopen(rowid).unwrap(); blob.reopen(rowid).unwrap();
blob.close().unwrap(); blob.close().unwrap();
blob = db.blob_open(DatabaseName::Main, "test", "content", rowid, true) blob = db
.blob_open(DatabaseName::Main, "test", "content", rowid, true)
.unwrap(); .unwrap();
let mut bytes = [0u8; 5]; let mut bytes = [0u8; 5];
assert_eq!(5, blob.read(&mut bytes[..]).unwrap()); assert_eq!(5, blob.read(&mut bytes[..]).unwrap());
@ -316,7 +316,8 @@ mod test {
fn test_blob_in_bufreader() { fn test_blob_in_bufreader() {
let (db, rowid) = db_with_test_blob().unwrap(); let (db, rowid) = db_with_test_blob().unwrap();
let mut blob = db.blob_open(DatabaseName::Main, "test", "content", rowid, false) let mut blob = db
.blob_open(DatabaseName::Main, "test", "content", rowid, false)
.unwrap(); .unwrap();
assert_eq!(8, blob.write(b"one\ntwo\n").unwrap()); assert_eq!(8, blob.write(b"one\ntwo\n").unwrap());
@ -341,7 +342,8 @@ mod test {
let (db, rowid) = db_with_test_blob().unwrap(); let (db, rowid) = db_with_test_blob().unwrap();
{ {
let blob = db.blob_open(DatabaseName::Main, "test", "content", rowid, false) let blob = db
.blob_open(DatabaseName::Main, "test", "content", rowid, false)
.unwrap(); .unwrap();
let mut writer = BufWriter::new(blob); let mut writer = BufWriter::new(blob);
@ -353,7 +355,8 @@ mod test {
{ {
// ... but it should've written the first 10 bytes // ... but it should've written the first 10 bytes
let mut blob = db.blob_open(DatabaseName::Main, "test", "content", rowid, false) let mut blob = db
.blob_open(DatabaseName::Main, "test", "content", rowid, false)
.unwrap(); .unwrap();
let mut bytes = [0u8; 10]; let mut bytes = [0u8; 10];
assert_eq!(10, blob.read(&mut bytes[..]).unwrap()); assert_eq!(10, blob.read(&mut bytes[..]).unwrap());
@ -361,7 +364,8 @@ mod test {
} }
{ {
let blob = db.blob_open(DatabaseName::Main, "test", "content", rowid, false) let blob = db
.blob_open(DatabaseName::Main, "test", "content", rowid, false)
.unwrap(); .unwrap();
let mut writer = BufWriter::new(blob); let mut writer = BufWriter::new(blob);
@ -372,7 +376,8 @@ mod test {
{ {
// ... but it should've written the first 10 bytes // ... but it should've written the first 10 bytes
let mut blob = db.blob_open(DatabaseName::Main, "test", "content", rowid, false) let mut blob = db
.blob_open(DatabaseName::Main, "test", "content", rowid, false)
.unwrap(); .unwrap();
let mut bytes = [0u8; 10]; let mut bytes = [0u8; 10];
assert_eq!(10, blob.read(&mut bytes[..]).unwrap()); assert_eq!(10, blob.read(&mut bytes[..]).unwrap());

View File

@ -1,8 +1,8 @@
///! Busy handler (when the database is locked) ///! Busy handler (when the database is locked)
use std::time::Duration;
use std::mem; use std::mem;
use std::os::raw::{c_int, c_void}; use std::os::raw::{c_int, c_void};
use std::ptr; use std::ptr;
use std::time::Duration;
use ffi; use ffi;
use {Connection, InnerConnection, Result}; use {Connection, InnerConnection, Result};
@ -67,8 +67,8 @@ mod test {
use self::tempdir::TempDir; use self::tempdir::TempDir;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::sync_channel; use std::sync::mpsc::sync_channel;
use std::time::Duration;
use std::thread; use std::thread;
use std::time::Duration;
use {Connection, Error, ErrorCode, TransactionBehavior}; use {Connection, Error, ErrorCode, TransactionBehavior};
@ -113,8 +113,8 @@ mod test {
}); });
assert_eq!(tx.recv().unwrap(), 1); assert_eq!(tx.recv().unwrap(), 1);
let _ = let _ = db2
db2.query_row("PRAGMA schema_version", &[], |row| { .query_row("PRAGMA schema_version", &[], |row| {
row.get_checked::<_, i32>(0) row.get_checked::<_, i32>(0)
}).expect("unexpected error"); }).expect("unexpected error");
@ -151,8 +151,8 @@ mod test {
}); });
assert_eq!(tx.recv().unwrap(), 1); assert_eq!(tx.recv().unwrap(), 1);
let _ = let _ = db2
db2.query_row("PRAGMA schema_version", &[], |row| { .query_row("PRAGMA schema_version", &[], |row| {
row.get_checked::<_, i32>(0) row.get_checked::<_, i32>(0)
}).expect("unexpected error"); }).expect("unexpected error");
assert_eq!(CALLED.load(Ordering::Relaxed), true); assert_eq!(CALLED.load(Ordering::Relaxed), true);

View File

@ -1,10 +1,10 @@
//! Prepared statements cache for faster execution. //! Prepared statements cache for faster execution.
use lru_cache::LruCache;
use raw_statement::RawStatement;
use std::cell::RefCell; use std::cell::RefCell;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use lru_cache::LruCache; use {Connection, Result, Statement};
use {Result, Connection, Statement};
use raw_statement::RawStatement;
impl Connection { impl Connection {
/// Prepare a SQL statement for execution, returning a previously prepared (but /// Prepare a SQL statement for execution, returning a previously prepared (but
@ -119,10 +119,11 @@ impl StatementCache {
// //
// Will return `Err` if no cached statement can be found and the underlying SQLite prepare // Will return `Err` if no cached statement can be found and the underlying SQLite prepare
// call fails. // call fails.
fn get<'conn>(&'conn self, fn get<'conn>(
conn: &'conn Connection, &'conn self,
sql: &str) conn: &'conn Connection,
-> Result<CachedStatement<'conn>> { sql: &str,
) -> Result<CachedStatement<'conn>> {
let mut cache = self.0.borrow_mut(); let mut cache = self.0.borrow_mut();
let stmt = match cache.remove(sql.trim()) { let stmt = match cache.remove(sql.trim()) {
Some(raw_stmt) => Ok(Statement::new(conn, raw_stmt)), Some(raw_stmt) => Ok(Statement::new(conn, raw_stmt)),
@ -135,7 +136,9 @@ impl StatementCache {
fn cache_stmt(&self, stmt: RawStatement) { fn cache_stmt(&self, stmt: RawStatement) {
let mut cache = self.0.borrow_mut(); let mut cache = self.0.borrow_mut();
stmt.clear_bindings(); stmt.clear_bindings();
let sql = String::from_utf8_lossy(stmt.sql().to_bytes()).trim().to_string(); let sql = String::from_utf8_lossy(stmt.sql().to_bytes())
.trim()
.to_string();
cache.insert(sql, stmt); cache.insert(sql, stmt);
} }
@ -147,8 +150,8 @@ impl StatementCache {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use Connection;
use super::StatementCache; use super::StatementCache;
use Connection;
impl StatementCache { impl StatementCache {
fn clear(&self) { fn clear(&self) {
@ -242,46 +245,51 @@ mod test {
#[test] #[test]
fn test_ddl() { fn test_ddl() {
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
db.execute_batch(r#" db.execute_batch(
r#"
CREATE TABLE foo (x INT); CREATE TABLE foo (x INT);
INSERT INTO foo VALUES (1); INSERT INTO foo VALUES (1);
"#) "#,
.unwrap(); ).unwrap();
let sql = "SELECT * FROM foo"; let sql = "SELECT * FROM foo";
{ {
let mut stmt = db.prepare_cached(sql).unwrap(); let mut stmt = db.prepare_cached(sql).unwrap();
assert_eq!(1i32, assert_eq!(
stmt.query_map::<i32, _>(&[], |r| r.get(0)) 1i32,
.unwrap() stmt.query_map::<i32, _>(&[], |r| r.get(0))
.next() .unwrap()
.unwrap() .next()
.unwrap()); .unwrap()
.unwrap()
);
} }
db.execute_batch(r#" db.execute_batch(
r#"
ALTER TABLE foo ADD COLUMN y INT; ALTER TABLE foo ADD COLUMN y INT;
UPDATE foo SET y = 2; UPDATE foo SET y = 2;
"#) "#,
.unwrap(); ).unwrap();
{ {
let mut stmt = db.prepare_cached(sql).unwrap(); let mut stmt = db.prepare_cached(sql).unwrap();
assert_eq!((1i32, 2i32), assert_eq!(
stmt.query_map(&[], |r| (r.get(0), r.get(1))) (1i32, 2i32),
.unwrap() stmt.query_map(&[], |r| (r.get(0), r.get(1)))
.next() .unwrap()
.unwrap() .next()
.unwrap()); .unwrap()
.unwrap()
);
} }
} }
#[test] #[test]
fn test_connection_close() { fn test_connection_close() {
let conn = Connection::open_in_memory().unwrap(); let conn = Connection::open_in_memory().unwrap();
conn.prepare_cached("SELECT * FROM sqlite_master;") conn.prepare_cached("SELECT * FROM sqlite_master;").unwrap();
.unwrap();
conn.close().expect("connection not closed"); conn.close().expect("connection not closed");
} }

View File

@ -1,10 +1,10 @@
use std::error; use std::error;
use std::fmt; use std::fmt;
use std::os::raw::c_int;
use std::path::PathBuf; use std::path::PathBuf;
use std::str; use std::str;
use std::os::raw::c_int;
use {ffi, errmsg_to_string};
use types::Type; use types::Type;
use {errmsg_to_string, ffi};
/// Old name for `Error`. `SqliteError` is deprecated. /// Old name for `Error`. `SqliteError` is deprecated.
#[deprecated(since = "0.6.0", note = "Use Error instead")] #[deprecated(since = "0.6.0", note = "Use Error instead")]
@ -99,17 +99,15 @@ impl fmt::Display for Error {
match *self { match *self {
Error::SqliteFailure(ref err, None) => err.fmt(f), Error::SqliteFailure(ref err, None) => err.fmt(f),
Error::SqliteFailure(_, Some(ref s)) => write!(f, "{}", s), Error::SqliteFailure(_, Some(ref s)) => write!(f, "{}", s),
Error::SqliteSingleThreadedMode => { Error::SqliteSingleThreadedMode => write!(
write!(f, f,
"SQLite was compiled or configured for single-threaded use only") "SQLite was compiled or configured for single-threaded use only"
} ),
Error::FromSqlConversionFailure(i, ref t, ref err) => { Error::FromSqlConversionFailure(i, ref t, ref err) => write!(
write!(f, f,
"Conversion error from type {} at index: {}, {}", "Conversion error from type {} at index: {}, {}",
t, t, i, err
i, ),
err)
}
Error::IntegralValueOutOfRange(col, val) => { Error::IntegralValueOutOfRange(col, val) => {
write!(f, "Integer {} out of range at index {}", val, col) write!(f, "Integer {} out of range at index {}", val, col)
} }
@ -145,14 +143,18 @@ impl error::Error for Error {
match *self { match *self {
Error::SqliteFailure(ref err, None) => err.description(), Error::SqliteFailure(ref err, None) => err.description(),
Error::SqliteFailure(_, Some(ref s)) => s, Error::SqliteFailure(_, Some(ref s)) => s,
Error::SqliteSingleThreadedMode => "SQLite was compiled or configured for single-threaded use only", Error::SqliteSingleThreadedMode => {
"SQLite was compiled or configured for single-threaded use only"
}
Error::FromSqlConversionFailure(_, _, ref err) => err.description(), Error::FromSqlConversionFailure(_, _, ref err) => err.description(),
Error::IntegralValueOutOfRange(_, _) => "integral value out of range of requested type", Error::IntegralValueOutOfRange(_, _) => "integral value out of range of requested type",
Error::Utf8Error(ref err) => err.description(), Error::Utf8Error(ref err) => err.description(),
Error::InvalidParameterName(_) => "invalid parameter name", Error::InvalidParameterName(_) => "invalid parameter name",
Error::NulError(ref err) => err.description(), Error::NulError(ref err) => err.description(),
Error::InvalidPath(_) => "invalid path", Error::InvalidPath(_) => "invalid path",
Error::ExecuteReturnedResults => "execute returned results - did you mean to call query?", Error::ExecuteReturnedResults => {
"execute returned results - did you mean to call query?"
}
Error::QueryReturnedNoRows => "query returned no rows", Error::QueryReturnedNoRows => "query returned no rows",
Error::InvalidColumnIndex(_) => "invalid column index", Error::InvalidColumnIndex(_) => "invalid column index",
Error::InvalidColumnName(_) => "invalid column name", Error::InvalidColumnName(_) => "invalid column name",
@ -174,17 +176,17 @@ impl error::Error for Error {
Error::Utf8Error(ref err) => Some(err), Error::Utf8Error(ref err) => Some(err),
Error::NulError(ref err) => Some(err), Error::NulError(ref err) => Some(err),
Error::IntegralValueOutOfRange(_, _) | Error::IntegralValueOutOfRange(_, _)
Error::SqliteSingleThreadedMode | | Error::SqliteSingleThreadedMode
Error::InvalidParameterName(_) | | Error::InvalidParameterName(_)
Error::ExecuteReturnedResults | | Error::ExecuteReturnedResults
Error::QueryReturnedNoRows | | Error::QueryReturnedNoRows
Error::InvalidColumnIndex(_) | | Error::InvalidColumnIndex(_)
Error::InvalidColumnName(_) | | Error::InvalidColumnName(_)
Error::InvalidColumnType(_, _) | | Error::InvalidColumnType(_, _)
Error::InvalidPath(_) | | Error::InvalidPath(_)
Error::StatementChangedRows(_) | | Error::StatementChangedRows(_)
Error::InvalidQuery => None, | Error::InvalidQuery => None,
#[cfg(feature = "functions")] #[cfg(feature = "functions")]
Error::InvalidFunctionParameterType(_, _) => None, Error::InvalidFunctionParameterType(_, _) => None,
@ -192,8 +194,8 @@ impl error::Error for Error {
#[cfg(feature = "functions")] #[cfg(feature = "functions")]
Error::UserFunctionError(ref err) => Some(&**err), Error::UserFunctionError(ref err) => Some(&**err),
Error::FromSqlConversionFailure(_, _, ref err) | Error::FromSqlConversionFailure(_, _, ref err)
Error::ToSqlConversionFailure(ref err) => Some(&**err), | Error::ToSqlConversionFailure(ref err) => Some(&**err),
} }
} }
} }

View File

@ -51,17 +51,17 @@
//! ``` //! ```
use std::error::Error as StdError; use std::error::Error as StdError;
use std::ffi::CStr; use std::ffi::CStr;
use std::os::raw::{c_char, c_int, c_void};
use std::ptr; use std::ptr;
use std::slice; use std::slice;
use std::os::raw::{c_int, c_char, c_void};
use ffi; use ffi;
use ffi::sqlite3_context; use ffi::sqlite3_context;
use ffi::sqlite3_value; use ffi::sqlite3_value;
use types::{ToSql, ToSqlOutput, FromSql, FromSqlError, ValueRef}; use types::{FromSql, FromSqlError, ToSql, ToSqlOutput, ValueRef};
use {Result, Error, Connection, str_to_cstring, InnerConnection}; use {str_to_cstring, Connection, Error, InnerConnection, Result};
fn set_result<'a>(ctx: *mut sqlite3_context, result: &ToSqlOutput<'a>) { fn set_result<'a>(ctx: *mut sqlite3_context, result: &ToSqlOutput<'a>) {
let value = match *result { let value = match *result {
@ -103,10 +103,12 @@ fn set_result<'a>(ctx: *mut sqlite3_context, result: &ToSqlOutput<'a>) {
} else if length == 0 { } else if length == 0 {
ffi::sqlite3_result_zeroblob(ctx, 0) ffi::sqlite3_result_zeroblob(ctx, 0)
} else { } else {
ffi::sqlite3_result_blob(ctx, ffi::sqlite3_result_blob(
b.as_ptr() as *const c_void, ctx,
length as c_int, b.as_ptr() as *const c_void,
ffi::SQLITE_TRANSIENT()); length as c_int,
ffi::SQLITE_TRANSIENT(),
);
} }
}, },
} }
@ -152,23 +154,33 @@ impl<'a> ValueRef<'a> {
ffi::SQLITE_FLOAT => ValueRef::Real(ffi::sqlite3_value_double(value)), ffi::SQLITE_FLOAT => ValueRef::Real(ffi::sqlite3_value_double(value)),
ffi::SQLITE_TEXT => { ffi::SQLITE_TEXT => {
let text = ffi::sqlite3_value_text(value); let text = ffi::sqlite3_value_text(value);
assert!(!text.is_null(), assert!(
"unexpected SQLITE_TEXT value type with NULL data"); !text.is_null(),
"unexpected SQLITE_TEXT value type with NULL data"
);
let s = CStr::from_ptr(text as *const c_char); let s = CStr::from_ptr(text as *const c_char);
// sqlite3_value_text returns UTF8 data, so our unwrap here should be fine. // sqlite3_value_text returns UTF8 data, so our unwrap here should be fine.
let s = s.to_str() let s = s
.to_str()
.expect("sqlite3_value_text returned invalid UTF-8"); .expect("sqlite3_value_text returned invalid UTF-8");
ValueRef::Text(s) ValueRef::Text(s)
} }
ffi::SQLITE_BLOB => { ffi::SQLITE_BLOB => {
let (blob, len) = (ffi::sqlite3_value_blob(value), ffi::sqlite3_value_bytes(value)); let (blob, len) = (
ffi::sqlite3_value_blob(value),
ffi::sqlite3_value_bytes(value),
);
assert!(len >= 0, assert!(
"unexpected negative return from sqlite3_value_bytes"); len >= 0,
"unexpected negative return from sqlite3_value_bytes"
);
if len > 0 { if len > 0 {
assert!(!blob.is_null(), assert!(
"unexpected SQLITE_BLOB value type with NULL data"); !blob.is_null(),
"unexpected SQLITE_BLOB value type with NULL data"
);
ValueRef::Blob(from_raw_parts(blob as *const u8, len as usize)) ValueRef::Blob(from_raw_parts(blob as *const u8, len as usize))
} else { } else {
// The return value from sqlite3_value_blob() for a zero-length BLOB // The return value from sqlite3_value_blob() for a zero-length BLOB
@ -212,17 +224,14 @@ impl<'a> Context<'a> {
let arg = self.args[idx]; let arg = self.args[idx];
let value = unsafe { ValueRef::from_value(arg) }; let value = unsafe { ValueRef::from_value(arg) };
FromSql::column_result(value).map_err(|err| match err { FromSql::column_result(value).map_err(|err| match err {
FromSqlError::InvalidType => { FromSqlError::InvalidType => {
Error::InvalidFunctionParameterType(idx, value.data_type()) Error::InvalidFunctionParameterType(idx, value.data_type())
} }
FromSqlError::OutOfRange(i) => { FromSqlError::OutOfRange(i) => Error::IntegralValueOutOfRange(idx, i),
Error::IntegralValueOutOfRange(idx, FromSqlError::Other(err) => {
i)
}
FromSqlError::Other(err) => {
Error::FromSqlConversionFailure(idx, value.data_type(), err) Error::FromSqlConversionFailure(idx, value.data_type(), err)
} }
}) })
} }
/// Sets the auxilliary data associated with a particular parameter. See /// Sets the auxilliary data associated with a particular parameter. See
@ -231,10 +240,12 @@ impl<'a> Context<'a> {
pub fn set_aux<T>(&self, arg: c_int, value: T) { pub fn set_aux<T>(&self, arg: c_int, value: T) {
let boxed = Box::into_raw(Box::new(value)); let boxed = Box::into_raw(Box::new(value));
unsafe { unsafe {
ffi::sqlite3_set_auxdata(self.ctx, ffi::sqlite3_set_auxdata(
arg, self.ctx,
boxed as *mut c_void, arg,
Some(free_boxed_value::<T>)) boxed as *mut c_void,
Some(free_boxed_value::<T>),
)
}; };
} }
@ -248,7 +259,11 @@ impl<'a> Context<'a> {
/// types must be identical. /// types must be identical.
pub unsafe fn get_aux<T>(&self, arg: c_int) -> Option<&T> { pub unsafe fn get_aux<T>(&self, arg: c_int) -> Option<&T> {
let p = ffi::sqlite3_get_auxdata(self.ctx, arg) as *mut T; let p = ffi::sqlite3_get_auxdata(self.ctx, arg) as *mut T;
if p.is_null() { None } else { Some(&*p) } if p.is_null() {
None
} else {
Some(&*p)
}
} }
} }
@ -257,7 +272,8 @@ impl<'a> Context<'a> {
/// `A` is the type of the aggregation context and `T` is the type of the final result. /// `A` is the type of the aggregation context and `T` is the type of the final result.
/// Implementations should be stateless. /// Implementations should be stateless.
pub trait Aggregate<A, T> pub trait Aggregate<A, T>
where T: ToSql where
T: ToSql,
{ {
/// Initializes the aggregation context. Will be called prior to the first call /// Initializes the aggregation context. Will be called prior to the first call
/// to `step()` to set up the context for an invocation of the function. (Note: /// to `step()` to set up the context for an invocation of the function. (Note:
@ -306,14 +322,16 @@ impl Connection {
/// # Failure /// # Failure
/// ///
/// Will return Err if the function could not be attached to the connection. /// Will return Err if the function could not be attached to the connection.
pub fn create_scalar_function<F, T>(&self, pub fn create_scalar_function<F, T>(
fn_name: &str, &self,
n_arg: c_int, fn_name: &str,
deterministic: bool, n_arg: c_int,
x_func: F) deterministic: bool,
-> Result<()> x_func: F,
where F: FnMut(&Context) -> Result<T>, ) -> Result<()>
T: ToSql where
F: FnMut(&Context) -> Result<T>,
T: ToSql,
{ {
self.db self.db
.borrow_mut() .borrow_mut()
@ -325,14 +343,16 @@ impl Connection {
/// # Failure /// # Failure
/// ///
/// Will return Err if the function could not be attached to the connection. /// Will return Err if the function could not be attached to the connection.
pub fn create_aggregate_function<A, D, T>(&self, pub fn create_aggregate_function<A, D, T>(
fn_name: &str, &self,
n_arg: c_int, fn_name: &str,
deterministic: bool, n_arg: c_int,
aggr: D) deterministic: bool,
-> Result<()> aggr: D,
where D: Aggregate<A, T>, ) -> Result<()>
T: ToSql where
D: Aggregate<A, T>,
T: ToSql,
{ {
self.db self.db
.borrow_mut() .borrow_mut()
@ -353,20 +373,24 @@ impl Connection {
} }
impl InnerConnection { impl InnerConnection {
fn create_scalar_function<F, T>(&mut self, fn create_scalar_function<F, T>(
fn_name: &str, &mut self,
n_arg: c_int, fn_name: &str,
deterministic: bool, n_arg: c_int,
x_func: F) deterministic: bool,
-> Result<()> x_func: F,
where F: FnMut(&Context) -> Result<T>, ) -> Result<()>
T: ToSql where
F: FnMut(&Context) -> Result<T>,
T: ToSql,
{ {
unsafe extern "C" fn call_boxed_closure<F, T>(ctx: *mut sqlite3_context, unsafe extern "C" fn call_boxed_closure<F, T>(
argc: c_int, ctx: *mut sqlite3_context,
argv: *mut *mut sqlite3_value) argc: c_int,
where F: FnMut(&Context) -> Result<T>, argv: *mut *mut sqlite3_value,
T: ToSql ) where
F: FnMut(&Context) -> Result<T>,
T: ToSql,
{ {
let ctx = Context { let ctx = Context {
ctx, ctx,
@ -392,31 +416,36 @@ impl InnerConnection {
flags |= ffi::SQLITE_DETERMINISTIC; flags |= ffi::SQLITE_DETERMINISTIC;
} }
let r = unsafe { let r = unsafe {
ffi::sqlite3_create_function_v2(self.db(), ffi::sqlite3_create_function_v2(
c_name.as_ptr(), self.db(),
n_arg, c_name.as_ptr(),
flags, n_arg,
boxed_f as *mut c_void, flags,
Some(call_boxed_closure::<F, T>), boxed_f as *mut c_void,
None, Some(call_boxed_closure::<F, T>),
None, None,
Some(free_boxed_value::<F>)) None,
Some(free_boxed_value::<F>),
)
}; };
self.decode_result(r) self.decode_result(r)
} }
fn create_aggregate_function<A, D, T>(&mut self, fn create_aggregate_function<A, D, T>(
fn_name: &str, &mut self,
n_arg: c_int, fn_name: &str,
deterministic: bool, n_arg: c_int,
aggr: D) deterministic: bool,
-> Result<()> aggr: D,
where D: Aggregate<A, T>, ) -> Result<()>
T: ToSql where
D: Aggregate<A, T>,
T: ToSql,
{ {
unsafe fn aggregate_context<A>(ctx: *mut sqlite3_context, unsafe fn aggregate_context<A>(
bytes: usize) ctx: *mut sqlite3_context,
-> Option<*mut *mut A> { bytes: usize,
) -> Option<*mut *mut A> {
let pac = ffi::sqlite3_aggregate_context(ctx, bytes as c_int) as *mut *mut A; let pac = ffi::sqlite3_aggregate_context(ctx, bytes as c_int) as *mut *mut A;
if pac.is_null() { if pac.is_null() {
return None; return None;
@ -424,15 +453,19 @@ impl InnerConnection {
Some(pac) Some(pac)
} }
unsafe extern "C" fn call_boxed_step<A, D, T>(ctx: *mut sqlite3_context, unsafe extern "C" fn call_boxed_step<A, D, T>(
argc: c_int, ctx: *mut sqlite3_context,
argv: *mut *mut sqlite3_value) argc: c_int,
where D: Aggregate<A, T>, argv: *mut *mut sqlite3_value,
T: ToSql ) where
D: Aggregate<A, T>,
T: ToSql,
{ {
let boxed_aggr: *mut D = ffi::sqlite3_user_data(ctx) as *mut D; let boxed_aggr: *mut D = ffi::sqlite3_user_data(ctx) as *mut D;
assert!(!boxed_aggr.is_null(), assert!(
"Internal error - null aggregate pointer"); !boxed_aggr.is_null(),
"Internal error - null aggregate pointer"
);
let pac = match aggregate_context(ctx, ::std::mem::size_of::<*mut A>()) { let pac = match aggregate_context(ctx, ::std::mem::size_of::<*mut A>()) {
Some(pac) => pac, Some(pac) => pac,
@ -458,12 +491,15 @@ impl InnerConnection {
} }
unsafe extern "C" fn call_boxed_final<A, D, T>(ctx: *mut sqlite3_context) unsafe extern "C" fn call_boxed_final<A, D, T>(ctx: *mut sqlite3_context)
where D: Aggregate<A, T>, where
T: ToSql D: Aggregate<A, T>,
T: ToSql,
{ {
let boxed_aggr: *mut D = ffi::sqlite3_user_data(ctx) as *mut D; let boxed_aggr: *mut D = ffi::sqlite3_user_data(ctx) as *mut D;
assert!(!boxed_aggr.is_null(), assert!(
"Internal error - null aggregate pointer"); !boxed_aggr.is_null(),
"Internal error - null aggregate pointer"
);
// Within the xFinal callback, it is customary to set N=0 in calls to // 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. // sqlite3_aggregate_context(C,N) so that no pointless memory allocations occur.
@ -495,15 +531,17 @@ impl InnerConnection {
flags |= ffi::SQLITE_DETERMINISTIC; flags |= ffi::SQLITE_DETERMINISTIC;
} }
let r = unsafe { let r = unsafe {
ffi::sqlite3_create_function_v2(self.db(), ffi::sqlite3_create_function_v2(
c_name.as_ptr(), self.db(),
n_arg, c_name.as_ptr(),
flags, n_arg,
boxed_aggr as *mut c_void, flags,
None, boxed_aggr as *mut c_void,
Some(call_boxed_step::<A, D, T>), None,
Some(call_boxed_final::<A, D, T>), Some(call_boxed_step::<A, D, T>),
Some(free_boxed_value::<D>)) Some(call_boxed_final::<A, D, T>),
Some(free_boxed_value::<D>),
)
}; };
self.decode_result(r) self.decode_result(r)
} }
@ -511,15 +549,17 @@ impl InnerConnection {
fn remove_function(&mut self, fn_name: &str, n_arg: c_int) -> Result<()> { fn remove_function(&mut self, fn_name: &str, n_arg: c_int) -> Result<()> {
let c_name = try!(str_to_cstring(fn_name)); let c_name = try!(str_to_cstring(fn_name));
let r = unsafe { let r = unsafe {
ffi::sqlite3_create_function_v2(self.db(), ffi::sqlite3_create_function_v2(
c_name.as_ptr(), self.db(),
n_arg, c_name.as_ptr(),
ffi::SQLITE_UTF8, n_arg,
ptr::null_mut(), ffi::SQLITE_UTF8,
None, ptr::null_mut(),
None, None,
None, None,
None) None,
None,
)
}; };
self.decode_result(r) self.decode_result(r)
} }
@ -529,13 +569,13 @@ impl InnerConnection {
mod test { mod test {
extern crate regex; extern crate regex;
use std::collections::HashMap;
use std::os::raw::c_double;
use self::regex::Regex; use self::regex::Regex;
use std::collections::HashMap;
use std::f64::EPSILON; use std::f64::EPSILON;
use std::os::raw::c_double;
use {Connection, Error, Result};
use functions::{Aggregate, Context}; use functions::{Aggregate, Context};
use {Connection, Error, Result};
fn half(ctx: &Context) -> Result<c_double> { fn half(ctx: &Context) -> Result<c_double> {
assert!(ctx.len() == 1, "called with unexpected number of arguments"); assert!(ctx.len() == 1, "called with unexpected number of arguments");
@ -597,41 +637,44 @@ mod test {
} }
#[test] #[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_function_regexp_with_auxilliary() { fn test_function_regexp_with_auxilliary() {
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
db.execute_batch("BEGIN; db.execute_batch(
CREATE TABLE foo (x string); "BEGIN;
INSERT INTO foo VALUES ('lisa'); CREATE TABLE foo (x string);
INSERT INTO foo VALUES ('lXsi'); INSERT INTO foo VALUES ('lisa');
INSERT INTO foo VALUES ('lisX'); INSERT INTO foo VALUES ('lXsi');
END;").unwrap(); INSERT INTO foo VALUES ('lisX');
db.create_scalar_function("regexp", 2, true, regexp_with_auxilliary).unwrap(); END;",
).unwrap();
db.create_scalar_function("regexp", 2, true, regexp_with_auxilliary)
.unwrap();
let result: Result<bool> = db.query_row("SELECT regexp('l.s[aeiouy]', 'lisa')", let result: Result<bool> =
&[], db.query_row("SELECT regexp('l.s[aeiouy]', 'lisa')", &[], |r| r.get(0));
|r| r.get(0));
assert_eq!(true, result.unwrap()); assert_eq!(true, result.unwrap());
let result: Result<i64> = let result: Result<i64> = db.query_row(
db.query_row("SELECT COUNT(*) FROM foo WHERE regexp('l.s[aeiouy]', x) == 1", "SELECT COUNT(*) FROM foo WHERE regexp('l.s[aeiouy]', x) == 1",
&[], &[],
|r| r.get(0)); |r| r.get(0),
);
assert_eq!(2, result.unwrap()); assert_eq!(2, result.unwrap());
} }
#[test] #[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_function_regexp_with_hashmap_cache() { fn test_function_regexp_with_hashmap_cache() {
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
db.execute_batch("BEGIN; db.execute_batch(
CREATE TABLE foo (x string); "BEGIN;
INSERT INTO foo VALUES ('lisa'); CREATE TABLE foo (x string);
INSERT INTO foo VALUES ('lXsi'); INSERT INTO foo VALUES ('lisa');
INSERT INTO foo VALUES ('lisX'); INSERT INTO foo VALUES ('lXsi');
END;").unwrap(); INSERT INTO foo VALUES ('lisX');
END;",
).unwrap();
// This implementation of a regexp scalar function uses a captured HashMap // This implementation of a regexp scalar function uses a captured HashMap
// to keep cached regular expressions around (even across multiple queries) // to keep cached regular expressions around (even across multiple queries)
@ -646,12 +689,10 @@ mod test {
use std::collections::hash_map::Entry::{Occupied, Vacant}; use std::collections::hash_map::Entry::{Occupied, Vacant};
match entry { match entry {
Occupied(occ) => occ.into_mut(), Occupied(occ) => occ.into_mut(),
Vacant(vac) => { Vacant(vac) => match Regex::new(&regex_s) {
match Regex::new(&regex_s) { Ok(r) => vac.insert(r),
Ok(r) => vac.insert(r), Err(err) => return Err(Error::UserFunctionError(Box::new(err))),
Err(err) => return Err(Error::UserFunctionError(Box::new(err))), },
}
}
} }
}; };
@ -659,16 +700,16 @@ mod test {
Ok(regex.is_match(&text)) Ok(regex.is_match(&text))
}).unwrap(); }).unwrap();
let result: Result<bool> = db.query_row("SELECT regexp('l.s[aeiouy]', 'lisa')", let result: Result<bool> =
&[], db.query_row("SELECT regexp('l.s[aeiouy]', 'lisa')", &[], |r| r.get(0));
|r| r.get(0));
assert_eq!(true, result.unwrap()); assert_eq!(true, result.unwrap());
let result: Result<i64> = let result: Result<i64> = db.query_row(
db.query_row("SELECT COUNT(*) FROM foo WHERE regexp('l.s[aeiouy]', x) == 1", "SELECT COUNT(*) FROM foo WHERE regexp('l.s[aeiouy]', x) == 1",
&[], &[],
|r| r.get(0)); |r| r.get(0),
);
assert_eq!(2, result.unwrap()); assert_eq!(2, result.unwrap());
} }
@ -677,21 +718,21 @@ mod test {
fn test_varargs_function() { fn test_varargs_function() {
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
db.create_scalar_function("my_concat", -1, true, |ctx| { db.create_scalar_function("my_concat", -1, true, |ctx| {
let mut ret = String::new(); let mut ret = String::new();
for idx in 0..ctx.len() { for idx in 0..ctx.len() {
let s = try!(ctx.get::<String>(idx)); let s = try!(ctx.get::<String>(idx));
ret.push_str(&s); ret.push_str(&s);
} }
Ok(ret) Ok(ret)
}) }).unwrap();
.unwrap();
for &(expected, query) in for &(expected, query) in &[
&[("", "SELECT my_concat()"), ("", "SELECT my_concat()"),
("onetwo", "SELECT my_concat('one', 'two')"), ("onetwo", "SELECT my_concat('one', 'two')"),
("abc", "SELECT my_concat('a', 'b', 'c')")] { ("abc", "SELECT my_concat('a', 'b', 'c')"),
] {
let result: String = db.query_row(query, &[], |r| r.get(0)).unwrap(); let result: String = db.query_row(query, &[], |r| r.get(0)).unwrap();
assert_eq!(expected, result); assert_eq!(expected, result);
} }
@ -747,7 +788,8 @@ mod test {
let dual_sum = "SELECT my_sum(i), my_sum(j) FROM (SELECT 2 AS i, 1 AS j UNION ALL SELECT \ let dual_sum = "SELECT my_sum(i), my_sum(j) FROM (SELECT 2 AS i, 1 AS j UNION ALL SELECT \
2, 1)"; 2, 1)";
let result: (i64, i64) = db.query_row(dual_sum, &[], |r| (r.get(0), r.get(1))) let result: (i64, i64) = db
.query_row(dual_sum, &[], |r| (r.get(0), r.get(1)))
.unwrap(); .unwrap();
assert_eq!((4, 2), result); assert_eq!((4, 2), result);
} }

View File

@ -1,8 +1,8 @@
//! Commit, Data Change and Rollback Notification Callbacks //! Commit, Data Change and Rollback Notification Callbacks
#![allow(non_camel_case_types)] #![allow(non_camel_case_types)]
use std::os::raw::{c_char, c_int, c_void};
use std::ptr; use std::ptr;
use std::os::raw::{c_int, c_char, c_void};
use ffi; use ffi;

View File

@ -60,71 +60,72 @@ extern crate bitflags;
#[macro_use] #[macro_use]
extern crate lazy_static; extern crate lazy_static;
use std::default::Default;
use std::convert;
use std::mem;
use std::ptr;
use std::fmt;
use std::path::{Path, PathBuf};
use std::cell::RefCell; use std::cell::RefCell;
use std::convert;
use std::default::Default;
use std::ffi::{CStr, CString}; use std::ffi::{CStr, CString};
use std::fmt;
use std::mem;
use std::os::raw::{c_char, c_int};
use std::path::{Path, PathBuf};
use std::ptr;
use std::result; use std::result;
use std::str; use std::str;
use std::sync::atomic::{AtomicBool, Ordering, ATOMIC_BOOL_INIT};
use std::sync::{Once, ONCE_INIT}; use std::sync::{Once, ONCE_INIT};
use std::sync::atomic::{AtomicBool, ATOMIC_BOOL_INIT, Ordering};
use std::os::raw::{c_int, c_char, c_void};
use types::{ToSql, ValueRef};
use error::{error_from_sqlite_code, error_from_handle};
use raw_statement::RawStatement;
use cache::StatementCache; use cache::StatementCache;
use error::{error_from_handle, error_from_sqlite_code};
use raw_statement::RawStatement;
use types::{ToSql, ValueRef};
pub use statement::Statement; pub use statement::Statement;
pub use row::{Row, Rows, MappedRows, AndThenRows, RowIndex}; pub use row::{AndThenRows, MappedRows, Row, RowIndex, Rows};
pub use transaction::{DropBehavior, Savepoint, Transaction, TransactionBehavior};
#[allow(deprecated)] #[allow(deprecated)]
pub use transaction::{SqliteTransaction, SqliteTransactionBehavior}; pub use transaction::{SqliteTransaction, SqliteTransactionBehavior};
pub use transaction::{DropBehavior, Savepoint, Transaction, TransactionBehavior};
pub use error::Error;
#[allow(deprecated)] #[allow(deprecated)]
pub use error::SqliteError; pub use error::SqliteError;
pub use error::Error;
pub use ffi::ErrorCode; pub use ffi::ErrorCode;
pub use cache::CachedStatement; pub use cache::CachedStatement;
pub use version::*; pub use version::*;
#[cfg(feature = "hooks")]
pub use hooks::*;
#[cfg(feature = "load_extension")] #[cfg(feature = "load_extension")]
#[allow(deprecated)] #[allow(deprecated)]
pub use load_extension_guard::{SqliteLoadExtensionGuard, LoadExtensionGuard}; pub use load_extension_guard::{LoadExtensionGuard, SqliteLoadExtensionGuard};
pub mod types; #[cfg(feature = "backup")]
mod version; pub mod backup;
mod transaction; #[cfg(feature = "blob")]
pub mod blob;
mod busy;
mod cache; mod cache;
mod error; mod error;
#[cfg(feature = "functions")]
pub mod functions;
#[cfg(feature = "hooks")]
mod hooks;
#[cfg(feature = "limits")]
pub mod limits;
#[cfg(feature = "load_extension")]
mod load_extension_guard;
mod raw_statement; mod raw_statement;
mod row; mod row;
mod statement; mod statement;
#[cfg(feature = "load_extension")]
mod load_extension_guard;
#[cfg(feature = "trace")] #[cfg(feature = "trace")]
pub mod trace; pub mod trace;
#[cfg(feature = "backup")] mod transaction;
pub mod backup; pub mod types;
#[cfg(feature = "functions")]
pub mod functions;
#[cfg(feature = "blob")]
pub mod blob;
#[cfg(feature = "limits")]
pub mod limits;
#[cfg(feature = "hooks")]
mod hooks;
#[cfg(feature = "hooks")]
pub use hooks::*;
mod unlock_notify; mod unlock_notify;
mod busy; mod version;
// Number of cached prepared statements we'll hold on to. // Number of cached prepared statements we'll hold on to.
const STATEMENT_CACHE_DEFAULT_CAPACITY: usize = 16; const STATEMENT_CACHE_DEFAULT_CAPACITY: usize = 16;
@ -151,7 +152,7 @@ fn path_to_cstring(p: &Path) -> Result<CString> {
} }
/// Name for a database within a SQLite connection. /// Name for a database within a SQLite connection.
#[derive(Copy,Clone)] #[derive(Copy, Clone)]
pub enum DatabaseName<'a> { pub enum DatabaseName<'a> {
/// The main database. /// The main database.
Main, Main,
@ -168,7 +169,7 @@ pub enum DatabaseName<'a> {
#[cfg(any(feature = "backup", feature = "blob"))] #[cfg(any(feature = "backup", feature = "blob"))]
impl<'a> DatabaseName<'a> { impl<'a> DatabaseName<'a> {
fn to_cstring(&self) -> Result<CString> { fn to_cstring(&self) -> Result<CString> {
use self::DatabaseName::{Main, Temp, Attached}; use self::DatabaseName::{Attached, Main, Temp};
match *self { match *self {
Main => str_to_cstring("main"), Main => str_to_cstring("main"),
Temp => str_to_cstring("temp"), Temp => str_to_cstring("temp"),
@ -232,13 +233,11 @@ impl Connection {
/// underlying SQLite open call fails. /// underlying SQLite open call fails.
pub fn open_with_flags<P: AsRef<Path>>(path: P, flags: OpenFlags) -> Result<Connection> { pub fn open_with_flags<P: AsRef<Path>>(path: P, flags: OpenFlags) -> Result<Connection> {
let c_path = try!(path_to_cstring(path.as_ref())); let c_path = try!(path_to_cstring(path.as_ref()));
InnerConnection::open_with_flags(&c_path, flags).map(|db| { InnerConnection::open_with_flags(&c_path, flags).map(|db| Connection {
Connection { db: RefCell::new(db),
db: RefCell::new(db), cache: StatementCache::with_capacity(STATEMENT_CACHE_DEFAULT_CAPACITY),
cache: StatementCache::with_capacity(STATEMENT_CACHE_DEFAULT_CAPACITY), path: Some(path.as_ref().to_path_buf()),
path: Some(path.as_ref().to_path_buf()), })
}
})
} }
/// Open a new connection to an in-memory SQLite database. /// Open a new connection to an in-memory SQLite database.
@ -251,13 +250,11 @@ impl Connection {
/// Will return `Err` if the underlying SQLite open call fails. /// Will return `Err` if the underlying SQLite open call fails.
pub fn open_in_memory_with_flags(flags: OpenFlags) -> Result<Connection> { pub fn open_in_memory_with_flags(flags: OpenFlags) -> Result<Connection> {
let c_memory = try!(str_to_cstring(":memory:")); let c_memory = try!(str_to_cstring(":memory:"));
InnerConnection::open_with_flags(&c_memory, flags).map(|db| { InnerConnection::open_with_flags(&c_memory, flags).map(|db| Connection {
Connection { db: RefCell::new(db),
db: RefCell::new(db), cache: StatementCache::with_capacity(STATEMENT_CACHE_DEFAULT_CAPACITY),
cache: StatementCache::with_capacity(STATEMENT_CACHE_DEFAULT_CAPACITY), path: None,
path: None, })
}
})
} }
/// Convenience method to run multiple SQL statements (that cannot take any parameters). /// Convenience method to run multiple SQL statements (that cannot take any parameters).
@ -306,8 +303,7 @@ impl Connection {
/// Will return `Err` if `sql` cannot be converted to a C-compatible string or if the /// Will return `Err` if `sql` cannot be converted to a C-compatible string or if the
/// underlying SQLite call fails. /// underlying SQLite call fails.
pub fn execute(&self, sql: &str, params: &[&ToSql]) -> Result<usize> { pub fn execute(&self, sql: &str, params: &[&ToSql]) -> Result<usize> {
self.prepare(sql) self.prepare(sql).and_then(|mut stmt| stmt.execute(params))
.and_then(|mut stmt| stmt.execute(params))
} }
/// Convenience method to prepare and execute a single SQL statement with named parameter(s). /// Convenience method to prepare and execute a single SQL statement with named parameter(s).
@ -361,7 +357,8 @@ impl Connection {
/// Will return `Err` if `sql` cannot be converted to a C-compatible string or if the /// Will return `Err` if `sql` cannot be converted to a C-compatible string or if the
/// underlying SQLite call fails. /// underlying SQLite call fails.
pub fn query_row<T, F>(&self, sql: &str, params: &[&ToSql], f: F) -> Result<T> pub fn query_row<T, F>(&self, sql: &str, params: &[&ToSql], f: F) -> Result<T>
where F: FnOnce(&Row) -> T where
F: FnOnce(&Row) -> T,
{ {
let mut stmt = try!(self.prepare(sql)); let mut stmt = try!(self.prepare(sql));
stmt.query_row(params, f) stmt.query_row(params, f)
@ -377,7 +374,8 @@ impl Connection {
/// Will return `Err` if `sql` cannot be converted to a C-compatible string or if the /// Will return `Err` if `sql` cannot be converted to a C-compatible string or if the
/// underlying SQLite call fails. /// underlying SQLite call fails.
pub fn query_row_named<T, F>(&self, sql: &str, params: &[(&str, &ToSql)], f: F) -> Result<T> pub fn query_row_named<T, F>(&self, sql: &str, params: &[(&str, &ToSql)], f: F) -> Result<T>
where F: FnOnce(&Row) -> T where
F: FnOnce(&Row) -> T,
{ {
let mut stmt = try!(self.prepare(sql)); let mut stmt = try!(self.prepare(sql));
let mut rows = try!(stmt.query_named(params)); let mut rows = try!(stmt.query_named(params));
@ -408,20 +406,20 @@ impl Connection {
/// ///
/// Will return `Err` if `sql` cannot be converted to a C-compatible string or if the /// Will return `Err` if `sql` cannot be converted to a C-compatible string or if the
/// underlying SQLite call fails. /// underlying SQLite call fails.
pub fn query_row_and_then<T, E, F>(&self, pub fn query_row_and_then<T, E, F>(
sql: &str, &self,
params: &[&ToSql], sql: &str,
f: F) params: &[&ToSql],
-> result::Result<T, E> f: F,
where F: FnOnce(&Row) -> result::Result<T, E>, ) -> result::Result<T, E>
E: convert::From<Error> where
F: FnOnce(&Row) -> result::Result<T, E>,
E: convert::From<Error>,
{ {
let mut stmt = try!(self.prepare(sql)); let mut stmt = try!(self.prepare(sql));
let mut rows = try!(stmt.query(params)); let mut rows = try!(stmt.query(params));
rows.get_expected_row() rows.get_expected_row().map_err(E::from).and_then(|r| f(&r))
.map_err(E::from)
.and_then(|r| f(&r))
} }
/// Convenience method to execute a query that is expected to return a single row. /// Convenience method to execute a query that is expected to return a single row.
@ -445,7 +443,8 @@ impl Connection {
/// does exactly the same thing. /// does exactly the same thing.
#[deprecated(since = "0.1.0", note = "Use query_row instead")] #[deprecated(since = "0.1.0", note = "Use query_row instead")]
pub fn query_row_safe<T, F>(&self, sql: &str, params: &[&ToSql], f: F) -> Result<T> pub fn query_row_safe<T, F>(&self, sql: &str, params: &[&ToSql], f: F) -> Result<T>
where F: FnOnce(&Row) -> T where
F: FnOnce(&Row) -> T,
{ {
self.query_row(sql, params, f) self.query_row(sql, params, f)
} }
@ -545,10 +544,11 @@ impl Connection {
/// ///
/// Will return `Err` if the underlying SQLite call fails. /// Will return `Err` if the underlying SQLite call fails.
#[cfg(feature = "load_extension")] #[cfg(feature = "load_extension")]
pub fn load_extension<P: AsRef<Path>>(&self, pub fn load_extension<P: AsRef<Path>>(
dylib_path: P, &self,
entry_point: Option<&str>) dylib_path: P,
-> Result<()> { entry_point: Option<&str>,
) -> Result<()> {
self.db self.db
.borrow_mut() .borrow_mut()
.load_extension(dylib_path.as_ref(), entry_point) .load_extension(dylib_path.as_ref(), entry_point)
@ -598,11 +598,11 @@ impl fmt::Debug for Connection {
struct InnerConnection { struct InnerConnection {
db: *mut ffi::sqlite3, db: *mut ffi::sqlite3,
#[cfg(feature = "hooks")] #[cfg(feature = "hooks")]
free_commit_hook: Option<fn(*mut c_void)>, free_commit_hook: Option<fn(*mut ::std::os::raw::c_void)>,
#[cfg(feature = "hooks")] #[cfg(feature = "hooks")]
free_rollback_hook: Option<fn(*mut c_void)>, free_rollback_hook: Option<fn(*mut ::std::os::raw::c_void)>,
#[cfg(feature = "hooks")] #[cfg(feature = "hooks")]
free_update_hook: Option<fn(*mut c_void)>, free_update_hook: Option<fn(*mut ::std::os::raw::c_void)>,
} }
/// Old name for `OpenFlags`. `SqliteOpenFlags` is deprecated. /// Old name for `OpenFlags`. `SqliteOpenFlags` is deprecated.
@ -628,8 +628,10 @@ bitflags! {
impl Default for OpenFlags { impl Default for OpenFlags {
fn default() -> OpenFlags { fn default() -> OpenFlags {
OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_CREATE | OpenFlags::SQLITE_OPEN_READ_WRITE
OpenFlags::SQLITE_OPEN_NO_MUTEX | OpenFlags::SQLITE_OPEN_URI | OpenFlags::SQLITE_OPEN_CREATE
| OpenFlags::SQLITE_OPEN_NO_MUTEX
| OpenFlags::SQLITE_OPEN_URI
} }
} }
@ -676,9 +678,11 @@ fn ensure_valid_sqlite_version() {
let buildtime_major = ffi::SQLITE_VERSION_NUMBER / 1_000_000; let buildtime_major = ffi::SQLITE_VERSION_NUMBER / 1_000_000;
let runtime_major = version_number / 1_000_000; let runtime_major = version_number / 1_000_000;
if buildtime_major != runtime_major { if buildtime_major != runtime_major {
panic!("rusqlite was built against SQLite {} but is running with SQLite {}", panic!(
str::from_utf8(ffi::SQLITE_VERSION).unwrap(), "rusqlite was built against SQLite {} but is running with SQLite {}",
version()); str::from_utf8(ffi::SQLITE_VERSION).unwrap(),
version()
);
} }
if BYPASS_VERSION_CHECK.load(Ordering::Relaxed) { if BYPASS_VERSION_CHECK.load(Ordering::Relaxed) {
@ -688,14 +692,16 @@ fn ensure_valid_sqlite_version() {
// Check that the runtime version number is compatible with the version number we found at // Check that the runtime version number is compatible with the version number we found at
// build-time. // build-time.
if version_number < ffi::SQLITE_VERSION_NUMBER { if version_number < ffi::SQLITE_VERSION_NUMBER {
panic!("\ panic!(
"\
rusqlite was built against SQLite {} but the runtime SQLite version is {}. To fix this, either: rusqlite was built against SQLite {} but the runtime SQLite version is {}. To fix this, either:
* Recompile rusqlite and link against the SQLite version you are using at runtime, or * Recompile rusqlite and link against the SQLite version you are using at runtime, or
* Call rusqlite::bypass_sqlite_version_check() prior to your first connection attempt. Doing this * Call rusqlite::bypass_sqlite_version_check() prior to your first connection attempt. Doing this
means you're sure everything will work correctly even though the runtime version is older than means you're sure everything will work correctly even though the runtime version is older than
the version we found at build time.", the version we found at build time.",
str::from_utf8(ffi::SQLITE_VERSION).unwrap(), str::from_utf8(ffi::SQLITE_VERSION).unwrap(),
version()); version()
);
} }
}); });
} }
@ -767,7 +773,12 @@ impl InnerConnection {
} }
#[cfg(feature = "hooks")] #[cfg(feature = "hooks")]
fn new(db: *mut ffi::sqlite3) -> InnerConnection { fn new(db: *mut ffi::sqlite3) -> InnerConnection {
InnerConnection { db, free_commit_hook: None, free_rollback_hook: None, free_update_hook: None } InnerConnection {
db,
free_commit_hook: None,
free_rollback_hook: None,
free_update_hook: None,
}
} }
fn open_with_flags(c_path: &CString, flags: OpenFlags) -> Result<InnerConnection> { fn open_with_flags(c_path: &CString, flags: OpenFlags) -> Result<InnerConnection> {
@ -778,9 +789,15 @@ impl InnerConnection {
// wasn't added until version 3.7.3. // wasn't added until version 3.7.3.
debug_assert_eq!(1 << OpenFlags::SQLITE_OPEN_READ_ONLY.bits, 0x02); debug_assert_eq!(1 << OpenFlags::SQLITE_OPEN_READ_ONLY.bits, 0x02);
debug_assert_eq!(1 << OpenFlags::SQLITE_OPEN_READ_WRITE.bits, 0x04); debug_assert_eq!(1 << OpenFlags::SQLITE_OPEN_READ_WRITE.bits, 0x04);
debug_assert_eq!(1 << (OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_CREATE).bits, 0x40); debug_assert_eq!(
1 << (OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_CREATE).bits,
0x40
);
if (1 << (flags.bits & 0x7)) & 0x46 == 0 { if (1 << (flags.bits & 0x7)) & 0x46 == 0 {
return Err(Error::SqliteFailure(ffi::Error::new(ffi::SQLITE_MISUSE), None)); return Err(Error::SqliteFailure(
ffi::Error::new(ffi::SQLITE_MISUSE),
None,
));
} }
unsafe { unsafe {
@ -841,11 +858,13 @@ impl InnerConnection {
fn execute_batch(&mut self, sql: &str) -> Result<()> { fn execute_batch(&mut self, sql: &str) -> Result<()> {
let c_sql = try!(str_to_cstring(sql)); let c_sql = try!(str_to_cstring(sql));
unsafe { unsafe {
let r = ffi::sqlite3_exec(self.db(), let r = ffi::sqlite3_exec(
c_sql.as_ptr(), self.db(),
None, c_sql.as_ptr(),
ptr::null_mut(), None,
ptr::null_mut()); ptr::null_mut(),
ptr::null_mut(),
);
self.decode_result(r) self.decode_result(r)
} }
} }
@ -863,10 +882,12 @@ impl InnerConnection {
let mut errmsg: *mut c_char = mem::uninitialized(); let mut errmsg: *mut c_char = mem::uninitialized();
let r = if let Some(entry_point) = entry_point { let r = if let Some(entry_point) = entry_point {
let c_entry = try!(str_to_cstring(entry_point)); let c_entry = try!(str_to_cstring(entry_point));
ffi::sqlite3_load_extension(self.db, ffi::sqlite3_load_extension(
dylib_str.as_ptr(), self.db,
c_entry.as_ptr(), dylib_str.as_ptr(),
&mut errmsg) c_entry.as_ptr(),
&mut errmsg,
)
} else { } 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(), &mut errmsg)
}; };
@ -921,9 +942,8 @@ impl InnerConnection {
) )
} }
}; };
self.decode_result(r).map(|_| { self.decode_result(r)
Statement::new(conn, RawStatement::new(c_stmt)) .map(|_| Statement::new(conn, RawStatement::new(c_stmt)))
})
} }
fn changes(&mut self) -> usize { fn changes(&mut self) -> usize {
@ -950,8 +970,7 @@ impl InnerConnection {
} }
#[cfg(not(feature = "hooks"))] #[cfg(not(feature = "hooks"))]
fn remove_hooks(&mut self) { fn remove_hooks(&mut self) {}
}
} }
impl Drop for InnerConnection { impl Drop for InnerConnection {
@ -977,18 +996,16 @@ pub type SqliteStatement<'conn> = Statement<'conn>;
#[deprecated(since = "0.6.0", note = "Use Rows instead")] #[deprecated(since = "0.6.0", note = "Use Rows instead")]
pub type SqliteRows<'stmt> = Rows<'stmt>; pub type SqliteRows<'stmt> = Rows<'stmt>;
/// Old name for `Row`. `SqliteRow` is deprecated. /// Old name for `Row`. `SqliteRow` is deprecated.
#[deprecated(since = "0.6.0", note = "Use Row instead")] #[deprecated(since = "0.6.0", note = "Use Row instead")]
pub type SqliteRow<'a, 'stmt> = Row<'a, 'stmt>; pub type SqliteRow<'a, 'stmt> = Row<'a, 'stmt>;
#[cfg(test)] #[cfg(test)]
mod test { mod test {
extern crate tempdir; extern crate tempdir;
use self::tempdir::TempDir;
pub use super::*; pub use super::*;
use ffi; use ffi;
use self::tempdir::TempDir;
pub use std::error::Error as StdError; pub use std::error::Error as StdError;
pub use std::fmt; pub use std::fmt;
@ -1010,10 +1027,13 @@ mod test {
let tmp = TempDir::new("locked").unwrap(); let tmp = TempDir::new("locked").unwrap();
let path = tmp.path().join("transactions.db3"); let path = tmp.path().join("transactions.db3");
Connection::open(&path).expect("create temp db").execute_batch(" Connection::open(&path)
.expect("create temp db")
.execute_batch(
"
BEGIN; CREATE TABLE foo(x INTEGER); BEGIN; CREATE TABLE foo(x INTEGER);
INSERT INTO foo VALUES(42); END;") INSERT INTO foo VALUES(42); END;",
.expect("create temp db"); ).expect("create temp db");
let mut db1 = Connection::open(&path).unwrap(); let mut db1 = Connection::open(&path).unwrap();
let mut db2 = Connection::open(&path).unwrap(); let mut db2 = Connection::open(&path).unwrap();
@ -1026,8 +1046,12 @@ mod test {
let tx2 = db2.transaction().unwrap(); let tx2 = db2.transaction().unwrap();
// SELECT first makes sqlite lock with a shared lock // SELECT first makes sqlite lock with a shared lock
let _ = tx1.query_row("SELECT x FROM foo LIMIT 1", &[], |_| ()).unwrap(); let _ = tx1
let _ = tx2.query_row("SELECT x FROM foo LIMIT 1", &[], |_| ()).unwrap(); .query_row("SELECT x FROM foo LIMIT 1", &[], |_| ())
.unwrap();
let _ = tx2
.query_row("SELECT x FROM foo LIMIT 1", &[], |_| ())
.unwrap();
tx1.execute("INSERT INTO foo VALUES(?1)", &[&1]).unwrap(); tx1.execute("INSERT INTO foo VALUES(?1)", &[&1]).unwrap();
let _ = tx2.execute("INSERT INTO foo VALUES(?1)", &[&2]); let _ = tx2.execute("INSERT INTO foo VALUES(?1)", &[&2]);
@ -1036,12 +1060,15 @@ mod test {
let _ = tx2.commit(); let _ = tx2.commit();
} }
let _ = db1.transaction().expect("commit should have closed transaction"); let _ = db1
let _ = db2.transaction().expect("commit should have closed transaction"); .transaction()
.expect("commit should have closed transaction");
let _ = db2
.transaction()
.expect("commit should have closed transaction");
} }
#[test] #[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_persistence() { fn test_persistence() {
let temp_dir = TempDir::new("test_open_file").unwrap(); let temp_dir = TempDir::new("test_open_file").unwrap();
let path = temp_dir.path().join("test.db3"); let path = temp_dir.path().join("test.db3");
@ -1077,20 +1104,22 @@ mod test {
// force the DB to be busy by preparing a statement; this must be done at the FFI // force the DB to be busy by preparing a statement; this must be done at the FFI
// level to allow us to call .close() without dropping the prepared statement first. // level to allow us to call .close() without dropping the prepared statement first.
let raw_stmt = { let raw_stmt = {
use std::mem;
use std::ptr;
use std::os::raw::c_int;
use super::str_to_cstring; use super::str_to_cstring;
use std::mem;
use std::os::raw::c_int;
use std::ptr;
let raw_db = db.db.borrow_mut().db; let raw_db = db.db.borrow_mut().db;
let sql = "SELECT 1"; let sql = "SELECT 1";
let mut raw_stmt: *mut ffi::sqlite3_stmt = unsafe { mem::uninitialized() }; let mut raw_stmt: *mut ffi::sqlite3_stmt = unsafe { mem::uninitialized() };
let rc = unsafe { let rc = unsafe {
ffi::sqlite3_prepare_v2(raw_db, ffi::sqlite3_prepare_v2(
str_to_cstring(sql).unwrap().as_ptr(), raw_db,
(sql.len() + 1) as c_int, str_to_cstring(sql).unwrap().as_ptr(),
&mut raw_stmt, (sql.len() + 1) as c_int,
ptr::null_mut()) &mut raw_stmt,
ptr::null_mut(),
)
}; };
assert_eq!(rc, ffi::SQLITE_OK); assert_eq!(rc, ffi::SQLITE_OK);
raw_stmt raw_stmt
@ -1109,15 +1138,16 @@ mod test {
#[test] #[test]
fn test_open_with_flags() { fn test_open_with_flags() {
for bad_flags in &[OpenFlags::empty(), for bad_flags in &[
OpenFlags::SQLITE_OPEN_READ_ONLY | OpenFlags::SQLITE_OPEN_READ_WRITE, OpenFlags::empty(),
OpenFlags::SQLITE_OPEN_READ_ONLY | OpenFlags::SQLITE_OPEN_CREATE] { OpenFlags::SQLITE_OPEN_READ_ONLY | OpenFlags::SQLITE_OPEN_READ_WRITE,
OpenFlags::SQLITE_OPEN_READ_ONLY | OpenFlags::SQLITE_OPEN_CREATE,
] {
assert!(Connection::open_in_memory_with_flags(*bad_flags).is_err()); assert!(Connection::open_in_memory_with_flags(*bad_flags).is_err());
} }
} }
#[test] #[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_execute_batch() { fn test_execute_batch() {
let db = checked_memory_handle(); let db = checked_memory_handle();
let sql = "BEGIN; let sql = "BEGIN;
@ -1129,7 +1159,8 @@ mod test {
END;"; END;";
db.execute_batch(sql).unwrap(); db.execute_batch(sql).unwrap();
db.execute_batch("UPDATE foo SET x = 3 WHERE x < 3").unwrap(); db.execute_batch("UPDATE foo SET x = 3 WHERE x < 3")
.unwrap();
assert!(db.execute_batch("INVALID SQL").is_err()); assert!(db.execute_batch("INVALID SQL").is_err());
} }
@ -1139,16 +1170,22 @@ mod test {
let db = checked_memory_handle(); let db = checked_memory_handle();
db.execute_batch("CREATE TABLE foo(x INTEGER)").unwrap(); db.execute_batch("CREATE TABLE foo(x INTEGER)").unwrap();
assert_eq!(1, assert_eq!(
db.execute("INSERT INTO foo(x) VALUES (?)", &[&1i32]) 1,
.unwrap()); db.execute("INSERT INTO foo(x) VALUES (?)", &[&1i32])
assert_eq!(1, .unwrap()
db.execute("INSERT INTO foo(x) VALUES (?)", &[&2i32]) );
.unwrap()); assert_eq!(
1,
db.execute("INSERT INTO foo(x) VALUES (?)", &[&2i32])
.unwrap()
);
assert_eq!(3i32, assert_eq!(
db.query_row::<i32, _>("SELECT SUM(x) FROM foo", &[], |r| r.get(0)) 3i32,
.unwrap()); db.query_row::<i32, _>("SELECT SUM(x) FROM foo", &[], |r| r.get(0))
.unwrap()
);
} }
#[test] #[test]
@ -1205,7 +1242,8 @@ mod test {
assert_eq!(insert_stmt.execute(&[&2i32]).unwrap(), 1); assert_eq!(insert_stmt.execute(&[&2i32]).unwrap(), 1);
assert_eq!(insert_stmt.execute(&[&3i32]).unwrap(), 1); assert_eq!(insert_stmt.execute(&[&3i32]).unwrap(), 1);
let mut query = db.prepare("SELECT x FROM foo WHERE x < ? ORDER BY x DESC") let mut query = db
.prepare("SELECT x FROM foo WHERE x < ? ORDER BY x DESC")
.unwrap(); .unwrap();
{ {
let mut rows = query.query(&[&4i32]).unwrap(); let mut rows = query.query(&[&4i32]).unwrap();
@ -1231,7 +1269,6 @@ mod test {
} }
#[test] #[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_query_map() { fn test_query_map() {
let db = checked_memory_handle(); let db = checked_memory_handle();
let sql = "BEGIN; let sql = "BEGIN;
@ -1244,15 +1281,13 @@ mod test {
db.execute_batch(sql).unwrap(); db.execute_batch(sql).unwrap();
let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap(); let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap();
let results: Result<Vec<String>> = query.query_map(&[], |row| row.get(1)) let results: Result<Vec<String>> =
.unwrap() query.query_map(&[], |row| row.get(1)).unwrap().collect();
.collect();
assert_eq!(results.unwrap().concat(), "hello, world!"); assert_eq!(results.unwrap().concat(), "hello, world!");
} }
#[test] #[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_query_row() { fn test_query_row() {
let db = checked_memory_handle(); let db = checked_memory_handle();
let sql = "BEGIN; let sql = "BEGIN;
@ -1264,9 +1299,11 @@ mod test {
END;"; END;";
db.execute_batch(sql).unwrap(); db.execute_batch(sql).unwrap();
assert_eq!(10i64, assert_eq!(
db.query_row::<i64, _>("SELECT SUM(x) FROM foo", &[], |r| r.get(0)) 10i64,
.unwrap()); db.query_row::<i64, _>("SELECT SUM(x) FROM foo", &[], |r| r.get(0))
.unwrap()
);
let result: Result<i64> = db.query_row("SELECT x FROM foo WHERE x > 5", &[], |r| r.get(0)); let result: Result<i64> = db.query_row("SELECT x FROM foo WHERE x > 5", &[], |r| r.get(0));
match result.unwrap_err() { match result.unwrap_err() {
@ -1293,8 +1330,7 @@ mod test {
let db = checked_memory_handle(); let db = checked_memory_handle();
db.execute_batch("CREATE TABLE foo(x INTEGER PRIMARY KEY)") db.execute_batch("CREATE TABLE foo(x INTEGER PRIMARY KEY)")
.unwrap(); .unwrap();
db.execute_batch("INSERT INTO foo DEFAULT VALUES") db.execute_batch("INSERT INTO foo DEFAULT VALUES").unwrap();
.unwrap();
assert_eq!(db.last_insert_rowid(), 1); assert_eq!(db.last_insert_rowid(), 1);
@ -1308,8 +1344,10 @@ mod test {
#[test] #[test]
fn test_is_autocommit() { fn test_is_autocommit() {
let db = checked_memory_handle(); let db = checked_memory_handle();
assert!(db.is_autocommit(), assert!(
"autocommit expected to be active by default"); db.is_autocommit(),
"autocommit expected to be active by default"
);
} }
#[test] #[test]
@ -1414,7 +1452,6 @@ mod test {
type CustomResult<T> = ::std::result::Result<T, CustomError>; type CustomResult<T> = ::std::result::Result<T, CustomError>;
#[test] #[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_query_and_then() { fn test_query_and_then() {
let db = checked_memory_handle(); let db = checked_memory_handle();
let sql = "BEGIN; let sql = "BEGIN;
@ -1427,8 +1464,8 @@ mod test {
db.execute_batch(sql).unwrap(); db.execute_batch(sql).unwrap();
let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap(); let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap();
let results: Result<Vec<String>> = query.query_and_then(&[], let results: Result<Vec<String>> = query
|row| row.get_checked(1)) .query_and_then(&[], |row| row.get_checked(1))
.unwrap() .unwrap()
.collect(); .collect();
@ -1436,7 +1473,6 @@ mod test {
} }
#[test] #[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_query_and_then_fails() { fn test_query_and_then_fails() {
let db = checked_memory_handle(); let db = checked_memory_handle();
let sql = "BEGIN; let sql = "BEGIN;
@ -1449,7 +1485,8 @@ mod test {
db.execute_batch(sql).unwrap(); db.execute_batch(sql).unwrap();
let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap(); 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(&[], |row| row.get_checked(1)) let bad_type: Result<Vec<f64>> = query
.query_and_then(&[], |row| row.get_checked(1))
.unwrap() .unwrap()
.collect(); .collect();
@ -1458,7 +1495,8 @@ mod test {
err => panic!("Unexpected error {}", err), err => panic!("Unexpected error {}", err),
} }
let bad_idx: Result<Vec<String>> = query.query_and_then(&[], |row| row.get_checked(3)) let bad_idx: Result<Vec<String>> = query
.query_and_then(&[], |row| row.get_checked(3))
.unwrap() .unwrap()
.collect(); .collect();
@ -1469,7 +1507,6 @@ mod test {
} }
#[test] #[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_query_and_then_custom_error() { fn test_query_and_then_custom_error() {
let db = checked_memory_handle(); let db = checked_memory_handle();
let sql = "BEGIN; let sql = "BEGIN;
@ -1482,18 +1519,15 @@ mod test {
db.execute_batch(sql).unwrap(); db.execute_batch(sql).unwrap();
let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap(); let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap();
let results: CustomResult<Vec<String>> = query.query_and_then(&[], |row| { let results: CustomResult<Vec<String>> = query
row.get_checked(1) .query_and_then(&[], |row| row.get_checked(1).map_err(CustomError::Sqlite))
.map_err(CustomError::Sqlite) .unwrap()
})
.unwrap()
.collect(); .collect();
assert_eq!(results.unwrap().concat(), "hello, world!"); assert_eq!(results.unwrap().concat(), "hello, world!");
} }
#[test] #[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_query_and_then_custom_error_fails() { fn test_query_and_then_custom_error_fails() {
let db = checked_memory_handle(); let db = checked_memory_handle();
let sql = "BEGIN; let sql = "BEGIN;
@ -1506,11 +1540,9 @@ mod test {
db.execute_batch(sql).unwrap(); db.execute_batch(sql).unwrap();
let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap(); 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(&[], |row| { let bad_type: CustomResult<Vec<f64>> = query
row.get_checked(1) .query_and_then(&[], |row| row.get_checked(1).map_err(CustomError::Sqlite))
.map_err(CustomError::Sqlite) .unwrap()
})
.unwrap()
.collect(); .collect();
match bad_type.unwrap_err() { match bad_type.unwrap_err() {
@ -1518,11 +1550,9 @@ mod test {
err => panic!("Unexpected error {}", err), err => panic!("Unexpected error {}", err),
} }
let bad_idx: CustomResult<Vec<String>> = query.query_and_then(&[], |row| { let bad_idx: CustomResult<Vec<String>> = query
row.get_checked(3) .query_and_then(&[], |row| row.get_checked(3).map_err(CustomError::Sqlite))
.map_err(CustomError::Sqlite) .unwrap()
})
.unwrap()
.collect(); .collect();
match bad_idx.unwrap_err() { match bad_idx.unwrap_err() {
@ -1530,10 +1560,9 @@ mod test {
err => panic!("Unexpected error {}", err), err => panic!("Unexpected error {}", err),
} }
let non_sqlite_err: CustomResult<Vec<String>> = query.query_and_then(&[], |_| { let non_sqlite_err: CustomResult<Vec<String>> = query
Err(CustomError::SomeError) .query_and_then(&[], |_| Err(CustomError::SomeError))
}) .unwrap()
.unwrap()
.collect(); .collect();
match non_sqlite_err.unwrap_err() { match non_sqlite_err.unwrap_err() {
@ -1543,7 +1572,6 @@ mod test {
} }
#[test] #[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_query_row_and_then_custom_error() { fn test_query_row_and_then_custom_error() {
let db = checked_memory_handle(); let db = checked_memory_handle();
let sql = "BEGIN; let sql = "BEGIN;
@ -1561,7 +1589,6 @@ mod test {
} }
#[test] #[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_query_row_and_then_custom_error_fails() { fn test_query_row_and_then_custom_error_fails() {
let db = checked_memory_handle(); let db = checked_memory_handle();
let sql = "BEGIN; let sql = "BEGIN;
@ -1589,9 +1616,8 @@ mod test {
err => panic!("Unexpected error {}", err), err => panic!("Unexpected error {}", err),
} }
let non_sqlite_err: CustomResult<String> = db.query_row_and_then(query, &[], |_| { let non_sqlite_err: CustomResult<String> =
Err(CustomError::SomeError) db.query_row_and_then(query, &[], |_| Err(CustomError::SomeError));
});
match non_sqlite_err.unwrap_err() { match non_sqlite_err.unwrap_err() {
CustomError::SomeError => (), CustomError::SomeError => (),
@ -1600,7 +1626,6 @@ mod test {
} }
#[test] #[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_dynamic() { fn test_dynamic() {
let db = checked_memory_handle(); let db = checked_memory_handle();
let sql = "BEGIN; let sql = "BEGIN;
@ -1609,7 +1634,9 @@ mod test {
END;"; END;";
db.execute_batch(sql).unwrap(); db.execute_batch(sql).unwrap();
db.query_row("SELECT * FROM foo", &[], |r| assert_eq!(2, r.column_count())).unwrap(); db.query_row("SELECT * FROM foo", &[], |r| {
assert_eq!(2, r.column_count())
}).unwrap();
} }
} }
} }

View File

@ -1,4 +1,4 @@
use {Result, Connection}; use {Connection, Result};
/// Old name for `LoadExtensionGuard`. `SqliteLoadExtensionGuard` is deprecated. /// Old name for `LoadExtensionGuard`. `SqliteLoadExtensionGuard` is deprecated.
#[deprecated(since = "0.6.0", note = "Use LoadExtensionGuard instead")] #[deprecated(since = "0.6.0", note = "Use LoadExtensionGuard instead")]

View File

@ -1,8 +1,8 @@
use std::ffi::CStr;
use std::ptr;
use std::os::raw::c_int;
use super::ffi; use super::ffi;
use super::unlock_notify; use super::unlock_notify;
use std::ffi::CStr;
use std::os::raw::c_int;
use std::ptr;
// Private newtype for raw sqlite3_stmts that finalize themselves when dropped. // Private newtype for raw sqlite3_stmts that finalize themselves when dropped.
#[derive(Debug)] #[derive(Debug)]

View File

@ -1,7 +1,7 @@
use std::{convert, result};
use std::marker::PhantomData; use std::marker::PhantomData;
use std::{convert, result};
use super::{Statement, Error, Result}; use super::{Error, Result, Statement};
use types::{FromSql, FromSqlError}; use types::{FromSql, FromSqlError};
/// An handle for the resulting rows of a query. /// An handle for the resulting rows of a query.
@ -28,23 +28,20 @@ impl<'stmt> Rows<'stmt> {
/// or `query_and_then` instead, which return types that implement `Iterator`. /// or `query_and_then` instead, which return types that implement `Iterator`.
#[cfg_attr(feature = "cargo-clippy", allow(should_implement_trait))] // cannot implement Iterator #[cfg_attr(feature = "cargo-clippy", allow(should_implement_trait))] // cannot implement Iterator
pub fn next<'a>(&'a mut self) -> Option<Result<Row<'a, 'stmt>>> { pub fn next<'a>(&'a mut self) -> Option<Result<Row<'a, 'stmt>>> {
self.stmt self.stmt.and_then(|stmt| match stmt.step() {
.and_then(|stmt| match stmt.step() { Ok(true) => Some(Ok(Row {
Ok(true) => { stmt,
Some(Ok(Row { phantom: PhantomData,
stmt, })),
phantom: PhantomData, Ok(false) => {
})) self.reset();
} None
Ok(false) => { }
self.reset(); Err(err) => {
None self.reset();
} Some(Err(err))
Err(err) => { }
self.reset(); })
Some(Err(err))
}
})
} }
} }
@ -74,7 +71,8 @@ pub struct MappedRows<'stmt, F> {
} }
impl<'stmt, T, F> MappedRows<'stmt, F> impl<'stmt, T, F> MappedRows<'stmt, F>
where F: FnMut(&Row) -> T where
F: FnMut(&Row) -> T,
{ {
pub(crate) fn new(rows: Rows<'stmt>, f: F) -> MappedRows<'stmt, F> { pub(crate) fn new(rows: Rows<'stmt>, f: F) -> MappedRows<'stmt, F> {
MappedRows { rows, map: f } MappedRows { rows, map: f }
@ -82,7 +80,8 @@ impl<'stmt, T, F> MappedRows<'stmt, F>
} }
impl<'conn, T, F> Iterator for MappedRows<'conn, F> impl<'conn, T, F> Iterator for MappedRows<'conn, F>
where F: FnMut(&Row) -> T where
F: FnMut(&Row) -> T,
{ {
type Item = Result<T>; type Item = Result<T>;
@ -102,7 +101,8 @@ pub struct AndThenRows<'stmt, F> {
} }
impl<'stmt, T, E, F> AndThenRows<'stmt, F> impl<'stmt, T, E, F> AndThenRows<'stmt, F>
where F: FnMut(&Row) -> result::Result<T, E> where
F: FnMut(&Row) -> result::Result<T, E>,
{ {
pub(crate) fn new(rows: Rows<'stmt>, f: F) -> AndThenRows<'stmt, F> { pub(crate) fn new(rows: Rows<'stmt>, f: F) -> AndThenRows<'stmt, F> {
AndThenRows { rows, map: f } AndThenRows { rows, map: f }
@ -110,8 +110,9 @@ impl<'stmt, T, E, F> AndThenRows<'stmt, F>
} }
impl<'stmt, T, E, F> Iterator for AndThenRows<'stmt, F> impl<'stmt, T, E, F> Iterator for AndThenRows<'stmt, F>
where E: convert::From<Error>, where
F: FnMut(&Row) -> result::Result<T, E> E: convert::From<Error>,
F: FnMut(&Row) -> result::Result<T, E>,
{ {
type Item = result::Result<T, E>; type Item = result::Result<T, E>;
@ -159,17 +160,12 @@ impl<'a, 'stmt> Row<'a, 'stmt> {
let idx = try!(idx.idx(self.stmt)); let idx = try!(idx.idx(self.stmt));
let value = self.stmt.value_ref(idx); let value = self.stmt.value_ref(idx);
FromSql::column_result(value).map_err(|err| match err { FromSql::column_result(value).map_err(|err| match err {
FromSqlError::InvalidType => { FromSqlError::InvalidType => Error::InvalidColumnType(idx, value.data_type()),
Error::InvalidColumnType(idx, FromSqlError::OutOfRange(i) => Error::IntegralValueOutOfRange(idx, i),
value.data_type()) FromSqlError::Other(err) => {
}
FromSqlError::OutOfRange(i) => {
Error::IntegralValueOutOfRange(idx, i)
}
FromSqlError::Other(err) => {
Error::FromSqlConversionFailure(idx as usize, value.data_type(), err) Error::FromSqlConversionFailure(idx as usize, value.data_type(), err)
} }
}) })
} }
/// Return the number of columns in the current row. /// Return the number of columns in the current row.

View File

@ -1,11 +1,13 @@
use std::{convert, fmt, mem, ptr, result, str};
use std::ffi::CStr; use std::ffi::CStr;
use std::os::raw::{c_char, c_int, c_void}; use std::os::raw::{c_char, c_int, c_void};
use std::slice::from_raw_parts; use std::slice::from_raw_parts;
use std::{convert, fmt, mem, ptr, result, str};
use super::ffi; use super::ffi;
use super::{Connection, RawStatement, Result, Error, ValueRef, Row, Rows, AndThenRows, MappedRows};
use super::str_to_cstring; use super::str_to_cstring;
use super::{
AndThenRows, Connection, Error, MappedRows, RawStatement, Result, Row, Rows, ValueRef,
};
use types::{ToSql, ToSqlOutput}; use types::{ToSql, ToSqlOutput};
/// A prepared statement. /// A prepared statement.
@ -211,7 +213,8 @@ impl<'conn> Statement<'conn> {
/// ///
/// Will return `Err` if binding parameters fails. /// Will return `Err` if binding parameters fails.
pub fn query_map<'a, T, F>(&'a mut self, params: &[&ToSql], f: F) -> Result<MappedRows<'a, F>> pub fn query_map<'a, T, F>(&'a mut self, params: &[&ToSql], f: F) -> Result<MappedRows<'a, F>>
where F: FnMut(&Row) -> T where
F: FnMut(&Row) -> T,
{ {
let rows = self.query(params)?; let rows = self.query(params)?;
Ok(MappedRows::new(rows, f)) Ok(MappedRows::new(rows, f))
@ -243,11 +246,13 @@ impl<'conn> Statement<'conn> {
/// ## Failure /// ## Failure
/// ///
/// Will return `Err` if binding parameters fails. /// Will return `Err` if binding parameters fails.
pub fn query_map_named<'a, T, F>(&'a mut self, pub fn query_map_named<'a, T, F>(
params: &[(&str, &ToSql)], &'a mut self,
f: F) params: &[(&str, &ToSql)],
-> Result<MappedRows<'a, F>> f: F,
where F: FnMut(&Row) -> T ) -> Result<MappedRows<'a, F>>
where
F: FnMut(&Row) -> T,
{ {
let rows = self.query_named(params)?; let rows = self.query_named(params)?;
Ok(MappedRows::new(rows, f)) Ok(MappedRows::new(rows, f))
@ -260,12 +265,14 @@ impl<'conn> Statement<'conn> {
/// # Failure /// # Failure
/// ///
/// Will return `Err` if binding parameters fails. /// Will return `Err` if binding parameters fails.
pub fn query_and_then<'a, T, E, F>(&'a mut self, pub fn query_and_then<'a, T, E, F>(
params: &[&ToSql], &'a mut self,
f: F) params: &[&ToSql],
-> Result<AndThenRows<'a, F>> f: F,
where E: convert::From<Error>, ) -> Result<AndThenRows<'a, F>>
F: FnMut(&Row) -> result::Result<T, E> where
E: convert::From<Error>,
F: FnMut(&Row) -> result::Result<T, E>,
{ {
let rows = self.query(params)?; let rows = self.query(params)?;
Ok(AndThenRows::new(rows, f)) Ok(AndThenRows::new(rows, f))
@ -306,12 +313,14 @@ impl<'conn> Statement<'conn> {
/// ## Failure /// ## Failure
/// ///
/// Will return `Err` if binding parameters fails. /// 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<'a, T, E, F>(
params: &[(&str, &ToSql)], &'a mut self,
f: F) params: &[(&str, &ToSql)],
-> Result<AndThenRows<'a, F>> f: F,
where E: convert::From<Error>, ) -> Result<AndThenRows<'a, F>>
F: FnMut(&Row) -> result::Result<T, E> where
E: convert::From<Error>,
F: FnMut(&Row) -> result::Result<T, E>,
{ {
let rows = self.query_named(params)?; let rows = self.query_named(params)?;
Ok(AndThenRows::new(rows, f)) Ok(AndThenRows::new(rows, f))
@ -338,7 +347,8 @@ impl<'conn> Statement<'conn> {
/// ///
/// Will return `Err` if the underlying SQLite call fails. /// Will return `Err` if the underlying SQLite call fails.
pub fn query_row<T, F>(&mut self, params: &[&ToSql], f: F) -> Result<T> pub fn query_row<T, F>(&mut self, params: &[&ToSql], f: F) -> Result<T>
where F: FnOnce(&Row) -> T where
F: FnOnce(&Row) -> T,
{ {
let mut rows = try!(self.query(params)); let mut rows = try!(self.query(params));
@ -369,10 +379,13 @@ impl<'conn> Statement<'conn> {
} }
fn bind_parameters(&mut self, params: &[&ToSql]) -> Result<()> { fn bind_parameters(&mut self, params: &[&ToSql]) -> Result<()> {
assert_eq!(params.len(), self.stmt.bind_parameter_count(), assert_eq!(
"incorrect number of parameters to query(): expected {}, got {}", params.len(),
self.stmt.bind_parameter_count(), self.stmt.bind_parameter_count(),
params.len()); "incorrect number of parameters to query(): expected {}, got {}",
self.stmt.bind_parameter_count(),
params.len()
);
for (i, p) in params.iter().enumerate() { for (i, p) in params.iter().enumerate() {
try!(self.bind_parameter(*p, i + 1)); try!(self.bind_parameter(*p, i + 1));
@ -402,20 +415,16 @@ impl<'conn> Statement<'conn> {
#[cfg(feature = "blob")] #[cfg(feature = "blob")]
ToSqlOutput::ZeroBlob(len) => { ToSqlOutput::ZeroBlob(len) => {
return self.conn return self
.decode_result(unsafe { ffi::sqlite3_bind_zeroblob(ptr, col as c_int, len) }); .conn
.decode_result(unsafe { ffi::sqlite3_bind_zeroblob(ptr, col as c_int, len) });
} }
}; };
self.conn self.conn.decode_result(match value {
.decode_result(match value { ValueRef::Null => unsafe { ffi::sqlite3_bind_null(ptr, col as c_int) },
ValueRef::Null => unsafe { ffi::sqlite3_bind_null(ptr, col as c_int) }, ValueRef::Integer(i) => unsafe { ffi::sqlite3_bind_int64(ptr, col as c_int, i) },
ValueRef::Integer(i) => unsafe { ValueRef::Real(r) => unsafe { ffi::sqlite3_bind_double(ptr, col as c_int, r) },
ffi::sqlite3_bind_int64(ptr, col as c_int, i) ValueRef::Text(s) => unsafe {
},
ValueRef::Real(r) => unsafe {
ffi::sqlite3_bind_double(ptr, col as c_int, r)
},
ValueRef::Text(s) => unsafe {
let length = s.len(); let length = s.len();
if length > ::std::i32::MAX as usize { if length > ::std::i32::MAX as usize {
ffi::SQLITE_TOOBIG ffi::SQLITE_TOOBIG
@ -426,24 +435,32 @@ impl<'conn> Statement<'conn> {
} else { } else {
ffi::SQLITE_STATIC() ffi::SQLITE_STATIC()
}; };
ffi::sqlite3_bind_text(ptr, col as c_int, c_str.as_ptr(), length as c_int, destructor) ffi::sqlite3_bind_text(
ptr,
col as c_int,
c_str.as_ptr(),
length as c_int,
destructor,
)
} }
}, },
ValueRef::Blob(b) => unsafe { ValueRef::Blob(b) => unsafe {
let length = b.len(); let length = b.len();
if length > ::std::i32::MAX as usize { if length > ::std::i32::MAX as usize {
ffi::SQLITE_TOOBIG ffi::SQLITE_TOOBIG
} else if length == 0 { } else if length == 0 {
ffi::sqlite3_bind_zeroblob(ptr, col as c_int, 0) ffi::sqlite3_bind_zeroblob(ptr, col as c_int, 0)
} else { } else {
ffi::sqlite3_bind_blob(ptr, ffi::sqlite3_bind_blob(
col as c_int, ptr,
b.as_ptr() as *const c_void, col as c_int,
length as c_int, b.as_ptr() as *const c_void,
ffi::SQLITE_TRANSIENT()) length as c_int,
ffi::SQLITE_TRANSIENT(),
)
} }
}, },
}) })
} }
fn execute_with_bound_parameters(&mut self) -> Result<usize> { fn execute_with_bound_parameters(&mut self) -> Result<usize> {
@ -485,7 +502,9 @@ impl<'conn> Statement<'conn> {
#[cfg(feature = "bundled")] #[cfg(feature = "bundled")]
pub fn expanded_sql(&self) -> Option<&str> { pub fn expanded_sql(&self) -> Option<&str> {
unsafe { unsafe {
self.stmt.expanded_sql().map(|s| str::from_utf8_unchecked(s.to_bytes())) self.stmt
.expanded_sql()
.map(|s| str::from_utf8_unchecked(s.to_bytes()))
} }
} }
} }
@ -518,10 +537,7 @@ impl<'conn> Drop for Statement<'conn> {
impl<'conn> Statement<'conn> { impl<'conn> Statement<'conn> {
pub(crate) fn new(conn: &Connection, stmt: RawStatement) -> Statement { pub(crate) fn new(conn: &Connection, stmt: RawStatement) -> Statement {
Statement { Statement { conn, stmt }
conn,
stmt,
}
} }
pub(crate) fn value_ref(&self, col: usize) -> ValueRef { pub(crate) fn value_ref(&self, col: usize) -> ValueRef {
@ -532,30 +548,42 @@ impl<'conn> Statement<'conn> {
ffi::SQLITE_INTEGER => { ffi::SQLITE_INTEGER => {
ValueRef::Integer(unsafe { ffi::sqlite3_column_int64(raw, col as c_int) }) ValueRef::Integer(unsafe { ffi::sqlite3_column_int64(raw, col as c_int) })
} }
ffi::SQLITE_FLOAT => ValueRef::Real(unsafe { ffi::sqlite3_column_double(raw, col as c_int) }), ffi::SQLITE_FLOAT => {
ValueRef::Real(unsafe { ffi::sqlite3_column_double(raw, col as c_int) })
}
ffi::SQLITE_TEXT => { ffi::SQLITE_TEXT => {
let s = unsafe { let s = unsafe {
let text = ffi::sqlite3_column_text(raw, col as c_int); let text = ffi::sqlite3_column_text(raw, col as c_int);
assert!(!text.is_null(), assert!(
"unexpected SQLITE_TEXT column type with NULL data"); !text.is_null(),
"unexpected SQLITE_TEXT column type with NULL data"
);
CStr::from_ptr(text as *const c_char) CStr::from_ptr(text as *const c_char)
}; };
// sqlite3_column_text returns UTF8 data, so our unwrap here should be fine. // sqlite3_column_text returns UTF8 data, so our unwrap here should be fine.
let s = s.to_str() let s = s
.to_str()
.expect("sqlite3_column_text returned invalid UTF-8"); .expect("sqlite3_column_text returned invalid UTF-8");
ValueRef::Text(s) ValueRef::Text(s)
} }
ffi::SQLITE_BLOB => { ffi::SQLITE_BLOB => {
let (blob, len) = unsafe { let (blob, len) = unsafe {
(ffi::sqlite3_column_blob(raw, col as c_int), ffi::sqlite3_column_bytes(raw, col as c_int)) (
ffi::sqlite3_column_blob(raw, col as c_int),
ffi::sqlite3_column_bytes(raw, col as c_int),
)
}; };
assert!(len >= 0, assert!(
"unexpected negative return from sqlite3_column_bytes"); len >= 0,
"unexpected negative return from sqlite3_column_bytes"
);
if len > 0 { if len > 0 {
assert!(!blob.is_null(), assert!(
"unexpected SQLITE_BLOB column type with NULL data"); !blob.is_null(),
"unexpected SQLITE_BLOB column type with NULL data"
);
ValueRef::Blob(unsafe { from_raw_parts(blob as *const u8, len as usize) }) ValueRef::Blob(unsafe { from_raw_parts(blob as *const u8, len as usize) })
} else { } else {
// The return value from sqlite3_column_blob() for a zero-length BLOB // The return value from sqlite3_column_blob() for a zero-length BLOB
@ -589,18 +617,25 @@ mod test {
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
db.execute_batch("CREATE TABLE foo(x INTEGER)").unwrap(); db.execute_batch("CREATE TABLE foo(x INTEGER)").unwrap();
assert_eq!(db.execute_named("INSERT INTO foo(x) VALUES (:x)", &[(":x", &1i32)]) assert_eq!(
.unwrap(), db.execute_named("INSERT INTO foo(x) VALUES (:x)", &[(":x", &1i32)])
1); .unwrap(),
assert_eq!(db.execute_named("INSERT INTO foo(x) VALUES (:x)", &[(":x", &2i32)]) 1
.unwrap(), );
1); assert_eq!(
db.execute_named("INSERT INTO foo(x) VALUES (:x)", &[(":x", &2i32)])
.unwrap(),
1
);
assert_eq!(3i32, assert_eq!(
db.query_row_named::<i32, _>("SELECT SUM(x) FROM foo WHERE x > :x", 3i32,
&[(":x", &0i32)], db.query_row_named::<i32, _>(
|r| r.get(0)) "SELECT SUM(x) FROM foo WHERE x > :x",
.unwrap()); &[(":x", &0i32)],
|r| r.get(0)
).unwrap()
);
} }
#[test] #[test]
@ -610,15 +645,19 @@ mod test {
INTEGER)"; INTEGER)";
db.execute_batch(sql).unwrap(); db.execute_batch(sql).unwrap();
let mut stmt = db.prepare("INSERT INTO test (name) VALUES (:name)") let mut stmt = db
.prepare("INSERT INTO test (name) VALUES (:name)")
.unwrap(); .unwrap();
stmt.execute_named(&[(":name", &"one")]).unwrap(); stmt.execute_named(&[(":name", &"one")]).unwrap();
assert_eq!(1i32, assert_eq!(
db.query_row_named::<i32, _>("SELECT COUNT(*) FROM test WHERE name = :name", 1i32,
&[(":name", &"one")], db.query_row_named::<i32, _>(
|r| r.get(0)) "SELECT COUNT(*) FROM test WHERE name = :name",
.unwrap()); &[(":name", &"one")],
|r| r.get(0)
).unwrap()
);
} }
#[test] #[test]
@ -630,7 +669,8 @@ mod test {
"#; "#;
db.execute_batch(sql).unwrap(); db.execute_batch(sql).unwrap();
let mut stmt = db.prepare("SELECT id FROM test where name = :name") let mut stmt = db
.prepare("SELECT id FROM test where name = :name")
.unwrap(); .unwrap();
let mut rows = stmt.query_named(&[(":name", &"one")]).unwrap(); let mut rows = stmt.query_named(&[(":name", &"one")]).unwrap();
@ -647,13 +687,14 @@ mod test {
"#; "#;
db.execute_batch(sql).unwrap(); db.execute_batch(sql).unwrap();
let mut stmt = db.prepare("SELECT id FROM test where name = :name") let mut stmt = db
.prepare("SELECT id FROM test where name = :name")
.unwrap(); .unwrap();
let mut rows = stmt.query_map_named(&[(":name", &"one")], |row| { let mut rows = stmt
.query_map_named(&[(":name", &"one")], |row| {
let id: i32 = row.get(0); let id: i32 = row.get(0);
2 * id 2 * id
}) }).unwrap();
.unwrap();
let doubled_id: i32 = rows.next().unwrap().unwrap(); let doubled_id: i32 = rows.next().unwrap().unwrap();
assert_eq!(2, doubled_id); assert_eq!(2, doubled_id);
@ -661,7 +702,6 @@ mod test {
#[test] #[test]
fn test_query_and_then_named() { fn test_query_and_then_named() {
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
let sql = r#" let sql = r#"
CREATE TABLE test (id INTEGER PRIMARY KEY NOT NULL, name TEXT NOT NULL, flag INTEGER); CREATE TABLE test (id INTEGER PRIMARY KEY NOT NULL, name TEXT NOT NULL, flag INTEGER);
@ -670,17 +710,18 @@ mod test {
"#; "#;
db.execute_batch(sql).unwrap(); db.execute_batch(sql).unwrap();
let mut stmt = db.prepare("SELECT id FROM test where name = :name ORDER BY id ASC") let mut stmt = db
.prepare("SELECT id FROM test where name = :name ORDER BY id ASC")
.unwrap(); .unwrap();
let mut rows = stmt.query_and_then_named(&[(":name", &"one")], |row| { 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 { if id == 1 {
Ok(id) Ok(id)
} else { } else {
Err(Error::SqliteSingleThreadedMode) Err(Error::SqliteSingleThreadedMode)
} }
}) }).unwrap();
.unwrap();
// first row should be Ok // first row should be Ok
let doubled_id: i32 = rows.next().unwrap().unwrap(); let doubled_id: i32 = rows.next().unwrap().unwrap();
@ -700,13 +741,14 @@ mod test {
let sql = "CREATE TABLE test (x TEXT, y TEXT)"; let sql = "CREATE TABLE test (x TEXT, y TEXT)";
db.execute_batch(sql).unwrap(); db.execute_batch(sql).unwrap();
let mut stmt = db.prepare("INSERT INTO test (x, y) VALUES (:x, :y)") let mut stmt = db
.prepare("INSERT INTO test (x, y) VALUES (:x, :y)")
.unwrap(); .unwrap();
stmt.execute_named(&[(":x", &"one")]).unwrap(); stmt.execute_named(&[(":x", &"one")]).unwrap();
let result: Option<String> = let result: Option<String> = db
db.query_row("SELECT y FROM test WHERE x = 'one'", &[], |row| row.get(0)) .query_row("SELECT y FROM test WHERE x = 'one'", &[], |row| row.get(0))
.unwrap(); .unwrap();
assert!(result.is_none()); assert!(result.is_none());
} }
@ -716,14 +758,15 @@ mod test {
let sql = "CREATE TABLE test (x TEXT, y TEXT)"; let sql = "CREATE TABLE test (x TEXT, y TEXT)";
db.execute_batch(sql).unwrap(); db.execute_batch(sql).unwrap();
let mut stmt = db.prepare("INSERT INTO test (x, y) VALUES (:x, :y)") let mut stmt = db
.prepare("INSERT INTO test (x, y) VALUES (:x, :y)")
.unwrap(); .unwrap();
stmt.execute_named(&[(":x", &"one")]).unwrap(); stmt.execute_named(&[(":x", &"one")]).unwrap();
stmt.execute_named(&[(":y", &"two")]).unwrap(); stmt.execute_named(&[(":y", &"two")]).unwrap();
let result: String = let result: String = db
db.query_row("SELECT x FROM test WHERE y = 'two'", &[], |row| row.get(0)) .query_row("SELECT x FROM test WHERE y = 'two'", &[], |row| row.get(0))
.unwrap(); .unwrap();
assert_eq!(result, "one"); assert_eq!(result, "one");
} }
@ -732,7 +775,8 @@ mod test {
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
db.execute_batch("CREATE TABLE foo(x INTEGER UNIQUE)") db.execute_batch("CREATE TABLE foo(x INTEGER UNIQUE)")
.unwrap(); .unwrap();
let mut stmt = db.prepare("INSERT OR IGNORE INTO foo (x) VALUES (?)") let mut stmt = db
.prepare("INSERT OR IGNORE INTO foo (x) VALUES (?)")
.unwrap(); .unwrap();
assert_eq!(stmt.insert(&[&1i32]).unwrap(), 1); assert_eq!(stmt.insert(&[&1i32]).unwrap(), 1);
assert_eq!(stmt.insert(&[&2i32]).unwrap(), 2); assert_eq!(stmt.insert(&[&2i32]).unwrap(), 2);
@ -740,7 +784,8 @@ mod test {
Error::StatementChangedRows(0) => (), Error::StatementChangedRows(0) => (),
err => panic!("Unexpected error {}", err), err => panic!("Unexpected error {}", err),
} }
let mut multi = db.prepare("INSERT INTO foo (x) SELECT 3 UNION ALL SELECT 4") let mut multi = db
.prepare("INSERT INTO foo (x) SELECT 3 UNION ALL SELECT 4")
.unwrap(); .unwrap();
match multi.insert(&[]).unwrap_err() { match multi.insert(&[]).unwrap_err() {
Error::StatementChangedRows(2) => (), Error::StatementChangedRows(2) => (),
@ -752,22 +797,27 @@ mod test {
fn test_insert_different_tables() { fn test_insert_different_tables() {
// Test for https://github.com/jgallagher/rusqlite/issues/171 // Test for https://github.com/jgallagher/rusqlite/issues/171
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
db.execute_batch(r" db.execute_batch(
r"
CREATE TABLE foo(x INTEGER); CREATE TABLE foo(x INTEGER);
CREATE TABLE bar(x INTEGER); CREATE TABLE bar(x INTEGER);
") ",
.unwrap(); ).unwrap();
assert_eq!(db.prepare("INSERT INTO foo VALUES (10)") assert_eq!(
.unwrap() db.prepare("INSERT INTO foo VALUES (10)")
.insert(&[]) .unwrap()
.unwrap(), .insert(&[])
1); .unwrap(),
assert_eq!(db.prepare("INSERT INTO bar VALUES (10)") 1
.unwrap() );
.insert(&[]) assert_eq!(
.unwrap(), db.prepare("INSERT INTO bar VALUES (10)")
1); .unwrap()
.insert(&[])
.unwrap(),
1
);
} }
#[test] #[test]

View File

@ -1,14 +1,14 @@
//! Tracing and profiling functions. Error and warning log. //! Tracing and profiling functions. Error and warning log.
use std::os::raw::{c_char, c_int, c_void};
use std::ffi::{CStr, CString}; use std::ffi::{CStr, CString};
use std::mem; use std::mem;
use std::os::raw::{c_char, c_int, c_void};
use std::ptr; use std::ptr;
use std::time::Duration; use std::time::Duration;
use super::ffi; use super::ffi;
use {Result, Connection};
use error::error_from_sqlite_code; use error::error_from_sqlite_code;
use {Connection, Result};
/// Set up the process-wide SQLite error logging callback. /// Set up the process-wide SQLite error logging callback.
/// This function is marked unsafe for two reasons: /// This function is marked unsafe for two reasons:
@ -33,9 +33,11 @@ pub unsafe fn config_log(callback: Option<fn(c_int, &str)>) -> Result<()> {
let rc = match callback { let rc = match callback {
Some(f) => { Some(f) => {
let p_arg: *mut c_void = mem::transmute(f); let p_arg: *mut c_void = mem::transmute(f);
ffi::sqlite3_config(ffi::SQLITE_CONFIG_LOG, ffi::sqlite3_config(
log_callback as extern "C" fn(_, _, _), ffi::SQLITE_CONFIG_LOG,
p_arg) log_callback as extern "C" fn(_, _, _),
p_arg,
)
} }
None => { None => {
let nullptr: *mut c_void = ptr::null_mut(); let nullptr: *mut c_void = ptr::null_mut();
@ -90,16 +92,20 @@ impl Connection {
/// There can only be a single profiler defined for each database connection. /// There can only be a single profiler defined for each database connection.
/// Setting a new profiler clears the old one. /// Setting a new profiler clears the old one.
pub fn profile(&mut self, profile_fn: Option<fn(&str, Duration)>) { pub fn profile(&mut self, profile_fn: Option<fn(&str, Duration)>) {
unsafe extern "C" fn profile_callback(p_arg: *mut c_void, unsafe extern "C" fn profile_callback(
z_sql: *const c_char, p_arg: *mut c_void,
nanoseconds: u64) { z_sql: *const c_char,
nanoseconds: u64,
) {
let profile_fn: fn(&str, Duration) = mem::transmute(p_arg); let profile_fn: fn(&str, Duration) = mem::transmute(p_arg);
let c_slice = CStr::from_ptr(z_sql).to_bytes(); let c_slice = CStr::from_ptr(z_sql).to_bytes();
let s = String::from_utf8_lossy(c_slice); let s = String::from_utf8_lossy(c_slice);
const NANOS_PER_SEC: u64 = 1_000_000_000; const NANOS_PER_SEC: u64 = 1_000_000_000;
let duration = Duration::new(nanoseconds / NANOS_PER_SEC, let duration = Duration::new(
(nanoseconds % NANOS_PER_SEC) as u32); nanoseconds / NANOS_PER_SEC,
(nanoseconds % NANOS_PER_SEC) as u32,
);
profile_fn(&s, duration); profile_fn(&s, duration);
} }

View File

@ -1,5 +1,5 @@
use std::ops::Deref; use std::ops::Deref;
use {Result, Connection}; use {Connection, Result};
/// Old name for `TransactionBehavior`. `SqliteTransactionBehavior` is deprecated. /// Old name for `TransactionBehavior`. `SqliteTransactionBehavior` is deprecated.
#[deprecated(since = "0.6.0", note = "Use TransactionBehavior instead")] #[deprecated(since = "0.6.0", note = "Use TransactionBehavior instead")]
@ -7,7 +7,7 @@ pub type SqliteTransactionBehavior = TransactionBehavior;
/// Options for transaction behavior. See [BEGIN /// Options for transaction behavior. See [BEGIN
/// TRANSACTION](http://www.sqlite.org/lang_transaction.html) for details. /// TRANSACTION](http://www.sqlite.org/lang_transaction.html) for details.
#[derive(Copy,Clone)] #[derive(Copy, Clone)]
pub enum TransactionBehavior { pub enum TransactionBehavior {
Deferred, Deferred,
Immediate, Immediate,
@ -15,7 +15,7 @@ pub enum TransactionBehavior {
} }
/// Options for how a Transaction or Savepoint should behave when it is dropped. /// Options for how a Transaction or Savepoint should behave when it is dropped.
#[derive(Copy,Clone,PartialEq,Eq)] #[derive(Copy, Clone, PartialEq, Eq)]
pub enum DropBehavior { pub enum DropBehavior {
/// Roll back the changes. This is the default. /// Roll back the changes. This is the default.
Rollback, Rollback,
@ -105,13 +105,10 @@ impl<'conn> Transaction<'conn> {
TransactionBehavior::Immediate => "BEGIN IMMEDIATE", TransactionBehavior::Immediate => "BEGIN IMMEDIATE",
TransactionBehavior::Exclusive => "BEGIN EXCLUSIVE", TransactionBehavior::Exclusive => "BEGIN EXCLUSIVE",
}; };
conn.execute_batch(query) conn.execute_batch(query).map(move |_| Transaction {
.map(move |_| { conn,
Transaction { drop_behavior: DropBehavior::Rollback,
conn, })
drop_behavior: DropBehavior::Rollback,
}
})
} }
/// Starts a new [savepoint](http://www.sqlite.org/lang_savepoint.html), allowing nested /// Starts a new [savepoint](http://www.sqlite.org/lang_savepoint.html), allowing nested
@ -217,20 +214,19 @@ impl<'conn> Drop for Transaction<'conn> {
} }
impl<'conn> Savepoint<'conn> { impl<'conn> Savepoint<'conn> {
fn with_depth_and_name<T: Into<String>>(conn: &Connection, fn with_depth_and_name<T: Into<String>>(
depth: u32, conn: &Connection,
name: T) depth: u32,
-> Result<Savepoint> { name: T,
) -> Result<Savepoint> {
let name = name.into(); let name = name.into();
conn.execute_batch(&format!("SAVEPOINT {}", name)) conn.execute_batch(&format!("SAVEPOINT {}", name))
.map(|_| { .map(|_| Savepoint {
Savepoint { conn,
conn, name,
name, depth,
depth, drop_behavior: DropBehavior::Rollback,
drop_behavior: DropBehavior::Rollback, committed: false,
committed: false,
}
}) })
} }
@ -275,8 +271,7 @@ impl<'conn> Savepoint<'conn> {
} }
fn commit_(&mut self) -> Result<()> { fn commit_(&mut self) -> Result<()> {
self.conn self.conn.execute_batch(&format!("RELEASE {}", self.name))?;
.execute_batch(&format!("RELEASE {}", self.name))?;
self.committed = true; self.committed = true;
Ok(()) Ok(())
} }
@ -365,9 +360,10 @@ impl Connection {
/// # Failure /// # Failure
/// ///
/// Will return `Err` if the underlying SQLite call fails. /// Will return `Err` if the underlying SQLite call fails.
pub fn transaction_with_behavior(&mut self, pub fn transaction_with_behavior(
behavior: TransactionBehavior) &mut self,
-> Result<Transaction> { behavior: TransactionBehavior,
) -> Result<Transaction> {
Transaction::new(self, behavior) Transaction::new(self, behavior)
} }
@ -413,8 +409,8 @@ impl Connection {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use Connection;
use super::DropBehavior; use super::DropBehavior;
use Connection;
fn checked_memory_handle() -> Connection { fn checked_memory_handle() -> Connection {
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
@ -437,9 +433,11 @@ mod test {
} }
{ {
let tx = db.transaction().unwrap(); let tx = db.transaction().unwrap();
assert_eq!(2i32, assert_eq!(
tx.query_row::<i32, _>("SELECT SUM(x) FROM foo", &[], |r| r.get(0)) 2i32,
.unwrap()); tx.query_row::<i32, _>("SELECT SUM(x) FROM foo", &[], |r| r.get(0))
.unwrap()
);
} }
} }
@ -464,9 +462,11 @@ mod test {
} }
{ {
let tx = db.transaction().unwrap(); let tx = db.transaction().unwrap();
assert_eq!(6i32, assert_eq!(
tx.query_row::<i32, _>("SELECT SUM(x) FROM foo", &[], |r| r.get(0)) 6i32,
.unwrap()); tx.query_row::<i32, _>("SELECT SUM(x) FROM foo", &[], |r| r.get(0))
.unwrap()
);
} }
} }
@ -560,7 +560,8 @@ mod test {
} }
fn assert_current_sum(x: i32, conn: &Connection) { fn assert_current_sum(x: i32, conn: &Connection) {
let i = conn.query_row::<i32, _>("SELECT SUM(x) FROM foo", &[], |r| r.get(0)) let i = conn
.query_row::<i32, _>("SELECT SUM(x) FROM foo", &[], |r| r.get(0))
.unwrap(); .unwrap();
assert_eq!(x, i); assert_eq!(x, i);
} }

View File

@ -3,10 +3,10 @@ extern crate chrono;
use std::borrow::Cow; use std::borrow::Cow;
use self::chrono::{NaiveDate, NaiveTime, NaiveDateTime, DateTime, TimeZone, Utc, Local}; use self::chrono::{DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
use Result;
use types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef}; use types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
use Result;
/// ISO 8601 calendar date without timezone => "YYYY-MM-DD" /// ISO 8601 calendar date without timezone => "YYYY-MM-DD"
impl ToSql for NaiveDate { impl ToSql for NaiveDate {
@ -22,9 +22,9 @@ impl FromSql for NaiveDate {
value value
.as_str() .as_str()
.and_then(|s| match NaiveDate::parse_from_str(s, "%Y-%m-%d") { .and_then(|s| match NaiveDate::parse_from_str(s, "%Y-%m-%d") {
Ok(dt) => Ok(dt), Ok(dt) => Ok(dt),
Err(err) => Err(FromSqlError::Other(Box::new(err))), Err(err) => Err(FromSqlError::Other(Box::new(err))),
}) })
} }
} }
@ -39,19 +39,17 @@ impl ToSql for NaiveTime {
/// "HH:MM"/"HH:MM:SS"/"HH:MM:SS.SSS" => ISO 8601 time without timezone. /// "HH:MM"/"HH:MM:SS"/"HH:MM:SS.SSS" => ISO 8601 time without timezone.
impl FromSql for NaiveTime { impl FromSql for NaiveTime {
fn column_result(value: ValueRef) -> FromSqlResult<Self> { fn column_result(value: ValueRef) -> FromSqlResult<Self> {
value value.as_str().and_then(|s| {
.as_str() let fmt = match s.len() {
.and_then(|s| { 5 => "%H:%M",
let fmt = match s.len() { 8 => "%H:%M:%S",
5 => "%H:%M", _ => "%H:%M:%S%.f",
8 => "%H:%M:%S", };
_ => "%H:%M:%S%.f", match NaiveTime::parse_from_str(s, fmt) {
}; Ok(dt) => Ok(dt),
match NaiveTime::parse_from_str(s, fmt) { Err(err) => Err(FromSqlError::Other(Box::new(err))),
Ok(dt) => Ok(dt), }
Err(err) => Err(FromSqlError::Other(Box::new(err))), })
}
})
} }
} }
@ -67,20 +65,18 @@ impl ToSql for NaiveDateTime {
/// without timezone. ("YYYY-MM-DDTHH:MM:SS"/"YYYY-MM-DDTHH:MM:SS.SSS" also supported) /// without timezone. ("YYYY-MM-DDTHH:MM:SS"/"YYYY-MM-DDTHH:MM:SS.SSS" also supported)
impl FromSql for NaiveDateTime { impl FromSql for NaiveDateTime {
fn column_result(value: ValueRef) -> FromSqlResult<Self> { fn column_result(value: ValueRef) -> FromSqlResult<Self> {
value value.as_str().and_then(|s| {
.as_str() let fmt = if s.len() >= 11 && s.as_bytes()[10] == b'T' {
.and_then(|s| { "%Y-%m-%dT%H:%M:%S%.f"
let fmt = if s.len() >= 11 && s.as_bytes()[10] == b'T' { } else {
"%Y-%m-%dT%H:%M:%S%.f" "%Y-%m-%d %H:%M:%S%.f"
} else { };
"%Y-%m-%d %H:%M:%S%.f"
};
match NaiveDateTime::parse_from_str(s, fmt) { match NaiveDateTime::parse_from_str(s, fmt) {
Ok(dt) => Ok(dt), Ok(dt) => Ok(dt),
Err(err) => Err(FromSqlError::Other(Box::new(err))), Err(err) => Err(FromSqlError::Other(Box::new(err))),
} }
}) })
} }
} }
@ -130,9 +126,10 @@ impl FromSql for DateTime<Local> {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::chrono::{
DateTime, Duration, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc,
};
use Connection; use Connection;
use super::chrono::{DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc,
Duration};
fn checked_memory_handle() -> Connection { fn checked_memory_handle() -> Connection {
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
@ -148,10 +145,12 @@ mod test {
db.execute("INSERT INTO foo (t) VALUES (?)", &[&date]) db.execute("INSERT INTO foo (t) VALUES (?)", &[&date])
.unwrap(); .unwrap();
let s: String = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)) let s: String = db
.query_row("SELECT t FROM foo", &[], |r| r.get(0))
.unwrap(); .unwrap();
assert_eq!("2016-02-23", s); assert_eq!("2016-02-23", s);
let t: NaiveDate = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)) let t: NaiveDate = db
.query_row("SELECT t FROM foo", &[], |r| r.get(0))
.unwrap(); .unwrap();
assert_eq!(date, t); assert_eq!(date, t);
} }
@ -163,10 +162,12 @@ mod test {
db.execute("INSERT INTO foo (t) VALUES (?)", &[&time]) db.execute("INSERT INTO foo (t) VALUES (?)", &[&time])
.unwrap(); .unwrap();
let s: String = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)) let s: String = db
.query_row("SELECT t FROM foo", &[], |r| r.get(0))
.unwrap(); .unwrap();
assert_eq!("23:56:04", s); assert_eq!("23:56:04", s);
let v: NaiveTime = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)) let v: NaiveTime = db
.query_row("SELECT t FROM foo", &[], |r| r.get(0))
.unwrap(); .unwrap();
assert_eq!(time, v); assert_eq!(time, v);
} }
@ -181,16 +182,18 @@ mod test {
db.execute("INSERT INTO foo (t) VALUES (?)", &[&dt]) db.execute("INSERT INTO foo (t) VALUES (?)", &[&dt])
.unwrap(); .unwrap();
let s: String = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)) let s: String = db
.query_row("SELECT t FROM foo", &[], |r| r.get(0))
.unwrap(); .unwrap();
assert_eq!("2016-02-23T23:56:04", s); assert_eq!("2016-02-23T23:56:04", s);
let v: NaiveDateTime = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)) let v: NaiveDateTime = db
.query_row("SELECT t FROM foo", &[], |r| r.get(0))
.unwrap(); .unwrap();
assert_eq!(dt, v); assert_eq!(dt, v);
db.execute("UPDATE foo set b = datetime(t)", &[]) db.execute("UPDATE foo set b = datetime(t)", &[]).unwrap(); // "YYYY-MM-DD HH:MM:SS"
.unwrap(); // "YYYY-MM-DD HH:MM:SS" let hms: NaiveDateTime = db
let hms: NaiveDateTime = db.query_row("SELECT b FROM foo", &[], |r| r.get(0)) .query_row("SELECT b FROM foo", &[], |r| r.get(0))
.unwrap(); .unwrap();
assert_eq!(dt, hms); assert_eq!(dt, hms);
} }
@ -206,25 +209,29 @@ mod test {
db.execute("INSERT INTO foo (t) VALUES (?)", &[&utc]) db.execute("INSERT INTO foo (t) VALUES (?)", &[&utc])
.unwrap(); .unwrap();
let s: String = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)) let s: String = db
.query_row("SELECT t FROM foo", &[], |r| r.get(0))
.unwrap(); .unwrap();
assert_eq!("2016-02-23T23:56:04.789+00:00", s); assert_eq!("2016-02-23T23:56:04.789+00:00", s);
let v1: DateTime<Utc> = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)) let v1: DateTime<Utc> = db
.query_row("SELECT t FROM foo", &[], |r| r.get(0))
.unwrap(); .unwrap();
assert_eq!(utc, v1); assert_eq!(utc, v1);
let v2: DateTime<Utc> = db.query_row("SELECT '2016-02-23 23:56:04.789'", &[], |r| r.get(0)) let v2: DateTime<Utc> = db
.query_row("SELECT '2016-02-23 23:56:04.789'", &[], |r| r.get(0))
.unwrap(); .unwrap();
assert_eq!(utc, v2); assert_eq!(utc, v2);
let v3: DateTime<Utc> = db.query_row("SELECT '2016-02-23 23:56:04'", &[], |r| r.get(0)) let v3: DateTime<Utc> = db
.query_row("SELECT '2016-02-23 23:56:04'", &[], |r| r.get(0))
.unwrap(); .unwrap();
assert_eq!(utc - Duration::milliseconds(789), v3); assert_eq!(utc - Duration::milliseconds(789), v3);
let v4: DateTime<Utc> = let v4: DateTime<Utc> = db
db.query_row("SELECT '2016-02-23 23:56:04.789+00:00'", &[], |r| r.get(0)) .query_row("SELECT '2016-02-23 23:56:04.789+00:00'", &[], |r| r.get(0))
.unwrap(); .unwrap();
assert_eq!(utc, v4); assert_eq!(utc, v4);
} }
@ -240,11 +247,13 @@ mod test {
.unwrap(); .unwrap();
// Stored string should be in UTC // Stored string should be in UTC
let s: String = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)) let s: String = db
.query_row("SELECT t FROM foo", &[], |r| r.get(0))
.unwrap(); .unwrap();
assert!(s.ends_with("+00:00")); assert!(s.ends_with("+00:00"));
let v: DateTime<Local> = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)) let v: DateTime<Local> = db
.query_row("SELECT t FROM foo", &[], |r| r.get(0))
.unwrap(); .unwrap();
assert_eq!(local, v); assert_eq!(local, v);
} }

View File

@ -1,4 +1,4 @@
use super::{ValueRef, Value}; use super::{Value, ValueRef};
use std::error::Error; use std::error::Error;
use std::fmt; use std::fmt;
@ -35,12 +35,11 @@ impl Error for FromSqlError {
} }
} }
#[cfg_attr(feature="clippy", allow(match_same_arms))] #[cfg_attr(feature = "clippy", allow(match_same_arms))]
fn cause(&self) -> Option<&Error> { fn cause(&self) -> Option<&Error> {
match *self { match *self {
FromSqlError::Other(ref err) => err.cause(), FromSqlError::Other(ref err) => err.cause(),
FromSqlError::InvalidType | FromSqlError::InvalidType | FromSqlError::OutOfRange(_) => None,
FromSqlError::OutOfRange(_) => None,
} }
} }
} }
@ -115,9 +114,9 @@ impl FromSql for f64 {
impl FromSql for bool { impl FromSql for bool {
fn column_result(value: ValueRef) -> FromSqlResult<Self> { fn column_result(value: ValueRef) -> FromSqlResult<Self> {
i64::column_result(value).map(|i| match i { i64::column_result(value).map(|i| match i {
0 => false, 0 => false,
_ => true, _ => true,
}) })
} }
} }
@ -150,8 +149,8 @@ impl FromSql for Value {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use {Connection, Error};
use super::FromSql; use super::FromSql;
use {Connection, Error};
fn checked_memory_handle() -> Connection { fn checked_memory_handle() -> Connection {
Connection::open_in_memory().unwrap() Connection::open_in_memory().unwrap()
@ -162,10 +161,12 @@ mod test {
let db = checked_memory_handle(); let db = checked_memory_handle();
fn check_ranges<T>(db: &Connection, out_of_range: &[i64], in_range: &[i64]) fn check_ranges<T>(db: &Connection, out_of_range: &[i64], in_range: &[i64])
where T: Into<i64> + FromSql + ::std::fmt::Debug where
T: Into<i64> + FromSql + ::std::fmt::Debug,
{ {
for n in out_of_range { for n in out_of_range {
let err = db.query_row("SELECT ?", &[n], |r| r.get_checked::<_, T>(0)) let err = db
.query_row("SELECT ?", &[n], |r| r.get_checked::<_, T>(0))
.unwrap() .unwrap()
.unwrap_err(); .unwrap_err();
match err { match err {
@ -174,18 +175,22 @@ mod test {
} }
} }
for n in in_range { for n in in_range {
assert_eq!(*n, assert_eq!(
db.query_row("SELECT ?", &[n], |r| r.get::<_, T>(0)) *n,
.unwrap() db.query_row("SELECT ?", &[n], |r| r.get::<_, T>(0))
.into()); .unwrap()
.into()
);
} }
} }
check_ranges::<i8>(&db, &[-129, 128], &[-128, 0, 1, 127]); check_ranges::<i8>(&db, &[-129, 128], &[-128, 0, 1, 127]);
check_ranges::<i16>(&db, &[-32769, 32768], &[-32768, -1, 0, 1, 32767]); check_ranges::<i16>(&db, &[-32769, 32768], &[-32768, -1, 0, 1, 32767]);
check_ranges::<i32>(&db, check_ranges::<i32>(
&[-2147483649, 2147483648], &db,
&[-2147483648, -1, 0, 1, 2147483647]); &[-2147483649, 2147483648],
&[-2147483648, -1, 0, 1, 2147483647],
);
check_ranges::<u8>(&db, &[-2, -1, 256], &[0, 1, 255]); check_ranges::<u8>(&db, &[-2, -1, 256], &[0, 1, 255]);
check_ranges::<u16>(&db, &[-2, -1, 65536], &[0, 1, 65535]); check_ranges::<u16>(&db, &[-2, -1, 65536], &[0, 1, 65535]);
check_ranges::<u32>(&db, &[-2, -1, 4294967296], &[0, 1, 4294967295]); check_ranges::<u32>(&db, &[-2, -1, 4294967296], &[0, 1, 4294967295]);

View File

@ -59,15 +59,15 @@ pub use self::value_ref::ValueRef;
use std::fmt; use std::fmt;
mod value;
mod value_ref;
mod from_sql;
mod to_sql;
mod time;
#[cfg(feature = "chrono")] #[cfg(feature = "chrono")]
mod chrono; mod chrono;
mod from_sql;
#[cfg(feature = "serde_json")] #[cfg(feature = "serde_json")]
mod serde_json; mod serde_json;
mod time;
mod to_sql;
mod value;
mod value_ref;
/// Empty struct that can be used to fill in a query parameter as `NULL`. /// Empty struct that can be used to fill in a query parameter as `NULL`.
/// ///
@ -83,10 +83,10 @@ mod serde_json;
/// conn.execute("INSERT INTO people (name) VALUES (?)", &[&Null]) /// conn.execute("INSERT INTO people (name) VALUES (?)", &[&Null])
/// } /// }
/// ``` /// ```
#[derive(Copy,Clone)] #[derive(Copy, Clone)]
pub struct Null; pub struct Null;
#[derive(Clone,Debug,PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum Type { pub enum Type {
Null, Null,
Integer, Integer,
@ -111,11 +111,11 @@ impl fmt::Display for Type {
mod test { mod test {
extern crate time; extern crate time;
use super::Value;
use std::f64::EPSILON;
use std::os::raw::{c_double, c_int};
use Connection; use Connection;
use Error; use Error;
use std::os::raw::{c_int, c_double};
use std::f64::EPSILON;
use super::Value;
fn checked_memory_handle() -> Connection { fn checked_memory_handle() -> Connection {
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
@ -132,7 +132,8 @@ mod test {
db.execute("INSERT INTO foo(b) VALUES (?)", &[&v1234]) db.execute("INSERT INTO foo(b) VALUES (?)", &[&v1234])
.unwrap(); .unwrap();
let v: Vec<u8> = db.query_row("SELECT b FROM foo", &[], |r| r.get(0)) let v: Vec<u8> = db
.query_row("SELECT b FROM foo", &[], |r| r.get(0))
.unwrap(); .unwrap();
assert_eq!(v, v1234); assert_eq!(v, v1234);
} }
@ -145,7 +146,8 @@ mod test {
db.execute("INSERT INTO foo(b) VALUES (?)", &[&empty]) db.execute("INSERT INTO foo(b) VALUES (?)", &[&empty])
.unwrap(); .unwrap();
let v: Vec<u8> = db.query_row("SELECT b FROM foo", &[], |r| r.get(0)) let v: Vec<u8> = db
.query_row("SELECT b FROM foo", &[], |r| r.get(0))
.unwrap(); .unwrap();
assert_eq!(v, empty); assert_eq!(v, empty);
} }
@ -155,10 +157,10 @@ mod test {
let db = checked_memory_handle(); let db = checked_memory_handle();
let s = "hello, world!"; let s = "hello, world!";
db.execute("INSERT INTO foo(t) VALUES (?)", &[&s]) db.execute("INSERT INTO foo(t) VALUES (?)", &[&s]).unwrap();
.unwrap();
let from: String = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)) let from: String = db
.query_row("SELECT t FROM foo", &[], |r| r.get(0))
.unwrap(); .unwrap();
assert_eq!(from, s); assert_eq!(from, s);
} }
@ -171,7 +173,8 @@ mod test {
db.execute("INSERT INTO foo(t) VALUES (?)", &[&s.to_owned()]) db.execute("INSERT INTO foo(t) VALUES (?)", &[&s.to_owned()])
.unwrap(); .unwrap();
let from: String = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)) let from: String = db
.query_row("SELECT t FROM foo", &[], |r| r.get(0))
.unwrap(); .unwrap();
assert_eq!(from, s); assert_eq!(from, s);
} }
@ -183,9 +186,11 @@ mod test {
db.execute("INSERT INTO foo(i) VALUES (?)", &[&Value::Integer(10)]) db.execute("INSERT INTO foo(i) VALUES (?)", &[&Value::Integer(10)])
.unwrap(); .unwrap();
assert_eq!(10i64, assert_eq!(
db.query_row::<i64, _>("SELECT i FROM foo", &[], |r| r.get(0)) 10i64,
.unwrap()); db.query_row::<i64, _>("SELECT i FROM foo", &[], |r| r.get(0))
.unwrap()
);
} }
#[test] #[test]
@ -195,12 +200,11 @@ mod test {
let s = Some("hello, world!"); let s = Some("hello, world!");
let b = Some(vec![1u8, 2, 3, 4]); let b = Some(vec![1u8, 2, 3, 4]);
db.execute("INSERT INTO foo(t) VALUES (?)", &[&s]) db.execute("INSERT INTO foo(t) VALUES (?)", &[&s]).unwrap();
.unwrap(); db.execute("INSERT INTO foo(b) VALUES (?)", &[&b]).unwrap();
db.execute("INSERT INTO foo(b) VALUES (?)", &[&b])
.unwrap();
let mut stmt = db.prepare("SELECT t, b FROM foo ORDER BY ROWID ASC") let mut stmt = db
.prepare("SELECT t, b FROM foo ORDER BY ROWID ASC")
.unwrap(); .unwrap();
let mut rows = stmt.query(&[]).unwrap(); let mut rows = stmt.query(&[]).unwrap();
@ -232,9 +236,10 @@ mod test {
let db = checked_memory_handle(); let db = checked_memory_handle();
db.execute("INSERT INTO foo(b, t, i, f) VALUES (X'0102', 'text', 1, 1.5)", db.execute(
&[]) "INSERT INTO foo(b, t, i, f) VALUES (X'0102', 'text', 1, 1.5)",
.unwrap(); &[],
).unwrap();
let mut stmt = db.prepare("SELECT b, t, i, f, n FROM foo").unwrap(); let mut stmt = db.prepare("SELECT b, t, i, f, n FROM foo").unwrap();
let mut rows = stmt.query(&[]).unwrap(); let mut rows = stmt.query(&[]).unwrap();
@ -246,53 +251,99 @@ mod test {
assert_eq!("text", row.get_checked::<_, String>(1).unwrap()); assert_eq!("text", row.get_checked::<_, String>(1).unwrap());
assert_eq!(1, row.get_checked::<_, c_int>(2).unwrap()); assert_eq!(1, row.get_checked::<_, c_int>(2).unwrap());
assert!((1.5 - row.get_checked::<_, c_double>(3).unwrap()).abs() < EPSILON); assert!((1.5 - row.get_checked::<_, c_double>(3).unwrap()).abs() < EPSILON);
assert!(row.get_checked::<_, Option<c_int>>(4) assert!(row.get_checked::<_, Option<c_int>>(4).unwrap().is_none());
.unwrap() assert!(row.get_checked::<_, Option<c_double>>(4).unwrap().is_none());
.is_none()); assert!(row.get_checked::<_, Option<String>>(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());
// check some invalid types // check some invalid types
// 0 is actually a blob (Vec<u8>) // 0 is actually a blob (Vec<u8>)
assert!(is_invalid_column_type(row.get_checked::<_, c_int>(0).err().unwrap())); assert!(is_invalid_column_type(
assert!(is_invalid_column_type(row.get_checked::<_, c_int>(0).err().unwrap())); row.get_checked::<_, c_int>(0).err().unwrap()
assert!(is_invalid_column_type(row.get_checked::<_, i64>(0).err().unwrap())); ));
assert!(is_invalid_column_type(row.get_checked::<_, c_double>(0).err().unwrap())); assert!(is_invalid_column_type(
assert!(is_invalid_column_type(row.get_checked::<_, String>(0).err().unwrap())); row.get_checked::<_, c_int>(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())); assert!(is_invalid_column_type(
row.get_checked::<_, i64>(0).err().unwrap()
));
assert!(is_invalid_column_type(
row.get_checked::<_, c_double>(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()
));
// 1 is actually a text (String) // 1 is actually a text (String)
assert!(is_invalid_column_type(row.get_checked::<_, c_int>(1).err().unwrap())); assert!(is_invalid_column_type(
assert!(is_invalid_column_type(row.get_checked::<_, i64>(1).err().unwrap())); row.get_checked::<_, c_int>(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(
assert!(is_invalid_column_type(row.get_checked::<_, Option<c_int>>(1).err().unwrap())); row.get_checked::<_, i64>(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()
));
// 2 is actually an integer // 2 is actually an integer
assert!(is_invalid_column_type(row.get_checked::<_, String>(2).err().unwrap())); assert!(is_invalid_column_type(
assert!(is_invalid_column_type(row.get_checked::<_, Vec<u8>>(2).err().unwrap())); row.get_checked::<_, String>(2).err().unwrap()
assert!(is_invalid_column_type(row.get_checked::<_, Option<String>>(2).err().unwrap())); ));
assert!(is_invalid_column_type(
row.get_checked::<_, Vec<u8>>(2).err().unwrap()
));
assert!(is_invalid_column_type(
row.get_checked::<_, Option<String>>(2).err().unwrap()
));
// 3 is actually a float (c_double) // 3 is actually a float (c_double)
assert!(is_invalid_column_type(row.get_checked::<_, c_int>(3).err().unwrap())); assert!(is_invalid_column_type(
assert!(is_invalid_column_type(row.get_checked::<_, i64>(3).err().unwrap())); row.get_checked::<_, c_int>(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(
assert!(is_invalid_column_type(row.get_checked::<_, Option<c_int>>(3).err().unwrap())); row.get_checked::<_, i64>(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()
));
// 4 is actually NULL // 4 is actually NULL
assert!(is_invalid_column_type(row.get_checked::<_, c_int>(4).err().unwrap())); assert!(is_invalid_column_type(
assert!(is_invalid_column_type(row.get_checked::<_, i64>(4).err().unwrap())); row.get_checked::<_, c_int>(4).err().unwrap()
assert!(is_invalid_column_type(row.get_checked::<_, c_double>(4).err().unwrap())); ));
assert!(is_invalid_column_type(row.get_checked::<_, String>(4).err().unwrap())); assert!(is_invalid_column_type(
assert!(is_invalid_column_type(row.get_checked::<_, Vec<u8>>(4).err().unwrap())); row.get_checked::<_, i64>(4).err().unwrap()
assert!(is_invalid_column_type(row.get_checked::<_, time::Timespec>(4).err().unwrap())); ));
assert!(is_invalid_column_type(
row.get_checked::<_, c_double>(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()
));
} }
#[test] #[test]
@ -300,18 +351,23 @@ mod test {
use super::Value; use super::Value;
let db = checked_memory_handle(); let db = checked_memory_handle();
db.execute("INSERT INTO foo(b, t, i, f) VALUES (X'0102', 'text', 1, 1.5)", db.execute(
&[]) "INSERT INTO foo(b, t, i, f) VALUES (X'0102', 'text', 1, 1.5)",
.unwrap(); &[],
).unwrap();
let mut stmt = db.prepare("SELECT b, t, i, f, n FROM foo").unwrap(); let mut stmt = db.prepare("SELECT b, t, i, f, n FROM foo").unwrap();
let mut rows = stmt.query(&[]).unwrap(); let mut rows = stmt.query(&[]).unwrap();
let row = rows.next().unwrap().unwrap(); let row = rows.next().unwrap().unwrap();
assert_eq!(Value::Blob(vec![1, 2]), assert_eq!(
row.get_checked::<_, Value>(0).unwrap()); Value::Blob(vec![1, 2]),
assert_eq!(Value::Text(String::from("text")), row.get_checked::<_, Value>(0).unwrap()
row.get_checked::<_, Value>(1).unwrap()); );
assert_eq!(
Value::Text(String::from("text")),
row.get_checked::<_, Value>(1).unwrap()
);
assert_eq!(Value::Integer(1), row.get_checked::<_, Value>(2).unwrap()); assert_eq!(Value::Integer(1), row.get_checked::<_, Value>(2).unwrap());
match row.get_checked::<_, Value>(3).unwrap() { match row.get_checked::<_, Value>(3).unwrap() {
Value::Real(val) => assert!((1.5 - val).abs() < EPSILON), Value::Real(val) => assert!((1.5 - val).abs() < EPSILON),

View File

@ -3,8 +3,8 @@ extern crate serde_json;
use self::serde_json::Value; use self::serde_json::Value;
use Result;
use types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef}; use types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
use Result;
/// Serialize JSON `Value` to text. /// Serialize JSON `Value` to text.
impl ToSql for Value { impl ToSql for Value {
@ -17,18 +17,17 @@ impl ToSql for Value {
impl FromSql for Value { impl FromSql for Value {
fn column_result(value: ValueRef) -> FromSqlResult<Self> { fn column_result(value: ValueRef) -> FromSqlResult<Self> {
match value { match value {
ValueRef::Text(s) => serde_json::from_str(s), ValueRef::Text(s) => serde_json::from_str(s),
ValueRef::Blob(b) => serde_json::from_slice(b), ValueRef::Blob(b) => serde_json::from_slice(b),
_ => return Err(FromSqlError::InvalidType), _ => return Err(FromSqlError::InvalidType),
} }.map_err(|err| FromSqlError::Other(Box::new(err)))
.map_err(|err| FromSqlError::Other(Box::new(err)))
} }
} }
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use Connection;
use super::serde_json; use super::serde_json;
use Connection;
fn checked_memory_handle() -> Connection { fn checked_memory_handle() -> Connection {
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
@ -43,14 +42,17 @@ mod test {
let json = r#"{"foo": 13, "bar": "baz"}"#; let json = r#"{"foo": 13, "bar": "baz"}"#;
let data: serde_json::Value = serde_json::from_str(json).unwrap(); let data: serde_json::Value = serde_json::from_str(json).unwrap();
db.execute("INSERT INTO foo (t, b) VALUES (?, ?)", db.execute(
&[&data, &json.as_bytes()]) "INSERT INTO foo (t, b) VALUES (?, ?)",
.unwrap(); &[&data, &json.as_bytes()],
).unwrap();
let t: serde_json::Value = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)) let t: serde_json::Value = db
.query_row("SELECT t FROM foo", &[], |r| r.get(0))
.unwrap(); .unwrap();
assert_eq!(data, t); assert_eq!(data, t);
let b: serde_json::Value = db.query_row("SELECT b FROM foo", &[], |r| r.get(0)) let b: serde_json::Value = db
.query_row("SELECT b FROM foo", &[], |r| r.get(0))
.unwrap(); .unwrap();
assert_eq!(data, b); assert_eq!(data, b);
} }

View File

@ -1,7 +1,7 @@
extern crate time; extern crate time;
use Result;
use types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef}; use types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
use Result;
const SQLITE_DATETIME_FMT: &str = "%Y-%m-%dT%H:%M:%S.%fZ"; const SQLITE_DATETIME_FMT: &str = "%Y-%m-%dT%H:%M:%S.%fZ";
const SQLITE_DATETIME_FMT_LEGACY: &str = "%Y-%m-%d %H:%M:%S:%f %Z"; const SQLITE_DATETIME_FMT_LEGACY: &str = "%Y-%m-%d %H:%M:%S:%f %Z";
@ -21,18 +21,18 @@ impl FromSql for time::Timespec {
value value
.as_str() .as_str()
.and_then(|s| { .and_then(|s| {
time::strptime(s, SQLITE_DATETIME_FMT) time::strptime(s, SQLITE_DATETIME_FMT).or_else(|err| {
.or_else(|err| { time::strptime(s, SQLITE_DATETIME_FMT_LEGACY)
time::strptime(s, SQLITE_DATETIME_FMT_LEGACY) .or_else(|_| Err(FromSqlError::Other(Box::new(err))))
.or_else(|_| Err(FromSqlError::Other(Box::new(err))))})}) })
.map(|tm| tm.to_timespec()) }).map(|tm| tm.to_timespec())
} }
} }
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use Connection;
use super::time; use super::time;
use Connection;
fn checked_memory_handle() -> Connection { fn checked_memory_handle() -> Connection {
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
@ -47,25 +47,23 @@ mod test {
let mut ts_vec = vec![]; let mut ts_vec = vec![];
ts_vec.push(time::Timespec::new(10_000, 0));//January 1, 1970 2:46:40 AM ts_vec.push(time::Timespec::new(10_000, 0)); //January 1, 1970 2:46:40 AM
ts_vec.push(time::Timespec::new(10_000, 1000));//January 1, 1970 2:46:40 AM (and one microsecond) ts_vec.push(time::Timespec::new(10_000, 1000)); //January 1, 1970 2:46:40 AM (and one microsecond)
ts_vec.push(time::Timespec::new(1500391124, 1_000_000));//July 18, 2017 ts_vec.push(time::Timespec::new(1500391124, 1_000_000)); //July 18, 2017
ts_vec.push(time::Timespec::new(2000000000, 2_000_000));//May 18, 2033 ts_vec.push(time::Timespec::new(2000000000, 2_000_000)); //May 18, 2033
ts_vec.push(time::Timespec::new(3000000000, 999_999_999));//January 24, 2065 ts_vec.push(time::Timespec::new(3000000000, 999_999_999)); //January 24, 2065
ts_vec.push(time::Timespec::new(10000000000, 0));//November 20, 2286 ts_vec.push(time::Timespec::new(10000000000, 0)); //November 20, 2286
for ts in ts_vec { for ts in ts_vec {
db.execute("INSERT INTO foo(t) VALUES (?)", &[&ts]).unwrap();
db.execute("INSERT INTO foo(t) VALUES (?)", &[&ts]) let from: time::Timespec = db
.unwrap(); .query_row("SELECT t FROM foo", &[], |r| r.get(0))
let from: time::Timespec = db.query_row("SELECT t FROM foo", &[], |r| r.get(0))
.unwrap(); .unwrap();
db.execute("DELETE FROM foo", &[]).unwrap(); db.execute("DELETE FROM foo", &[]).unwrap();
assert_eq!(from, ts); assert_eq!(from, ts);
} }
} }
} }

View File

@ -1,9 +1,9 @@
use std::borrow::Cow;
use super::{Null, Value, ValueRef}; use super::{Null, Value, ValueRef};
use std::borrow::Cow;
use Result; use Result;
/// `ToSqlOutput` represents the possible output types for implementors of the `ToSql` trait. /// `ToSqlOutput` represents the possible output types for implementors of the `ToSql` trait.
#[derive(Clone,Debug,PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum ToSqlOutput<'a> { pub enum ToSqlOutput<'a> {
/// A borrowed SQLite-representable value. /// A borrowed SQLite-representable value.
Borrowed(ValueRef<'a>), Borrowed(ValueRef<'a>),
@ -19,7 +19,8 @@ pub enum ToSqlOutput<'a> {
// Generically allow any type that can be converted into a ValueRef // Generically allow any type that can be converted into a ValueRef
// to be converted into a ToSqlOutput as well. // to be converted into a ToSqlOutput as well.
impl<'a, T: ?Sized> From<&'a T> for ToSqlOutput<'a> impl<'a, T: ?Sized> From<&'a T> for ToSqlOutput<'a>
where &'a T: Into<ValueRef<'a>> where
&'a T: Into<ValueRef<'a>>,
{ {
fn from(t: &'a T) -> Self { fn from(t: &'a T) -> Self {
ToSqlOutput::Borrowed(t.into()) ToSqlOutput::Borrowed(t.into())
@ -55,12 +56,12 @@ from_value!(Vec<u8>);
impl<'a> ToSql for ToSqlOutput<'a> { impl<'a> ToSql for ToSqlOutput<'a> {
fn to_sql(&self) -> Result<ToSqlOutput> { fn to_sql(&self) -> Result<ToSqlOutput> {
Ok(match *self { Ok(match *self {
ToSqlOutput::Borrowed(v) => ToSqlOutput::Borrowed(v), ToSqlOutput::Borrowed(v) => ToSqlOutput::Borrowed(v),
ToSqlOutput::Owned(ref v) => ToSqlOutput::Borrowed(ValueRef::from(v)), ToSqlOutput::Owned(ref v) => ToSqlOutput::Borrowed(ValueRef::from(v)),
#[cfg(feature = "blob")] #[cfg(feature = "blob")]
ToSqlOutput::ZeroBlob(i) => ToSqlOutput::ZeroBlob(i), ToSqlOutput::ZeroBlob(i) => ToSqlOutput::ZeroBlob(i),
}) })
} }
} }
@ -104,7 +105,8 @@ to_sql_self!(u32);
to_sql_self!(f64); to_sql_self!(f64);
impl<'a, T: ?Sized> ToSql for &'a T impl<'a, T: ?Sized> ToSql for &'a T
where &'a T: Into<ToSqlOutput<'a>> where
&'a T: Into<ToSqlOutput<'a>>,
{ {
fn to_sql(&self) -> Result<ToSqlOutput> { fn to_sql(&self) -> Result<ToSqlOutput> {
Ok((*self).into()) Ok((*self).into())

View File

@ -4,7 +4,7 @@ use super::{Null, Type};
/// dictated by SQLite (not by the caller). /// dictated by SQLite (not by the caller).
/// ///
/// See [`ValueRef`](enum.ValueRef.html) for a non-owning dynamic type value. /// See [`ValueRef`](enum.ValueRef.html) for a non-owning dynamic type value.
#[derive(Clone,Debug,PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum Value { pub enum Value {
/// The value is a `NULL` value. /// The value is a `NULL` value.
Null, Null,
@ -31,7 +31,9 @@ impl From<bool> for Value {
} }
impl From<isize> for Value { impl From<isize> for Value {
fn from(i: isize) -> Value { Value::Integer(i as i64) } fn from(i: isize) -> Value {
Value::Integer(i as i64)
}
} }
macro_rules! from_i64( macro_rules! from_i64(

View File

@ -1,11 +1,11 @@
use super::{Type, Value};
use types::{FromSqlError, FromSqlResult}; use types::{FromSqlError, FromSqlResult};
use super::{Value, Type};
/// A non-owning [dynamic type value](http://sqlite.org/datatype3.html). Typically the /// A non-owning [dynamic type value](http://sqlite.org/datatype3.html). Typically the
/// memory backing this value is owned by SQLite. /// memory backing this value is owned by SQLite.
/// ///
/// See [`Value`](enum.Value.html) for an owning dynamic type value. /// See [`Value`](enum.Value.html) for an owning dynamic type value.
#[derive(Copy,Clone,Debug,PartialEq)] #[derive(Copy, Clone, Debug, PartialEq)]
pub enum ValueRef<'a> { pub enum ValueRef<'a> {
/// The value is a `NULL` value. /// The value is a `NULL` value.
Null, Null,

View File

@ -1,10 +1,10 @@
//! [Unlock Notification](http://sqlite.org/unlock_notify.html) //! [Unlock Notification](http://sqlite.org/unlock_notify.html)
#[cfg(feature = "unlock_notify")]
use std::sync::{Condvar, Mutex};
use std::os::raw::c_int; use std::os::raw::c_int;
#[cfg(feature = "unlock_notify")] #[cfg(feature = "unlock_notify")]
use std::os::raw::c_void; use std::os::raw::c_void;
#[cfg(feature = "unlock_notify")]
use std::sync::{Condvar, Mutex};
use ffi; use ffi;
@ -49,10 +49,9 @@ unsafe extern "C" fn unlock_notify_cb(ap_arg: *mut *mut c_void, n_arg: c_int) {
#[cfg(feature = "unlock_notify")] #[cfg(feature = "unlock_notify")]
pub fn is_locked(db: *mut ffi::sqlite3, rc: c_int) -> bool { pub fn is_locked(db: *mut ffi::sqlite3, rc: c_int) -> bool {
rc == ffi::SQLITE_LOCKED_SHAREDCACHE || (rc & 0xFF) == ffi::SQLITE_LOCKED && unsafe { rc == ffi::SQLITE_LOCKED_SHAREDCACHE
ffi::sqlite3_extended_errcode(db) || (rc & 0xFF) == ffi::SQLITE_LOCKED
} && unsafe { ffi::sqlite3_extended_errcode(db) } == ffi::SQLITE_LOCKED_SHAREDCACHE
== ffi::SQLITE_LOCKED_SHAREDCACHE
} }
/// This function assumes that an SQLite API call (either `sqlite3_prepare_v2()` /// This function assumes that an SQLite API call (either `sqlite3_prepare_v2()`

View File

@ -1,8 +1,8 @@
//! Ensure we reject connections when SQLite is in single-threaded mode, as it //! Ensure we reject connections when SQLite is in single-threaded mode, as it
//! would violate safety if multiple Rust threads tried to use connections. //! would violate safety if multiple Rust threads tried to use connections.
extern crate rusqlite;
extern crate libsqlite3_sys as ffi; extern crate libsqlite3_sys as ffi;
extern crate rusqlite;
use rusqlite::Connection; use rusqlite::Connection;