Skip to content

Commit

Permalink
as of this commit, rustic elmo performed a successful non-indexed que…
Browse files Browse the repository at this point in the history
…ry. the main change here is to eliminate ResultSet from my private fork of dckc/rust-sqlite3. PreparedStatement.execute() is gone. step() moved to PreparedStatement. ResultRow is largely unchanged except it references the statement instead of the resultset. storage/sqlite3 now contains two iterators, one which takes ownership of the statement and one which needs a mut ref to it.
  • Loading branch information
ericsink committed Aug 21, 2015
1 parent 8ebd3f0 commit 79fed65
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 122 deletions.
97 changes: 38 additions & 59 deletions rust-sqlite/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@
//! let mut stmt = try!(conn.prepare(
//! "insert into items (id, description, price)
//! values (1, 'stuff', 10)"));
//! let mut results = stmt.execute();
//! match try!(results.step()) {
//! match try!(stmt.step()) {
//! None => (),
//! Some(_) => panic!("row from insert?!")
//! };
Expand All @@ -42,8 +41,7 @@
//! {
//! let mut stmt = try!(conn.prepare(
//! "select * from items"));
//! let mut results = stmt.execute();
//! match results.step() {
//! match stmt.step() {
//! Ok(Some(ref mut row1)) => {
//! let id = row1.column_int(0);
//! let desc_opt = row1.column_text(1).expect("desc_opt should be non-null");
Expand Down Expand Up @@ -399,9 +397,20 @@ impl Drop for PreparedStatement {
pub type ParamIx = u16;

impl PreparedStatement {
/// Begin executing a statement.
pub fn execute(&mut self) -> ResultSet {
ResultSet { statement: self }
/// Execute the next step of a prepared statement.
///
/// An sqlite "row" only lasts until the next call to `ffi::sqlite3_step()`,
/// so we need a lifetime constraint. The unfortunate result is that
/// we cannot implement the `Iterator` trait here.
pub fn step<'stmt>(&'stmt mut self) -> SqliteResult<Option<ResultRow<'stmt>>> {
let result = unsafe { ffi::sqlite3_step(self.stmt) };
match Step::from_i32(result) {
Some(SQLITE_ROW) => {
Ok(Some(ResultRow{ statement: self }))
},
Some(SQLITE_DONE) => Ok(None),
None => Err(error_result(result, "step", self.get_detail()))
}
}
}

Expand Down Expand Up @@ -514,59 +523,31 @@ impl PreparedStatement {
let count = unsafe { ffi::sqlite3_changes(dbh) };
count as u64
}
}


/// Results of executing a `prepare()`d statement.
pub struct ResultSet<'res> {
statement: &'res mut PreparedStatement,
}

enum_from_primitive! {
#[derive(Debug, PartialEq, Eq)]
#[allow(non_camel_case_types)]
enum Step {
SQLITE_ROW = 100,
SQLITE_DONE = 101,
}
}

