//! Port of C [vtablog](http://www.sqlite.org/cgi/src/finfo?name=ext/misc/vtablog.c) use std::default::Default; use std::marker::PhantomData; use std::os::raw::c_int; use std::str::FromStr; use std::sync::atomic::{AtomicUsize, Ordering}; use crate::vtab::{ update_module, Context, CreateVTab, IndexInfo, UpdateVTab, VTab, VTabConnection, VTabCursor, VTabKind, Values, }; use crate::{ffi, ValueRef}; use crate::{Connection, Error, Result}; /// Register the "vtablog" module. pub fn load_module(conn: &Connection) -> Result<()> { let aux: Option<()> = None; conn.create_module("vtablog", update_module::(), aux) } /// An instance of the vtablog virtual table #[repr(C)] struct VTabLog { /// Base class. Must be first base: ffi::sqlite3_vtab, /// Number of rows in the table n_row: i64, /// Instance number for this vtablog table i_inst: usize, /// Number of cursors created n_cursor: usize, } impl VTabLog { fn connect_create( _: &mut VTabConnection, _: Option<&()>, args: &[&[u8]], is_create: bool, ) -> Result<(String, VTabLog)> { static N_INST: AtomicUsize = AtomicUsize::new(1); let i_inst = N_INST.fetch_add(1, Ordering::SeqCst); println!( "VTabLog::{}(tab={}, args={:?}):", if is_create { "create" } else { "connect" }, i_inst, args, ); let mut schema = None; let mut n_row = None; let args = &args[3..]; for c_slice in args { let (param, value) = super::parameter(c_slice)?; match param { "schema" => { if schema.is_some() { return Err(Error::ModuleError(format!( "more than one '{param}' parameter" ))); } schema = Some(value.to_owned()) } "rows" => { if n_row.is_some() { return Err(Error::ModuleError(format!( "more than one '{param}' parameter" ))); } if let Ok(n) = i64::from_str(value) { n_row = Some(n) } } _ => { return Err(Error::ModuleError(format!( "unrecognized parameter '{param}'" ))); } } } if schema.is_none() { return Err(Error::ModuleError("no schema defined".to_owned())); } let vtab = VTabLog { base: ffi::sqlite3_vtab::default(), n_row: n_row.unwrap_or(10), i_inst, n_cursor: 0, }; Ok((schema.unwrap(), vtab)) } } impl Drop for VTabLog { fn drop(&mut self) { println!("VTabLog::drop({})", self.i_inst); } } unsafe impl<'vtab> VTab<'vtab> for VTabLog { type Aux = (); type Cursor = VTabLogCursor<'vtab>; fn connect( db: &mut VTabConnection, aux: Option<&Self::Aux>, args: &[&[u8]], ) -> Result<(String, Self)> { VTabLog::connect_create(db, aux, args, false) } fn best_index(&self, info: &mut IndexInfo) -> Result<()> { println!("VTabLog::best_index({})", self.i_inst); info.set_estimated_cost(500.); info.set_estimated_rows(500); Ok(()) } fn open(&'vtab mut self) -> Result { self.n_cursor += 1; println!( "VTabLog::open(tab={}, cursor={})", self.i_inst, self.n_cursor ); Ok(VTabLogCursor { base: ffi::sqlite3_vtab_cursor::default(), i_cursor: self.n_cursor, row_id: 0, phantom: PhantomData, }) } } impl<'vtab> CreateVTab<'vtab> for VTabLog { const KIND: VTabKind = VTabKind::Default; fn create( db: &mut VTabConnection, aux: Option<&Self::Aux>, args: &[&[u8]], ) -> Result<(String, Self)> { VTabLog::connect_create(db, aux, args, true) } fn destroy(&self) -> Result<()> { println!("VTabLog::destroy({})", self.i_inst); Ok(()) } } impl<'vtab> UpdateVTab<'vtab> for VTabLog { fn delete(&mut self, arg: ValueRef<'_>) -> Result<()> { println!("VTabLog::delete({}, {arg:?})", self.i_inst); Ok(()) } fn insert(&mut self, args: &Values<'_>) -> Result { println!( "VTabLog::insert({}, {:?})", self.i_inst, args.iter().collect::>>() ); Ok(self.n_row) } fn update(&mut self, args: &Values<'_>) -> Result<()> { println!( "VTabLog::update({}, {:?})", self.i_inst, args.iter().collect::>>() ); Ok(()) } } /// A cursor for the Series virtual table #[repr(C)] struct VTabLogCursor<'vtab> { /// Base class. Must be first base: ffi::sqlite3_vtab_cursor, /// Cursor number i_cursor: usize, /// The rowid row_id: i64, phantom: PhantomData<&'vtab VTabLog>, } impl VTabLogCursor<'_> { fn vtab(&self) -> &VTabLog { unsafe { &*(self.base.pVtab as *const VTabLog) } } } impl Drop for VTabLogCursor<'_> { fn drop(&mut self) { println!( "VTabLogCursor::drop(tab={}, cursor={})", self.vtab().i_inst, self.i_cursor ); } } unsafe impl VTabCursor for VTabLogCursor<'_> { fn filter(&mut self, _: c_int, _: Option<&str>, _: &Values<'_>) -> Result<()> { println!( "VTabLogCursor::filter(tab={}, cursor={})", self.vtab().i_inst, self.i_cursor ); self.row_id = 0; Ok(()) } fn next(&mut self) -> Result<()> { println!( "VTabLogCursor::next(tab={}, cursor={}): rowid {} -> {}", self.vtab().i_inst, self.i_cursor, self.row_id, self.row_id + 1 ); self.row_id += 1; Ok(()) } fn eof(&self) -> bool { let eof = self.row_id >= self.vtab().n_row; println!( "VTabLogCursor::eof(tab={}, cursor={}): {}", self.vtab().i_inst, self.i_cursor, eof, ); eof } fn column(&self, ctx: &mut Context, i: c_int) -> Result<()> { let value = if i < 26 { format!( "{}{}", "abcdefghijklmnopqrstuvwyz".chars().nth(i as usize).unwrap(), self.row_id ) } else { format!("{i}{}", self.row_id) }; println!( "VTabLogCursor::column(tab={}, cursor={}, i={}): {}", self.vtab().i_inst, self.i_cursor, i, value, ); ctx.set_result(&value) } fn rowid(&self) -> Result { println!( "VTabLogCursor::rowid(tab={}, cursor={}): {}", self.vtab().i_inst, self.i_cursor, self.row_id, ); Ok(self.row_id) } } #[cfg(test)] mod test { use crate::{Connection, Result}; #[test] fn test_module() -> Result<()> { let db = Connection::open_in_memory()?; super::load_module(&db)?; db.execute_batch( "CREATE VIRTUAL TABLE temp.log USING vtablog( schema='CREATE TABLE x(a,b,c)', rows=25 );", )?; let mut stmt = db.prepare("SELECT * FROM log;")?; let mut rows = stmt.query([])?; while rows.next()?.is_some() {} db.execute("DELETE FROM log WHERE a = ?1", ["a1"])?; db.execute( "INSERT INTO log (a, b, c) VALUES (?1, ?2, ?3)", ["a", "b", "c"], )?; db.execute( "UPDATE log SET b = ?1, c = ?2 WHERE a = ?3", ["bn", "cn", "a1"], )?; Ok(()) } }