mirror of
				https://github.com/isar/rusqlite.git
				synced 2025-10-31 22:08:55 +08:00 
			
		
		
		
	Port vtablog as an example of a writable VTab
				
					
				
			This commit is contained in:
		| @@ -117,7 +117,7 @@ unsafe impl<'vtab> VTab<'vtab> for ArrayTab { | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     fn open(&self) -> Result<ArrayTabCursor<'_>> { | ||||
|     fn open(&mut self) -> Result<ArrayTabCursor<'_>> { | ||||
|         Ok(ArrayTabCursor::new()) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -30,8 +30,8 @@ use std::str; | ||||
| use crate::ffi; | ||||
| use crate::types::Null; | ||||
| use crate::vtab::{ | ||||
|     dequote, escape_double_quote, parse_boolean, read_only_module, Context, CreateVTab, IndexInfo, | ||||
|     VTab, VTabConfig, VTabConnection, VTabCursor, VTabKind, Values, | ||||
|     escape_double_quote, parse_boolean, read_only_module, Context, CreateVTab, IndexInfo, VTab, | ||||
|     VTabConfig, VTabConnection, VTabCursor, VTabKind, Values, | ||||
| }; | ||||
| use crate::{Connection, Error, Result}; | ||||
|  | ||||
| @@ -74,19 +74,6 @@ impl CsvTab { | ||||
|             .from_path(&self.filename) | ||||
|     } | ||||
|  | ||||
|     fn parameter(c_slice: &[u8]) -> Result<(&str, &str)> { | ||||
|         let arg = str::from_utf8(c_slice)?.trim(); | ||||
|         let mut split = arg.split('='); | ||||
|         if let Some(key) = split.next() { | ||||
|             if let Some(value) = split.next() { | ||||
|                 let param = key.trim(); | ||||
|                 let value = dequote(value); | ||||
|                 return Ok((param, value)); | ||||
|             } | ||||
|         } | ||||
|         Err(Error::ModuleError(format!("illegal argument: '{}'", arg))) | ||||
|     } | ||||
|  | ||||
|     fn parse_byte(arg: &str) -> Option<u8> { | ||||
|         if arg.len() == 1 { | ||||
|             arg.bytes().next() | ||||
| @@ -122,7 +109,7 @@ unsafe impl<'vtab> VTab<'vtab> for CsvTab { | ||||
|  | ||||
|         let args = &args[3..]; | ||||
|         for c_slice in args { | ||||
|             let (param, value) = CsvTab::parameter(c_slice)?; | ||||
|             let (param, value) = super::parameter(c_slice)?; | ||||
|             match param { | ||||
|                 "filename" => { | ||||
|                     if !Path::new(value).exists() { | ||||
| @@ -259,7 +246,7 @@ unsafe impl<'vtab> VTab<'vtab> for CsvTab { | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     fn open(&self) -> Result<CsvTabCursor<'_>> { | ||||
|     fn open(&mut self) -> Result<CsvTabCursor<'_>> { | ||||
|         Ok(CsvTabCursor::new(self.reader()?)) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -263,7 +263,7 @@ pub unsafe trait VTab<'vtab>: Sized { | ||||
|  | ||||
|     /// Create a new cursor used for accessing a virtual table. | ||||
|     /// (See [SQLite doc](https://sqlite.org/vtab.html#the_xopen_method)) | ||||
|     fn open(&'vtab self) -> Result<Self::Cursor>; | ||||
|     fn open(&'vtab mut self) -> Result<Self::Cursor>; | ||||
| } | ||||
|  | ||||
| /// Read-only virtual table instance trait. | ||||
| @@ -882,7 +882,7 @@ pub fn dequote(s: &str) -> &str { | ||||
|     } | ||||
|     match s.bytes().next() { | ||||
|         Some(b) if b == b'"' || b == b'\'' => match s.bytes().rev().next() { | ||||
|             Some(e) if e == b => &s[1..s.len() - 1], | ||||
|             Some(e) if e == b => &s[1..s.len() - 1], // FIXME handle inner escaped quote(s) | ||||
|             _ => s, | ||||
|         }, | ||||
|         _ => s, | ||||
| @@ -912,6 +912,20 @@ pub fn parse_boolean(s: &str) -> Option<bool> { | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// `<param_name>=['"]?<param_value>['"]?` => `(<param_name>, <param_value>)` | ||||
| pub fn parameter(c_slice: &[u8]) -> Result<(&str, &str)> { | ||||
|     let arg = std::str::from_utf8(c_slice)?.trim(); | ||||
|     let mut split = arg.split('='); | ||||
|     if let Some(key) = split.next() { | ||||
|         if let Some(value) = split.next() { | ||||
|             let param = key.trim(); | ||||
|             let value = dequote(value); | ||||
|             return Ok((param, value)); | ||||
|         } | ||||
|     } | ||||
|     Err(Error::ModuleError(format!("illegal argument: '{}'", arg))) | ||||
| } | ||||
|  | ||||
| // FIXME copy/paste from function.rs | ||||
| unsafe extern "C" fn free_boxed_value<T>(p: *mut c_void) { | ||||
|     drop(Box::from_raw(p.cast::<T>())); | ||||
| @@ -1309,6 +1323,8 @@ pub mod csvtab; | ||||
| #[cfg(feature = "series")] | ||||
| #[cfg_attr(docsrs, doc(cfg(feature = "series")))] | ||||
| pub mod series; // SQLite >= 3.9.0 | ||||
| #[cfg(test)] | ||||
| mod vtablog; | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod test { | ||||
|   | ||||
| @@ -153,7 +153,7 @@ unsafe impl<'vtab> VTab<'vtab> for SeriesTab { | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     fn open(&self) -> Result<SeriesTabCursor<'_>> { | ||||
|     fn open(&mut self) -> Result<SeriesTabCursor<'_>> { | ||||
|         Ok(SeriesTabCursor::new()) | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										291
									
								
								src/vtab/vtablog.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										291
									
								
								src/vtab/vtablog.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,291 @@ | ||||
| ///! 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::<VTabLog>(), 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 '{}' parameter", | ||||
|                             param | ||||
|                         ))); | ||||
|                     } | ||||
|                     schema = Some(value.to_owned()) | ||||
|                 } | ||||
|                 "rows" => { | ||||
|                     if n_row.is_some() { | ||||
|                         return Err(Error::ModuleError(format!( | ||||
|                             "more than one '{}' parameter", | ||||
|                             param | ||||
|                         ))); | ||||
|                     } | ||||
|                     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::Cursor> { | ||||
|         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({}, {:?})", self.i_inst, arg); | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     fn insert(&mut self, args: &Values<'_>) -> Result<i64> { | ||||
|         println!( | ||||
|             "VTabLog::insert({}, {:?})", | ||||
|             self.i_inst, | ||||
|             args.iter().collect::<Vec<ValueRef<'_>>>() | ||||
|         ); | ||||
|         Ok(self.n_row as i64) | ||||
|     } | ||||
|  | ||||
|     fn update(&mut self, args: &Values<'_>) -> Result<()> { | ||||
|         println!( | ||||
|             "VTabLog::update({}, {:?})", | ||||
|             self.i_inst, | ||||
|             args.iter().collect::<Vec<ValueRef<'_>>>() | ||||
|         ); | ||||
|         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<i64> { | ||||
|         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 let Some(_) = rows.next()? {} | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user