Merge branch 'master' into gwenn-invalid-column-type

This commit is contained in:
John Gallagher 2016-12-31 00:35:47 -05:00
commit 4181441d63
21 changed files with 497 additions and 352 deletions

View File

@ -1,6 +1,6 @@
[package] [package]
name = "rusqlite" name = "rusqlite"
version = "0.7.2" version = "0.7.3"
authors = ["John Gallagher <jgallagher@bignerdranch.com>"] authors = ["John Gallagher <jgallagher@bignerdranch.com>"]
description = "Ergonomic wrapper for SQLite" description = "Ergonomic wrapper for SQLite"
repository = "https://github.com/jgallagher/rusqlite" repository = "https://github.com/jgallagher/rusqlite"
@ -22,9 +22,8 @@ trace = []
[dependencies] [dependencies]
time = "~0.1.0" time = "~0.1.0"
bitflags = "0.7" bitflags = "0.7"
lru-cache = "0.0.7" lru-cache = "0.1.0"
libc = "~0.2" libc = "~0.2"
clippy = {version = "~0.0.58", optional = true}
chrono = { version = "~0.2", optional = true } chrono = { version = "~0.2", optional = true }
serde_json = { version = "0.6", optional = true } serde_json = { version = "0.6", optional = true }

View File

@ -3,6 +3,23 @@
* BREAKING CHANGE: The `FromSql` trait has been redesigned. It now requires a single, safe * BREAKING CHANGE: The `FromSql` trait has been redesigned. It now requires a single, safe
method instead of the previous definition which required implementing one or two unsafe method instead of the previous definition which required implementing one or two unsafe
methods. methods.
* BREAKING CHANGE: The `ToSql` trait has been redesigned. It can now be implemented without
`unsafe`, and implementors can choose to return either borrowed or owned results.
* BREAKING CHANGE: The closure passed to `query_row`, `query_row_and_then`, `query_row_safe`,
and `query_row_named` now expects a `&Row` instead of a `Row`. The vast majority of calls
to these functions will probably not need to change; see
https://github.com/jgallagher/rusqlite/pull/184.
* Added `#[deprecated(since = "...", note = "...")]` flags (new in Rust 1.9 for libraries) to
all deprecated APIs.
* Added `query_row` convenience function to `Statement`.
* Fixed a bug where using cached prepared statements resulted in attempting to close a connection
failing with `DatabaseBusy`; see https://github.com/jgallagher/rusqlite/issues/186.
# Version 0.7.3 (2016-06-01)
* Fixes an incorrect failure from the `insert()` convenience function when back-to-back inserts to
different tables both returned the same row ID
([#171](https://github.com/jgallagher/rusqlite/issues/171)).
# Version 0.7.2 (2016-05-19) # Version 0.7.2 (2016-05-19)

View File

@ -1,7 +1,7 @@
# Rusqlite # Rusqlite
[![Travis Build Status](https://api.travis-ci.org/jgallagher/rusqlite.svg?branch=master)](https://travis-ci.org/jgallagher/rusqlite) [![Travis Build Status](https://api.travis-ci.org/jgallagher/rusqlite.svg?branch=master)](https://travis-ci.org/jgallagher/rusqlite)
[![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/github/jgallagher/rusqlite?branch=master&svg=true)](https://ci.appveyor.com/project/jgallagher/rusqlite) [![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/github/jgallagher/rusqlite?branch=master&svg=true)](https://ci.appveyor.com/project/jgallagher/rusqlite) [![Latest Version](https://img.shields.io/crates/v/rusqlite.svg)](https://crates.io/crates/rusqlite)
Rusqlite is an ergonomic wrapper for using SQLite from Rust. It attempts to expose Rusqlite is an ergonomic wrapper for using SQLite from Rust. It attempts to expose
an interface similar to [rust-postgres](https://github.com/sfackler/rust-postgres). View the full an interface similar to [rust-postgres](https://github.com/sfackler/rust-postgres). View the full
@ -38,7 +38,7 @@ fn main() {
data: None data: None
}; };
conn.execute("INSERT INTO person (name, time_created, data) conn.execute("INSERT INTO person (name, time_created, data)
VALUES ($1, $2, $3)", VALUES (?1, ?2, ?3)",
&[&me.name, &me.time_created, &me.data]).unwrap(); &[&me.name, &me.time_created, &me.data]).unwrap();
let mut stmt = conn.prepare("SELECT id, name, time_created, data FROM person").unwrap(); let mut stmt = conn.prepare("SELECT id, name, time_created, data FROM person").unwrap();

View File

@ -1,5 +1,5 @@
environment: environment:
TARGET: 1.8.0-x86_64-pc-windows-gnu TARGET: 1.9.0-x86_64-pc-windows-gnu
MSYS2_BITS: 64 MSYS2_BITS: 64
install: install:
- ps: Start-FileDownload "https://static.rust-lang.org/dist/rust-${env:TARGET}.exe" - ps: Start-FileDownload "https://static.rust-lang.org/dist/rust-${env:TARGET}.exe"

View File

@ -52,10 +52,9 @@ use std::io;
use std::cmp::min; use std::cmp::min;
use std::mem; use std::mem;
use std::ptr; use std::ptr;
use libc::c_int;
use super::ffi; use super::ffi;
use super::types::ToSql; use super::types::{ToSql, ToSqlOutput};
use {Result, Connection, DatabaseName}; use {Result, Connection, DatabaseName};
/// Handle to an open BLOB. /// Handle to an open BLOB.
@ -244,9 +243,9 @@ impl<'conn> Drop for Blob<'conn> {
pub struct ZeroBlob(pub i32); pub struct ZeroBlob(pub i32);
impl ToSql for ZeroBlob { impl ToSql for ZeroBlob {
unsafe fn bind_parameter(&self, stmt: *mut ffi::sqlite3_stmt, col: c_int) -> c_int { fn to_sql(&self) -> Result<ToSqlOutput> {
let ZeroBlob(length) = *self; let ZeroBlob(length) = *self;
ffi::sqlite3_bind_zeroblob(stmt, col, length) Ok(ToSqlOutput::ZeroBlob(length))
} }
} }

View File

@ -44,6 +44,10 @@ impl Connection {
pub fn set_prepared_statement_cache_capacity(&self, capacity: usize) { pub fn set_prepared_statement_cache_capacity(&self, capacity: usize) {
self.cache.set_capacity(capacity) self.cache.set_capacity(capacity)
} }
pub fn flush_prepared_statement_cache(&self) {
self.cache.flush()
}
} }
/// Prepared statements LRU cache. /// Prepared statements LRU cache.
@ -133,6 +137,11 @@ impl StatementCache {
let sql = String::from_utf8_lossy(stmt.sql().to_bytes()).to_string(); let sql = String::from_utf8_lossy(stmt.sql().to_bytes()).to_string();
cache.insert(sql, stmt); cache.insert(sql, stmt);
} }
fn flush(&self) {
let mut cache = self.0.borrow_mut();
cache.clear()
}
} }
#[cfg(test)] #[cfg(test)]
@ -268,4 +277,12 @@ mod test {
.unwrap()); .unwrap());
} }
} }
#[test]
fn test_connection_close() {
let conn = Connection::open_in_memory().unwrap();
conn.prepare_cached("SELECT * FROM sqlite_master;").unwrap();
conn.close().expect("connection not closed");
}
} }

View File

