From f7f9af43edf35446ddd29f179e365d809bf28a2c Mon Sep 17 00:00:00 2001 From: Ivan Ukhov Date: Fri, 29 May 2015 11:24:01 -0400 Subject: [PATCH] Implement prepared statements --- src/database.rs | 41 +++++++++++++++++++----------- src/lib.rs | 22 +++++++++++----- src/statement.rs | 65 ++++++++++++++++++++++++++++++++++++++++++++++++ tests/lib.rs | 11 +++++--- 4 files changed, 115 insertions(+), 24 deletions(-) create mode 100644 src/statement.rs diff --git a/src/database.rs b/src/database.rs index 8ad01b3..5738c50 100644 --- a/src/database.rs +++ b/src/database.rs @@ -3,25 +3,25 @@ use raw; use std::marker::PhantomData; use std::path::Path; -use Result; +use {Result, Statement}; /// A database. -pub struct Database<'d> { - db: *mut raw::sqlite3, - _phantom: PhantomData<&'d raw::sqlite3>, +pub struct Database<'l> { + raw: *mut raw::sqlite3, + _phantom: PhantomData<&'l raw::sqlite3>, } /// A callback executed for each row of the result of an SQL query. -pub type ExecuteCallback<'c> = FnMut(Vec<(String, String)>) -> bool + 'c; +pub type ExecuteCallback<'l> = FnMut(Vec<(String, String)>) -> bool + 'l; -impl<'d> Database<'d> { +impl<'l> Database<'l> { /// Open a database. - pub fn open(path: &Path) -> Result> { - let mut db = 0 as *mut _; + pub fn open(path: &Path) -> Result { + let mut raw = 0 as *mut _; unsafe { - success!(raw::sqlite3_open(path_to_c_str!(path), &mut db)); + success!(raw::sqlite3_open(path_to_c_str!(path), &mut raw)); } - Ok(Database { db: db, _phantom: PhantomData }) + Ok(Database { raw: raw, _phantom: PhantomData }) } /// Execute an SQL statement. @@ -32,24 +32,35 @@ impl<'d> Database<'d> { match callback { Some(callback) => { let mut callback = Box::new(callback); - success!(raw::sqlite3_exec(self.db, str_to_c_str!(sql), Some(execute_callback), + success!(raw::sqlite3_exec(self.raw, str_to_c_str!(sql), + Some(execute_callback), &mut callback as *mut _ as *mut _, 0 as *mut _)); }, None => { - success!(raw::sqlite3_exec(self.db, str_to_c_str!(sql), None, - 0 as *mut _, 0 as *mut _)); + success!(raw::sqlite3_exec(self.raw, str_to_c_str!(sql), None, 0 as *mut _, + 0 as *mut _)); }, } } Ok(()) } + + /// Create a prepared statement. + pub fn statement(&mut self, sql: &str) -> Result> { + let mut raw = 0 as *mut _; + unsafe { + success!(raw::sqlite3_prepare(self.raw, str_to_c_str!(sql), -1, &mut raw, + 0 as *mut _)); + } + Ok(::statement::from_raw(raw)) + } } -impl<'d> Drop for Database<'d> { +impl<'l> Drop for Database<'l> { #[inline] fn drop(&mut self) { - unsafe { ::raw::sqlite3_close(self.db) }; + unsafe { ::raw::sqlite3_close(self.raw) }; } } diff --git a/src/lib.rs b/src/lib.rs index ec5ed73..c25fe76 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ extern crate libc; extern crate sqlite3_sys as raw; +use libc::c_int; use std::path::Path; /// A result. @@ -11,13 +12,13 @@ pub type Result = std::result::Result; /// An error. #[derive(Debug)] pub struct Error { - pub code: ErrorCode, + pub code: ResultCode, pub message: Option, } -/// An error code. -#[derive(Clone, Copy, Debug)] -pub enum ErrorCode { +/// A result code. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum ResultCode { Abort = raw::SQLITE_ABORT as isize, Authorization = raw::SQLITE_AUTH as isize, Busy = raw::SQLITE_BUSY as isize, @@ -51,9 +52,16 @@ pub enum ErrorCode { Warning = raw::SQLITE_WARNING as isize, } +impl ResultCode { + #[inline] + fn from_raw(code: c_int) -> ResultCode { + unsafe { std::mem::transmute(code as i8) } + } +} + macro_rules! raise( ($message:expr) => ( - return Err(::Error { code: ::ErrorCode::Error, message: Some($message.to_string()) }) + return Err(::Error { code: ::ResultCode::Error, message: Some($message.to_string()) }) ); ($code:expr, $message:expr) => ( return Err(::Error { code: $code, message: $message }) @@ -64,7 +72,7 @@ macro_rules! success( ($result:expr) => ( match $result { ::raw::SQLITE_OK => {}, - code => raise!(unsafe { ::std::mem::transmute(code as i8) }, None), + code => raise!(::ResultCode::from_raw(code), None), } ); ); @@ -91,8 +99,10 @@ macro_rules! str_to_c_str( ); mod database; +mod statement; pub use database::{Database, ExecuteCallback}; +pub use statement::{Statement, Binding}; /// Open a database. #[inline] diff --git a/src/statement.rs b/src/statement.rs new file mode 100644 index 0000000..d246a7e --- /dev/null +++ b/src/statement.rs @@ -0,0 +1,65 @@ +use libc::{c_double, c_int}; +use raw; +use std::marker::PhantomData; + +use {Result, ResultCode}; + +/// A prepared statement. +pub struct Statement<'l> { + raw: *mut raw::sqlite3_stmt, + _phantom: PhantomData<&'l raw::sqlite3_stmt>, +} + +/// A binding of a prepared statement. +pub enum Binding<'l> { + Float(usize, f64), + Integer(usize, i64), + Text(usize, &'l str), +} + +impl<'l> Statement<'l> { + /// Assign values to the placeholders. + pub fn bind(&mut self, bindings: &[Binding]) -> Result<()> { + for binding in bindings.iter() { + match *binding { + Binding::Float(i, value) => unsafe { + success!(raw::sqlite3_bind_double(self.raw, i as c_int, value as c_double)); + }, + Binding::Integer(i, value) => unsafe { + success!(raw::sqlite3_bind_int64(self.raw, i as c_int, + value as raw::sqlite3_int64)); + }, + Binding::Text(i, value) => unsafe { + success!(raw::sqlite3_bind_text(self.raw, i as c_int, str_to_c_str!(value), + -1, None)); + }, + } + } + Ok(()) + } + + /// Take a step. + #[inline] + pub fn step(&mut self) -> ResultCode { + unsafe { ResultCode::from_raw(raw::sqlite3_step(self.raw)) } + } + + /// Reset. + #[inline] + pub fn reset(&mut self) -> Result<()> { + unsafe { success!(raw::sqlite3_reset(self.raw)) }; + Ok(()) + } +} + +impl<'l> Drop for Statement<'l> { + #[inline] + fn drop(&mut self) { + unsafe { ::raw::sqlite3_finalize(self.raw) }; + } +} + +#[inline] +pub fn from_raw<'l>(raw: *mut raw::sqlite3_stmt) -> Statement<'l> { + Statement { raw: raw, _phantom: PhantomData } +} diff --git a/tests/lib.rs b/tests/lib.rs index 9d8ddc9..8379f79 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -9,7 +9,10 @@ macro_rules! ok( ); #[test] -fn execute() { +fn workflow() { + use sqlite::Binding::*; + use sqlite::ResultCode; + macro_rules! pair( ($one:expr, $two:expr) => ((String::from($one), String::from($two))); ); @@ -20,8 +23,10 @@ fn execute() { let sql = r#"CREATE TABLE `users` (id INTEGER, name VARCHAR(255), age REAL);"#; ok!(database.execute(sql, None)); - let sql = r#"INSERT INTO `users` (id, name, age) VALUES (1, "Alice", 20.99);"#; - ok!(database.execute(sql, None)); + let sql = r#"INSERT INTO `users` (id, name, age) VALUES (?, ?, ?);"#; + let mut statement = ok!(database.statement(sql)); + ok!(statement.bind(&[Integer(1, 1), Text(2, "Alice"), Float(3, 20.99)])); + assert!(statement.step() == ResultCode::Done); let mut done = false; let sql = r#"SELECT * FROM `users`;"#;