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::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<Database<'d>> {
let mut db = 0 as *mut _;
pub fn open(path: &Path) -> Result<Database> {
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<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]
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 sqlite3_sys as raw;
use libc::c_int;
use std::path::Path;
/// A result.
@ -11,13 +12,13 @@ pub type Result<T> = std::result::Result<T, Error>;
/// An error.
#[derive(Debug)]
pub struct Error {
pub code: ErrorCode,
pub code: ResultCode,
pub message: Option<String>,
}
/// 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]

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]
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`;"#;