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