@ -1,21 +1,23 @@
use {Error, Result, Statement}; use {Error, Result, Row, Statement};
use types::ToSql; use types::ToSql;
impl<'conn> Statement<'conn> { impl<'conn> Statement<'conn> {
/// Execute an INSERT and return the ROWID. /// Execute an INSERT and return the ROWID.
/// ///
/// # Note
///
/// This function is a convenience wrapper around `execute()` intended for queries that
/// insert a single item. It is possible to misuse this function in a way that it cannot
/// detect, such as by calling it on a statement which _updates_ a single item rather than
/// inserting one. Please don't do that.
///
/// # Failure /// # Failure
///
/// Will return `Err` if no row is inserted or many rows are inserted. /// Will return `Err` if no row is inserted or many rows are inserted.
pub fn insert(&mut self, params: &[&ToSql]) -> Result<i64> { pub fn insert(&mut self, params: &[&ToSql]) -> Result<i64> {
// Some non-insertion queries could still return 1 change (an UPDATE, for example), so
// to guard against that we can check that the connection's last_insert_rowid() changes
// after we execute the statement.
let prev_rowid = self.conn.last_insert_rowid();
let changes = try!(self.execute(params)); let changes = try!(self.execute(params));
let new_rowid = self.conn.last_insert_rowid();
match changes { match changes {
1 if prev_rowid != new_rowid => Ok(new_rowid), 1 => Ok(self.conn.last_insert_rowid()),
1 if prev_rowid == new_rowid => Err(Error::StatementFailedToInsertRow),
_ => Err(Error::StatementChangedRows(changes)), _ => Err(Error::StatementChangedRows(changes)),
} }
} }
@ -32,11 +34,26 @@ impl<'conn> Statement<'conn> {
}; };
Ok(exists) Ok(exists)
} }
/// Convenience method to execute a query that is expected to return a single row.
///
/// If the query returns more than one row, all rows except the first are ignored.
///
/// # Failure
///
/// Will return `Err` if the underlying SQLite call fails.
pub fn query_row<T, F>(&mut self, params: &[&ToSql], f: F) -> Result<T>
where F: FnOnce(&Row) -> T
{
let mut rows = try!(self.query(params));
rows.get_expected_row().map(|r| f(&r))
}
} }
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use {Connection, Error}; use {Connection, Error, Result};
#[test] #[test]
fn test_insert() { fn test_insert() {
@ -57,18 +74,19 @@ mod test {
} }
#[test] #[test]
fn test_insert_failures() { fn test_insert_different_tables() {
// 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("CREATE TABLE foo(x INTEGER UNIQUE)").unwrap(); db.execute_batch(r"
let mut insert = db.prepare("INSERT INTO foo (x) VALUES (?)").unwrap(); CREATE TABLE foo(x INTEGER);
let mut update = db.prepare("UPDATE foo SET x = ?").unwrap(); CREATE TABLE bar(x INTEGER);
")
.unwrap();
assert_eq!(insert.insert(&[&1i32]).unwrap(), 1); assert_eq!(db.prepare("INSERT INTO foo VALUES (10)").unwrap().insert(&[]).unwrap(),
1);
match update.insert(&[&2i32]) { assert_eq!(db.prepare("INSERT INTO bar VALUES (10)").unwrap().insert(&[]).unwrap(),
Err(Error::StatementFailedToInsertRow) => (), 1);
r => panic!("Unexpected result {:?}", r),
}
} }
#[test] #[test]
@ -85,4 +103,18 @@ mod test {
assert!(stmt.exists(&[&2i32]).unwrap()); assert!(stmt.exists(&[&2i32]).unwrap());
assert!(!stmt.exists(&[&0i32]).unwrap()); assert!(!stmt.exists(&[&0i32]).unwrap());
} }
#[test]
fn test_query_row() {
let db = Connection::open_in_memory().unwrap();
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER, y INTEGER);
INSERT INTO foo VALUES(1, 3);
INSERT INTO foo VALUES(2, 4);
END;";
db.execute_batch(sql).unwrap();
let mut stmt = db.prepare("SELECT y FROM foo WHERE x = ?").unwrap();
let y: Result<i64> = stmt.query_row(&[&1i32], |r| r.get(0));
assert_eq!(3i64, y.unwrap());
}
} }

View File

