diff --git a/crates/core/src/connection.rs b/crates/core/src/connection.rs index e5d263a481..6463a4cc0d 100644 --- a/crates/core/src/connection.rs +++ b/crates/core/src/connection.rs @@ -73,10 +73,13 @@ impl Connection { pub fn query(&self, sql: S, params: P) -> Result> where S: Into, - P: Into, + P: TryInto, + P::Error: Into, { let stmt = Statement::prepare(self.clone(), self.raw, sql.into().as_str())?; - let params = params.into(); + let params = params + .try_into() + .map_err(|e| Error::ToSqlConversionFailure(e.into()))?; let ret = stmt.query(¶ms)?; Ok(Some(ret)) } @@ -134,10 +137,13 @@ impl Connection { pub fn execute(&self, sql: S, params: P) -> Result where S: Into, - P: Into, + P: TryInto, + P::Error: Into, { let stmt = Statement::prepare(self.clone(), self.raw, sql.into().as_str())?; - let params = params.into(); + let params = params + .try_into() + .map_err(|e| Error::ToSqlConversionFailure(e.into()))?; stmt.execute(¶ms) } diff --git a/crates/core/src/errors.rs b/crates/core/src/errors.rs index 6c921308d5..b976b144bb 100644 --- a/crates/core/src/errors.rs +++ b/crates/core/src/errors.rs @@ -20,6 +20,8 @@ pub enum Error { QueryReturnedNoRows, #[error("Execute returned rows")] ExecuteReturnedRows, + #[error("unable to convert to sql: `{0}`")] + ToSqlConversionFailure(crate::BoxError), } pub(crate) fn error_from_handle(raw: *mut libsql_sys::ffi::sqlite3) -> String { diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index ca481713c7..79b0b18a04 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -51,6 +51,7 @@ pub mod statement; pub mod transaction; pub type Result = std::result::Result; +type BoxError = Box; pub use libsql_sys::ffi; pub use libsql_sys::ValueType; @@ -75,5 +76,10 @@ pub fn version_number() -> i32 { /// Return the version of the underlying SQLite library as a string. pub fn version() -> &'static str { - unsafe { std::ffi::CStr::from_ptr(ffi::sqlite3_libversion()).to_str().unwrap() } -} \ No newline at end of file + unsafe { + std::ffi::CStr::from_ptr(ffi::sqlite3_libversion()) + .to_str() + .unwrap() + } +} + diff --git a/crates/core/src/params.rs b/crates/core/src/params.rs index ca6bc0accb..2bf22ba4bf 100644 --- a/crates/core/src/params.rs +++ b/crates/core/src/params.rs @@ -2,6 +2,8 @@ use std::ffi::c_char; use libsql_sys::ValueType; +use crate::{Error, Result}; + pub enum Params { None, Positional(Vec), @@ -40,18 +42,23 @@ macro_rules! named_params { /// /// conn.query( /// "SELECT * FROM users WHERE id IN (?1, ?2, ?3)", -/// params_from_iter(iter) +/// params_from_iter(iter)? /// ) /// # } /// ``` -pub fn params_from_iter(iter: I) -> Params +pub fn params_from_iter(iter: I) -> Result where I: IntoIterator, - I::Item: Into, + I::Item: TryInto, + >::Error: Into, { - let vec = iter.into_iter().map(Into::into).collect(); + let vec = iter + .into_iter() + .map(|i| i.try_into()) + .collect::, _>>() + .map_err(|e| Error::ToSqlConversionFailure(e.into()))?; - Params::Positional(vec) + Ok(Params::Positional(vec)) } impl From<()> for Params { diff --git a/crates/core/src/statement.rs b/crates/core/src/statement.rs index 0a681827ac..21108b36e2 100644 --- a/crates/core/src/statement.rs +++ b/crates/core/src/statement.rs @@ -101,7 +101,10 @@ impl Statement { match err as u32 { crate::ffi::SQLITE_DONE => Ok(self.conn.changes()), crate::ffi::SQLITE_ROW => Err(Error::ExecuteReturnedRows), - _ => Err(Error::LibError(err, errors::error_from_handle(self.conn.raw))), + _ => Err(Error::LibError( + err, + errors::error_from_handle(self.conn.raw), + )), } } diff --git a/crates/core/tests/integration_tests.rs b/crates/core/tests/integration_tests.rs index 4c137a1f53..80ca53fd94 100644 --- a/crates/core/tests/integration_tests.rs +++ b/crates/core/tests/integration_tests.rs @@ -213,3 +213,32 @@ fn transaction() { assert_eq!(row.get::<&str>(1).unwrap(), "Alice"); assert!(rows.next().unwrap().is_none()); } + +#[test] +fn custom_params() { + let conn = setup(); + + enum MyValue { + Text(String), + Int(i64), + } + + impl TryInto for MyValue { + type Error = std::io::Error; + + fn try_into(self) -> Result { + match self { + MyValue::Text(s) => Ok(Value::Text(s)), + MyValue::Int(i) => Ok(Value::Integer(i)), + } + } + } + + let params = vec![MyValue::Int(2), MyValue::Text("Alice".into())]; + + conn.execute( + "INSERT INTO users (id, name) VALUES (?1, ?2)", + libsql::params_from_iter(params).unwrap(), + ) + .unwrap(); +}