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