From 3d82f7199af44007aa8f6231322e12bbed3fe9f4 Mon Sep 17 00:00:00 2001 From: Thom Chiovoloni Date: Thu, 18 Oct 2018 11:59:30 -0700 Subject: [PATCH] Allow getting a ValueRef out of Row and Context, fixes #259 --- src/functions.rs | 16 ++++++++++++-- src/lib.rs | 23 +++++++++++++++++++++ src/row.rs | 47 ++++++++++++++++++++++++++++++++++++++++-- src/types/value_ref.rs | 4 ++-- 4 files changed, 84 insertions(+), 6 deletions(-) diff --git a/src/functions.rs b/src/functions.rs index 92cee25..b4f0523 100644 --- a/src/functions.rs +++ b/src/functions.rs @@ -139,6 +139,16 @@ impl<'a> Context<'a> { }) } + /// Returns the `idx`th argument as a `ValueRef`. + /// + /// # Failure + /// + /// Will panic if `idx` is greater than or equal to `self.len()`. + pub fn get_raw(&self, idx: usize) -> ValueRef<'a> { + let arg = self.args[idx]; + unsafe { ValueRef::from_value(arg) } + } + /// Sets the auxilliary data associated with a particular parameter. See /// https://www.sqlite.org/c3ref/get_auxdata.html for a discussion of /// this feature, or the unit tests of this module for an example. @@ -530,8 +540,10 @@ mod test { let is_match = { let re = saved_re.unwrap_or_else(|| new_re.as_ref().unwrap()); - let text = try!(ctx.get::(1)); - re.is_match(&text) + let text = ctx.get_raw(1).as_str().map_err(|e| + Error::UserFunctionError(e.into()))?; + + re.is_match(text) }; if let Some(re) = new_re { diff --git a/src/lib.rs b/src/lib.rs index 9529fec..9c70131 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1592,6 +1592,29 @@ mod test { // degree of reliability. } + #[test] + fn test_get_raw() { + let db = checked_memory_handle(); + db.execute_batch("CREATE TABLE foo(i, x);").unwrap(); + let vals = ["foobar", "1234", "qwerty"]; + let mut insert_stmt = db.prepare("INSERT INTO foo(i, x) VALUES(?, ?)").unwrap(); + for (i, v) in vals.iter().enumerate() { + let i_to_insert = i as i64; + assert_eq!(insert_stmt.execute(&[&i_to_insert as &dyn ToSql, &v]).unwrap(), 1); + } + + let mut query = db.prepare("SELECT i, x FROM foo").unwrap(); + let mut rows = query.query(NO_PARAMS).unwrap(); + + while let Some(res) = rows.next() { + let row = res.unwrap(); + let i = row.get_raw(0).as_i64().unwrap(); + let expect = vals[i as usize]; + let x = row.get_raw("x").as_str().unwrap(); + assert_eq!(x, expect); + } + } + mod query_and_then_tests { extern crate libsqlite3_sys as ffi; use super::*; diff --git a/src/row.rs b/src/row.rs index 60ebe58..6a5f972 100644 --- a/src/row.rs +++ b/src/row.rs @@ -2,7 +2,7 @@ use std::marker::PhantomData; use std::{convert, result}; use super::{Error, Result, Statement}; -use types::{FromSql, FromSqlError}; +use types::{FromSql, FromSqlError, ValueRef}; /// An handle for the resulting rows of a query. pub struct Rows<'stmt> { @@ -126,7 +126,7 @@ where } /// A single result row of a query. -pub struct Row<'a, 'stmt> { +pub struct Row<'a, 'stmt: 'a> { stmt: &'stmt Statement<'stmt>, phantom: PhantomData<&'a ()>, } @@ -173,6 +173,49 @@ impl<'a, 'stmt> Row<'a, 'stmt> { }) } + /// Get the value of a particular column of the result row as a `ValueRef`, + /// allowing data to be read out of a row without copying. + /// + /// This `ValueRef` is valid only as long as this Row, which is enforced by + /// it's lifetime. This means that while this method is completely safe, + /// it can be somewhat difficult to use, and most callers will be better + /// served by `get` or `get_checked`. + /// + /// ## Failure + /// + /// Returns an `Error::InvalidColumnIndex` if `idx` is outside the valid + /// column range for this row. + /// + /// Returns an `Error::InvalidColumnName` if `idx` is not a valid column + /// name for this row. + pub fn get_raw_checked(&self, idx: I) -> Result> { + let idx = idx.idx(self.stmt)?; + // Narrowing from `ValueRef<'stmt>` (which `self.stmt.value_ref(idx)` + // returns) to `ValueRef<'a>` is needed because it's only valid until + // the next call to sqlite3_step. + let val_ref = self.stmt.value_ref(idx); + Ok(val_ref) + } + + /// Get the value of a particular column of the result row as a `ValueRef`, + /// allowing data to be read out of a row without copying. + /// + /// This `ValueRef` is valid only as long as this Row, which is enforced by + /// it's lifetime. This means that while this method is completely safe, + /// it can be difficult to use, and most callers will be better served by + /// `get` or `get_checked`. + /// + /// ## Failure + /// + /// Panics if calling `row.get_raw_checked(idx)` would return an error, + /// including: + /// + /// * If `idx` is outside the range of columns in the returned query. + /// * If `idx` is not a valid column name for this row. + pub fn get_raw(&self, idx: I) -> ValueRef<'a> { + self.get_raw_checked(idx).unwrap() + } + /// Return the number of columns in the current row. pub fn column_count(&self) -> usize { self.stmt.column_count() diff --git a/src/types/value_ref.rs b/src/types/value_ref.rs index 531bf1c..cd99bb2 100644 --- a/src/types/value_ref.rs +++ b/src/types/value_ref.rs @@ -52,7 +52,7 @@ impl<'a> ValueRef<'a> { /// If `self` is case `Text`, returns the string value. Otherwise, returns /// `Err(Error::InvalidColumnType)`. - pub fn as_str(&self) -> FromSqlResult<&str> { + pub fn as_str(&self) -> FromSqlResult<&'a str> { match *self { ValueRef::Text(t) => Ok(t), _ => Err(FromSqlError::InvalidType), @@ -61,7 +61,7 @@ impl<'a> ValueRef<'a> { /// If `self` is case `Blob`, returns the byte slice. Otherwise, returns /// `Err(Error::InvalidColumnType)`. - pub fn as_blob(&self) -> FromSqlResult<&[u8]> { + pub fn as_blob(&self) -> FromSqlResult<&'a [u8]> { match *self { ValueRef::Blob(b) => Ok(b), _ => Err(FromSqlError::InvalidType),