|
@@ -7,7 +7,7 @@ use std::{
|
|
|
sync::{Arc, Mutex},
|
|
|
};
|
|
|
|
|
|
-fn check_rcode(sql: Option<&str>, rcode: i32) -> Result<(), Error> {
|
|
|
+fn check_rcode<'a>(sql: impl FnOnce() -> Option<&'a str>, rcode: i32) -> Result<(), Error> {
|
|
|
if rcode == sq::SQLITE_OK {
|
|
|
Ok(())
|
|
|
} else {
|
|
@@ -16,7 +16,7 @@ fn check_rcode(sql: Option<&str>, rcode: i32) -> Result<(), Error> {
|
|
|
msg: unsafe { CStr::from_ptr(sq::sqlite3_errstr(rcode)) }
|
|
|
.to_str()?
|
|
|
.to_string(),
|
|
|
- sql: sql.map(|s| s.to_string()),
|
|
|
+ sql: sql().map(String::from),
|
|
|
})
|
|
|
}
|
|
|
}
|
|
@@ -69,7 +69,7 @@ impl Connection {
|
|
|
let url = CString::new(url)?;
|
|
|
let mut db_ptr = std::ptr::null_mut();
|
|
|
check_rcode(
|
|
|
- None,
|
|
|
+ || None,
|
|
|
sq::sqlite3_open_v2(
|
|
|
url.as_ptr(),
|
|
|
&mut db_ptr,
|
|
@@ -86,6 +86,10 @@ impl Connection {
|
|
|
));
|
|
|
}
|
|
|
|
|
|
+ unsafe {
|
|
|
+ sq::sqlite3_busy_timeout(db_ptr, 1000);
|
|
|
+ }
|
|
|
+
|
|
|
Ok(Self(Arc::new(Mutex::new(ConnectionData {
|
|
|
sqlite: db_ptr,
|
|
|
stmts: Default::default(),
|
|
@@ -95,6 +99,8 @@ impl Connection {
|
|
|
pub fn execute_raw_sql(&self, sql: impl AsRef<str>) -> DBResult<()> {
|
|
|
let data = self.0.lock()?;
|
|
|
|
|
|
+ println!("executing: {sql}", sql = sql.as_ref());
|
|
|
+
|
|
|
unsafe {
|
|
|
let c_sql = CString::new(sql.as_ref())?;
|
|
|
let mut err = std::ptr::null_mut();
|
|
@@ -143,11 +149,13 @@ impl Connection {
|
|
|
Entry::Vacant(e) => {
|
|
|
let sql = build_query();
|
|
|
|
|
|
+ log::trace!("preparing query: {sql}");
|
|
|
+
|
|
|
// prepare the statement
|
|
|
let mut stmt = std::ptr::null_mut();
|
|
|
unsafe {
|
|
|
check_rcode(
|
|
|
- Some(sql.as_str()),
|
|
|
+ || Some(sql.as_str()),
|
|
|
sq::sqlite3_prepare_v2(
|
|
|
conn,
|
|
|
sql.as_ptr().cast(),
|
|
@@ -173,6 +181,59 @@ impl Connection {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+pub(crate) struct Transaction<'l> {
|
|
|
+ db: &'l Connection,
|
|
|
+ committed: bool,
|
|
|
+}
|
|
|
+
|
|
|
+impl<'l> Transaction<'l> {
|
|
|
+ pub fn new(db: &'l Connection) -> DBResult<Self> {
|
|
|
+ println!("backtrace: {}", std::backtrace::Backtrace::force_capture());
|
|
|
+ db.execute_raw_sql("BEGIN TRANSACTION")?;
|
|
|
+ /*struct BeginQuery;
|
|
|
+ db.with_prepared(
|
|
|
+ std::any::TypeId::of::<BeginQuery>(),
|
|
|
+ || "BEGIN".to_string(),
|
|
|
+ |ctx| {
|
|
|
+ ctx.run().map(|_| ())
|
|
|
+ })?; */
|
|
|
+ Ok(Self {
|
|
|
+ db,
|
|
|
+ committed: false,
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn commit(mut self) -> DBResult<()> {
|
|
|
+ self.committed = true;
|
|
|
+
|
|
|
+ self.db.execute_raw_sql("COMMIT")
|
|
|
+ /*
|
|
|
+ struct CommitQuery;
|
|
|
+ self.db.with_prepared(
|
|
|
+ std::any::TypeId::of::<CommitQuery>(),
|
|
|
+ || "COMMIT".to_string(),
|
|
|
+ |ctx| {
|
|
|
+ ctx.run().map(|_| ())
|
|
|
+ })*/
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl<'l> Drop for Transaction<'l> {
|
|
|
+ fn drop(&mut self) {
|
|
|
+ if !self.committed {
|
|
|
+ /*
|
|
|
+ struct AbortQuery;
|
|
|
+ let _ = self.db.with_prepared(
|
|
|
+ std::any::TypeId::of::<AbortQuery>(),
|
|
|
+ || "ROLLBACK".to_string(),
|
|
|
+ |ctx| {
|
|
|
+ ctx.run().map(|_| ())
|
|
|
+ });*/
|
|
|
+ let _ = self.db.execute_raw_sql("ROLLBACK");
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
struct Statement {
|
|
|
#[allow(unused)]
|
|
|
sqlite: *mut sq::sqlite3,
|
|
@@ -183,8 +244,11 @@ impl Statement {
|
|
|
fn make_context(&mut self) -> DBResult<StatementContext> {
|
|
|
// begin by resetting the statement
|
|
|
unsafe {
|
|
|
- check_rcode(None, sq::sqlite3_reset(self.stmt))?;
|
|
|
+ check_rcode(|| None, sq::sqlite3_reset(self.stmt))?;
|
|
|
}
|
|
|
+
|
|
|
+ let v = unsafe { CStr::from_ptr(sq::sqlite3_sql(self.stmt)).to_str().unwrap() };
|
|
|
+ println!("making Statement context for SQL: {}", v);
|
|
|
Ok(StatementContext {
|
|
|
stmt: self,
|
|
|
owned_strings: Default::default(),
|
|
@@ -256,7 +320,7 @@ mod test {
|
|
|
|
|
|
pub struct StatementRow<'a> {
|
|
|
stmt: &'a Statement,
|
|
|
- owned: Option<Vec<Pin<Box<String>>>>,
|
|
|
+ _ctx: Option<StatementContext<'a>>,
|
|
|
}
|
|
|
|
|
|
impl<'a> StatementRow<'a> {
|
|
@@ -272,7 +336,7 @@ pub struct StatementContext<'a> {
|
|
|
|
|
|
impl<'a> StatementContext<'a> {
|
|
|
pub fn bind<B: Bindable>(&self, index: i32, bindable: B) -> DBResult<()> {
|
|
|
- bindable.bind_to(self, index)
|
|
|
+ bindable.bind(self, index)
|
|
|
}
|
|
|
|
|
|
pub fn transfer(&mut self, s: Pin<Box<String>>) {
|
|
@@ -281,23 +345,34 @@ impl<'a> StatementContext<'a> {
|
|
|
|
|
|
fn step(&self) -> Option<()> {
|
|
|
match unsafe { sq::sqlite3_step(self.stmt.stmt) } {
|
|
|
- sq::SQLITE_ROW => Some(()),
|
|
|
- sq::SQLITE_DONE => None,
|
|
|
- _ => {
|
|
|
- // check_rcode(None, v)?;
|
|
|
+ sq::SQLITE_ROW => {
|
|
|
+ println!("sqlite3_step: row");
|
|
|
+ Some(())
|
|
|
+ }
|
|
|
+ sq::SQLITE_DONE => {
|
|
|
+ println!("sqlite3_step: done");
|
|
|
+ None
|
|
|
+ }
|
|
|
+ sq::SQLITE_BUSY => {
|
|
|
+ println!("Concurrent database access!");
|
|
|
+ None
|
|
|
+ }
|
|
|
+ err => {
|
|
|
+ println!("unexpected error during sqlite3_step: {:?}", err);
|
|
|
+ // let _ = check_rcode(|| None, err);
|
|
|
// Ok(false)
|
|
|
None
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- pub fn run(mut self) -> DBResult<Option<StatementRow<'a>>> {
|
|
|
+ // this needs to be replaced with a "single" version that keeps the StatementContext alive, or
|
|
|
+ // StatementRow needs an optional StatementContext to keep alive
|
|
|
+ pub fn run(self) -> DBResult<Option<StatementRow<'a>>> {
|
|
|
if self.step().is_some() {
|
|
|
- let mut owned = vec![];
|
|
|
- owned.append(&mut self.owned_strings);
|
|
|
Ok(Some(StatementRow {
|
|
|
- owned: Some(owned),
|
|
|
stmt: self.stmt,
|
|
|
+ _ctx: Some(self),
|
|
|
}))
|
|
|
} else {
|
|
|
Ok(None)
|
|
@@ -312,7 +387,7 @@ impl<'a> StatementContext<'a> {
|
|
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
|
self.0.step().map(|_| StatementRow {
|
|
|
- owned: None,
|
|
|
+ _ctx: None,
|
|
|
stmt: self.0.stmt,
|
|
|
})
|
|
|
}
|
|
@@ -326,13 +401,16 @@ impl<'a> Drop for StatementContext<'a> {
|
|
|
fn drop(&mut self) {
|
|
|
// attempt to bind NULLs into each parameter
|
|
|
unsafe {
|
|
|
+ println!("clearing bindings...");
|
|
|
+ // clear out the rest of the rows
|
|
|
+ while self.step().is_some() {}
|
|
|
sq::sqlite3_clear_bindings(self.stmt.stmt);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
pub trait Bindable {
|
|
|
- fn bind_to<'ctx, 'data: 'ctx>(
|
|
|
+ fn bind<'ctx, 'data: 'ctx>(
|
|
|
&'data self,
|
|
|
ctx: &StatementContext<'ctx>,
|
|
|
index: i32,
|
|
@@ -340,56 +418,61 @@ pub trait Bindable {
|
|
|
}
|
|
|
|
|
|
impl Bindable for () {
|
|
|
- fn bind_to<'ctx, 'data: 'ctx>(
|
|
|
+ fn bind<'ctx, 'data: 'ctx>(
|
|
|
&'data self,
|
|
|
ctx: &StatementContext<'ctx>,
|
|
|
index: i32,
|
|
|
) -> DBResult<()> {
|
|
|
- unsafe { check_rcode(None, sq::sqlite3_bind_null(ctx.stmt.stmt, index)) }
|
|
|
+ unsafe { check_rcode(|| None, sq::sqlite3_bind_null(ctx.stmt.stmt, index)) }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
impl Bindable for i64 {
|
|
|
- fn bind_to<'ctx, 'data: 'ctx>(
|
|
|
+ fn bind<'ctx, 'data: 'ctx>(
|
|
|
&'data self,
|
|
|
ctx: &StatementContext<'ctx>,
|
|
|
index: i32,
|
|
|
) -> DBResult<()> {
|
|
|
- unsafe { check_rcode(None, sq::sqlite3_bind_int64(ctx.stmt.stmt, index, *self)) }
|
|
|
+ unsafe { check_rcode(|| None, sq::sqlite3_bind_int64(ctx.stmt.stmt, index, *self)) }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
impl Bindable for usize {
|
|
|
- fn bind_to<'ctx, 'data: 'ctx>(
|
|
|
+ fn bind<'ctx, 'data: 'ctx>(
|
|
|
&'data self,
|
|
|
ctx: &StatementContext<'ctx>,
|
|
|
index: i32,
|
|
|
) -> DBResult<()> {
|
|
|
- (*self as i64).bind_to(ctx, index)
|
|
|
+ (*self as i64).bind(ctx, index)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
impl Bindable for f32 {
|
|
|
- fn bind_to<'ctx, 'data: 'ctx>(&self, ctx: &StatementContext<'ctx>, index: i32) -> DBResult<()> {
|
|
|
- (*self as f64).bind_to(ctx, index)
|
|
|
+ fn bind<'ctx, 'data: 'ctx>(&self, ctx: &StatementContext<'ctx>, index: i32) -> DBResult<()> {
|
|
|
+ (*self as f64).bind(ctx, index)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
impl Bindable for f64 {
|
|
|
- fn bind_to<'ctx, 'data: 'ctx>(&self, ctx: &StatementContext<'ctx>, index: i32) -> DBResult<()> {
|
|
|
- unsafe { check_rcode(None, sq::sqlite3_bind_double(ctx.stmt.stmt, index, *self)) }
|
|
|
+ fn bind<'ctx, 'data: 'ctx>(&self, ctx: &StatementContext<'ctx>, index: i32) -> DBResult<()> {
|
|
|
+ unsafe {
|
|
|
+ check_rcode(
|
|
|
+ || None,
|
|
|
+ sq::sqlite3_bind_double(ctx.stmt.stmt, index, *self),
|
|
|
+ )
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
impl<'a> Bindable for &'a str {
|
|
|
- fn bind_to<'ctx, 'data: 'ctx>(
|
|
|
+ fn bind<'ctx, 'data: 'ctx>(
|
|
|
&'data self,
|
|
|
ctx: &StatementContext<'ctx>,
|
|
|
index: i32,
|
|
|
) -> DBResult<()> {
|
|
|
unsafe {
|
|
|
check_rcode(
|
|
|
- None,
|
|
|
+ || None,
|
|
|
sq::sqlite3_bind_text(
|
|
|
ctx.stmt.stmt,
|
|
|
index,
|
|
@@ -403,34 +486,34 @@ impl<'a> Bindable for &'a str {
|
|
|
}
|
|
|
|
|
|
impl Bindable for str {
|
|
|
- fn bind_to<'ctx, 'data: 'ctx>(
|
|
|
+ fn bind<'ctx, 'data: 'ctx>(
|
|
|
&'data self,
|
|
|
ctx: &StatementContext<'ctx>,
|
|
|
index: i32,
|
|
|
) -> DBResult<()> {
|
|
|
- <&'_ str>::bind_to(&self, ctx, index)
|
|
|
+ <&'_ str>::bind(&self, ctx, index)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
impl Bindable for String {
|
|
|
- fn bind_to<'ctx, 'data: 'ctx>(
|
|
|
+ fn bind<'ctx, 'data: 'ctx>(
|
|
|
&'data self,
|
|
|
ctx: &StatementContext<'ctx>,
|
|
|
index: i32,
|
|
|
) -> DBResult<()> {
|
|
|
- self.as_str().bind_to(ctx, index)
|
|
|
+ self.as_str().bind(ctx, index)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
impl<'a> Bindable for &'a [u8] {
|
|
|
- fn bind_to<'ctx, 'data: 'ctx>(
|
|
|
+ fn bind<'ctx, 'data: 'ctx>(
|
|
|
&'data self,
|
|
|
ctx: &StatementContext<'ctx>,
|
|
|
index: i32,
|
|
|
) -> DBResult<()> {
|
|
|
unsafe {
|
|
|
check_rcode(
|
|
|
- None,
|
|
|
+ || None,
|
|
|
sq::sqlite3_bind_blob64(
|
|
|
ctx.stmt.stmt,
|
|
|
index,
|
|
@@ -447,6 +530,20 @@ pub trait Readable: Sized {
|
|
|
fn read_from(sr: &StatementRow<'_>, index: i32) -> DBResult<Self>;
|
|
|
}
|
|
|
|
|
|
+pub struct IsNull(pub bool);
|
|
|
+
|
|
|
+// NULL-checker
|
|
|
+impl Readable for IsNull {
|
|
|
+ fn read_from(sr: &StatementRow<'_>, index: i32) -> DBResult<Self> {
|
|
|
+ let column_type = unsafe { sq::sqlite3_column_type(sr.stmt.stmt, index) };
|
|
|
+ if column_type == sq::SQLITE_NULL {
|
|
|
+ Ok(IsNull(true))
|
|
|
+ } else {
|
|
|
+ Ok(IsNull(false))
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
impl Readable for i64 {
|
|
|
fn read_from(sr: &StatementRow<'_>, index: i32) -> DBResult<Self> {
|
|
|
unsafe { Ok(sq::sqlite3_column_int64(sr.stmt.stmt, index)) }
|
|
@@ -480,7 +577,6 @@ impl Readable for String {
|
|
|
"NULL pointer result from sqlite3_column_text",
|
|
|
))
|
|
|
} else {
|
|
|
- let cstr = CStr::from_ptr(text.cast());
|
|
|
Ok(CStr::from_ptr(text.cast()).to_str()?.to_string())
|
|
|
}
|
|
|
}
|