@ -7,6 +7,7 @@ use {ffi, errmsg_to_string};
use types::Type; use types::Type;
/// Old name for `Error`. `SqliteError` is deprecated. /// Old name for `Error`. `SqliteError` is deprecated.
#[deprecated(since = "0.6.0", note = "Use Error instead")]
pub type SqliteError = Error; pub type SqliteError = Error;
/// Enum listing possible errors from rusqlite. /// Enum listing possible errors from rusqlite.
@ -57,10 +58,6 @@ pub enum Error {
/// Error when a query that was expected to insert one row did not insert any or insert many. /// Error when a query that was expected to insert one row did not insert any or insert many.
StatementChangedRows(c_int), StatementChangedRows(c_int),
/// Error when a query that was expected to insert a row did not change the connection's
/// last_insert_rowid().
StatementFailedToInsertRow,
/// Error returned by `functions::Context::get` when the function argument cannot be converted /// Error returned by `functions::Context::get` when the function argument cannot be converted
/// to the requested type. /// to the requested type.
#[cfg(feature = "functions")] #[cfg(feature = "functions")]
@ -115,7 +112,6 @@ impl fmt::Display for Error {
write!(f, "Invalid column type {} at index: {}", t, i) write!(f, "Invalid column type {} at index: {}", t, i)
} }
Error::StatementChangedRows(i) => write!(f, "Query changed {} rows", i), Error::StatementChangedRows(i) => write!(f, "Query changed {} rows", i),
Error::StatementFailedToInsertRow => write!(f, "Statement failed to insert new row"),
#[cfg(feature = "functions")] #[cfg(feature = "functions")]
Error::InvalidFunctionParameterType(i, ref t) => { Error::InvalidFunctionParameterType(i, ref t) => {
@ -148,7 +144,6 @@ impl error::Error for Error {
Error::InvalidColumnName(_) => "invalid column name", Error::InvalidColumnName(_) => "invalid column name",
Error::InvalidColumnType(_, _) => "invalid column type", Error::InvalidColumnType(_, _) => "invalid column type",
Error::StatementChangedRows(_) => "query inserted zero or more than one row", Error::StatementChangedRows(_) => "query inserted zero or more than one row",
Error::StatementFailedToInsertRow => "statement failed to insert new row",
#[cfg(feature = "functions")] #[cfg(feature = "functions")]
Error::InvalidFunctionParameterType(_, _) => "invalid function parameter type", Error::InvalidFunctionParameterType(_, _) => "invalid function parameter type",
@ -173,8 +168,7 @@ impl error::Error for Error {
Error::InvalidColumnName(_) | Error::InvalidColumnName(_) |
Error::InvalidColumnType(_, _) | Error::InvalidColumnType(_, _) |
Error::InvalidPath(_) | Error::InvalidPath(_) |
Error::StatementChangedRows(_) | Error::StatementChangedRows(_) => None,
Error::StatementFailedToInsertRow => None,
#[cfg(feature = "functions")] #[cfg(feature = "functions")]
Error::InvalidFunctionParameterType(_, _) => None, Error::InvalidFunctionParameterType(_, _) => None,

View File

@ -54,105 +54,79 @@ use std::ffi::CStr;
use std::mem; use std::mem;
use std::ptr; use std::ptr;
use std::slice; use std::slice;
use libc::{c_int, c_double, c_char, c_void}; use libc::{c_int, c_char, c_void};
use ffi; use ffi;
pub use ffi::sqlite3_context; use ffi::sqlite3_context;
pub use ffi::sqlite3_value; use ffi::sqlite3_value;
pub use ffi::sqlite3_value_type;
pub use ffi::sqlite3_value_numeric_type;
use types::{Null, FromSql, FromSqlError, ValueRef}; use types::{ToSql, ToSqlOutput, FromSql, FromSqlError, ValueRef};
use {Result, Error, Connection, str_to_cstring, InnerConnection}; use {Result, Error, Connection, str_to_cstring, InnerConnection};
/// A trait for types that can be converted into the result of an SQL function. fn set_result<'a>(ctx: *mut sqlite3_context, result: &ToSqlOutput<'a>) {
pub trait ToResult { let value = match *result {
unsafe fn set_result(&self, ctx: *mut sqlite3_context); ToSqlOutput::Borrowed(v) => v,
} ToSqlOutput::Owned(ref v) => ValueRef::from(v),
macro_rules! raw_to_impl( #[cfg(feature = "blob")]
($t:ty, $f:ident) => ( ToSqlOutput::ZeroBlob(len) => {
impl ToResult for $t { return unsafe { ffi::sqlite3_result_zeroblob(ctx, len) };
unsafe fn set_result(&self, ctx: *mut sqlite3_context) { }
ffi::$f(ctx, *self) };
match value {
ValueRef::Null => unsafe { ffi::sqlite3_result_null(ctx) },
ValueRef::Integer(i) => unsafe { ffi::sqlite3_result_int64(ctx, i) },
ValueRef::Real(r) => unsafe { ffi::sqlite3_result_double(ctx, r) },
ValueRef::Text(ref s) => unsafe {
let length = s.len();
if length > ::std::i32::MAX as usize {
ffi::sqlite3_result_error_toobig(ctx);
} else {
let c_str = match str_to_cstring(s) {
Ok(c_str) => c_str,
// TODO sqlite3_result_error
Err(_) => return ffi::sqlite3_result_error_code(ctx, ffi::SQLITE_MISUSE),
};
let destructor = if length > 0 {
ffi::SQLITE_TRANSIENT()
} else {
ffi::SQLITE_STATIC()
};
ffi::sqlite3_result_text(ctx, c_str.as_ptr(), length as c_int, destructor);
} }
} },
) ValueRef::Blob(ref b) => unsafe {
); let length = b.len();
if length > ::std::i32::MAX as usize {
raw_to_impl!(c_int, sqlite3_result_int); ffi::sqlite3_result_error_toobig(ctx);
raw_to_impl!(i64, sqlite3_result_int64); } else if length == 0 {
raw_to_impl!(c_double, sqlite3_result_double); ffi::sqlite3_result_zeroblob(ctx, 0)
} else {
impl<'a> ToResult for bool { ffi::sqlite3_result_blob(ctx,
unsafe fn set_result(&self, ctx: *mut sqlite3_context) { b.as_ptr() as *const c_void,
if *self {
ffi::sqlite3_result_int(ctx, 1)
} else {
ffi::sqlite3_result_int(ctx, 0)
}
}
}
impl<'a> ToResult for &'a str {
unsafe fn set_result(&self, ctx: *mut sqlite3_context) {
let length = self.len();
if length > ::std::i32::MAX as usize {
ffi::sqlite3_result_error_toobig(ctx);
return;
}
match str_to_cstring(self) {
Ok(c_str) => {
ffi::sqlite3_result_text(ctx,
c_str.as_ptr(),
length as c_int, length as c_int,
ffi::SQLITE_TRANSIENT()) ffi::SQLITE_TRANSIENT());
}
},
}
}
unsafe fn report_error(ctx: *mut sqlite3_context, err: &Error) {
match err {
&Error::SqliteFailure(ref err, ref s) => {
ffi::sqlite3_result_error_code(ctx, err.extended_code);
if let Some(Ok(cstr)) = s.as_ref().map(|s| str_to_cstring(s)) {
ffi::sqlite3_result_error(ctx, cstr.as_ptr(), -1);
} }
// TODO sqlite3_result_error
Err(_) => ffi::sqlite3_result_error_code(ctx, ffi::SQLITE_MISUSE),
} }
} _ => {
} ffi::sqlite3_result_error_code(ctx, ffi::SQLITE_CONSTRAINT_FUNCTION);
if let Ok(cstr) = str_to_cstring(err.description()) {
impl ToResult for String { ffi::sqlite3_result_error(ctx, cstr.as_ptr(), -1);
unsafe fn set_result(&self, ctx: *mut sqlite3_context) { }
(&self[..]).set_result(ctx)
}
}
impl<'a> ToResult for &'a [u8] {
unsafe fn set_result(&self, ctx: *mut sqlite3_context) {
if self.len() > ::std::i32::MAX as usize {
ffi::sqlite3_result_error_toobig(ctx);
return;
} }
ffi::sqlite3_result_blob(ctx,
mem::transmute(self.as_ptr()),
self.len() as c_int,
ffi::SQLITE_TRANSIENT())
}
}
impl ToResult for Vec<u8> {
unsafe fn set_result(&self, ctx: *mut sqlite3_context) {
(&self[..]).set_result(ctx)
}
}
impl<T: ToResult> ToResult for Option<T> {
unsafe fn set_result(&self, ctx: *mut sqlite3_context) {
match *self {
None => ffi::sqlite3_result_null(ctx),
Some(ref t) => t.set_result(ctx),
}
}
}
impl ToResult for Null {
unsafe fn set_result(&self, ctx: *mut sqlite3_context) {
ffi::sqlite3_result_null(ctx)
} }
} }
@ -166,7 +140,8 @@ 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(), "unexpected SQLITE_TEXT value type with NULL data"); assert!(!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.
@ -175,10 +150,12 @@ impl<'a> ValueRef<'a> {
} }
ffi::SQLITE_BLOB => { ffi::SQLITE_BLOB => {
let blob = ffi::sqlite3_value_blob(value); let blob = ffi::sqlite3_value_blob(value);
assert!(!blob.is_null(), "unexpected SQLITE_BLOB value type with NULL data"); assert!(!blob.is_null(),
"unexpected SQLITE_BLOB value type with NULL data");
let len = ffi::sqlite3_value_bytes(value); let len = ffi::sqlite3_value_bytes(value);
assert!(len >= 0, "unexpected negative return from sqlite3_value_bytes"); assert!(len >= 0,
"unexpected negative return from sqlite3_value_bytes");
ValueRef::Blob(from_raw_parts(blob as *const u8, len as usize)) ValueRef::Blob(from_raw_parts(blob as *const u8, len as usize))
} }
@ -263,7 +240,7 @@ 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: ToResult 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:
@ -320,7 +297,7 @@ impl Connection {
x_func: F) x_func: F)
-> Result<()> -> Result<()>
where F: FnMut(&Context) -> Result<T>, where F: FnMut(&Context) -> Result<T>,
T: ToResult T: ToSql
{ {
self.db.borrow_mut().create_scalar_function(fn_name, n_arg, deterministic, x_func) self.db.borrow_mut().create_scalar_function(fn_name, n_arg, deterministic, x_func)
} }
@ -337,7 +314,7 @@ impl Connection {
aggr: D) aggr: D)
-> Result<()> -> Result<()>
where D: Aggregate<A, T>, where D: Aggregate<A, T>,
T: ToResult T: ToSql
{ {
self.db self.db
.borrow_mut() .borrow_mut()
@ -365,13 +342,13 @@ impl InnerConnection {
x_func: F) x_func: F)
-> Result<()> -> Result<()>
where F: FnMut(&Context) -> Result<T>, where F: FnMut(&Context) -> Result<T>,
T: ToResult T: ToSql
{ {
unsafe extern "C" fn call_boxed_closure<F, T>(ctx: *mut sqlite3_context, unsafe extern "C" fn call_boxed_closure<F, T>(ctx: *mut sqlite3_context,
argc: c_int, argc: c_int,
argv: *mut *mut sqlite3_value) argv: *mut *mut sqlite3_value)
where F: FnMut(&Context) -> Result<T>, where F: FnMut(&Context) -> Result<T>,
T: ToResult T: ToSql
{ {
let ctx = Context { let ctx = Context {
ctx: ctx, ctx: ctx,
@ -379,20 +356,14 @@ impl InnerConnection {
}; };
let boxed_f: *mut F = mem::transmute(ffi::sqlite3_user_data(ctx.ctx)); let boxed_f: *mut F = mem::transmute(ffi::sqlite3_user_data(ctx.ctx));
assert!(!boxed_f.is_null(), "Internal error - null function pointer"); assert!(!boxed_f.is_null(), "Internal error - null function pointer");
match (*boxed_f)(&ctx) {
Ok(r) => r.set_result(ctx.ctx), let t = (*boxed_f)(&ctx);
Err(Error::SqliteFailure(err, s)) => { let t = t.as_ref().map(|t| ToSql::to_sql(t));
ffi::sqlite3_result_error_code(ctx.ctx, err.extended_code);
if let Some(Ok(cstr)) = s.map(|s| str_to_cstring(&s)) { match t {
ffi::sqlite3_result_error(ctx.ctx, cstr.as_ptr(), -1); Ok(Ok(ref value)) => set_result(ctx.ctx, value),
} Ok(Err(err)) => report_error(ctx.ctx, &err),
} Err(err) => report_error(ctx.ctx, err),
Err(err) => {
ffi::sqlite3_result_error_code(ctx.ctx, ffi::SQLITE_CONSTRAINT_FUNCTION);
if let Ok(cstr) = str_to_cstring(err.description()) {
ffi::sqlite3_result_error(ctx.ctx, cstr.as_ptr(), -1);
}
}
} }
} }
@ -423,7 +394,7 @@ impl InnerConnection {
aggr: D) aggr: D)
-> Result<()> -> Result<()>
where D: Aggregate<A, T>, where D: Aggregate<A, T>,
T: ToResult T: ToSql
{ {
unsafe fn aggregate_context<A>(ctx: *mut sqlite3_context, unsafe fn aggregate_context<A>(ctx: *mut sqlite3_context,
bytes: usize) bytes: usize)
@ -435,28 +406,11 @@ impl InnerConnection {
Some(pac) Some(pac)
} }
unsafe fn report_aggregate_error(ctx: *mut sqlite3_context, err: Error) {
match err {
Error::SqliteFailure(err, s) => {
ffi::sqlite3_result_error_code(ctx, err.extended_code);
if let Some(Ok(cstr)) = s.map(|s| str_to_cstring(&s)) {
ffi::sqlite3_result_error(ctx, cstr.as_ptr(), -1);
}
}
_ => {
ffi::sqlite3_result_error_code(ctx, ffi::SQLITE_CONSTRAINT_FUNCTION);
if let Ok(cstr) = str_to_cstring(err.description()) {
ffi::sqlite3_result_error(ctx, cstr.as_ptr(), -1);
}
}
}
}
unsafe extern "C" fn call_boxed_step<A, D, T>(ctx: *mut sqlite3_context, unsafe extern "C" fn call_boxed_step<A, D, T>(ctx: *mut sqlite3_context,
argc: c_int, argc: c_int,
argv: *mut *mut sqlite3_value) argv: *mut *mut sqlite3_value)
where D: Aggregate<A, T>, where D: Aggregate<A, T>,
T: ToResult T: ToSql
{ {
let boxed_aggr: *mut D = mem::transmute(ffi::sqlite3_user_data(ctx)); let boxed_aggr: *mut D = mem::transmute(ffi::sqlite3_user_data(ctx));
assert!(!boxed_aggr.is_null(), assert!(!boxed_aggr.is_null(),
@ -481,13 +435,13 @@ impl InnerConnection {
match (*boxed_aggr).step(&mut ctx, &mut **pac) { match (*boxed_aggr).step(&mut ctx, &mut **pac) {
Ok(_) => {} Ok(_) => {}
Err(err) => report_aggregate_error(ctx.ctx, err), Err(err) => report_error(ctx.ctx, &err),
}; };
} }
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 D: Aggregate<A, T>,
T: ToResult T: ToSql
{ {
let boxed_aggr: *mut D = mem::transmute(ffi::sqlite3_user_data(ctx)); let boxed_aggr: *mut D = mem::transmute(ffi::sqlite3_user_data(ctx));
assert!(!boxed_aggr.is_null(), assert!(!boxed_aggr.is_null(),
@ -507,10 +461,13 @@ impl InnerConnection {
None => None, None => None,
}; };
match (*boxed_aggr).finalize(a) { let t = (*boxed_aggr).finalize(a);
Ok(r) => r.set_result(ctx), let t = t.as_ref().map(|t| ToSql::to_sql(t));
Err(err) => report_aggregate_error(ctx, err), match t {
}; Ok(Ok(ref value)) => set_result(ctx, value),
Ok(Err(err)) => report_error(ctx, &err),
Err(err) => report_error(ctx, err),
}
} }
let boxed_aggr: *mut D = Box::into_raw(Box::new(aggr)); let boxed_aggr: *mut D = Box::into_raw(Box::new(aggr));

View File

@ -32,7 +32,7 @@
//! data: None //! data: None
//! }; //! };
//! conn.execute("INSERT INTO person (name, time_created, data) //! conn.execute("INSERT INTO person (name, time_created, data)
//! VALUES ($1, $2, $3)", //! VALUES (?1, ?2, ?3)",
//! &[&me.name, &me.time_created, &me.data]).unwrap(); //! &[&me.name, &me.time_created, &me.data]).unwrap();
//! //!
//! let mut stmt = conn.prepare("SELECT id, name, time_created, data FROM person").unwrap(); //! let mut stmt = conn.prepare("SELECT id, name, time_created, data FROM person").unwrap();
@ -50,8 +50,6 @@
//! } //! }
//! } //! }
//! ``` //! ```
#![cfg_attr(feature="clippy", feature(plugin))]
#![cfg_attr(feature="clippy", plugin(clippy))]
extern crate libc; extern crate libc;
extern crate libsqlite3_sys as ffi; extern crate libsqlite3_sys as ffi;
@ -73,9 +71,9 @@ use std::cell::RefCell;
use std::ffi::{CStr, CString}; use std::ffi::{CStr, CString};
use std::result; use std::result;
use std::str; use std::str;
use libc::{c_int, c_char}; use libc::{c_int, c_char, c_void};
use types::{ToSql, FromSql, FromSqlError, ValueRef}; use types::{ToSql, ToSqlOutput, FromSql, FromSqlError, ValueRef};
use error::{error_from_sqlite_code, error_from_handle}; use error::{error_from_sqlite_code, error_from_handle};
use raw_statement::RawStatement; use raw_statement::RawStatement;
use cache::StatementCache; use cache::StatementCache;
@ -105,6 +103,7 @@ mod raw_statement;
const STATEMENT_CACHE_DEFAULT_CAPACITY: usize = 16; const STATEMENT_CACHE_DEFAULT_CAPACITY: usize = 16;
/// Old name for `Result`. `SqliteResult` is deprecated. /// Old name for `Result`. `SqliteResult` is deprecated.
#[deprecated(since = "0.6.0", note = "Use Result instead")]
pub type SqliteResult<T> = Result<T>; pub type SqliteResult<T> = Result<T>;
/// A typedef of the result returned by many methods. /// A typedef of the result returned by many methods.
@ -151,6 +150,7 @@ impl<'a> DatabaseName<'a> {
} }
/// Old name for `Connection`. `SqliteConnection` is deprecated. /// Old name for `Connection`. `SqliteConnection` is deprecated.
#[deprecated(since = "0.6.0", note = "Use Connection instead")]
pub type SqliteConnection = Connection; pub type SqliteConnection = Connection;
/// A connection to a SQLite database. /// A connection to a SQLite database.
@ -303,12 +303,10 @@ 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));
let mut rows = try!(stmt.query(params)); stmt.query_row(params, f)
rows.get_expected_row().map(f)
} }
/// 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,
@ -339,13 +337,13 @@ impl Connection {
params: &[&ToSql], params: &[&ToSql],
f: F) f: F)
-> result::Result<T, E> -> result::Result<T, E>
where F: FnOnce(Row) -> result::Result<T, E>, where F: FnOnce(&Row) -> result::Result<T, E>,
E: convert::From<Error> 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().map_err(E::from).and_then(f) rows.get_expected_row().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.
@ -367,8 +365,9 @@ impl Connection {
/// ///
/// This method should be considered deprecated. Use `query_row` instead, which now /// This method should be considered deprecated. Use `query_row` instead, which now
/// does exactly the same thing. /// does exactly the same thing.
#[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)
} }
@ -404,6 +403,7 @@ impl Connection {
/// ///
/// Will return `Err` if the underlying SQLite call fails. /// Will return `Err` if the underlying SQLite call fails.
pub fn close(self) -> Result<()> { pub fn close(self) -> Result<()> {
self.flush_prepared_statement_cache();
let mut db = self.db.borrow_mut(); let mut db = self.db.borrow_mut();
db.close() db.close()
} }
@ -507,6 +507,7 @@ struct InnerConnection {
} }
/// Old name for `OpenFlags`. `SqliteOpenFlags` is deprecated. /// Old name for `OpenFlags`. `SqliteOpenFlags` is deprecated.
#[deprecated(since = "0.6.0", note = "Use OpenFlags instead")]
pub type SqliteOpenFlags = OpenFlags; pub type SqliteOpenFlags = OpenFlags;
bitflags! { bitflags! {
@ -678,6 +679,7 @@ impl Drop for InnerConnection {
} }
/// Old name for `Statement`. `SqliteStatement` is deprecated. /// Old name for `Statement`. `SqliteStatement` is deprecated.
#[deprecated(since = "0.6.0", note = "Use Statement instead")]
pub type SqliteStatement<'conn> = Statement<'conn>; pub type SqliteStatement<'conn> = Statement<'conn>;
/// A prepared statement. /// A prepared statement.
@ -874,6 +876,55 @@ impl<'conn> Statement<'conn> {
self.finalize_() self.finalize_()
} }
fn bind_parameter(&self, param: &ToSql, col: c_int) -> Result<()> {
let value = try!(param.to_sql());
let ptr = unsafe { self.stmt.ptr() };
let value = match value {
ToSqlOutput::Borrowed(v) => v,
ToSqlOutput::Owned(ref v) => ValueRef::from(v),
#[cfg(feature = "blob")]
ToSqlOutput::ZeroBlob(len) => {
return self.conn
.decode_result(unsafe { ffi::sqlite3_bind_zeroblob(ptr, col, len) });
}
};
self.conn.decode_result(match value {
ValueRef::Null => unsafe { ffi::sqlite3_bind_null(ptr, col) },
ValueRef::Integer(i) => unsafe { ffi::sqlite3_bind_int64(ptr, col, i) },
ValueRef::Real(r) => unsafe { ffi::sqlite3_bind_double(ptr, col, r) },
ValueRef::Text(ref s) => unsafe {
let length = s.len();
if length > ::std::i32::MAX as usize {
ffi::SQLITE_TOOBIG
} else {
let c_str = try!(str_to_cstring(s));
let destructor = if length > 0 {
ffi::SQLITE_TRANSIENT()
} else {
ffi::SQLITE_STATIC()
};
ffi::sqlite3_bind_text(ptr, col, c_str.as_ptr(), length as c_int, destructor)
}
},
ValueRef::Blob(ref b) => unsafe {
let length = b.len();
if length > ::std::i32::MAX as usize {
ffi::SQLITE_TOOBIG
} else if length == 0 {
ffi::sqlite3_bind_zeroblob(ptr, col, 0)
} else {
ffi::sqlite3_bind_blob(ptr,
col,
b.as_ptr() as *const c_void,
length as c_int,
ffi::SQLITE_TRANSIENT())
}
},
})
}
fn bind_parameters(&mut self, params: &[&ToSql]) -> Result<()> { fn bind_parameters(&mut self, params: &[&ToSql]) -> Result<()> {
assert!(params.len() as c_int == self.stmt.bind_parameter_count(), assert!(params.len() as c_int == self.stmt.bind_parameter_count(),
"incorrect number of parameters to query(): expected {}, got {}", "incorrect number of parameters to query(): expected {}, got {}",
@ -881,14 +932,7 @@ impl<'conn> Statement<'conn> {
params.len()); params.len());
for (i, p) in params.iter().enumerate() { for (i, p) in params.iter().enumerate() {
try!(unsafe { try!(self.bind_parameter(*p, (i + 1) as c_int));
self.conn.decode_result(
// This should be
// `p.bind_parameter(self.stmt.ptr(), (i + 1) as c_int)`
// but that doesn't compile until Rust 1.9 due to a compiler bug.
ToSql::bind_parameter(*p, self.stmt.ptr(), (i + 1) as c_int)
)
});
} }
Ok(()) Ok(())
@ -967,6 +1011,7 @@ impl<'stmt, T, E, F> Iterator for AndThenRows<'stmt, F>
} }
/// Old name for `Rows`. `SqliteRows` is deprecated. /// Old name for `Rows`. `SqliteRows` is deprecated.
#[deprecated(since = "0.6.0", note = "Use Rows instead")]
pub type SqliteRows<'stmt> = Rows<'stmt>; pub type SqliteRows<'stmt> = Rows<'stmt>;
/// An handle for the resulting rows of a query. /// An handle for the resulting rows of a query.
@ -1031,6 +1076,7 @@ impl<'stmt> Drop for 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")]
pub type SqliteRow<'a, 'stmt> = Row<'a, 'stmt>; pub type SqliteRow<'a, 'stmt> = Row<'a, 'stmt>;
/// A single result row of a query. /// A single result row of a query.
@ -1127,14 +1173,16 @@ impl<'a> ValueRef<'a> {
} }
ffi::SQLITE_BLOB => { ffi::SQLITE_BLOB => {
let blob = ffi::sqlite3_column_blob(raw, col); let blob = ffi::sqlite3_column_blob(raw, col);
assert!(!blob.is_null(),
"unexpected SQLITE_BLOB column type with NULL data");
let len = ffi::sqlite3_column_bytes(raw, col); let len = ffi::sqlite3_column_bytes(raw, col);
assert!(len >= 0, assert!(len >= 0, "unexpected negative return from sqlite3_column_bytes");
"unexpected negative return from sqlite3_column_bytes"); if len > 0 {
assert!(!blob.is_null(), "unexpected SQLITE_BLOB column 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 {
// The return value from sqlite3_column_blob() for a zero-length BLOB is a NULL pointer.
ValueRef::Blob(&[])
}
} }
_ => unreachable!("sqlite3_column_type returned invalid value"), _ => unreachable!("sqlite3_column_type returned invalid value"),
} }

