diff --git a/README.md b/README.md index 665b6ec..a430af0 100644 --- a/README.md +++ b/README.md @@ -16,13 +16,14 @@ fn main() { println!("Column {i}: {} {}", rst.column_name(i), rst.column_type(i)); } - while let Some(row) = (&mut rst).next() { + while let Some(row) = rst.fetch_row() { dbg!(row); } } ``` ## TODO +* NULL value * Add more apis * Windows support * Dynamic link crossdb diff --git a/examples/basic.rs b/examples/basic.rs index b40df0a..850e643 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -8,7 +8,7 @@ fn main() { println!("Column {i}: {} {}", rst.column_name(i), rst.column_type(i)); } - while let Some(row) = (&mut rst).next() { + while let Some(row) = rst.fetch_row() { dbg!(row); } } diff --git a/src/error.rs b/src/error.rs index fd92603..f5b7be4 100644 --- a/src/error.rs +++ b/src/error.rs @@ -10,4 +10,8 @@ pub enum Error { Utf8(#[from] std::str::Utf8Error), #[error("Query error: {0}, {1}")] Query(u16, String), + #[error("Clear bindings error")] + ClearBindings, + #[error("Bind params error")] + BindParams, } diff --git a/src/lib.rs b/src/lib.rs index f9d344d..31687ba 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,8 +18,10 @@ pub use error::{Error, Result}; pub use value::Value; use crossdb_sys::*; +use params::{IntoParams, Value as ParamValue}; use std::ffi::{CStr, CString}; use std::fmt::Display; +mod params; #[derive(Debug)] pub struct Connection { @@ -54,26 +56,70 @@ impl Connection { let msg = CStr::from_ptr(xdb_errmsg(ptr)).to_str()?.to_string(); return Err(Error::Query(res.errcode, msg)); } - Ok(ExecResult { - ptr, - col_meta: res.col_meta, - column_count: res.col_count as usize, - row_count: res.row_count as usize, - column_types: ColumnType::all(&res), - row_index: 0, - }) + let types = ColumnType::all(&res); + Ok(ExecResult { res, ptr, types }) + } + } + + pub fn begin(&self) -> bool { + unsafe { xdb_begin(self.ptr) == 0 } + } + + pub fn commit(&self) -> bool { + unsafe { xdb_commit(self.ptr) == 0 } + } + + pub fn rollback(&self) -> bool { + unsafe { xdb_rollback(self.ptr) == 0 } + } + + // TODO: LRU cache + pub fn prepare>(&mut self, sql: S) -> Result { + unsafe { + let sql = CString::new(sql.as_ref())?; + let ptr = xdb_stmt_prepare(self.ptr, sql.as_ptr()); + Ok(Stmt { ptr }) + } + } +} + +pub struct Stmt { + ptr: *mut xdb_stmt_t, +} + +impl Drop for Stmt { + fn drop(&mut self) { + unsafe { + xdb_stmt_close(self.ptr); + } + } +} + +impl Stmt { + pub fn exec(&self, params: impl IntoParams) -> Result { + unsafe { + let ret = xdb_clear_bindings(self.ptr); + if ret != 0 { + return Err(Error::ClearBindings); + } + params.into_params()?.bind(self.ptr)?; + let ptr = xdb_stmt_exec(self.ptr); + let res = *ptr; + if res.errcode as u32 != xdb_errno_e_XDB_OK { + let msg = CStr::from_ptr(xdb_errmsg(ptr)).to_str()?.to_string(); + return Err(Error::Query(res.errcode, msg)); + } + let types = ColumnType::all(&res); + Ok(ExecResult { res, ptr, types }) } } } #[derive(Debug)] pub struct ExecResult { + res: xdb_res_t, ptr: *mut xdb_res_t, - col_meta: u64, - column_count: usize, - row_count: usize, - column_types: Vec, - row_index: usize, + types: Vec, } impl Drop for ExecResult { @@ -86,41 +132,41 @@ impl Drop for ExecResult { impl ExecResult { pub fn column_count(&self) -> usize { - self.column_count + self.res.col_count as usize } pub fn row_count(&self) -> usize { - self.row_count + self.res.row_count as usize + } + + pub fn affected_rows(&self) -> u64 { + self.res.affected_rows } - pub fn column_name<'a>(&'a self, i: usize) -> &'a str { + pub fn column_name(&self, i: usize) -> &str { unsafe { - let name = xdb_column_name(self.col_meta, i as u16); + let name = xdb_column_name(self.res.col_meta, i as u16); CStr::from_ptr(name).to_str().unwrap() } } pub fn column_type(&self, i: usize) -> ColumnType { - self.column_types[i] + self.types[i] } -} - -impl<'a> Iterator for &'a mut ExecResult { - type Item = Vec>; - fn next(&mut self) -> Option { - if self.row_count <= self.row_index { - return None; - } - let mut values = Vec::with_capacity(self.column_count); + pub fn fetch_row(&mut self) -> Option>> { unsafe { - let y = xdb_fetch_row(self.ptr); - for x in 0..self.column_count { - let value = Value::from_result(self.col_meta, y, x as u16, self.column_type(x)); + let row = xdb_fetch_row(self.ptr); + if row.is_null() { + return None; + } + let mut values = Vec::with_capacity(self.column_count()); + for col in 0..self.column_count() { + let value = + Value::from_result(self.res.col_meta, row, col as u16, self.column_type(col)); values.push(value); } + Some(values) } - self.row_index += 1; - Some(values) } } diff --git a/src/params.rs b/src/params.rs new file mode 100644 index 0000000..a414d4f --- /dev/null +++ b/src/params.rs @@ -0,0 +1,138 @@ +use crate::*; + +pub enum Value { + Int(i32), + Int64(i64), + Float(f32), + Double(f64), + String(String), +} + +trait IntoValue { + fn into_value(self) -> Result; +} + +macro_rules! impl_value { + ($t: ty, $v: ident) => { + impl IntoValue for $t { + fn into_value(self) -> Result { + Ok(Value::$v(self as _)) + } + } + }; +} +impl_value!(i8, Int); +impl_value!(u8, Int); +impl_value!(i16, Int); +impl_value!(u16, Int); +impl_value!(i32, Int); +impl_value!(u32, Int); +impl_value!(u64, Int64); +impl_value!(i64, Int64); +impl_value!(f32, Float); +impl_value!(f64, Double); +impl_value!(String, String); + +impl IntoValue for &str { + fn into_value(self) -> Result { + Ok(Value::String(self.into())) + } +} + +impl IntoValue for Value { + fn into_value(self) -> Result { + Ok(self) + } +} + +pub enum Params { + None, + Positional(Vec), +} + +impl Params { + pub(crate) unsafe fn bind(self, ptr: *mut xdb_stmt_t) -> Result<()> { + if let Params::Positional(params) = self { + for (i, p) in params.into_iter().enumerate() { + let i = i as u16 + 1; + let ret = match p { + ParamValue::Int(v) => xdb_bind_int(ptr, i, v), + ParamValue::Int64(v) => xdb_bind_int64(ptr, i, v), + ParamValue::Float(v) => xdb_bind_float(ptr, i, v), + ParamValue::Double(v) => xdb_bind_double(ptr, i, v), + ParamValue::String(v) => xdb_bind_str(ptr, i, CString::new(v)?.as_ptr()), + }; + if ret != 0 { + return Err(Error::BindParams); + } + } + } + Ok(()) + } +} + +pub trait IntoParams { + fn into_params(self) -> Result; +} + +impl IntoParams for () { + fn into_params(self) -> Result { + Ok(Params::None) + } +} + +impl IntoParams for Params { + fn into_params(self) -> Result { + Ok(self) + } +} + +impl IntoParams for Vec { + fn into_params(self) -> Result { + let mut params = Vec::with_capacity(self.len()); + for param in self { + params.push(param.into_value()?); + } + Ok(Params::Positional(params)) + } +} + +impl IntoParams for &[T] { + fn into_params(self) -> Result { + self.to_vec().into_params() + } +} + +impl IntoParams for &[T; N] { + fn into_params(self) -> Result { + self.to_vec().into_params() + } +} + +// Copy from:https://github.com/tursodatabase/libsql/blob/main/libsql/src/params.rs#L206-L207 +macro_rules! tuple_into_params { + ($count:literal : $(($field:tt $ftype:ident)),* $(,)?) => { + impl<$($ftype,)*> IntoParams for ($($ftype,)*) where $($ftype: IntoValue,)* { + fn into_params(self) -> Result { + let params = Params::Positional(vec![$(self.$field.into_value()?),*]); + Ok(params) + } + } + } +} +tuple_into_params!(1: (0 A)); +tuple_into_params!(2: (0 A), (1 B)); +tuple_into_params!(3: (0 A), (1 B), (2 C)); +tuple_into_params!(4: (0 A), (1 B), (2 C), (3 D)); +tuple_into_params!(5: (0 A), (1 B), (2 C), (3 D), (4 E)); +tuple_into_params!(6: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F)); +tuple_into_params!(7: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G)); +tuple_into_params!(8: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H)); +tuple_into_params!(9: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I)); +tuple_into_params!(10: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J)); +tuple_into_params!(11: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K)); +tuple_into_params!(12: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L)); +tuple_into_params!(13: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L), (12 M)); +tuple_into_params!(14: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L), (12 M), (13 N)); +tuple_into_params!(15: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L), (12 M), (13 N), (14 O)); +tuple_into_params!(16: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L), (12 M), (13 N), (14 O), (15 P));