mirror of
https://github.com/isar/rusqlite.git
synced 2024-11-25 02:21:37 +08:00
Port vtablog
as an example of a writable VTab
This commit is contained in:
parent
762321c15e
commit
fe1150b0cf
@ -117,7 +117,7 @@ unsafe impl<'vtab> VTab<'vtab> for ArrayTab {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open(&self) -> Result<ArrayTabCursor<'_>> {
|
fn open(&mut self) -> Result<ArrayTabCursor<'_>> {
|
||||||
Ok(ArrayTabCursor::new())
|
Ok(ArrayTabCursor::new())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,8 +30,8 @@ use std::str;
|
|||||||
use crate::ffi;
|
use crate::ffi;
|
||||||
use crate::types::Null;
|
use crate::types::Null;
|
||||||
use crate::vtab::{
|
use crate::vtab::{
|
||||||
dequote, escape_double_quote, parse_boolean, read_only_module, Context, CreateVTab, IndexInfo,
|
escape_double_quote, parse_boolean, read_only_module, Context, CreateVTab, IndexInfo, VTab,
|
||||||
VTab, VTabConfig, VTabConnection, VTabCursor, VTabKind, Values,
|
VTabConfig, VTabConnection, VTabCursor, VTabKind, Values,
|
||||||
};
|
};
|
||||||
use crate::{Connection, Error, Result};
|
use crate::{Connection, Error, Result};
|
||||||
|
|
||||||
@ -74,19 +74,6 @@ impl CsvTab {
|
|||||||
.from_path(&self.filename)
|
.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> {
|
fn parse_byte(arg: &str) -> Option<u8> {
|
||||||
if arg.len() == 1 {
|
if arg.len() == 1 {
|
||||||
arg.bytes().next()
|
arg.bytes().next()
|
||||||
@ -122,7 +109,7 @@ unsafe impl<'vtab> VTab<'vtab> for CsvTab {
|
|||||||
|
|
||||||
let args = &args[3..];
|
let args = &args[3..];
|
||||||
for c_slice in args {
|
for c_slice in args {
|
||||||
let (param, value) = CsvTab::parameter(c_slice)?;
|
let (param, value) = super::parameter(c_slice)?;
|
||||||
match param {
|
match param {
|
||||||
"filename" => {
|
"filename" => {
|
||||||
if !Path::new(value).exists() {
|
if !Path::new(value).exists() {
|
||||||
@ -259,7 +246,7 @@ unsafe impl<'vtab> VTab<'vtab> for CsvTab {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open(&self) -> Result<CsvTabCursor<'_>> {
|
fn open(&mut self) -> Result<CsvTabCursor<'_>> {
|
||||||
Ok(CsvTabCursor::new(self.reader()?))
|
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.
|
/// Create a new cursor used for accessing a virtual table.
|
||||||
/// (See [SQLite doc](https://sqlite.org/vtab.html#the_xopen_method))
|
/// (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.
|
/// Read-only virtual table instance trait.
|
||||||
@ -882,7 +882,7 @@ pub fn dequote(s: &str) -> &str {
|
|||||||
}
|
}
|
||||||
match s.bytes().next() {
|
match s.bytes().next() {
|
||||||
Some(b) if b == b'"' || b == b'\'' => match s.bytes().rev().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,
|
||||||
},
|
},
|
||||||
_ => 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
|
// FIXME copy/paste from function.rs
|
||||||
unsafe extern "C" fn free_boxed_value<T>(p: *mut c_void) {
|
unsafe extern "C" fn free_boxed_value<T>(p: *mut c_void) {
|
||||||
drop(Box::from_raw(p.cast::<T>()));
|
drop(Box::from_raw(p.cast::<T>()));
|
||||||
@ -1309,6 +1323,8 @@ pub mod csvtab;
|
|||||||
#[cfg(feature = "series")]
|
#[cfg(feature = "series")]
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "series")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "series")))]
|
||||||
pub mod series; // SQLite >= 3.9.0
|
pub mod series; // SQLite >= 3.9.0
|
||||||
|
#[cfg(test)]
|
||||||
|
mod vtablog;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
@ -153,7 +153,7 @@ unsafe impl<'vtab> VTab<'vtab> for SeriesTab {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open(&self) -> Result<SeriesTabCursor<'_>> {
|
fn open(&mut self) -> Result<SeriesTabCursor<'_>> {
|
||||||
Ok(SeriesTabCursor::new())
|
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(())
|
||||||
|
}
|
||||||
|
}
|
@ -39,7 +39,7 @@ fn test_dummy_module() -> rusqlite::Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open(&'vtab self) -> Result<DummyTabCursor<'vtab>> {
|
fn open(&'vtab mut self) -> Result<DummyTabCursor<'vtab>> {
|
||||||
Ok(DummyTabCursor::default())
|
Ok(DummyTabCursor::default())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user