1 ///! Port of C [vtablog](http://www.sqlite.org/cgi/src/finfo?name=ext/misc/vtablog.c)
2 use std::default::Default;
3 use std::marker::PhantomData;
4 use std::os::raw::c_int;
5 use std::str::FromStr;
6 use std::sync::atomic::{AtomicUsize, Ordering};
7 
8 use crate::vtab::{
9     update_module, Context, CreateVTab, IndexInfo, UpdateVTab, VTab, VTabConnection, VTabCursor,
10     VTabKind, Values,
11 };
12 use crate::{ffi, ValueRef};
13 use crate::{Connection, Error, Result};
14 
15 /// Register the "vtablog" module.
load_module(conn: &Connection) -> Result<()>16 pub fn load_module(conn: &Connection) -> Result<()> {
17     let aux: Option<()> = None;
18     conn.create_module("vtablog", update_module::<VTabLog>(), aux)
19 }
20 
21 /// An instance of the vtablog virtual table
22 #[repr(C)]
23 struct VTabLog {
24     /// Base class. Must be first
25     base: ffi::sqlite3_vtab,
26     /// Number of rows in the table
27     n_row: i64,
28     /// Instance number for this vtablog table
29     i_inst: usize,
30     /// Number of cursors created
31     n_cursor: usize,
32 }
33 
34 impl VTabLog {
connect_create( _: &mut VTabConnection, _: Option<&()>, args: &[&[u8]], is_create: bool, ) -> Result<(String, VTabLog)>35     fn connect_create(
36         _: &mut VTabConnection,
37         _: Option<&()>,
38         args: &[&[u8]],
39         is_create: bool,
40     ) -> Result<(String, VTabLog)> {
41         static N_INST: AtomicUsize = AtomicUsize::new(1);
42         let i_inst = N_INST.fetch_add(1, Ordering::SeqCst);
43         println!(
44             "VTabLog::{}(tab={}, args={:?}):",
45             if is_create { "create" } else { "connect" },
46             i_inst,
47             args,
48         );
49         let mut schema = None;
50         let mut n_row = None;
51 
52         let args = &args[3..];
53         for c_slice in args {
54             let (param, value) = super::parameter(c_slice)?;
55             match param {
56                 "schema" => {
57                     if schema.is_some() {
58                         return Err(Error::ModuleError(format!(
59                             "more than one '{}' parameter",
60                             param
61                         )));
62                     }
63                     schema = Some(value.to_owned())
64                 }
65                 "rows" => {
66                     if n_row.is_some() {
67                         return Err(Error::ModuleError(format!(
68                             "more than one '{}' parameter",
69                             param
70                         )));
71                     }
72                     if let Ok(n) = i64::from_str(value) {
73                         n_row = Some(n)
74                     }
75                 }
76                 _ => {
77                     return Err(Error::ModuleError(format!(
78                         "unrecognized parameter '{}'",
79                         param
80                     )));
81                 }
82             }
83         }
84         if schema.is_none() {
85             return Err(Error::ModuleError("no schema defined".to_owned()));
86         }
87         let vtab = VTabLog {
88             base: ffi::sqlite3_vtab::default(),
89             n_row: n_row.unwrap_or(10),
90             i_inst,
91             n_cursor: 0,
92         };
93         Ok((schema.unwrap(), vtab))
94     }
95 }
96 
97 impl Drop for VTabLog {
drop(&mut self)98     fn drop(&mut self) {
99         println!("VTabLog::drop({})", self.i_inst);
100     }
101 }
102 
103 unsafe impl<'vtab> VTab<'vtab> for VTabLog {
104     type Aux = ();
105     type Cursor = VTabLogCursor<'vtab>;
106 
connect( db: &mut VTabConnection, aux: Option<&Self::Aux>, args: &[&[u8]], ) -> Result<(String, Self)>107     fn connect(
108         db: &mut VTabConnection,
109         aux: Option<&Self::Aux>,
110         args: &[&[u8]],
111     ) -> Result<(String, Self)> {
112         VTabLog::connect_create(db, aux, args, false)
113     }
114 
best_index(&self, info: &mut IndexInfo) -> Result<()>115     fn best_index(&self, info: &mut IndexInfo) -> Result<()> {
116         println!("VTabLog::best_index({})", self.i_inst);
117         info.set_estimated_cost(500.);
118         info.set_estimated_rows(500);
119         Ok(())
120     }
121 
open(&'vtab mut self) -> Result<Self::Cursor>122     fn open(&'vtab mut self) -> Result<Self::Cursor> {
123         self.n_cursor += 1;
124         println!(
125             "VTabLog::open(tab={}, cursor={})",
126             self.i_inst, self.n_cursor
127         );
128         Ok(VTabLogCursor {
129             base: ffi::sqlite3_vtab_cursor::default(),
130             i_cursor: self.n_cursor,
131             row_id: 0,
132             phantom: PhantomData,
133         })
134     }
135 }
136 
137 impl<'vtab> CreateVTab<'vtab> for VTabLog {
138     const KIND: VTabKind = VTabKind::Default;
139 
create( db: &mut VTabConnection, aux: Option<&Self::Aux>, args: &[&[u8]], ) -> Result<(String, Self)>140     fn create(
141         db: &mut VTabConnection,
142         aux: Option<&Self::Aux>,
143         args: &[&[u8]],
144     ) -> Result<(String, Self)> {
145         VTabLog::connect_create(db, aux, args, true)
146     }
147 
destroy(&self) -> Result<()>148     fn destroy(&self) -> Result<()> {
149         println!("VTabLog::destroy({})", self.i_inst);
150         Ok(())
151     }
152 }
153 
154 impl<'vtab> UpdateVTab<'vtab> for VTabLog {
delete(&mut self, arg: ValueRef<'_>) -> Result<()>155     fn delete(&mut self, arg: ValueRef<'_>) -> Result<()> {
156         println!("VTabLog::delete({}, {arg:?})", self.i_inst);
157         Ok(())
158     }
159 
insert(&mut self, args: &Values<'_>) -> Result<i64>160     fn insert(&mut self, args: &Values<'_>) -> Result<i64> {
161         println!(
162             "VTabLog::insert({}, {:?})",
163             self.i_inst,
164             args.iter().collect::<Vec<ValueRef<'_>>>()
165         );
166         Ok(self.n_row)
167     }
168 
update(&mut self, args: &Values<'_>) -> Result<()>169     fn update(&mut self, args: &Values<'_>) -> Result<()> {
170         println!(
171             "VTabLog::update({}, {:?})",
172             self.i_inst,
173             args.iter().collect::<Vec<ValueRef<'_>>>()
174         );
175         Ok(())
176     }
177 }
178 
179 /// A cursor for the Series virtual table
180 #[repr(C)]
181 struct VTabLogCursor<'vtab> {
182     /// Base class. Must be first
183     base: ffi::sqlite3_vtab_cursor,
184     /// Cursor number
185     i_cursor: usize,
186     /// The rowid
187     row_id: i64,
188     phantom: PhantomData<&'vtab VTabLog>,
189 }
190 
191 impl VTabLogCursor<'_> {
vtab(&self) -> &VTabLog192     fn vtab(&self) -> &VTabLog {
193         unsafe { &*(self.base.pVtab as *const VTabLog) }
194     }
195 }
196 
197 impl Drop for VTabLogCursor<'_> {
drop(&mut self)198     fn drop(&mut self) {
199         println!(
200             "VTabLogCursor::drop(tab={}, cursor={})",
201             self.vtab().i_inst,
202             self.i_cursor
203         );
204     }
205 }
206 
207 unsafe impl VTabCursor for VTabLogCursor<'_> {
filter(&mut self, _: c_int, _: Option<&str>, _: &Values<'_>) -> Result<()>208     fn filter(&mut self, _: c_int, _: Option<&str>, _: &Values<'_>) -> Result<()> {
209         println!(
210             "VTabLogCursor::filter(tab={}, cursor={})",
211             self.vtab().i_inst,
212             self.i_cursor
213         );
214         self.row_id = 0;
215         Ok(())
216     }
217 
next(&mut self) -> Result<()>218     fn next(&mut self) -> Result<()> {
219         println!(
220             "VTabLogCursor::next(tab={}, cursor={}): rowid {} -> {}",
221             self.vtab().i_inst,
222             self.i_cursor,
223             self.row_id,
224             self.row_id + 1
225         );
226         self.row_id += 1;
227         Ok(())
228     }
229 
eof(&self) -> bool230     fn eof(&self) -> bool {
231         let eof = self.row_id >= self.vtab().n_row;
232         println!(
233             "VTabLogCursor::eof(tab={}, cursor={}): {}",
234             self.vtab().i_inst,
235             self.i_cursor,
236             eof,
237         );
238         eof
239     }
240 
column(&self, ctx: &mut Context, i: c_int) -> Result<()>241     fn column(&self, ctx: &mut Context, i: c_int) -> Result<()> {
242         let value = if i < 26 {
243             format!(
244                 "{}{}",
245                 "abcdefghijklmnopqrstuvwyz".chars().nth(i as usize).unwrap(),
246                 self.row_id
247             )
248         } else {
249             format!("{i}{}", self.row_id)
250         };
251         println!(
252             "VTabLogCursor::column(tab={}, cursor={}, i={}): {}",
253             self.vtab().i_inst,
254             self.i_cursor,
255             i,
256             value,
257         );
258         ctx.set_result(&value)
259     }
260 
rowid(&self) -> Result<i64>261     fn rowid(&self) -> Result<i64> {
262         println!(
263             "VTabLogCursor::rowid(tab={}, cursor={}): {}",
264             self.vtab().i_inst,
265             self.i_cursor,
266             self.row_id,
267         );
268         Ok(self.row_id)
269     }
270 }
271 
272 #[cfg(test)]
273 mod test {
274     use crate::{Connection, Result};
275     #[test]
test_module() -> Result<()>276     fn test_module() -> Result<()> {
277         let db = Connection::open_in_memory()?;
278         super::load_module(&db)?;
279 
280         db.execute_batch(
281             "CREATE VIRTUAL TABLE temp.log USING vtablog(
282                     schema='CREATE TABLE x(a,b,c)',
283                     rows=25
284                 );",
285         )?;
286         let mut stmt = db.prepare("SELECT * FROM log;")?;
287         let mut rows = stmt.query([])?;
288         while rows.next()?.is_some() {}
289         db.execute("DELETE FROM log WHERE a = ?1", ["a1"])?;
290         db.execute(
291             "INSERT INTO log (a, b, c) VALUES (?1, ?2, ?3)",
292             ["a", "b", "c"],
293         )?;
294         db.execute(
295             "UPDATE log SET b = ?1, c = ?2 WHERE a = ?3",
296             ["bn", "cn", "a1"],
297         )?;
298         Ok(())
299     }
300 }
301