mirror of
https://github.com/isar/rusqlite.git
synced 2025-08-20 21:09:31 +08:00
Merge remote-tracking branch 'jgallagher/master' into vtab
This commit is contained in:
@@ -18,7 +18,7 @@
|
||||
//! # use std::path::Path;
|
||||
//! # use std::time;
|
||||
//!
|
||||
//! fn backupDb<P: AsRef<Path>>(src: &Connection, dst: P, progress: fn(backup::Progress))
|
||||
//! fn backup_db<P: AsRef<Path>>(src: &Connection, dst: P, progress: fn(backup::Progress))
|
||||
//! -> Result<()> {
|
||||
//! let mut dst = try!(Connection::open(dst));
|
||||
//! let backup = try!(backup::Backup::new(src, &mut dst));
|
||||
|
@@ -52,10 +52,9 @@ use std::io;
|
||||
use std::cmp::min;
|
||||
use std::mem;
|
||||
use std::ptr;
|
||||
use libc::c_int;
|
||||
|
||||
use super::ffi;
|
||||
use super::types::ToSql;
|
||||
use super::types::{ToSql, ToSqlOutput};
|
||||
use {Result, Connection, DatabaseName};
|
||||
|
||||
/// Handle to an open BLOB.
|
||||
@@ -240,9 +239,9 @@ impl<'conn> Drop for Blob<'conn> {
|
||||
pub struct ZeroBlob(pub i32);
|
||||
|
||||
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;
|
||||
ffi::sqlite3_bind_zeroblob(stmt, col, length)
|
||||
Ok(ToSqlOutput::ZeroBlob(length))
|
||||
}
|
||||
}
|
||||
|
||||
|
17
src/cache.rs
17
src/cache.rs
@@ -44,6 +44,10 @@ impl Connection {
|
||||
pub fn set_prepared_statement_cache_capacity(&self, capacity: usize) {
|
||||
self.cache.set_capacity(capacity)
|
||||
}
|
||||
|
||||
pub fn flush_prepared_statement_cache(&self) {
|
||||
self.cache.flush()
|
||||
}
|
||||
}
|
||||
|
||||
/// Prepared statements LRU cache.
|
||||
@@ -133,6 +137,11 @@ impl StatementCache {
|
||||
let sql = String::from_utf8_lossy(stmt.sql().to_bytes()).to_string();
|
||||
cache.insert(sql, stmt);
|
||||
}
|
||||
|
||||
fn flush(&self) {
|
||||
let mut cache = self.0.borrow_mut();
|
||||
cache.clear()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -268,4 +277,12 @@ mod test {
|
||||
.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");
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
use {Error, Result, Statement};
|
||||
use {Error, Result, Row, Statement};
|
||||
use types::ToSql;
|
||||
|
||||
impl<'conn> Statement<'conn> {
|
||||
@@ -34,11 +34,26 @@ impl<'conn> Statement<'conn> {
|
||||
};
|
||||
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)]
|
||||
mod test {
|
||||
use {Connection, Error};
|
||||
use {Connection, Error, Result};
|
||||
|
||||
#[test]
|
||||
fn test_insert() {
|
||||
@@ -88,4 +103,18 @@ mod test {
|
||||
assert!(stmt.exists(&[&2i32]).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());
|
||||
}
|
||||
}
|
||||
|
40
src/error.rs
40
src/error.rs
@@ -4,6 +4,7 @@ use std::path::PathBuf;
|
||||
use std::str;
|
||||
use libc::c_int;
|
||||
use {ffi, errmsg_to_string};
|
||||
use types::Type;
|
||||
|
||||
/// Old name for `Error`. `SqliteError` is deprecated.
|
||||
#[deprecated(since = "0.6.0", note = "Use Error instead")]
|
||||
@@ -11,6 +12,7 @@ pub type SqliteError = Error;
|
||||
|
||||
/// Enum listing possible errors from rusqlite.
|
||||
#[derive(Debug)]
|
||||
#[allow(enum_variant_names)]
|
||||
pub enum Error {
|
||||
/// An error from an underlying SQLite call.
|
||||
SqliteFailure(ffi::Error, Option<String>),
|
||||
@@ -19,8 +21,9 @@ pub enum Error {
|
||||
/// allow single-threaded use only.
|
||||
SqliteSingleThreadedMode,
|
||||
|
||||
/// An error case available for implementors of the `FromSql` trait.
|
||||
FromSqlConversionFailure(Box<error::Error + Send + Sync>),
|
||||
/// Error when the value of a particular column is requested, but it cannot be converted to
|
||||
/// the requested Rust type.
|
||||
FromSqlConversionFailure(usize, Type, Box<error::Error + Send + Sync>),
|
||||
|
||||
/// Error converting a string to UTF-8.
|
||||
Utf8Error(str::Utf8Error),
|
||||
@@ -51,7 +54,7 @@ pub enum Error {
|
||||
|
||||
/// Error when the value of a particular column is requested, but the type of the result in
|
||||
/// that column cannot be converted to the requested Rust type.
|
||||
InvalidColumnType,
|
||||
InvalidColumnType(c_int, Type),
|
||||
|
||||
/// Error when a query that was expected to insert one row did not insert any or insert many.
|
||||
StatementChangedRows(c_int),
|
||||
@@ -59,7 +62,7 @@ pub enum Error {
|
||||
/// Error returned by `functions::Context::get` when the function argument cannot be converted
|
||||
/// to the requested type.
|
||||
#[cfg(feature = "functions")]
|
||||
InvalidFunctionParameterType,
|
||||
InvalidFunctionParameterType(usize, Type),
|
||||
|
||||
/// An error case available for implementors of custom user functions (e.g.,
|
||||
/// `create_scalar_function`).
|
||||
@@ -95,7 +98,13 @@ impl fmt::Display for Error {
|
||||
write!(f,
|
||||
"SQLite was compiled or configured for single-threaded use only")
|
||||
}
|
||||
Error::FromSqlConversionFailure(ref err) => err.fmt(f),
|
||||
Error::FromSqlConversionFailure(i, ref t, ref err) => {
|
||||
write!(f,
|
||||
"Conversion error from type {} at index: {}, {}",
|
||||
t,
|
||||
i,
|
||||
err)
|
||||
}
|
||||
Error::Utf8Error(ref err) => err.fmt(f),
|
||||
Error::NulError(ref err) => err.fmt(f),
|
||||
Error::InvalidParameterName(ref name) => write!(f, "Invalid parameter name: {}", name),
|
||||
@@ -106,11 +115,15 @@ impl fmt::Display for Error {
|
||||
Error::QueryReturnedNoRows => write!(f, "Query returned no rows"),
|
||||
Error::InvalidColumnIndex(i) => write!(f, "Invalid column index: {}", i),
|
||||
Error::InvalidColumnName(ref name) => write!(f, "Invalid column name: {}", name),
|
||||
Error::InvalidColumnType => write!(f, "Invalid column type"),
|
||||
Error::InvalidColumnType(i, ref t) => {
|
||||
write!(f, "Invalid column type {} at index: {}", t, i)
|
||||
}
|
||||
Error::StatementChangedRows(i) => write!(f, "Query changed {} rows", i),
|
||||
|
||||
#[cfg(feature = "functions")]
|
||||
Error::InvalidFunctionParameterType => write!(f, "Invalid function parameter type"),
|
||||
Error::InvalidFunctionParameterType(i, ref t) => {
|
||||
write!(f, "Invalid function parameter type {} at index {}", t, i)
|
||||
}
|
||||
#[cfg(feature = "functions")]
|
||||
Error::UserFunctionError(ref err) => err.fmt(f),
|
||||
#[cfg(feature = "vtab")]
|
||||
@@ -127,7 +140,7 @@ impl error::Error for Error {
|
||||
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::Utf8Error(ref err) => err.description(),
|
||||
Error::InvalidParameterName(_) => "invalid parameter name",
|
||||
Error::NulError(ref err) => err.description(),
|
||||
@@ -138,11 +151,11 @@ impl error::Error for Error {
|
||||
Error::QueryReturnedNoRows => "query returned no rows",
|
||||
Error::InvalidColumnIndex(_) => "invalid column index",
|
||||
Error::InvalidColumnName(_) => "invalid column name",
|
||||
Error::InvalidColumnType => "invalid column type",
|
||||
Error::InvalidColumnType(_, _) => "invalid column type",
|
||||
Error::StatementChangedRows(_) => "query inserted zero or more than one row",
|
||||
|
||||
#[cfg(feature = "functions")]
|
||||
Error::InvalidFunctionParameterType => "invalid function parameter type",
|
||||
Error::InvalidFunctionParameterType(_, _) => "invalid function parameter type",
|
||||
#[cfg(feature = "functions")]
|
||||
Error::UserFunctionError(ref err) => err.description(),
|
||||
#[cfg(feature = "vtab")]
|
||||
@@ -150,11 +163,10 @@ impl error::Error for Error {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature="clippy", allow(match_same_arms))]
|
||||
fn cause(&self) -> Option<&error::Error> {
|
||||
match *self {
|
||||
Error::SqliteFailure(ref err, _) => Some(err),
|
||||
Error::FromSqlConversionFailure(ref err) => Some(&**err),
|
||||
Error::FromSqlConversionFailure(_, _, ref err) => Some(&**err),
|
||||
Error::Utf8Error(ref err) => Some(err),
|
||||
Error::NulError(ref err) => Some(err),
|
||||
|
||||
@@ -164,12 +176,12 @@ impl error::Error for Error {
|
||||
Error::QueryReturnedNoRows |
|
||||
Error::InvalidColumnIndex(_) |
|
||||
Error::InvalidColumnName(_) |
|
||||
Error::InvalidColumnType |
|
||||
Error::InvalidColumnType(_, _) |
|
||||
Error::InvalidPath(_) |
|
||||
Error::StatementChangedRows(_) => None,
|
||||
|
||||
#[cfg(feature = "functions")]
|
||||
Error::InvalidFunctionParameterType => None,
|
||||
Error::InvalidFunctionParameterType(_, _) => None,
|
||||
|
||||
#[cfg(feature = "functions")]
|
||||
Error::UserFunctionError(ref err) => Some(&**err),
|
||||
|
263
src/functions.rs
263
src/functions.rs
@@ -54,125 +54,79 @@ use std::ffi::CStr;
|
||||
use std::mem;
|
||||
use std::ptr;
|
||||
use std::slice;
|
||||
use libc::{c_int, c_double, c_char, c_void};
|
||||
use libc::{c_int, c_char, c_void};
|
||||
|
||||
use ffi;
|
||||
pub use ffi::sqlite3_context;
|
||||
pub use ffi::sqlite3_value;
|
||||
pub use ffi::sqlite3_value_type;
|
||||
pub use ffi::sqlite3_value_numeric_type;
|
||||
use ffi::sqlite3_context;
|
||||
use ffi::sqlite3_value;
|
||||
|
||||
use types::{Null, FromSql, ValueRef};
|
||||
use types::{ToSql, ToSqlOutput, FromSql, FromSqlError, ValueRef};
|
||||
|
||||
use {Result, Error, Connection, str_to_cstring, InnerConnection};
|
||||
|
||||
/// A trait for types that can be converted into the result of an SQL function.
|
||||
pub trait ToResult {
|
||||
unsafe fn set_result(&self, ctx: *mut sqlite3_context);
|
||||
}
|
||||
pub fn set_result<'a>(ctx: *mut sqlite3_context, result: &ToSqlOutput<'a>) {
|
||||
let value = match *result {
|
||||
ToSqlOutput::Borrowed(v) => v,
|
||||
ToSqlOutput::Owned(ref v) => ValueRef::from(v),
|
||||
|
||||
macro_rules! raw_to_impl(
|
||||
($t:ty, $f:ident) => (
|
||||
impl ToResult for $t {
|
||||
unsafe fn set_result(&self, ctx: *mut sqlite3_context) {
|
||||
ffi::$f(ctx, *self)
|
||||
#[cfg(feature = "blob")]
|
||||
ToSqlOutput::ZeroBlob(len) => {
|
||||
return unsafe { ffi::sqlite3_result_zeroblob(ctx, len) };
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
raw_to_impl!(c_int, sqlite3_result_int);
|
||||
raw_to_impl!(i64, sqlite3_result_int64);
|
||||
raw_to_impl!(c_double, sqlite3_result_double);
|
||||
|
||||
impl<'a> ToResult for bool {
|
||||
unsafe fn set_result(&self, ctx: *mut sqlite3_context) {
|
||||
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(),
|
||||
},
|
||||
ValueRef::Blob(ref b) => unsafe {
|
||||
let length = b.len();
|
||||
if length > ::std::i32::MAX as usize {
|
||||
ffi::sqlite3_result_error_toobig(ctx);
|
||||
} else if length == 0 {
|
||||
ffi::sqlite3_result_zeroblob(ctx, 0)
|
||||
} else {
|
||||
ffi::sqlite3_result_blob(ctx,
|
||||
b.as_ptr() as *const c_void,
|
||||
length as c_int,
|
||||
ffi::SQLITE_TRANSIENT())
|
||||
ffi::SQLITE_TRANSIENT());
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub 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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToResult for String {
|
||||
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_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);
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
/// Error indicating that a memory allocation failed.
|
||||
#[derive(Copy,Clone)]
|
||||
pub struct NoMem;
|
||||
|
||||
impl ToResult for NoMem {
|
||||
unsafe fn set_result(&self, ctx: *mut sqlite3_context) {
|
||||
ffi::sqlite3_result_error_nomem(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
/// Error indicating that a string or BLOB is too long to represent.
|
||||
#[derive(Copy,Clone)]
|
||||
pub struct TooBig;
|
||||
|
||||
impl ToResult for TooBig {
|
||||
unsafe fn set_result(&self, ctx: *mut sqlite3_context) {
|
||||
ffi::sqlite3_result_error_toobig(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,7 +140,8 @@ impl<'a> ValueRef<'a> {
|
||||
ffi::SQLITE_FLOAT => ValueRef::Real(ffi::sqlite3_value_double(value)),
|
||||
ffi::SQLITE_TEXT => {
|
||||
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);
|
||||
|
||||
// sqlite3_value_text returns UTF8 data, so our unwrap here should be fine.
|
||||
@@ -195,10 +150,12 @@ impl<'a> ValueRef<'a> {
|
||||
}
|
||||
ffi::SQLITE_BLOB => {
|
||||
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);
|
||||
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))
|
||||
}
|
||||
@@ -238,8 +195,12 @@ impl<'a> Context<'a> {
|
||||
let arg = self.args[idx];
|
||||
let value = unsafe { ValueRef::from_value(arg) };
|
||||
FromSql::column_result(value).map_err(|err| match err {
|
||||
Error::InvalidColumnType => Error::InvalidFunctionParameterType,
|
||||
_ => err,
|
||||
FromSqlError::InvalidType => {
|
||||
Error::InvalidFunctionParameterType(idx, value.data_type())
|
||||
}
|
||||
FromSqlError::Other(err) => {
|
||||
Error::FromSqlConversionFailure(idx, value.data_type(), err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -275,7 +236,7 @@ impl<'a> Context<'a> {
|
||||
/// `A` is the type of the aggregation context and `T` is the type of the final result.
|
||||
/// Implementations should be stateless.
|
||||
pub trait Aggregate<A, T>
|
||||
where T: ToResult
|
||||
where T: ToSql
|
||||
{
|
||||
/// 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:
|
||||
@@ -309,10 +270,9 @@ impl Connection {
|
||||
///
|
||||
/// ```rust
|
||||
/// # use rusqlite::{Connection, Result};
|
||||
/// # type c_double = f64;
|
||||
/// fn scalar_function_example(db: Connection) -> Result<()> {
|
||||
/// try!(db.create_scalar_function("halve", 1, true, |ctx| {
|
||||
/// let value = try!(ctx.get::<c_double>(0));
|
||||
/// let value = try!(ctx.get::<f64>(0));
|
||||
/// Ok(value / 2f64)
|
||||
/// }));
|
||||
///
|
||||
@@ -332,7 +292,7 @@ impl Connection {
|
||||
x_func: F)
|
||||
-> Result<()>
|
||||
where F: FnMut(&Context) -> Result<T>,
|
||||
T: ToResult
|
||||
T: ToSql
|
||||
{
|
||||
self.db.borrow_mut().create_scalar_function(fn_name, n_arg, deterministic, x_func)
|
||||
}
|
||||
@@ -349,7 +309,7 @@ impl Connection {
|
||||
aggr: D)
|
||||
-> Result<()>
|
||||
where D: Aggregate<A, T>,
|
||||
T: ToResult
|
||||
T: ToSql
|
||||
{
|
||||
self.db
|
||||
.borrow_mut()
|
||||
@@ -377,13 +337,13 @@ impl InnerConnection {
|
||||
x_func: F)
|
||||
-> Result<()>
|
||||
where F: FnMut(&Context) -> Result<T>,
|
||||
T: ToResult
|
||||
T: ToSql
|
||||
{
|
||||
unsafe extern "C" fn call_boxed_closure<F, T>(ctx: *mut sqlite3_context,
|
||||
argc: c_int,
|
||||
argv: *mut *mut sqlite3_value)
|
||||
where F: FnMut(&Context) -> Result<T>,
|
||||
T: ToResult
|
||||
T: ToSql
|
||||
{
|
||||
let ctx = Context {
|
||||
ctx: ctx,
|
||||
@@ -391,20 +351,14 @@ impl InnerConnection {
|
||||
};
|
||||
let boxed_f: *mut F = mem::transmute(ffi::sqlite3_user_data(ctx.ctx));
|
||||
assert!(!boxed_f.is_null(), "Internal error - null function pointer");
|
||||
match (*boxed_f)(&ctx) {
|
||||
Ok(r) => r.set_result(ctx.ctx),
|
||||
Err(Error::SqliteFailure(err, s)) => {
|
||||
ffi::sqlite3_result_error_code(ctx.ctx, err.extended_code);
|
||||
if let Some(Ok(cstr)) = s.map(|s| str_to_cstring(&s)) {
|
||||
ffi::sqlite3_result_error(ctx.ctx, cstr.as_ptr(), -1);
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
let t = (*boxed_f)(&ctx);
|
||||
let t = t.as_ref().map(|t| ToSql::to_sql(t));
|
||||
|
||||
match t {
|
||||
Ok(Ok(ref value)) => set_result(ctx.ctx, value),
|
||||
Ok(Err(err)) => report_error(ctx.ctx, &err),
|
||||
Err(err) => report_error(ctx.ctx, err),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -435,7 +389,7 @@ impl InnerConnection {
|
||||
aggr: D)
|
||||
-> Result<()>
|
||||
where D: Aggregate<A, T>,
|
||||
T: ToResult
|
||||
T: ToSql
|
||||
{
|
||||
unsafe fn aggregate_context<A>(ctx: *mut sqlite3_context,
|
||||
bytes: usize)
|
||||
@@ -447,28 +401,11 @@ impl InnerConnection {
|
||||
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,
|
||||
argc: c_int,
|
||||
argv: *mut *mut sqlite3_value)
|
||||
where D: Aggregate<A, T>,
|
||||
T: ToResult
|
||||
T: ToSql
|
||||
{
|
||||
let boxed_aggr: *mut D = mem::transmute(ffi::sqlite3_user_data(ctx));
|
||||
assert!(!boxed_aggr.is_null(),
|
||||
@@ -493,13 +430,13 @@ impl InnerConnection {
|
||||
|
||||
match (*boxed_aggr).step(&mut ctx, &mut **pac) {
|
||||
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)
|
||||
where D: Aggregate<A, T>,
|
||||
T: ToResult
|
||||
T: ToSql
|
||||
{
|
||||
let boxed_aggr: *mut D = mem::transmute(ffi::sqlite3_user_data(ctx));
|
||||
assert!(!boxed_aggr.is_null(),
|
||||
@@ -519,10 +456,13 @@ impl InnerConnection {
|
||||
None => None,
|
||||
};
|
||||
|
||||
match (*boxed_aggr).finalize(a) {
|
||||
Ok(r) => r.set_result(ctx),
|
||||
Err(err) => report_aggregate_error(ctx, err),
|
||||
};
|
||||
let t = (*boxed_aggr).finalize(a);
|
||||
let t = t.as_ref().map(|t| ToSql::to_sql(t));
|
||||
match t {
|
||||
Ok(Ok(ref value)) => set_result(ctx, value),
|
||||
Ok(Err(err)) => report_error(ctx, &err),
|
||||
Err(err) => report_error(ctx, err),
|
||||
}
|
||||
}
|
||||
|
||||
let boxed_aggr: *mut D = Box::into_raw(Box::new(aggr));
|
||||
@@ -725,9 +665,10 @@ mod test {
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
for &(expected, query) in &[("", "SELECT my_concat()"),
|
||||
("onetwo", "SELECT my_concat('one', 'two')"),
|
||||
("abc", "SELECT my_concat('a', 'b', 'c')")] {
|
||||
for &(expected, query) in
|
||||
&[("", "SELECT my_concat()"),
|
||||
("onetwo", "SELECT my_concat('one', 'two')"),
|
||||
("abc", "SELECT my_concat('a', 'b', 'c')")] {
|
||||
let result: String = db.query_row(query, &[], |r| r.get(0)).unwrap();
|
||||
assert_eq!(expected, result);
|
||||
}
|
||||
|
90
src/lib.rs
90
src/lib.rs
@@ -32,11 +32,11 @@
|
||||
//! data: None
|
||||
//! };
|
||||
//! 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();
|
||||
//!
|
||||
//! let mut stmt = conn.prepare("SELECT id, name, time_created, data FROM person").unwrap();
|
||||
//! let mut person_iter = stmt.query_map(&[], |row| {
|
||||
//! let person_iter = stmt.query_map(&[], |row| {
|
||||
//! Person {
|
||||
//! id: row.get(0),
|
||||
//! name: row.get(1),
|
||||
@@ -50,6 +50,7 @@
|
||||
//! }
|
||||
//! }
|
||||
//! ```
|
||||
#![allow(unknown_lints)]
|
||||
|
||||
extern crate libc;
|
||||
extern crate libsqlite3_sys as ffi;
|
||||
@@ -71,9 +72,9 @@ use std::cell::RefCell;
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::result;
|
||||
use std::str;
|
||||
use libc::{c_int, c_char};
|
||||
use libc::{c_int, c_char, c_void};
|
||||
|
||||
use types::{ToSql, FromSql, ValueRef};
|
||||
use types::{ToSql, ToSqlOutput, FromSql, FromSqlError, ValueRef};
|
||||
use error::{error_from_sqlite_code, error_from_handle};
|
||||
use raw_statement::RawStatement;
|
||||
use cache::StatementCache;
|
||||
@@ -310,12 +311,10 @@ impl Connection {
|
||||
/// Will return `Err` if `sql` cannot be converted to a C-compatible string or if the
|
||||
/// underlying SQLite call fails.
|
||||
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 rows = try!(stmt.query(params));
|
||||
|
||||
rows.get_expected_row().map(f)
|
||||
stmt.query_row(params, f)
|
||||
}
|
||||
|
||||
/// Convenience method to execute a query that is expected to return a single row,
|
||||
@@ -346,13 +345,13 @@ impl Connection {
|
||||
params: &[&ToSql],
|
||||
f: F)
|
||||
-> result::Result<T, E>
|
||||
where F: FnOnce(Row) -> result::Result<T, E>,
|
||||
where F: FnOnce(&Row) -> result::Result<T, E>,
|
||||
E: convert::From<Error>
|
||||
{
|
||||
let mut stmt = try!(self.prepare(sql));
|
||||
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.
|
||||
@@ -376,7 +375,7 @@ impl Connection {
|
||||
/// 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>
|
||||
where F: FnOnce(Row) -> T
|
||||
where F: FnOnce(&Row) -> T
|
||||
{
|
||||
self.query_row(sql, params, f)
|
||||
}
|
||||
@@ -412,6 +411,7 @@ impl Connection {
|
||||
///
|
||||
/// Will return `Err` if the underlying SQLite call fails.
|
||||
pub fn close(self) -> Result<()> {
|
||||
self.flush_prepared_statement_cache();
|
||||
let mut db = self.db.borrow_mut();
|
||||
db.close()
|
||||
}
|
||||
@@ -884,6 +884,55 @@ impl<'conn> Statement<'conn> {
|
||||
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<()> {
|
||||
assert!(params.len() as c_int == self.stmt.bind_parameter_count(),
|
||||
"incorrect number of parameters to query(): expected {}, got {}",
|
||||
@@ -891,9 +940,7 @@ impl<'conn> Statement<'conn> {
|
||||
params.len());
|
||||
|
||||
for (i, p) in params.iter().enumerate() {
|
||||
try!(unsafe {
|
||||
self.conn.decode_result(p.bind_parameter(self.stmt.ptr(), (i + 1) as c_int))
|
||||
});
|
||||
try!(self.bind_parameter(*p, (i + 1) as c_int));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -980,6 +1027,7 @@ pub struct Rows<'stmt> {
|
||||
stmt: Option<&'stmt Statement<'stmt>>,
|
||||
}
|
||||
|
||||
#[allow(should_implement_trait)]
|
||||
impl<'stmt> Rows<'stmt> {
|
||||
fn new(stmt: &'stmt Statement<'stmt>) -> Rows<'stmt> {
|
||||
Rows { stmt: Some(stmt) }
|
||||
@@ -1073,7 +1121,12 @@ impl<'a, 'stmt> Row<'a, 'stmt> {
|
||||
pub fn get_checked<I: RowIndex, T: FromSql>(&self, idx: I) -> Result<T> {
|
||||
let idx = try!(idx.idx(self.stmt));
|
||||
let value = unsafe { ValueRef::new(&self.stmt.stmt, idx) };
|
||||
FromSql::column_result(value)
|
||||
FromSql::column_result(value).map_err(|err| match err {
|
||||
FromSqlError::InvalidType => Error::InvalidColumnType(idx, value.data_type()),
|
||||
FromSqlError::Other(err) => {
|
||||
Error::FromSqlConversionFailure(idx as usize, value.data_type(), err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Return the number of columns in the current row.
|
||||
@@ -1141,7 +1194,6 @@ impl<'a> ValueRef<'a> {
|
||||
// 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"),
|
||||
}
|
||||
@@ -1503,7 +1555,7 @@ mod test {
|
||||
.collect();
|
||||
|
||||
match bad_type.unwrap_err() {
|
||||
Error::InvalidColumnType => (),
|
||||
Error::InvalidColumnType(_, _) => (),
|
||||
err => panic!("Unexpected error {}", err),
|
||||
}
|
||||
|
||||
@@ -1563,7 +1615,7 @@ mod test {
|
||||
.collect();
|
||||
|
||||
match bad_type.unwrap_err() {
|
||||
CustomError::Sqlite(Error::InvalidColumnType) => (),
|
||||
CustomError::Sqlite(Error::InvalidColumnType(_, _)) => (),
|
||||
err => panic!("Unexpected error {}", err),
|
||||
}
|
||||
|
||||
@@ -1625,7 +1677,7 @@ mod test {
|
||||
});
|
||||
|
||||
match bad_type.unwrap_err() {
|
||||
CustomError::Sqlite(Error::InvalidColumnType) => (),
|
||||
CustomError::Sqlite(Error::InvalidColumnType(_, _)) => (),
|
||||
err => panic!("Unexpected error {}", err),
|
||||
}
|
||||
|
||||
|
@@ -38,12 +38,12 @@ impl Connection {
|
||||
/// Will return `Err` if `sql` cannot be converted to a C-compatible string or if the
|
||||
/// underlying SQLite call fails.
|
||||
pub fn query_row_named<T, F>(&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 rows = try!(stmt.query_named(params));
|
||||
|
||||
rows.get_expected_row().map(f)
|
||||
rows.get_expected_row().map(|r| f(&r))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ impl<'conn> Statement<'conn> {
|
||||
/// ## Example
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use rusqlite::{Connection, Result, Rows};
|
||||
/// # use rusqlite::{Connection, Result};
|
||||
/// fn query(conn: &Connection) -> Result<()> {
|
||||
/// let mut stmt = try!(conn.prepare("SELECT * FROM test where name = :name"));
|
||||
/// let mut rows = try!(stmt.query_named(&[(":name", &"one")]));
|
||||
@@ -204,7 +204,7 @@ impl<'conn> Statement<'conn> {
|
||||
fn bind_parameters_named(&mut self, params: &[(&str, &ToSql)]) -> Result<()> {
|
||||
for &(name, value) in params {
|
||||
if let Some(i) = try!(self.parameter_index(name)) {
|
||||
try!(self.conn.decode_result(unsafe { value.bind_parameter(self.stmt.ptr(), i) }));
|
||||
try!(self.bind_parameter(value, i));
|
||||
} else {
|
||||
return Err(Error::InvalidParameterName(name.into()));
|
||||
}
|
||||
|
@@ -44,8 +44,8 @@ pub type SqliteTransaction<'conn> = Transaction<'conn>;
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use rusqlite::{Connection, Result};
|
||||
/// # fn do_queries_part_1(conn: &Connection) -> Result<()> { Ok(()) }
|
||||
/// # fn do_queries_part_2(conn: &Connection) -> Result<()> { Ok(()) }
|
||||
/// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }
|
||||
/// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }
|
||||
/// fn perform_queries(conn: &mut Connection) -> Result<()> {
|
||||
/// let tx = try!(conn.transaction());
|
||||
///
|
||||
@@ -73,8 +73,8 @@ pub struct Transaction<'conn> {
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use rusqlite::{Connection, Result};
|
||||
/// # fn do_queries_part_1(conn: &Connection) -> Result<()> { Ok(()) }
|
||||
/// # fn do_queries_part_2(conn: &Connection) -> Result<()> { Ok(()) }
|
||||
/// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }
|
||||
/// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }
|
||||
/// fn perform_queries(conn: &mut Connection) -> Result<()> {
|
||||
/// let sp = try!(conn.savepoint());
|
||||
///
|
||||
@@ -120,7 +120,7 @@ impl<'conn> Transaction<'conn> {
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use rusqlite::{Connection, Result};
|
||||
/// # fn perform_queries_part_1_succeeds(conn: &Connection) -> bool { true }
|
||||
/// # fn perform_queries_part_1_succeeds(_conn: &Connection) -> bool { true }
|
||||
/// fn perform_queries(conn: &mut Connection) -> Result<()> {
|
||||
/// let mut tx = try!(conn.transaction());
|
||||
///
|
||||
@@ -328,8 +328,8 @@ impl Connection {
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use rusqlite::{Connection, Result};
|
||||
/// # fn do_queries_part_1(conn: &Connection) -> Result<()> { Ok(()) }
|
||||
/// # fn do_queries_part_2(conn: &Connection) -> Result<()> { Ok(()) }
|
||||
/// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }
|
||||
/// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }
|
||||
/// fn perform_queries(conn: &mut Connection) -> Result<()> {
|
||||
/// let tx = try!(conn.transaction());
|
||||
///
|
||||
@@ -369,8 +369,8 @@ impl Connection {
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use rusqlite::{Connection, Result};
|
||||
/// # fn do_queries_part_1(conn: &Connection) -> Result<()> { Ok(()) }
|
||||
/// # fn do_queries_part_2(conn: &Connection) -> Result<()> { Ok(()) }
|
||||
/// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }
|
||||
/// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }
|
||||
/// fn perform_queries(conn: &mut Connection) -> Result<()> {
|
||||
/// let sp = try!(conn.savepoint());
|
||||
///
|
||||
@@ -401,7 +401,6 @@ impl Connection {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg_attr(feature="clippy", allow(similar_names))]
|
||||
mod test {
|
||||
use Connection;
|
||||
use super::DropBehavior;
|
||||
|
@@ -4,42 +4,39 @@ extern crate chrono;
|
||||
use std::borrow::Cow;
|
||||
|
||||
use self::chrono::{NaiveDate, NaiveTime, NaiveDateTime, DateTime, TimeZone, UTC, Local};
|
||||
use libc::c_int;
|
||||
|
||||
use {Error, Result};
|
||||
use types::{FromSql, ToSql, ValueRef};
|
||||
|
||||
use ffi::sqlite3_stmt;
|
||||
use ::Result;
|
||||
use types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
|
||||
|
||||
/// ISO 8601 calendar date without timezone => "YYYY-MM-DD"
|
||||
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();
|
||||
date_str.bind_parameter(stmt, col)
|
||||
Ok(ToSqlOutput::from(date_str))
|
||||
}
|
||||
}
|
||||
|
||||
/// "YYYY-MM-DD" => ISO 8601 calendar date without timezone.
|
||||
impl FromSql for NaiveDate {
|
||||
fn column_result(value: ValueRef) -> Result<Self> {
|
||||
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
|
||||
value.as_str().and_then(|s| match NaiveDate::parse_from_str(s, "%Y-%m-%d") {
|
||||
Ok(dt) => Ok(dt),
|
||||
Err(err) => Err(Error::FromSqlConversionFailure(Box::new(err))),
|
||||
Err(err) => Err(FromSqlError::Other(Box::new(err))),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// ISO 8601 time without timezone => "HH:MM:SS.SSS"
|
||||
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();
|
||||
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.
|
||||
impl FromSql for NaiveTime {
|
||||
fn column_result(value: ValueRef) -> Result<Self> {
|
||||
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
|
||||
value.as_str().and_then(|s| {
|
||||
let fmt = match s.len() {
|
||||
5 => "%H:%M",
|
||||
@@ -48,7 +45,7 @@ impl FromSql for NaiveTime {
|
||||
};
|
||||
match NaiveTime::parse_from_str(s, fmt) {
|
||||
Ok(dt) => Ok(dt),
|
||||
Err(err) => Err(Error::FromSqlConversionFailure(Box::new(err))),
|
||||
Err(err) => Err(FromSqlError::Other(Box::new(err))),
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -56,16 +53,16 @@ impl FromSql for NaiveTime {
|
||||
|
||||
/// ISO 8601 combined date and time without timezone => "YYYY-MM-DD HH:MM:SS.SSS"
|
||||
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();
|
||||
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
|
||||
/// without timezone. ("YYYY-MM-DDTHH:MM:SS"/"YYYY-MM-DDTHH:MM:SS.SSS" also supported)
|
||||
impl FromSql for NaiveDateTime {
|
||||
fn column_result(value: ValueRef) -> Result<Self> {
|
||||
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
|
||||
value.as_str().and_then(|s| {
|
||||
let fmt = if s.len() >= 11 && s.as_bytes()[10] == b'T' {
|
||||
"%Y-%m-%dT%H:%M:%S%.f"
|
||||
@@ -75,7 +72,7 @@ impl FromSql for NaiveDateTime {
|
||||
|
||||
match NaiveDateTime::parse_from_str(s, fmt) {
|
||||
Ok(dt) => Ok(dt),
|
||||
Err(err) => Err(Error::FromSqlConversionFailure(Box::new(err))),
|
||||
Err(err) => Err(FromSqlError::Other(Box::new(err))),
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -83,15 +80,14 @@ impl FromSql for NaiveDateTime {
|
||||
|
||||
/// Date and time with time zone => UTC RFC3339 timestamp ("YYYY-MM-DDTHH:MM:SS.SSS+00:00").
|
||||
impl<Tz: TimeZone> ToSql for DateTime<Tz> {
|
||||
unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int {
|
||||
let utc_dt = self.with_timezone(&UTC);
|
||||
utc_dt.to_rfc3339().bind_parameter(stmt, col)
|
||||
fn to_sql(&self) -> Result<ToSqlOutput> {
|
||||
Ok(ToSqlOutput::from(self.with_timezone(&UTC).to_rfc3339()))
|
||||
}
|
||||
}
|
||||
|
||||
/// RFC3339 ("YYYY-MM-DDTHH:MM:SS.SSS[+-]HH:MM") into DateTime<UTC>.
|
||||
impl FromSql for DateTime<UTC> {
|
||||
fn column_result(value: ValueRef) -> Result<Self> {
|
||||
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
|
||||
{
|
||||
// Try to parse value as rfc3339 first.
|
||||
let s = try!(value.as_str());
|
||||
@@ -121,7 +117,7 @@ impl FromSql for DateTime<UTC> {
|
||||
|
||||
/// RFC3339 ("YYYY-MM-DDTHH:MM:SS.SSS[+-]HH:MM") into DateTime<Local>.
|
||||
impl FromSql for DateTime<Local> {
|
||||
fn column_result(value: ValueRef) -> Result<Self> {
|
||||
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
|
||||
let utc_dt = try!(DateTime::<UTC>::column_result(value));
|
||||
Ok(utc_dt.with_timezone(&Local))
|
||||
}
|
||||
|
@@ -1,36 +1,75 @@
|
||||
use super::{ValueRef, Value};
|
||||
use ::Result;
|
||||
use ::error::Error;
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
|
||||
/// Enum listing possible errors from `FromSql` trait.
|
||||
#[derive(Debug)]
|
||||
pub enum FromSqlError {
|
||||
/// Error when an SQLite value is requested, but the type of the result cannot be converted to the
|
||||
/// requested Rust type.
|
||||
InvalidType,
|
||||
/// An error case available for implementors of the `FromSql` trait.
|
||||
Other(Box<Error + Send + Sync>),
|
||||
}
|
||||
|
||||
impl fmt::Display for FromSqlError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
FromSqlError::InvalidType => write!(f, "Invalid type"),
|
||||
FromSqlError::Other(ref err) => err.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for FromSqlError {
|
||||
fn description(&self) -> &str {
|
||||
match *self {
|
||||
FromSqlError::InvalidType => "invalid type",
|
||||
FromSqlError::Other(ref err) => err.description(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature="clippy", allow(match_same_arms))]
|
||||
fn cause(&self) -> Option<&Error> {
|
||||
match *self {
|
||||
FromSqlError::InvalidType => None,
|
||||
FromSqlError::Other(ref err) => err.cause(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
pub trait FromSql: Sized {
|
||||
fn column_result(value: ValueRef) -> Result<Self>;
|
||||
fn column_result(value: ValueRef) -> FromSqlResult<Self>;
|
||||
}
|
||||
|
||||
impl FromSql for i32 {
|
||||
fn column_result(value: ValueRef) -> Result<Self> {
|
||||
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
|
||||
i64::column_result(value).map(|i| i as i32)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromSql for i64 {
|
||||
fn column_result(value: ValueRef) -> Result<Self> {
|
||||
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
|
||||
value.as_i64()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromSql for f64 {
|
||||
fn column_result(value: ValueRef) -> Result<Self> {
|
||||
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
|
||||
match value {
|
||||
ValueRef::Integer(i) => Ok(i as f64),
|
||||
ValueRef::Real(f) => Ok(f),
|
||||
_ => Err(Error::InvalidColumnType),
|
||||
_ => Err(FromSqlError::InvalidType),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromSql for bool {
|
||||
fn column_result(value: ValueRef) -> Result<Self> {
|
||||
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
|
||||
i64::column_result(value).map(|i| match i {
|
||||
0 => false,
|
||||
_ => true,
|
||||
@@ -39,19 +78,19 @@ impl FromSql for bool {
|
||||
}
|
||||
|
||||
impl FromSql for String {
|
||||
fn column_result(value: ValueRef) -> Result<Self> {
|
||||
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
|
||||
value.as_str().map(|s| s.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromSql for Vec<u8> {
|
||||
fn column_result(value: ValueRef) -> Result<Self> {
|
||||
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
|
||||
value.as_blob().map(|b| b.to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: FromSql> FromSql for Option<T> {
|
||||
fn column_result(value: ValueRef) -> Result<Self> {
|
||||
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
|
||||
match value {
|
||||
ValueRef::Null => Ok(None),
|
||||
_ => FromSql::column_result(value).map(Some),
|
||||
@@ -60,7 +99,7 @@ impl<T: FromSql> FromSql for Option<T> {
|
||||
}
|
||||
|
||||
impl FromSql for Value {
|
||||
fn column_result(value: ValueRef) -> Result<Self> {
|
||||
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
|
||||
Ok(value.into())
|
||||
}
|
||||
}
|
||||
|
@@ -50,12 +50,14 @@
|
||||
//! `FromSql` for the cases where you want to know if a value was NULL (which gets translated to
|
||||
//! `None`).
|
||||
|
||||
pub use ffi::sqlite3_stmt;
|
||||
|
||||
pub use self::from_sql::FromSql;
|
||||
pub use self::to_sql::ToSql;
|
||||
pub use self::from_sql::{FromSql, FromSqlError, FromSqlResult};
|
||||
pub use self::to_sql::{ToSql, ToSqlOutput};
|
||||
pub use self::value::Value;
|
||||
pub use self::value_ref::ValueRef;
|
||||
|
||||
use std::fmt;
|
||||
|
||||
mod value;
|
||||
mod value_ref;
|
||||
mod from_sql;
|
||||
mod to_sql;
|
||||
@@ -84,27 +86,28 @@ mod serde_json;
|
||||
#[derive(Copy,Clone)]
|
||||
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.
|
||||
pub enum Type {
|
||||
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>),
|
||||
Integer,
|
||||
Real,
|
||||
Text,
|
||||
Blob,
|
||||
}
|
||||
|
||||
impl fmt::Display for Type {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
Type::Null => write!(f, "Null"),
|
||||
Type::Integer => write!(f, "Integer"),
|
||||
Type::Real => write!(f, "Real"),
|
||||
Type::Text => write!(f, "Text"),
|
||||
Type::Blob => write!(f, "Blob"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg_attr(feature="clippy", allow(similar_names))]
|
||||
mod test {
|
||||
extern crate time;
|
||||
|
||||
@@ -112,6 +115,7 @@ mod test {
|
||||
use Error;
|
||||
use libc::{c_int, c_double};
|
||||
use std::f64::EPSILON;
|
||||
use super::Value;
|
||||
|
||||
fn checked_memory_handle() -> Connection {
|
||||
let db = Connection::open_in_memory().unwrap();
|
||||
@@ -145,6 +149,17 @@ mod test {
|
||||
fn test_str() {
|
||||
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!";
|
||||
db.execute("INSERT INTO foo(t) VALUES (?)", &[&s.to_owned()]).unwrap();
|
||||
|
||||
@@ -152,6 +167,16 @@ mod test {
|
||||
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]
|
||||
fn test_option() {
|
||||
let db = checked_memory_handle();
|
||||
@@ -183,11 +208,10 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(feature="clippy", allow(cyclomatic_complexity))]
|
||||
fn test_mismatched_types() {
|
||||
fn is_invalid_column_type(err: Error) -> bool {
|
||||
match err {
|
||||
Error::InvalidColumnType => true,
|
||||
Error::InvalidColumnType(_, _) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
@@ -1,31 +1,27 @@
|
||||
//! `ToSql` and `FromSql` implementation for JSON `Value`.
|
||||
extern crate serde_json;
|
||||
|
||||
use libc::c_int;
|
||||
use self::serde_json::Value;
|
||||
|
||||
use {Error, Result};
|
||||
use types::{FromSql, ToSql, ValueRef};
|
||||
|
||||
use ffi::sqlite3_stmt;
|
||||
use ::Result;
|
||||
use types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
|
||||
|
||||
/// Serialize JSON `Value` to text.
|
||||
impl ToSql for Value {
|
||||
unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int {
|
||||
let s = serde_json::to_string(self).unwrap();
|
||||
s.bind_parameter(stmt, col)
|
||||
fn to_sql(&self) -> Result<ToSqlOutput> {
|
||||
Ok(ToSqlOutput::from(serde_json::to_string(self).unwrap()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Deserialize text/blob to JSON `Value`.
|
||||
impl FromSql for Value {
|
||||
fn column_result(value: ValueRef) -> Result<Self> {
|
||||
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
|
||||
match value {
|
||||
ValueRef::Text(ref s) => serde_json::from_str(s),
|
||||
ValueRef::Blob(ref b) => serde_json::from_slice(b),
|
||||
_ => return Err(Error::InvalidColumnType),
|
||||
_ => return Err(FromSqlError::InvalidType),
|
||||
}
|
||||
.map_err(|err| Error::FromSqlConversionFailure(Box::new(err)))
|
||||
.map_err(|err| FromSqlError::Other(Box::new(err)))
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,25 +1,22 @@
|
||||
extern crate time;
|
||||
|
||||
use libc::c_int;
|
||||
use {Error, Result};
|
||||
use types::{FromSql, ToSql, ValueRef};
|
||||
|
||||
use ffi::sqlite3_stmt;
|
||||
use Result;
|
||||
use types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
|
||||
|
||||
const SQLITE_DATETIME_FMT: &'static str = "%Y-%m-%d %H:%M:%S";
|
||||
|
||||
impl ToSql for time::Timespec {
|
||||
unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int {
|
||||
let time_str = time::at_utc(*self).strftime(SQLITE_DATETIME_FMT).unwrap().to_string();
|
||||
time_str.bind_parameter(stmt, col)
|
||||
fn to_sql(&self) -> Result<ToSqlOutput> {
|
||||
let time_string = time::at_utc(*self).strftime(SQLITE_DATETIME_FMT).unwrap().to_string();
|
||||
Ok(ToSqlOutput::from(time_string))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromSql for time::Timespec {
|
||||
fn column_result(value: ValueRef) -> Result<Self> {
|
||||
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
|
||||
value.as_str().and_then(|s| match time::strptime(s, SQLITE_DATETIME_FMT) {
|
||||
Ok(tm) => Ok(tm.to_timespec()),
|
||||
Err(err) => Err(Error::FromSqlConversionFailure(Box::new(err))),
|
||||
Err(err) => Err(FromSqlError::Other(Box::new(err))),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
use ::{ffi, str_to_cstring};
|
||||
use ffi::sqlite3_stmt;
|
||||
/// An owned SQLite-representable value.
|
||||
Owned(Value),
|
||||
|
||||
/// 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.
|
||||
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(
|
||||
($t:ty, $f:ident) => (
|
||||
// We should be able to use a generic impl like this:
|
||||
//
|
||||
// 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 {
|
||||
unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int {
|
||||
ffi::$f(stmt, col, *self)
|
||||
fn to_sql(&self) -> Result<ToSqlOutput> {
|
||||
Ok(ToSqlOutput::from(*self))
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
raw_to_impl!(c_int, sqlite3_bind_int); // i32
|
||||
raw_to_impl!(i64, sqlite3_bind_int64);
|
||||
raw_to_impl!(c_double, sqlite3_bind_double);
|
||||
to_sql_self!(Null);
|
||||
to_sql_self!(bool);
|
||||
to_sql_self!(i32);
|
||||
to_sql_self!(i64);
|
||||
to_sql_self!(f64);
|
||||
|
||||
impl ToSql for bool {
|
||||
unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int {
|
||||
if *self {
|
||||
ffi::sqlite3_bind_int(stmt, col, 1)
|
||||
} else {
|
||||
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<'a, T: ?Sized> ToSql for &'a T
|
||||
where &'a T: Into<ToSqlOutput<'a>>
|
||||
{
|
||||
fn to_sql(&self) -> Result<ToSqlOutput> {
|
||||
Ok(ToSqlOutput::from((*self).into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl ToSql for String {
|
||||
unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int {
|
||||
(&self[..]).bind_parameter(stmt, col)
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
fn to_sql(&self) -> Result<ToSqlOutput> {
|
||||
Ok(ToSqlOutput::from(self.as_str()))
|
||||
}
|
||||
}
|
||||
|
||||
impl ToSql for Vec<u8> {
|
||||
unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int {
|
||||
(&self[..]).bind_parameter(stmt, col)
|
||||
fn to_sql(&self) -> Result<ToSqlOutput> {
|
||||
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> {
|
||||
unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int {
|
||||
fn to_sql(&self) -> Result<ToSqlOutput> {
|
||||
match *self {
|
||||
None => ffi::sqlite3_bind_null(stmt, col),
|
||||
Some(ref t) => t.bind_parameter(stmt, col),
|
||||
None => Ok(ToSqlOutput::from(Null)),
|
||||
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
73
src/types/value.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,6 +1,5 @@
|
||||
use ::Result;
|
||||
use ::error::Error;
|
||||
use super::Value;
|
||||
use ::types::{FromSqlError, FromSqlResult};
|
||||
use super::{Value, Type};
|
||||
|
||||
/// A non-owning [dynamic type value](http://sqlite.org/datatype3.html). Typically the
|
||||
/// memory backing this value is owned by SQLite.
|
||||
@@ -20,40 +19,52 @@ pub enum ValueRef<'a> {
|
||||
Blob(&'a [u8]),
|
||||
}
|
||||
|
||||
impl<'a> ValueRef<'a> {
|
||||
pub fn data_type(&self) -> Type {
|
||||
match *self {
|
||||
ValueRef::Null => Type::Null,
|
||||
ValueRef::Integer(_) => Type::Integer,
|
||||
ValueRef::Real(_) => Type::Real,
|
||||
ValueRef::Text(_) => Type::Text,
|
||||
ValueRef::Blob(_) => Type::Blob,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ValueRef<'a> {
|
||||
/// If `self` is case `Integer`, returns the integral value. Otherwise, returns
|
||||
/// `Err(Error::InvalidColumnType)`.
|
||||
pub fn as_i64(&self) -> Result<i64> {
|
||||
pub fn as_i64(&self) -> FromSqlResult<i64> {
|
||||
match *self {
|
||||
ValueRef::Integer(i) => Ok(i),
|
||||
_ => Err(Error::InvalidColumnType),
|
||||
_ => Err(FromSqlError::InvalidType),
|
||||
}
|
||||
}
|
||||
|
||||
/// If `self` is case `Real`, returns the floating point value. Otherwise, returns
|
||||
/// `Err(Error::InvalidColumnType)`.
|
||||
pub fn as_f64(&self) -> Result<f64> {
|
||||
pub fn as_f64(&self) -> FromSqlResult<f64> {
|
||||
match *self {
|
||||
ValueRef::Real(f) => Ok(f),
|
||||
_ => Err(Error::InvalidColumnType),
|
||||
_ => Err(FromSqlError::InvalidType),
|
||||
}
|
||||
}
|
||||
|
||||
/// If `self` is case `Text`, returns the string value. Otherwise, returns
|
||||
/// `Err(Error::InvalidColumnType)`.
|
||||
pub fn as_str(&self) -> Result<&str> {
|
||||
pub fn as_str(&self) -> FromSqlResult<&str> {
|
||||
match *self {
|
||||
ValueRef::Text(ref t) => Ok(t),
|
||||
_ => Err(Error::InvalidColumnType),
|
||||
_ => Err(FromSqlError::InvalidType),
|
||||
}
|
||||
}
|
||||
|
||||
/// If `self` is case `Blob`, returns the byte slice. Otherwise, returns
|
||||
/// `Err(Error::InvalidColumnType)`.
|
||||
pub fn as_blob(&self) -> Result<&[u8]> {
|
||||
pub fn as_blob(&self) -> FromSqlResult<&[u8]> {
|
||||
match *self {
|
||||
ValueRef::Blob(ref b) => Ok(b),
|
||||
_ => Err(Error::InvalidColumnType),
|
||||
_ => Err(FromSqlError::InvalidType),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,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> {
|
||||
fn from(value: &'a Value) -> ValueRef<'a> {
|
||||
match *value {
|
||||
|
@@ -10,8 +10,8 @@ use libc;
|
||||
use {Connection, Error, Result, InnerConnection, str_to_cstring};
|
||||
use error::error_from_sqlite_code;
|
||||
use ffi;
|
||||
use functions::ToResult;
|
||||
use types::{FromSql, ValueRef};
|
||||
use functions::{set_result, report_error};
|
||||
use types::{FromSql, FromSqlError, ToSql, ValueRef};
|
||||
|
||||
// let conn: Connection = ...;
|
||||
// let mod: Module = ...; // VTab builder
|
||||
@@ -128,7 +128,7 @@ impl IndexInfo {
|
||||
}
|
||||
}
|
||||
pub struct IndexConstraintIter<'a> {
|
||||
iter: slice::Iter<'a, ffi::Struct_sqlite3_index_constraint>,
|
||||
iter: slice::Iter<'a, ffi::sqlite3_index_constraint>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for IndexConstraintIter<'a> {
|
||||
@@ -143,7 +143,7 @@ impl<'a> Iterator for IndexConstraintIter<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct IndexConstraint<'a>(&'a ffi::Struct_sqlite3_index_constraint);
|
||||
pub struct IndexConstraint<'a>(&'a ffi::sqlite3_index_constraint);
|
||||
|
||||
impl<'a> IndexConstraint<'a> {
|
||||
/// Column constrained. -1 for ROWID
|
||||
@@ -160,7 +160,7 @@ impl<'a> IndexConstraint<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct IndexConstraintUsage<'a>(&'a mut ffi::Struct_sqlite3_index_constraint_usage);
|
||||
pub struct IndexConstraintUsage<'a>(&'a mut ffi::sqlite3_index_constraint_usage);
|
||||
|
||||
impl<'a> IndexConstraintUsage<'a> {
|
||||
/// if `argv_index` > 0, constraint is part of argv to xFilter
|
||||
@@ -196,9 +196,11 @@ pub trait VTabCursor<V: VTab<Self>>: Sized {
|
||||
pub struct Context(*mut ffi::sqlite3_context);
|
||||
|
||||
impl Context {
|
||||
pub fn set_result<T: ToResult>(&mut self, value: &T) {
|
||||
unsafe {
|
||||
value.set_result(self.0);
|
||||
pub fn set_result<T: ToSql>(&mut self, value: &T) {
|
||||
let t = value.to_sql();
|
||||
match t {
|
||||
Ok(ref value) => set_result(self.0, value),
|
||||
Err(err) => unsafe { report_error(self.0, &err) },
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -220,8 +222,12 @@ impl<'a> Values<'a> {
|
||||
let arg = self.args[idx];
|
||||
let value = unsafe { ValueRef::from_value(arg) };
|
||||
FromSql::column_result(value).map_err(|err| match err {
|
||||
Error::InvalidColumnType => Error::InvalidFunctionParameterType,
|
||||
_ => err,
|
||||
FromSqlError::InvalidType => {
|
||||
Error::InvalidFunctionParameterType(idx, value.data_type())
|
||||
}
|
||||
FromSqlError::Other(err) => {
|
||||
Error::FromSqlConversionFailure(idx, value.data_type(), err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user