From d89cc13d7bc9802c94122f668674644419471ca3 Mon Sep 17 00:00:00 2001 From: Ivan Ukhov Date: Sun, 2 Aug 2015 22:29:04 -0400 Subject: [PATCH] Introduce Iterator --- Cargo.toml | 2 +- src/connection.rs | 32 ++++++++++++++++---------- src/iterator.rs | 57 +++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 2 ++ src/statement.rs | 10 +++++++-- tests/lib.rs | 40 ++++++++++++++++++++++----------- 6 files changed, 115 insertions(+), 28 deletions(-) create mode 100644 src/iterator.rs diff --git a/Cargo.toml b/Cargo.toml index 5f57567..ab82e96 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sqlite" -version = "0.17.2" +version = "0.18.0" authors = ["Ivan Ukhov "] license = "MIT" repository = "https://github.com/stainless-steel/sqlite" diff --git a/src/connection.rs b/src/connection.rs index 31d1837..083168a 100644 --- a/src/connection.rs +++ b/src/connection.rs @@ -3,9 +3,9 @@ use libc::{c_char, c_int, c_void}; use std::marker::PhantomData; use std::path::Path; -use {Result, Statement}; +use {Iterator, Result, Statement}; -/// A connection to a database. +/// A database connection. pub struct Connection { raw: *mut ffi::sqlite3, busy_callback: Option bool>>, @@ -24,28 +24,29 @@ impl Connection { Ok(Connection { raw: raw, busy_callback: None, phantom: PhantomData }) } - /// Execute a query without processing the resulting rows if any. + /// Execute a statement without processing the resulting rows if any. #[inline] - pub fn execute>(&self, query: T) -> Result<()> { + pub fn execute>(&self, statement: T) -> Result<()> { unsafe { - ok!(self.raw, ffi::sqlite3_exec(self.raw, str_to_cstr!(query.as_ref()).as_ptr(), None, - 0 as *mut _, 0 as *mut _)); + ok!(self.raw, ffi::sqlite3_exec(self.raw, str_to_cstr!(statement.as_ref()).as_ptr(), + None, 0 as *mut _, 0 as *mut _)); } Ok(()) } - /// Execute a query and process the resulting rows if any. + /// Execute a statement and process the resulting rows as plain text. /// /// The callback is triggered for each row. If the callback returns `false`, /// no more rows will be processed. For large queries and non-string data - /// types, prepared statement are highly preferable; see `prepare`. + /// types, prepared statement are highly preferable; see `iterate` and + /// `prepare`. #[inline] - pub fn process, F>(&self, query: T, callback: F) -> Result<()> + pub fn process, F>(&self, statement: T, callback: F) -> Result<()> where F: FnMut(&[(&str, Option<&str>)]) -> bool { unsafe { let callback = Box::new(callback); - ok!(self.raw, ffi::sqlite3_exec(self.raw, str_to_cstr!(query.as_ref()).as_ptr(), + ok!(self.raw, ffi::sqlite3_exec(self.raw, str_to_cstr!(statement.as_ref()).as_ptr(), Some(process_callback::), &*callback as *const F as *mut F as *mut _, 0 as *mut _)); @@ -55,8 +56,15 @@ impl Connection { /// Create a prepared statement. #[inline] - pub fn prepare<'l, T: AsRef>(&'l self, query: T) -> Result> { - ::statement::new(self.raw, query) + pub fn prepare<'l, T: AsRef>(&'l self, statement: T) -> Result> { + ::statement::new(self.raw, statement) + } + + /// Create a reusable iterator over the resulting rows of a prepared + /// statement. + #[inline] + pub fn iterate<'l, T: AsRef>(&'l self, statement: T) -> Result> { + ::iterator::new(try!(::statement::new(self.raw, statement))) } /// Set a callback for handling busy events. diff --git a/src/iterator.rs b/src/iterator.rs new file mode 100644 index 0000000..ccb28f2 --- /dev/null +++ b/src/iterator.rs @@ -0,0 +1,57 @@ +use statement::{State, Statement, Bindable, Readable}; +use {Result, Value}; + +/// A reusable iterator over the results of a prepared statement. +pub struct Iterator<'l> { + state: Option, + values: Option>, + statement: Statement<'l>, +} + +impl<'l> Iterator<'l> { + /// Bind parameters and start iterating over the resulting rows. + /// + /// The function assigns values to the parameters of the underlaying + /// prepared statement, execute it, and start iterating over the resulting + /// rows. + pub fn start(&mut self, values: &[Value]) -> Result<()> { + try!(self.statement.reset()); + for (i, value) in values.iter().enumerate() { + try!(self.statement.bind(i + 1, value)); + } + self.state = Some(try!(self.statement.step())); + Ok(()) + } + + /// Read the next row. + pub fn next(&mut self) -> Result> { + match self.state { + Some(State::Row) => {}, + _ => return Ok(None), + } + let values = match self.values.take() { + Some(mut values) => { + for (i, value) in values.iter_mut().enumerate() { + *value = try!(self.statement.read(i)); + } + values + }, + _ => { + let count = self.statement.columns(); + let mut values = Vec::with_capacity(count); + for i in 0..count { + values.push(try!(self.statement.read(i))); + } + values + }, + }; + self.state = Some(try!(self.statement.step())); + self.values = Some(values); + Ok(Some(self.values.as_ref().unwrap())) + } +} + +#[inline] +pub fn new<'l>(statement: Statement<'l>) -> Result> { + Ok(Iterator { state: None, values: None, statement: statement }) +} diff --git a/src/lib.rs b/src/lib.rs index 4cc902d..9a792ea 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -163,9 +163,11 @@ impl error::Error for Error { } mod connection; +mod iterator; mod statement; pub use connection::Connection; +pub use iterator::Iterator; pub use statement::{Statement, State, Bindable, Readable}; /// Open a connection to a new or existing database. diff --git a/src/statement.rs b/src/statement.rs index 053eef9..3ab623a 100644 --- a/src/statement.rs +++ b/src/statement.rs @@ -172,6 +172,12 @@ impl Bindable for () { } } +impl<'l, T: Bindable> Bindable for &'l T { + fn bind(&self, statement: &mut Statement, i: usize) -> Result<()> { + (*self).bind(statement, i) + } +} + impl Readable for Value { fn read(statement: &Statement, i: usize) -> Result { Ok(match statement.kind(i) { @@ -230,10 +236,10 @@ impl Readable for Vec { } #[inline] -pub fn new<'l, T: AsRef>(raw1: *mut ffi::sqlite3, query: T) -> Result> { +pub fn new<'l, T: AsRef>(raw1: *mut ffi::sqlite3, statement: T) -> Result> { let mut raw0 = 0 as *mut _; unsafe { - ok!(raw1, ffi::sqlite3_prepare_v2(raw1, str_to_cstr!(query.as_ref()).as_ptr(), -1, + ok!(raw1, ffi::sqlite3_prepare_v2(raw1, str_to_cstr!(statement.as_ref()).as_ptr(), -1, &mut raw0, 0 as *mut _)); } Ok(Statement { raw: (raw0, raw1), phantom: PhantomData }) diff --git a/tests/lib.rs b/tests/lib.rs index 1f68525..f8d10ae 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -1,7 +1,7 @@ extern crate sqlite; extern crate temporary; -use sqlite::{Connection, State, Type}; +use sqlite::{Connection, State, Type, Value}; use std::path::Path; macro_rules! ok( @@ -26,8 +26,8 @@ fn connection_process() { let connection = setup(":memory:"); let mut done = false; - let query = "SELECT * FROM users"; - ok!(connection.process(query, |pairs| { + let statement = "SELECT * FROM users"; + ok!(connection.process(statement, |pairs| { assert_eq!(pairs.len(), 4); assert_eq!(pairs[0], pair!("id", "1")); assert_eq!(pairs[1], pair!("name", "Alice")); @@ -53,8 +53,8 @@ fn connection_set_busy_handler() { thread::spawn(move || { let mut connection = ok!(sqlite::open(&path)); ok!(connection.set_busy_handler(|_| true)); - let query = "INSERT INTO `users` (id, name, age, photo) VALUES (?, ?, ?, ?)"; - let mut statement = ok!(connection.prepare(query)); + let statement = "INSERT INTO `users` (id, name, age, photo) VALUES (?, ?, ?, ?)"; + let mut statement = ok!(connection.prepare(statement)); ok!(statement.bind(1, 2i64)); ok!(statement.bind(2, "Bob")); ok!(statement.bind(3, 69.42)); @@ -69,11 +69,25 @@ fn connection_set_busy_handler() { } } +#[test] +fn iterator() { + let connection = setup(":memory:"); + let statement = "SELECT id FROM users WHERE id = ?"; + let mut iterator = ok!(connection.iterate(statement)); + + ok!(iterator.start(&[Value::Integer(1)])); + assert_eq!(ok!(ok!(iterator.next())), &[Value::Integer(1)]); + assert_eq!(ok!(iterator.next()), None); + + ok!(iterator.start(&[Value::Integer(42)])); + assert_eq!(ok!(iterator.next()), None); +} + #[test] fn statement_columns() { let connection = setup(":memory:"); - let query = "SELECT * FROM users"; - let mut statement = ok!(connection.prepare(query)); + let statement = "SELECT * FROM users"; + let mut statement = ok!(connection.prepare(statement)); assert_eq!(statement.columns(), 4); @@ -85,8 +99,8 @@ fn statement_columns() { #[test] fn statement_kind() { let connection = setup(":memory:"); - let query = "SELECT * FROM users"; - let mut statement = ok!(connection.prepare(query)); + let statement = "SELECT * FROM users"; + let mut statement = ok!(connection.prepare(statement)); assert_eq!(statement.kind(0), Type::Null); assert_eq!(statement.kind(1), Type::Null); @@ -104,8 +118,8 @@ fn statement_kind() { #[test] fn statement_bind() { let connection = setup(":memory:"); - let query = "INSERT INTO users (id, name, age, photo) VALUES (?, ?, ?, ?)"; - let mut statement = ok!(connection.prepare(query)); + let statement = "INSERT INTO users (id, name, age, photo) VALUES (?, ?, ?, ?)"; + let mut statement = ok!(connection.prepare(statement)); ok!(statement.bind(1, 2i64)); ok!(statement.bind(2, "Bob")); @@ -117,8 +131,8 @@ fn statement_bind() { #[test] fn statement_read() { let connection = setup(":memory:"); - let query = "SELECT * FROM users"; - let mut statement = ok!(connection.prepare(query)); + let statement = "SELECT * FROM users"; + let mut statement = ok!(connection.prepare(statement)); assert_eq!(ok!(statement.step()), State::Row); assert_eq!(ok!(statement.read::(0)), 1);