Implement prepared statements

This commit is contained in:
Ivan Ukhov 2015-05-29 11:24:01 -04:00
parent 1dfeee881b
commit f7f9af43ed
4 changed files with 115 additions and 24 deletions

View File

@ -3,25 +3,25 @@ use raw;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::path::Path; use std::path::Path;
use Result; use {Result, Statement};
/// A database. /// A database.
pub struct Database<'d> { pub struct Database<'l> {
db: *mut raw::sqlite3, raw: *mut raw::sqlite3,
_phantom: PhantomData<&'d raw::sqlite3>, _phantom: PhantomData<&'l raw::sqlite3>,
} }
/// A callback executed for each row of the result of an SQL query. /// 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. /// Open a database.
pub fn open(path: &Path) -> Result<Database<'d>> { pub fn open(path: &Path) -> Result<Database> {
let mut db = 0 as *mut _; let mut raw = 0 as *mut _;
unsafe { 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. /// Execute an SQL statement.
@ -32,24 +32,35 @@ impl<'d> Database<'d> {
match callback { match callback {
Some(callback) => { Some(callback) => {
let mut callback = Box::new(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 _)); &mut callback as *mut _ as *mut _, 0 as *mut _));
}, },
None => { None => {
success!(raw::sqlite3_exec(self.db, str_to_c_str!(sql), None, success!(raw::sqlite3_exec(self.raw, str_to_c_str!(sql), None, 0 as *mut _,
0 as *mut _, 0 as *mut _)); 0 as *mut _));
}, },
} }
} }
Ok(()) Ok(())
} }
/// Create a prepared statement.
pub fn statement(&mut self, sql: &str) -> Result<Statement<'l>> {
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] #[inline]
fn drop(&mut self) { fn drop(&mut self) {
unsafe { ::raw::sqlite3_close(self.db) }; unsafe { ::raw::sqlite3_close(self.raw) };
} }
} }

View File

@ -3,6 +3,7 @@
extern crate libc; extern crate libc;
extern crate sqlite3_sys as raw; extern crate sqlite3_sys as raw;
use libc::c_int;
use std::path::Path; use std::path::Path;
/// A result. /// A result.
@ -11,13 +12,13 @@ pub type Result<T> = std::result::Result<T, Error>;
/// An error. /// An error.
#[derive(Debug)] #[derive(Debug)]
pub struct Error { pub struct Error {
pub code: ErrorCode, pub code: ResultCode,
pub message: Option<String>, pub message: Option<String>,
} }
/// An error code. /// A result code.
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ErrorCode { pub enum ResultCode {
Abort = raw::SQLITE_ABORT as isize, Abort = raw::SQLITE_ABORT as isize,
Authorization = raw::SQLITE_AUTH as isize, Authorization = raw::SQLITE_AUTH as isize,
Busy = raw::SQLITE_BUSY as isize, Busy = raw::SQLITE_BUSY as isize,
@ -51,9 +52,16 @@ pub enum ErrorCode {
Warning = raw::SQLITE_WARNING as isize, 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( macro_rules! raise(
($message:expr) => ( ($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) => ( ($code:expr, $message:expr) => (
return Err(::Error { code: $code, message: $message }) return Err(::Error { code: $code, message: $message })
@ -64,7 +72,7 @@ macro_rules! success(
($result:expr) => ( ($result:expr) => (
match $result { match $result {
::raw::SQLITE_OK => {}, ::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 database;
mod statement;
pub use database::{Database, ExecuteCallback}; pub use database::{Database, ExecuteCallback};
pub use statement::{Statement, Binding};
/// Open a database. /// Open a database.
#[inline] #[inline]

65
src/statement.rs Normal file
View File

@ -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 }
}

View File

@ -9,7 +9,10 @@ macro_rules! ok(
); );
#[test] #[test]
fn execute() { fn workflow() {
use sqlite::Binding::*;
use sqlite::ResultCode;
macro_rules! pair( macro_rules! pair(
($one:expr, $two:expr) => ((String::from($one), String::from($two))); ($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);"#; let sql = r#"CREATE TABLE `users` (id INTEGER, name VARCHAR(255), age REAL);"#;
ok!(database.execute(sql, None)); ok!(database.execute(sql, None));
let sql = r#"INSERT INTO `users` (id, name, age) VALUES (1, "Alice", 20.99);"#; let sql = r#"INSERT INTO `users` (id, name, age) VALUES (?, ?, ?);"#;
ok!(database.execute(sql, None)); 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 mut done = false;
let sql = r#"SELECT * FROM `users`;"#; let sql = r#"SELECT * FROM `users`;"#;