use std::ffi::CStr; use std::io::{Read, Write}; use std::marker::PhantomData; use std::os::raw::{c_char, c_int, c_uchar, c_void}; use std::panic::{catch_unwind, RefUnwindSafe}; use std::ptr; use std::slice::{from_raw_parts, from_raw_parts_mut};
use fallible_streaming_iterator::FallibleStreamingIterator;
/// An instance of this object is a session that can be /// used to record changes to a database. pubstruct Session<'conn> {
phantom: PhantomData<&'conn Connection>,
s: *mut ffi::sqlite3_session,
filter: Filter,
}
impl Session<'_> { /// Create a new session object #[inline] pubfn new(db: &Connection) -> Result<Session<'_>> {
Session::new_with_name(db, DatabaseName::Main)
}
/// Create a new session object #[inline] pubfn new_with_name<'conn>(
db: &'conn Connection,
name: DatabaseName<'_>,
) -> Result<Session<'conn>> { let name = name.as_cstring()?;
/// Set a table filter pubfn table_filter<F>(&mutself, filter: Option<F>) where
F: Fn(&str) -> bool + Send + RefUnwindSafe + 'static,
{ unsafeextern"C"fn call_boxed_closure<F>(
p_arg: *mut c_void,
tbl_str: *const c_char,
) -> c_int where
F: Fn(&str) -> bool + RefUnwindSafe,
{ use std::str;
let boxed_filter: *mut F = p_arg as *mut F; let tbl_name = { let c_slice = CStr::from_ptr(tbl_str).to_bytes();
str::from_utf8(c_slice)
};
c_int::from(
catch_unwind(|| (*boxed_filter)(tbl_name.expect("non-utf8 table name")))
.unwrap_or_default(),
)
}
match filter {
Some(filter) => { let boxed_filter = Box::new(filter); unsafe {
ffi::sqlite3session_table_filter( self.s,
Some(call_boxed_closure::<F>),
&*boxed_filter as *const F as *mut _,
);
} self.filter = Some(boxed_filter);
}
_ => { unsafe { ffi::sqlite3session_table_filter(self.s, None, ptr::null_mut()) } self.filter = None;
}
};
}
/// Attach a table. `None` means all tables. pubfn attach(&mutself, table: Option<&str>) -> Result<()> { let table = iflet Some(table) = table {
Some(str_to_cstring(table)?)
} else {
None
}; let table = table.as_ref().map(|s| s.as_ptr()).unwrap_or(ptr::null());
check(unsafe { ffi::sqlite3session_attach(self.s, table) })
}
/// Generate a Changeset pubfn changeset(&mutself) -> Result<Changeset> { letmut n = 0; letmut cs: *mut c_void = ptr::null_mut();
check(unsafe { ffi::sqlite3session_changeset(self.s, &mut n, &an style='color:red'>mut cs) })?;
Ok(Changeset { cs, n })
}
/// Write the set of changes represented by this session to `output`. #[inline] pubfn changeset_strm(&mutself, output: &>mutdyn Write) -> Result<()> { let output_ref = &output;
check(unsafe {
ffi::sqlite3session_changeset_strm( self.s,
Some(x_output),
output_ref as *const &mutdyn Write as *mut c_void,
)
})
}
/// Generate a Patchset #[inline] pubfn patchset(&mutself) -> Result<Changeset> { letmut n = 0; letmut ps: *mut c_void = ptr::null_mut();
check(unsafe { ffi::sqlite3session_patchset(self.s, &mut n, &n style='color:red'>mut ps) })?; // TODO Validate: same struct
Ok(Changeset { cs: ps, n })
}
/// Write the set of patches represented by this session to `output`. #[inline] pubfn patchset_strm(&mutself, output: &mutdyn Write) -> Result<()> { let output_ref = &output;
check(unsafe {
ffi::sqlite3session_patchset_strm( self.s,
Some(x_output),
output_ref as *const &mutdyn Write as *mut c_void,
)
})
}
/// Load the difference between tables. pubfn diff(&mutself, from: DatabaseName<'_>, table: &str) -> Result<()> { let from = from.as_cstring()?; let table = str_to_cstring(table)?; let table = table.as_ptr(); unsafe { letmut errmsg = ptr::null_mut(); let r =
ffi::sqlite3session_diff(self.s, from.as_ptr(), table, &mut errmsg as *mut *mut _); if r != ffi::SQLITE_OK { let errmsg: *mut c_char = errmsg; let message = errmsg_to_string(&*errmsg);
ffi::sqlite3_free(errmsg as *mut c_void); return Err(error_from_sqlite_code(r, Some(message)));
}
}
Ok(())
}
/// Test if a changeset has recorded any changes #[inline] pubfn is_empty(&self) -> bool { unsafe { ffi::sqlite3session_isempty(self.s) != 0 }
}
/// Query the current state of the session #[inline] pubfn is_enabled(&self) -> bool { unsafe { ffi::sqlite3session_enable(self.s, -1) != 0 }
}
/// Enable or disable the recording of changes #[inline] pubfn set_enabled(&mutself, enabled: bool) { unsafe {
ffi::sqlite3session_enable(self.s, c_int::from(enabled));
}
}
/// Query the current state of the indirect flag #[inline] pubfn is_indirect(&self) -> bool { unsafe { ffi::sqlite3session_indirect(self.s, -1) != 0 }
}
/// Set or clear the indirect change flag #[inline] pubfn set_indirect(&mutself, indirect: bool) { unsafe {
ffi::sqlite3session_indirect(self.s, c_int::from(indirect));
}
}
}
impl Changeset { /// Invert a changeset #[inline] pubfn invert(&self) -> Result<Changeset> { letmut n = 0; letmut cs = ptr::null_mut();
check(unsafe {
ffi::sqlite3changeset_invert(self.n, self.cs, &mut n, &le='color:red'>mut cs as *mut *mut _)
})?;
Ok(Changeset { cs, n })
}
/// Create an iterator to traverse a changeset #[inline] pubfn iter(&self) -> Result<ChangesetIter<'_>> { letmut it = ptr::null_mut();
check(unsafe { ffi::sqlite3changeset_start(&mut it as *mut *mut _, self.n, self.cs) })?;
Ok(ChangesetIter {
phantom: PhantomData,
it,
item: None,
})
}
/// Concatenate two changeset objects #[inline] pubfn concat(a: &Changeset, b: &Changeset) -> Result<Changeset> { letmut n = 0; letmut cs = ptr::null_mut();
check(unsafe {
ffi::sqlite3changeset_concat(a.n, a.cs, b.n, b.cs, &mut n, &style='color:red'>mut cs as *mut *mut _)
})?;
Ok(Changeset { cs, n })
}
}
impl Drop for Changeset { #[inline] fn drop(&mutself) { unsafe {
ffi::sqlite3_free(self.cs);
}
}
}
/// Cursor for iterating over the elements of a changeset /// or patchset. pubstruct ChangesetIter<'changeset> {
phantom: PhantomData<&'changeset Changeset>,
it: *mut ffi::sqlite3_changeset_iter,
item: Option<ChangesetItem>,
}
impl ChangesetIter<'_> { /// Create an iterator on `input` #[inline] pubfn start_strm<'input>(input: &&'input mutdyn Read) -> Result<ChangesetIter<'input>> { letmut it = ptr::null_mut();
check(unsafe {
ffi::sqlite3changeset_start_strm(
&mut it as *mut *mut _,
Some(x_input),
input as *const &mutdyn Read as *mut c_void,
)
})?;
Ok(ChangesetIter {
phantom: PhantomData,
it,
item: None,
})
}
}
impl FallibleStreamingIterator for ChangesetIter<'_> { type Error = crate::error::Error; type Item = ChangesetItem;
/// Returns `true` for an 'indirect' change. #[inline] pubfn indirect(&self) -> bool { self.indirect
}
}
impl Drop for ChangesetIter<'_> { #[inline] fn drop(&mutself) { unsafe {
ffi::sqlite3changeset_finalize(self.it);
}
}
}
/// An item passed to a conflict-handler by /// [`Connection::apply`](Connection::apply), or an item generated by /// [`ChangesetIter::next`](ChangesetIter::next). // TODO enum ? Delete, Insert, Update, ... pubstruct ChangesetItem {
it: *mut ffi::sqlite3_changeset_iter,
}
impl ChangesetItem { /// Obtain conflicting row values /// /// May only be called with an `SQLITE_CHANGESET_DATA` or /// `SQLITE_CHANGESET_CONFLICT` conflict handler callback. #[inline] pubfn conflict(&self, col: usize) -> Result<ValueRef<'_>> { unsafe { letmut p_value: *mut ffi::sqlite3_value = ptr::null_mut();
check(ffi::sqlite3changeset_conflict( self.it,
col as i32,
&mut p_value,
))?;
Ok(ValueRef::from_value(p_value))
}
}
/// Determine the number of foreign key constraint violations /// /// May only be called with an `SQLITE_CHANGESET_FOREIGN_KEY` conflict /// handler callback. #[inline] pubfn fk_conflicts(&self) -> Result<i32> { unsafe { letmut p_out = 0;
check(ffi::sqlite3changeset_fk_conflicts(self.it, &mut p_out))?;
Ok(p_out)
}
}
/// Obtain new.* Values /// /// May only be called if the type of change is either `SQLITE_UPDATE` or /// `SQLITE_INSERT`. #[inline] pubfn new_value(&self, col: usize) -> Result<ValueRef<'_>> { unsafe { letmut p_value: *mut ffi::sqlite3_value = ptr::null_mut();
check(ffi::sqlite3changeset_new(self.it, col as i32, &mut p_value))?;
Ok(ValueRef::from_value(p_value))
}
}
/// Obtain old.* Values /// /// May only be called if the type of change is either `SQLITE_DELETE` or /// `SQLITE_UPDATE`. #[inline] pubfn old_value(&self, col: usize) -> Result<ValueRef<'_>> { unsafe { letmut p_value: *mut ffi::sqlite3_value = ptr::null_mut();
check(ffi::sqlite3changeset_old(self.it, col as i32, &mut p_value))?;
Ok(ValueRef::from_value(p_value))
}
}
let tuple: *mut (Option<F>, C) = p_ctx as *mut (Option<F>, C); let tbl_name = { let c_slice = CStr::from_ptr(tbl_str).to_bytes();
str::from_utf8(c_slice)
}; match *tuple {
(Some(ref filter), _) => c_int::from(
catch_unwind(|| filter(tbl_name.expect("illegal table name"))).unwrap_or_default(),
),
_ => unimplemented!(),
}
}
unsafeextern"C"fn call_conflict<F, C>(
p_ctx: *mut c_void,
e_conflict: c_int,
p: *mut ffi::sqlite3_changeset_iter,
) -> c_int where
F: Fn(&str) -> bool + Send + RefUnwindSafe + 'static,
C: Fn(ConflictType, ChangesetItem) -> ConflictAction + Send + RefUnwindSafe + 'static,
{ let tuple: *mut (Option<F>, C) = p_ctx as *mut (Option<F>, C); let conflict_type = ConflictType::from(e_conflict); let item = ChangesetItem { it: p }; iflet Ok(action) = catch_unwind(|| (*tuple).1(conflict_type, item)) {
action as c_int
} else {
ffi::SQLITE_CHANGESET_ABORT
}
}
unsafeextern"C"fn x_input(p_in: *mut c_void, data: *mut c_void, len: *mut c_int) -> c_int { if p_in.is_null() { return ffi::SQLITE_MISUSE;
} let bytes: &mut [u8] = from_raw_parts_mut(data as *mut u8, *len as usize); let input = p_in as *mut &mutdyn Read; match (*input).read(bytes) {
Ok(n) => {
*len = n as i32; // TODO Validate: n = 0 may not mean the reader will always no longer be able to // produce bytes.
ffi::SQLITE_OK
}
Err(_) => ffi::SQLITE_IOERR_READ, // TODO check if err is a (ru)sqlite Error => propagate
}
}
unsafeextern"C"fn x_output(p_out: *mut c_void, data: *const c_void, len: c_int) -> c_int { if p_out.is_null() { return ffi::SQLITE_MISUSE;
} // The sessions module never invokes an xOutput callback with the third // parameter set to a value less than or equal to zero. let bytes: &[u8] = from_raw_parts(data as *const u8, len as usize); let output = p_out as *mut &mutdyn Write; match (*output).write_all(bytes) {
Ok(_) => ffi::SQLITE_OK,
Err(_) => ffi::SQLITE_IOERR_WRITE, // TODO check if err is a (ru)sqlite Error => propagate
}
}
#[cfg(test)] mod test { use fallible_streaming_iterator::FallibleStreamingIterator; use std::io::Read; use std::sync::atomic::{AtomicBool, Ordering};
session.attach(None)?;
db.execute("INSERT INTO foo (t) VALUES (?1);", ["bar"])?;
session.changeset()
}
fn one_changeset_strm() -> Result<Vec<u8>> { let db = Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);")?;
#[test] fn test_changeset() -> Result<()> { let changeset = one_changeset()?; letmut iter = changeset.iter()?; let item = iter.next()?;
assert!(item.is_some());
let item = item.unwrap(); let op = item.op()?;
assert_eq!("foo", op.table_name());
assert_eq!(1, op.number_of_columns());
assert_eq!(Action::SQLITE_INSERT, op.code());
assert!(!op.indirect());
let pk = item.pk()?;
assert_eq!(&[1], pk);
let new_value = item.new_value(0)?;
assert_eq!(Ok("bar"), new_value.as_str());
Ok(())
}
assert!(!CALLED.load(Ordering::Relaxed)); let check = db.query_row("SELECT 1 FROM foo WHERE t = ?1", ["bar"], |row| {
row.get::<_, i32>(0)
})?;
assert_eq!(1, check);
// conflict expected when same changeset applied again on the same db
db.apply(
&changeset,
None::<fn(&str) -> bool>,
|conflict_type, item| {
CALLED.store(true, Ordering::Relaxed);
assert_eq!(ConflictType::SQLITE_CHANGESET_CONFLICT, conflict_type); let conflict = item.conflict(0).unwrap();
assert_eq!(Ok("bar"), conflict.as_str());
ConflictAction::SQLITE_CHANGESET_OMIT
},
)?;
assert!(CALLED.load(Ordering::Relaxed));
Ok(())
}
#[test] fn test_changeset_apply_strm() -> Result<()> { let output = one_changeset_strm()?;
let db = Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);")?;
let check = db.query_row("SELECT 1 FROM foo WHERE t = ?1", ["bar"], |row| {
row.get::<_, i32>(0)
})?;
assert_eq!(1, check);
Ok(())
}
#[test] fn test_session_empty() -> Result<()> { let db = Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);")?;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.