impl<'res> Drop for ResultSet<'res> {
fn drop(&mut self) {

pub fn reset(&self) {
// We ignore the return code from reset because it has already
// been reported:
//
// "If the most recent call to sqlite3_step(S) for the prepared
// statement S indicated an error, then sqlite3_reset(S)
// returns an appropriate error code."
unsafe { ffi::sqlite3_reset(self.statement.stmt) };
unsafe { ffi::sqlite3_reset(self.stmt) };
}
}


impl<'res:'row, 'row> ResultSet<'res> {
/// Execute the next step of a prepared statement.
///
/// An sqlite "row" only lasts until the next call to `ffi::sqlite3_step()`,
/// so we need a lifetime constraint. The unfortunate result is that
/// `ResultSet` cannot implement the `Iterator` trait.
pub fn step(&'row mut self) -> SqliteResult<Option<ResultRow<'res, 'row>>> {
let result = unsafe { ffi::sqlite3_step(self.statement.stmt) };
match Step::from_i32(result) {
Some(SQLITE_ROW) => {
Ok(Some(ResultRow{ rows: self }))
},
Some(SQLITE_DONE) => Ok(None),
None => Err(error_result(result, "step", self.statement.get_detail()))
}
enum_from_primitive! {
#[derive(Debug, PartialEq, Eq)]
#[allow(non_camel_case_types)]
enum Step {
SQLITE_ROW = 100,
SQLITE_DONE = 101,
}
}


/// Access to columns of a row.
pub struct ResultRow<'res:'row, 'row> {
rows: &'row mut ResultSet<'res>
pub struct ResultRow<'stmt> {
statement: &'stmt mut PreparedStatement
}

/// Column index for accessing parts of a row.
Expand All @@ -581,15 +562,15 @@ pub type ColIx = u32;
/// `sqlite3_column_type()` is undefined."[1]
///
/// [1]: http://www.sqlite.org/c3ref/column_blob.html
impl<'res, 'row> ResultRow<'res, 'row> {
impl<'stmt> ResultRow<'stmt> {

/// cf `sqlite3_column_count`
///
/// *TODO: consider returning Option<uint>
/// "This routine returns 0 if pStmt is an SQL statement that does
/// not return data (for example an UPDATE)."*
pub fn column_count(&self) -> ColIx {
let stmt = self.rows.statement.stmt;
let stmt = self.statement.stmt;
let result = unsafe { ffi::sqlite3_column_count(stmt) };
result as ColIx
}
Expand All @@ -600,7 +581,7 @@ impl<'res, 'row> ResultRow<'res, 'row> {
///
/// cf `sqlite_column_name`
pub fn with_column_name<T, F: Fn(&str) -> T>(&mut self, i: ColIx, default: T, f: F) -> T {
let stmt = self.rows.statement.stmt;
let stmt = self.statement.stmt;
let n = i as c_int;
let result = unsafe { ffi::sqlite3_column_name(stmt, n) };
match charstar_str(&result) {
Expand All @@ -613,7 +594,7 @@ impl<'res, 'row> ResultRow<'res, 'row> {
///
/// Return `SQLITE_NULL` if there is no such `col`.
pub fn column_type(&self, col: ColIx) -> ColumnType {
let stmt = self.rows.statement.stmt;
let stmt = self.statement.stmt;
let i_col = col as c_int;
let result = unsafe { ffi::sqlite3_column_type(stmt, i_col) };
// fail on out-of-range result instead?
Expand All @@ -622,21 +603,21 @@ impl<'res, 'row> ResultRow<'res, 'row> {

/// Get `int` value of a column.
pub fn column_int(&self, col: ColIx) -> i32 {
let stmt = self.rows.statement.stmt;
let stmt = self.statement.stmt;
let i_col = col as c_int;
unsafe { ffi::sqlite3_column_int(stmt, i_col) }
}

/// Get `int64` value of a column.
pub fn column_int64(&self, col: ColIx) -> i64 {
let stmt = self.rows.statement.stmt;
let stmt = self.statement.stmt;
let i_col = col as c_int;
unsafe { ffi::sqlite3_column_int64(stmt, i_col) }
}

/// Get `f64` (aka double) value of a column.
pub fn column_double(&self, col: ColIx) -> f64 {
let stmt = self.rows.statement.stmt;
let stmt = self.statement.stmt;
let i_col = col as c_int;
unsafe { ffi::sqlite3_column_double(stmt, i_col) }
}
Expand All @@ -658,7 +639,7 @@ impl<'res, 'row> ResultRow<'res, 'row> {

/// Get `Option<&[u8]>` (aka blob) value of a column.
pub fn column_slice<'a>(&'a self, col: ColIx) -> Option<&'a [u8]> {
let stmt = self.rows.statement.stmt;
let stmt = self.statement.stmt;
let i_col = col as c_int;
let bs = unsafe { ffi::sqlite3_column_blob(stmt, i_col) } as *const ::libc::c_uchar;
if bs == ptr::null() {
Expand Down Expand Up @@ -730,7 +711,7 @@ mod test_opening {

#[cfg(test)]
mod tests {
use super::{DatabaseConnection, SqliteResult, ResultSet};
use super::{DatabaseConnection, SqliteResult, PreparedStatement};
use std::str;

#[test]
Expand All @@ -745,12 +726,11 @@ mod tests {


fn with_query<T, F>(sql: &str, mut f: F) -> SqliteResult<T>
where F: FnMut(&mut ResultSet) -> T
where F: FnMut(&mut PreparedStatement) -> T
{
let db = try!(DatabaseConnection::in_memory());
let mut s = try!(db.prepare(sql));
let mut rows = s.execute();
Ok(f(&mut rows))
Ok(f(&mut s))
}

#[test]
Expand Down Expand Up @@ -825,8 +805,7 @@ mod tests {
#[test]
fn non_utf8_str() {
let mut stmt = DatabaseConnection::in_memory().unwrap().prepare("SELECT x'4546FF'").unwrap();
let mut rows = stmt.execute();
let row = rows.step().unwrap().unwrap();
let row = stmt.step().unwrap().unwrap();
assert_eq!(row.column_str(0), None);
assert!(str::from_utf8(&[0x45u8, 0x46, 0xff]).is_err());
}
Expand Down
26 changes: 11 additions & 15 deletions rust-sqlite/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ use std::fmt::Display;
use std::fmt;

pub use core::Access;
pub use core::{DatabaseConnection, PreparedStatement, ResultSet, ResultRow};
pub use core::{DatabaseConnection, PreparedStatement, ResultRow};
pub use core::{ColIx, ParamIx};
pub use types::{FromSql, ToSql};

Expand Down Expand Up @@ -141,8 +141,7 @@ impl StatementUpdate for core::PreparedStatement {
values: &[&ToSql]) -> SqliteResult<u64> {
let check = {
try!(bind_values(self, values));
let mut results = self.execute();
match try!(results.step()) {
match try!(self.step()) {
None => Ok(()),
Some(_row) => Err(SqliteError {
kind: SQLITE_MISUSE,
Expand Down Expand Up @@ -180,9 +179,8 @@ impl<F> Query<F> for core::PreparedStatement
) -> SqliteResult<()>
{
try!(bind_values(self, values));
let mut results = self.execute();
loop {
match try!(results.step()) {
match try!(self.step()) {
None => break,
Some(ref mut row) => try!(each_row(row)),
}
Expand Down Expand Up @@ -213,7 +211,7 @@ pub trait ResultRowAccess {
fn get_opt<I: RowIndex + Display + Clone, T: FromSql>(&mut self, idx: I) -> SqliteResult<T>;
}

impl<'res, 'row> ResultRowAccess for core::ResultRow<'res, 'row> {
impl<'stmt> ResultRowAccess for core::ResultRow<'stmt> {
fn get<I: RowIndex + Display + Clone, T: FromSql>(&mut self, idx: I) -> T {
match self.get_opt(idx.clone()) {
Ok(ok) => ok,
Expand Down Expand Up @@ -355,9 +353,10 @@ enum_from_primitive! {

#[cfg(test)]
mod bind_tests {
use super::{DatabaseConnection, ResultSet};
use super::{DatabaseConnection};
use super::{ResultRowAccess};
use super::{SqliteResult};
use super::{PreparedStatement};

#[test]
fn bind_fun() {
Expand All @@ -377,14 +376,12 @@ mod bind_tests {
try!(tx.bind_int(1, 2));
try!(tx.bind_text(2, "Jane Doe"));
try!(tx.bind_text(3, "345 e Walnut"));
let mut results = tx.execute();
assert!(results.step().ok().unwrap().is_none());
assert!(tx.step().ok().unwrap().is_none());
}
assert_eq!(database.changes(), 1);

let mut q = try!(database.prepare("select * from test order by id"));
let mut rows = q.execute();
match rows.step() {
match q.step() {
Ok(Some(ref mut row)) => {
assert_eq!(row.get::<u32, i32>(0), 1);
// TODO let name = q.get_text(1);
Expand All @@ -393,7 +390,7 @@ mod bind_tests {
_ => panic!()
}

match rows.step() {
match q.step() {
Ok(Some(ref mut row)) => {
assert_eq!(row.get::<u32, i32>(0), 2);
//TODO let addr = q.get_text(2);
Expand All @@ -410,12 +407,11 @@ mod bind_tests {
}

fn with_query<T, F>(sql: &str, mut f: F) -> SqliteResult<T>
where F: FnMut(&mut ResultSet) -> T
where F: FnMut(&mut PreparedStatement) -> T
{
let db = try!(DatabaseConnection::in_memory());
let mut s = try!(db.prepare(sql));
let mut rows = s.execute();
let x = f(&mut rows);
let x = f(&mut s);
return Ok(x);
}

Expand Down
10 changes: 4 additions & 6 deletions rust-sqlite/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,16 +158,15 @@ impl FromSql for time::Timespec {
#[cfg(test)]
mod tests {
use time::Tm;
use super::super::{DatabaseConnection, SqliteResult, ResultSet};
use super::super::{DatabaseConnection, SqliteResult, PreparedStatement};
use super::super::{ResultRowAccess};

fn with_query<T, F>(sql: &str, mut f: F) -> SqliteResult<T>
where F: FnMut(&mut ResultSet) -> T
where F: FnMut(&mut PreparedStatement) -> T
{
let db = try!(DatabaseConnection::in_memory());
let mut s = try!(db.prepare(sql));
let mut rows = s.execute();
Ok(f(&mut rows))
Ok(f(&mut s))
}

#[test]
Expand All @@ -176,8 +175,7 @@ mod tests {
let conn = try!(DatabaseConnection::in_memory());
let mut stmt = try!(
conn.prepare("select datetime('2001-01-01', 'weekday 3', '3 hours')"));
let mut results = stmt.execute();
match results.step() {
match stmt.step() {
Ok(Some(ref mut row)) => {
assert_eq!(
row.get::<u32, Tm>(0),
Expand Down
6 changes: 2 additions & 4 deletions rust-sqlite/tests/typical.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ fn typical_usage(conn: &mut DatabaseConnection) -> SqliteResult<String> {
let mut stmt = try!(conn.prepare(
"insert into items (id, description, price)
values (1, 'stuff', 10)"));
let mut results = stmt.execute();
match try!(results.step()) {
match try!(stmt.step()) {
None => (),
Some(_) => panic!("row from insert?!"),
};
Expand All @@ -34,8 +33,7 @@ fn typical_usage(conn: &mut DatabaseConnection) -> SqliteResult<String> {
{
let mut stmt = try!(conn.prepare(
"select * from items"));
let mut results = stmt.execute();
let res = match results.step() {
let res = match stmt.step() {
Ok(Some(ref mut row1)) => {
let id = row1.column_int(0);
let desc_opt = row1.column_text(1).expect("desc_opt should be non-null");
Expand Down
Loading

0 comments on commit 79fed65

Please sign in to comment.