View File

@ -1,6 +1,7 @@
use {Result, Connection}; use {Result, Connection};
/// Old name for `LoadExtensionGuard`. `SqliteLoadExtensionGuard` is deprecated. /// Old name for `LoadExtensionGuard`. `SqliteLoadExtensionGuard` is deprecated.
#[deprecated(since = "0.6.0", note = "Use LoadExtensionGuard instead")]
pub type SqliteLoadExtensionGuard<'conn> = LoadExtensionGuard<'conn>; pub type SqliteLoadExtensionGuard<'conn> = LoadExtensionGuard<'conn>;
/// RAII guard temporarily enabling SQLite extensions to be loaded. /// RAII guard temporarily enabling SQLite extensions to be loaded.

View File

@ -38,12 +38,12 @@ 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));
rows.get_expected_row().map(f) rows.get_expected_row().map(|r| f(&r))
} }
} }
@ -204,12 +204,7 @@ impl<'conn> Statement<'conn> {
fn bind_parameters_named(&mut self, params: &[(&str, &ToSql)]) -> Result<()> { fn bind_parameters_named(&mut self, params: &[(&str, &ToSql)]) -> Result<()> {
for &(name, value) in params { for &(name, value) in params {
if let Some(i) = try!(self.parameter_index(name)) { if let Some(i) = try!(self.parameter_index(name)) {
try!(self.conn.decode_result(unsafe { try!(self.bind_parameter(value, i));
// This should be
// `value.bind_parameter(self.stmt.ptr(), i)`
// but that doesn't compile until Rust 1.9 due to a compiler bug.
ToSql::bind_parameter(value, self.stmt.ptr(), i)
}));
} else { } else {
return Err(Error::InvalidParameterName(name.into())); return Err(Error::InvalidParameterName(name.into()));
} }

View File

@ -2,6 +2,7 @@ use std::ops::Deref;
use {Result, Connection}; use {Result, Connection};
/// Old name for `TransactionBehavior`. `SqliteTransactionBehavior` is deprecated. /// Old name for `TransactionBehavior`. `SqliteTransactionBehavior` is deprecated.
#[deprecated(since = "0.6.0", note = "Use TransactionBehavior instead")]
pub type SqliteTransactionBehavior = TransactionBehavior; pub type SqliteTransactionBehavior = TransactionBehavior;
/// Options for transaction behavior. See [BEGIN /// Options for transaction behavior. See [BEGIN
@ -28,6 +29,7 @@ pub enum DropBehavior {
} }
/// Old name for `Transaction`. `SqliteTransaction` is deprecated. /// Old name for `Transaction`. `SqliteTransaction` is deprecated.
#[deprecated(since = "0.6.0", note = "Use Transaction instead")]
pub type SqliteTransaction<'conn> = Transaction<'conn>; pub type SqliteTransaction<'conn> = Transaction<'conn>;
/// Represents a transaction on a database connection. /// Represents a transaction on a database connection.

View File

@ -4,23 +4,21 @@ extern crate chrono;
use std::borrow::Cow; use std::borrow::Cow;
use self::chrono::{NaiveDate, NaiveTime, NaiveDateTime, DateTime, TimeZone, UTC, Local}; use self::chrono::{NaiveDate, NaiveTime, NaiveDateTime, DateTime, TimeZone, UTC, Local};
use libc::c_int;
use types::{FromSql, FromSqlError, ToSql, ValueRef}; use ::Result;
use types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
use ffi::sqlite3_stmt;
/// 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 {
unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int { fn to_sql(&self) -> Result<ToSqlOutput> {
let date_str = self.format("%Y-%m-%d").to_string(); let date_str = self.format("%Y-%m-%d").to_string();
date_str.bind_parameter(stmt, col) Ok(ToSqlOutput::from(date_str))
} }
} }
/// "YYYY-MM-DD" => ISO 8601 calendar date without timezone. /// "YYYY-MM-DD" => ISO 8601 calendar date without timezone.
impl FromSql for NaiveDate { impl FromSql for NaiveDate {
fn column_result(value: ValueRef) -> Result<Self, FromSqlError> { fn column_result(value: ValueRef) -> FromSqlResult<Self> {
value.as_str().and_then(|s| match NaiveDate::parse_from_str(s, "%Y-%m-%d") { value.as_str().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))),
@ -30,15 +28,15 @@ impl FromSql for NaiveDate {
/// ISO 8601 time without timezone => "HH:MM:SS.SSS" /// ISO 8601 time without timezone => "HH:MM:SS.SSS"
impl ToSql for NaiveTime { impl ToSql for NaiveTime {
unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int { fn to_sql(&self) -> Result<ToSqlOutput> {
let date_str = self.format("%H:%M:%S%.f").to_string(); let date_str = self.format("%H:%M:%S%.f").to_string();
date_str.bind_parameter(stmt, col) Ok(ToSqlOutput::from(date_str))
} }
} }
/// "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) -> Result<Self, FromSqlError> { fn column_result(value: ValueRef) -> FromSqlResult<Self> {
value.as_str().and_then(|s| { value.as_str().and_then(|s| {
let fmt = match s.len() { let fmt = match s.len() {
5 => "%H:%M", 5 => "%H:%M",
@ -55,16 +53,16 @@ impl FromSql for NaiveTime {
/// ISO 8601 combined date and time without timezone => "YYYY-MM-DD HH:MM:SS.SSS" /// ISO 8601 combined date and time without timezone => "YYYY-MM-DD HH:MM:SS.SSS"
impl ToSql for NaiveDateTime { impl ToSql for NaiveDateTime {
unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int { fn to_sql(&self) -> Result<ToSqlOutput> {
let date_str = self.format("%Y-%m-%dT%H:%M:%S%.f").to_string(); let date_str = self.format("%Y-%m-%dT%H:%M:%S%.f").to_string();
date_str.bind_parameter(stmt, col) Ok(ToSqlOutput::from(date_str))
} }
} }
/// "YYYY-MM-DD HH:MM:SS"/"YYYY-MM-DD HH:MM:SS.SSS" => ISO 8601 combined date and time /// "YYYY-MM-DD HH:MM:SS"/"YYYY-MM-DD HH:MM:SS.SSS" => ISO 8601 combined date and time
/// 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) -> Result<Self, FromSqlError> { fn column_result(value: ValueRef) -> FromSqlResult<Self> {
value.as_str().and_then(|s| { value.as_str().and_then(|s| {
let fmt = if s.len() >= 11 && s.as_bytes()[10] == b'T' { let fmt = if s.len() >= 11 && s.as_bytes()[10] == b'T' {
"%Y-%m-%dT%H:%M:%S%.f" "%Y-%m-%dT%H:%M:%S%.f"
@ -82,15 +80,14 @@ impl FromSql for NaiveDateTime {
/// Date and time with time zone => UTC RFC3339 timestamp ("YYYY-MM-DDTHH:MM:SS.SSS+00:00"). /// Date and time with time zone => UTC RFC3339 timestamp ("YYYY-MM-DDTHH:MM:SS.SSS+00:00").
impl<Tz: TimeZone> ToSql for DateTime<Tz> { impl<Tz: TimeZone> ToSql for DateTime<Tz> {
unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int { fn to_sql(&self) -> Result<ToSqlOutput> {
let utc_dt = self.with_timezone(&UTC); Ok(ToSqlOutput::from(self.with_timezone(&UTC).to_rfc3339()))
utc_dt.to_rfc3339().bind_parameter(stmt, col)
} }
} }
/// RFC3339 ("YYYY-MM-DDTHH:MM:SS.SSS[+-]HH:MM") into DateTime<UTC>. /// RFC3339 ("YYYY-MM-DDTHH:MM:SS.SSS[+-]HH:MM") into DateTime<UTC>.
impl FromSql for DateTime<UTC> { impl FromSql for DateTime<UTC> {
fn column_result(value: ValueRef) -> Result<Self, FromSqlError> { fn column_result(value: ValueRef) -> FromSqlResult<Self> {
{ {
// Try to parse value as rfc3339 first. // Try to parse value as rfc3339 first.
let s = try!(value.as_str()); let s = try!(value.as_str());
@ -120,7 +117,7 @@ impl FromSql for DateTime<UTC> {
/// RFC3339 ("YYYY-MM-DDTHH:MM:SS.SSS[+-]HH:MM") into DateTime<Local>. /// RFC3339 ("YYYY-MM-DDTHH:MM:SS.SSS[+-]HH:MM") into DateTime<Local>.
impl FromSql for DateTime<Local> { impl FromSql for DateTime<Local> {
fn column_result(value: ValueRef) -> Result<Self, FromSqlError> { fn column_result(value: ValueRef) -> FromSqlResult<Self> {
let utc_dt = try!(DateTime::<UTC>::column_result(value)); let utc_dt = try!(DateTime::<UTC>::column_result(value));
Ok(utc_dt.with_timezone(&Local)) Ok(utc_dt.with_timezone(&Local))
} }

View File

@ -38,26 +38,28 @@ impl Error for FromSqlError {
} }
} }
/// Result type for implementors of the `FromSql` trait.
pub type FromSqlResult<T> = Result<T, FromSqlError>;
/// A trait for types that can be created from a SQLite value. /// A trait for types that can be created from a SQLite value.
pub trait FromSql: Sized { pub trait FromSql: Sized {
fn column_result(value: ValueRef) -> Result<Self, FromSqlError>; fn column_result(value: ValueRef) -> FromSqlResult<Self>;
} }
impl FromSql for i32 { impl FromSql for i32 {
fn column_result(value: ValueRef) -> Result<Self, FromSqlError> { fn column_result(value: ValueRef) -> FromSqlResult<Self> {
i64::column_result(value).map(|i| i as i32) i64::column_result(value).map(|i| i as i32)
} }
} }
impl FromSql for i64 { impl FromSql for i64 {
fn column_result(value: ValueRef) -> Result<Self, FromSqlError> { fn column_result(value: ValueRef) -> FromSqlResult<Self> {
value.as_i64() value.as_i64()
} }
} }
impl FromSql for f64 { impl FromSql for f64 {
fn column_result(value: ValueRef) -> Result<Self, FromSqlError> { fn column_result(value: ValueRef) -> FromSqlResult<Self> {
match value { match value {
ValueRef::Integer(i) => Ok(i as f64), ValueRef::Integer(i) => Ok(i as f64),
ValueRef::Real(f) => Ok(f), ValueRef::Real(f) => Ok(f),
@ -67,7 +69,7 @@ impl FromSql for f64 {
} }
impl FromSql for bool { impl FromSql for bool {
fn column_result(value: ValueRef) -> Result<Self, FromSqlError> { 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,
@ -76,19 +78,19 @@ impl FromSql for bool {
} }
impl FromSql for String { impl FromSql for String {
fn column_result(value: ValueRef) -> Result<Self, FromSqlError> { fn column_result(value: ValueRef) -> FromSqlResult<Self> {
value.as_str().map(|s| s.to_string()) value.as_str().map(|s| s.to_string())
} }
} }
impl FromSql for Vec<u8> { impl FromSql for Vec<u8> {
fn column_result(value: ValueRef) -> Result<Self, FromSqlError> { fn column_result(value: ValueRef) -> FromSqlResult<Self> {
value.as_blob().map(|b| b.to_vec()) value.as_blob().map(|b| b.to_vec())
} }
} }
impl<T: FromSql> FromSql for Option<T> { impl<T: FromSql> FromSql for Option<T> {
fn column_result(value: ValueRef) -> Result<Self, FromSqlError> { fn column_result(value: ValueRef) -> FromSqlResult<Self> {
match value { match value {
ValueRef::Null => Ok(None), ValueRef::Null => Ok(None),
_ => FromSql::column_result(value).map(Some), _ => FromSql::column_result(value).map(Some),
@ -97,7 +99,7 @@ impl<T: FromSql> FromSql for Option<T> {
} }
impl FromSql for Value { impl FromSql for Value {
fn column_result(value: ValueRef) -> Result<Self, FromSqlError> { fn column_result(value: ValueRef) -> FromSqlResult<Self> {
Ok(value.into()) Ok(value.into())
} }
} }

View File

@ -50,14 +50,14 @@
//! `FromSql` for the cases where you want to know if a value was NULL (which gets translated to //! `FromSql` for the cases where you want to know if a value was NULL (which gets translated to
//! `None`). //! `None`).
pub use ffi::sqlite3_stmt; pub use self::from_sql::{FromSql, FromSqlError, FromSqlResult};
pub use self::to_sql::{ToSql, ToSqlOutput};
pub use self::from_sql::{FromSql, FromSqlError}; pub use self::value::Value;
pub use self::to_sql::ToSql;
pub use self::value_ref::ValueRef; pub use self::value_ref::ValueRef;
use std::fmt; use std::fmt;
mod value;
mod value_ref; mod value_ref;
mod from_sql; mod from_sql;
mod to_sql; mod to_sql;
@ -86,36 +86,6 @@ mod serde_json;
#[derive(Copy,Clone)] #[derive(Copy,Clone)]
pub struct Null; pub struct Null;
/// Owning [dynamic type value](http://sqlite.org/datatype3.html). Value's type is typically
/// dictated by SQLite (not by the caller).
///
/// See [`ValueRef`](enum.ValueRef.html) for a non-owning dynamic type value.
#[derive(Clone,Debug,PartialEq)]
pub enum Value {
/// The value is a `NULL` value.
Null,
/// The value is a signed integer.
Integer(i64),
/// The value is a floating point number.
Real(f64),
/// The value is a text string.
Text(String),
/// The value is a blob of data
Blob(Vec<u8>),
}
impl Value {
pub fn data_type(&self) -> Type {
match *self {
Value::Null => Type::Null,
Value::Integer(_) => Type::Integer,
Value::Real(_) => Type::Real,
Value::Text(_) => Type::Text,
Value::Blob(_) => Type::Blob,
}
}
}
#[derive(Clone,Debug,PartialEq)] #[derive(Clone,Debug,PartialEq)]
pub enum Type { pub enum Type {
Null, Null,
@ -146,6 +116,7 @@ mod test {
use Error; use Error;
use libc::{c_int, c_double}; use libc::{c_int, c_double};
use std::f64::EPSILON; 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();
@ -164,10 +135,32 @@ mod test {
assert_eq!(v, v1234); assert_eq!(v, v1234);
} }
#[test]
fn test_empty_blob() {
let db = checked_memory_handle();
let empty = vec![];
db.execute("INSERT INTO foo(b) VALUES (?)", &[&empty]).unwrap();
let v: Vec<u8> = db.query_row("SELECT b FROM foo", &[], |r| r.get(0)).unwrap();
assert_eq!(v, empty);
}
#[test] #[test]
fn test_str() { fn test_str() {
let db = checked_memory_handle(); let db = checked_memory_handle();
let s = "hello, world!";
db.execute("INSERT INTO foo(t) VALUES (?)", &[&s]).unwrap();
let from: String = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)).unwrap();
assert_eq!(from, s);
}
#[test]
fn test_string() {
let db = checked_memory_handle();
let s = "hello, world!"; let s = "hello, world!";
db.execute("INSERT INTO foo(t) VALUES (?)", &[&s.to_owned()]).unwrap(); db.execute("INSERT INTO foo(t) VALUES (?)", &[&s.to_owned()]).unwrap();
@ -175,6 +168,16 @@ mod test {
assert_eq!(from, s); assert_eq!(from, s);
} }
#[test]
fn test_value() {
let db = checked_memory_handle();
db.execute("INSERT INTO foo(i) VALUES (?)", &[&Value::Integer(10)]).unwrap();
assert_eq!(10i64,
db.query_row("SELECT i FROM foo", &[], |r| r.get(0)).unwrap());
}
#[test] #[test]
fn test_option() { fn test_option() {
let db = checked_memory_handle(); let db = checked_memory_handle();

View File

@ -1,24 +1,21 @@
//! `ToSql` and `FromSql` implementation for JSON `Value`. //! `ToSql` and `FromSql` implementation for JSON `Value`.
extern crate serde_json; extern crate serde_json;
use libc::c_int;
use self::serde_json::Value; use self::serde_json::Value;
use types::{FromSql, FromSqlError, ToSql, ValueRef}; use ::Result;
use types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
use ffi::sqlite3_stmt;
/// Serialize JSON `Value` to text. /// Serialize JSON `Value` to text.
impl ToSql for Value { impl ToSql for Value {
unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int { fn to_sql(&self) -> Result<ToSqlOutput> {
let s = serde_json::to_string(self).unwrap(); Ok(ToSqlOutput::from(serde_json::to_string(self).unwrap()))
s.bind_parameter(stmt, col)
} }
} }
/// Deserialize text/blob to JSON `Value`. /// Deserialize text/blob to JSON `Value`.
impl FromSql for Value { impl FromSql for Value {
fn column_result(value: ValueRef) -> Result<Self, FromSqlError> { fn column_result(value: ValueRef) -> FromSqlResult<Self> {
match value { match value {
ValueRef::Text(ref s) => serde_json::from_str(s), ValueRef::Text(ref s) => serde_json::from_str(s),
ValueRef::Blob(ref b) => serde_json::from_slice(b), ValueRef::Blob(ref b) => serde_json::from_slice(b),

View File

@ -1,21 +1,19 @@
extern crate time; extern crate time;
use libc::c_int; use Result;
use types::{FromSql, FromSqlError, ToSql, ValueRef}; use types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
use ffi::sqlite3_stmt;
const SQLITE_DATETIME_FMT: &'static str = "%Y-%m-%d %H:%M:%S"; const SQLITE_DATETIME_FMT: &'static str = "%Y-%m-%d %H:%M:%S";
impl ToSql for time::Timespec { impl ToSql for time::Timespec {
unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int { fn to_sql(&self) -> Result<ToSqlOutput> {
let time_str = time::at_utc(*self).strftime(SQLITE_DATETIME_FMT).unwrap().to_string(); let time_string = time::at_utc(*self).strftime(SQLITE_DATETIME_FMT).unwrap().to_string();
time_str.bind_parameter(stmt, col) Ok(ToSqlOutput::from(time_string))
} }
} }
impl FromSql for time::Timespec { impl FromSql for time::Timespec {
fn column_result(value: ValueRef) -> Result<Self, FromSqlError> { fn column_result(value: ValueRef) -> FromSqlResult<Self> {
value.as_str().and_then(|s| match time::strptime(s, SQLITE_DATETIME_FMT) { value.as_str().and_then(|s| match time::strptime(s, SQLITE_DATETIME_FMT) {
Ok(tm) => Ok(tm.to_timespec()), Ok(tm) => Ok(tm.to_timespec()),
Err(err) => Err(FromSqlError::Other(Box::new(err))), Err(err) => Err(FromSqlError::Other(Box::new(err))),

View File

@ -1,95 +1,97 @@
use std::mem; use super::{Null, Value, ValueRef};
use ::Result;
use libc::{c_double, c_int}; /// `ToSqlOutput` represents the possible output types for implementors of the `ToSql` trait.
pub enum ToSqlOutput<'a> {
/// A borrowed SQLite-representable value.
Borrowed(ValueRef<'a>),
use super::Null; /// An owned SQLite-representable value.
use ::{ffi, str_to_cstring}; Owned(Value),
use ffi::sqlite3_stmt;
/// A BLOB of the given length that is filled with zeroes.
#[cfg(feature = "blob")]
ZeroBlob(i32),
}
impl<'a, T: ?Sized> From<&'a T> for ToSqlOutput<'a>
where &'a T: Into<ValueRef<'a>>
{
fn from(t: &'a T) -> Self {
ToSqlOutput::Borrowed(t.into())
}
}
impl<'a, T: Into<Value>> From<T> for ToSqlOutput<'a> {
fn from(t: T) -> Self {
ToSqlOutput::Owned(t.into())
}
}
/// A trait for types that can be converted into SQLite values. /// A trait for types that can be converted into SQLite values.
pub trait ToSql { pub trait ToSql {
unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int; fn to_sql(&self) -> Result<ToSqlOutput>;
} }
macro_rules! raw_to_impl( // We should be able to use a generic impl like this:
($t:ty, $f:ident) => ( //
// impl<T: Copy> ToSql for T where T: Into<Value> {
// fn to_sql(&self) -> Result<ToSqlOutput> {
// Ok(ToSqlOutput::from((*self).into()))
// }
// }
//
// instead of the following macro, but this runs afoul of
// https://github.com/rust-lang/rust/issues/30191 and reports conflicting
// implementations even when there aren't any.
macro_rules! to_sql_self(
($t:ty) => (
impl ToSql for $t { impl ToSql for $t {
unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int { fn to_sql(&self) -> Result<ToSqlOutput> {
ffi::$f(stmt, col, *self) Ok(ToSqlOutput::from(*self))
} }
} }
) )
); );
raw_to_impl!(c_int, sqlite3_bind_int); // i32 to_sql_self!(Null);
raw_to_impl!(i64, sqlite3_bind_int64); to_sql_self!(bool);
raw_to_impl!(c_double, sqlite3_bind_double); to_sql_self!(i32);
to_sql_self!(i64);
to_sql_self!(f64);
impl ToSql for bool { impl<'a, T: ?Sized> ToSql for &'a T
unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int { where &'a T: Into<ToSqlOutput<'a>>
if *self { {
ffi::sqlite3_bind_int(stmt, col, 1) fn to_sql(&self) -> Result<ToSqlOutput> {
} else { Ok(ToSqlOutput::from((*self).into()))
ffi::sqlite3_bind_int(stmt, col, 0)
}
}
}
impl<'a> ToSql for &'a str {
unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int {
let length = self.len();
if length > ::std::i32::MAX as usize {
return ffi::SQLITE_TOOBIG;
}
match str_to_cstring(self) {
Ok(c_str) => {
ffi::sqlite3_bind_text(stmt,
col,
c_str.as_ptr(),
length as c_int,
ffi::SQLITE_TRANSIENT())
}
Err(_) => ffi::SQLITE_MISUSE,
}
} }
} }
impl ToSql for String { impl ToSql for String {
unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int { fn to_sql(&self) -> Result<ToSqlOutput> {
(&self[..]).bind_parameter(stmt, col) Ok(ToSqlOutput::from(self.as_str()))
}
}
impl<'a> ToSql for &'a [u8] {
unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int {
if self.len() > ::std::i32::MAX as usize {
return ffi::SQLITE_TOOBIG;
}
ffi::sqlite3_bind_blob(stmt,
col,
mem::transmute(self.as_ptr()),
self.len() as c_int,
ffi::SQLITE_TRANSIENT())
} }
} }
impl ToSql for Vec<u8> { impl ToSql for Vec<u8> {
unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int { fn to_sql(&self) -> Result<ToSqlOutput> {
(&self[..]).bind_parameter(stmt, col) Ok(ToSqlOutput::from(self.as_slice()))
}
}
impl ToSql for Value {
fn to_sql(&self) -> Result<ToSqlOutput> {
Ok(ToSqlOutput::from(self))
} }
} }
impl<T: ToSql> ToSql for Option<T> { impl<T: ToSql> ToSql for Option<T> {
unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int { fn to_sql(&self) -> Result<ToSqlOutput> {
match *self { match *self {
None => ffi::sqlite3_bind_null(stmt, col), None => Ok(ToSqlOutput::from(Null)),
Some(ref t) => t.bind_parameter(stmt, col), Some(ref t) => t.to_sql(),
} }
} }
} }
impl ToSql for Null {
unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int {
ffi::sqlite3_bind_null(stmt, col)
}
}

73
src/types/value.rs Normal file
View File

@ -0,0 +1,73 @@
use super::{Null, Type};
/// Owning [dynamic type value](http://sqlite.org/datatype3.html). Value's type is typically
/// dictated by SQLite (not by the caller).
///
/// See [`ValueRef`](enum.ValueRef.html) for a non-owning dynamic type value.
#[derive(Clone,Debug,PartialEq)]
pub enum Value {
/// The value is a `NULL` value.
Null,
/// The value is a signed integer.
Integer(i64),
/// The value is a floating point number.
Real(f64),
/// The value is a text string.
Text(String),
/// The value is a blob of data
Blob(Vec<u8>),
}
impl From<Null> for Value {
fn from(_: Null) -> Value {
Value::Null
}
}
impl From<bool> for Value {
fn from(i: bool) -> Value {
Value::Integer(i as i64)
}
}
impl From<i32> for Value {
fn from(i: i32) -> Value {
Value::Integer(i as i64)
}
}
impl From<i64> for Value {
fn from(i: i64) -> Value {
Value::Integer(i)
}
}
impl From<f64> for Value {
fn from(f: f64) -> Value {
Value::Real(f)
}
}
impl From<String> for Value {
fn from(s: String) -> Value {
Value::Text(s)
}
}
impl From<Vec<u8>> for Value {
fn from(v: Vec<u8>) -> Value {
Value::Blob(v)
}
}
impl Value {
pub fn data_type(&self) -> Type {
match *self {
Value::Null => Type::Null,
Value::Integer(_) => Type::Integer,
Value::Real(_) => Type::Real,
Value::Text(_) => Type::Text,
Value::Blob(_) => Type::Blob,
}
}
}

View File

@ -1,4 +1,4 @@
use ::types::FromSqlError; use ::types::{FromSqlError, FromSqlResult};
use super::{Value, Type}; 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
@ -34,7 +34,7 @@ impl<'a> ValueRef<'a> {
impl<'a> ValueRef<'a> { impl<'a> ValueRef<'a> {
/// If `self` is case `Integer`, returns the integral value. Otherwise, returns /// If `self` is case `Integer`, returns the integral value. Otherwise, returns
/// `Err(Error::InvalidColumnType)`. /// `Err(Error::InvalidColumnType)`.
pub fn as_i64(&self) -> Result<i64, FromSqlError> { pub fn as_i64(&self) -> FromSqlResult<i64> {
match *self { match *self {
ValueRef::Integer(i) => Ok(i), ValueRef::Integer(i) => Ok(i),
_ => Err(FromSqlError::InvalidType), _ => Err(FromSqlError::InvalidType),
@ -43,7 +43,7 @@ impl<'a> ValueRef<'a> {
/// If `self` is case `Real`, returns the floating point value. Otherwise, returns /// If `self` is case `Real`, returns the floating point value. Otherwise, returns
/// `Err(Error::InvalidColumnType)`. /// `Err(Error::InvalidColumnType)`.
pub fn as_f64(&self) -> Result<f64, FromSqlError> { pub fn as_f64(&self) -> FromSqlResult<f64> {
match *self { match *self {
ValueRef::Real(f) => Ok(f), ValueRef::Real(f) => Ok(f),
_ => Err(FromSqlError::InvalidType), _ => Err(FromSqlError::InvalidType),
@ -52,7 +52,7 @@ impl<'a> ValueRef<'a> {
/// If `self` is case `Text`, returns the string value. Otherwise, returns /// If `self` is case `Text`, returns the string value. Otherwise, returns
/// `Err(Error::InvalidColumnType)`. /// `Err(Error::InvalidColumnType)`.
pub fn as_str(&self) -> Result<&str, FromSqlError> { pub fn as_str(&self) -> FromSqlResult<&str> {
match *self { match *self {
ValueRef::Text(ref t) => Ok(t), ValueRef::Text(ref t) => Ok(t),
_ => Err(FromSqlError::InvalidType), _ => Err(FromSqlError::InvalidType),
@ -61,7 +61,7 @@ impl<'a> ValueRef<'a> {
/// If `self` is case `Blob`, returns the byte slice. Otherwise, returns /// If `self` is case `Blob`, returns the byte slice. Otherwise, returns
/// `Err(Error::InvalidColumnType)`. /// `Err(Error::InvalidColumnType)`.
pub fn as_blob(&self) -> Result<&[u8], FromSqlError> { pub fn as_blob(&self) -> FromSqlResult<&[u8]> {
match *self { match *self {
ValueRef::Blob(ref b) => Ok(b), ValueRef::Blob(ref b) => Ok(b),
_ => Err(FromSqlError::InvalidType), _ => Err(FromSqlError::InvalidType),
@ -81,6 +81,18 @@ impl<'a> From<ValueRef<'a>> for Value {
} }
} }
impl<'a> From<&'a str> for ValueRef<'a> {
fn from(s: &str) -> ValueRef {
ValueRef::Text(s)
}
}
impl<'a> From<&'a [u8]> for ValueRef<'a> {
fn from(s: &[u8]) -> ValueRef {
ValueRef::Blob(s)
}
}
impl<'a> From<&'a Value> for ValueRef<'a> { impl<'a> From<&'a Value> for ValueRef<'a> {
fn from(value: &'a Value) -> ValueRef<'a> { fn from(value: &'a Value) -> ValueRef<'a> {
match *value { match *value {