From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- third_party/rust/rusqlite/src/vtab/array.rs | 223 +++++ third_party/rust/rusqlite/src/vtab/csvtab.rs | 387 +++++++ third_party/rust/rusqlite/src/vtab/mod.rs | 1333 +++++++++++++++++++++++++ third_party/rust/rusqlite/src/vtab/series.rs | 341 +++++++ third_party/rust/rusqlite/src/vtab/vtablog.rs | 297 ++++++ 5 files changed, 2581 insertions(+) create mode 100644 third_party/rust/rusqlite/src/vtab/array.rs create mode 100644 third_party/rust/rusqlite/src/vtab/csvtab.rs create mode 100644 third_party/rust/rusqlite/src/vtab/mod.rs create mode 100644 third_party/rust/rusqlite/src/vtab/series.rs create mode 100644 third_party/rust/rusqlite/src/vtab/vtablog.rs (limited to 'third_party/rust/rusqlite/src/vtab') diff --git a/third_party/rust/rusqlite/src/vtab/array.rs b/third_party/rust/rusqlite/src/vtab/array.rs new file mode 100644 index 0000000000..be19a3e127 --- /dev/null +++ b/third_party/rust/rusqlite/src/vtab/array.rs @@ -0,0 +1,223 @@ +//! Array Virtual Table. +//! +//! Note: `rarray`, not `carray` is the name of the table valued function we +//! define. +//! +//! Port of [carray](http://www.sqlite.org/cgi/src/finfo?name=ext/misc/carray.c) +//! C extension: `https://www.sqlite.org/carray.html` +//! +//! # Example +//! +//! ```rust,no_run +//! # use rusqlite::{types::Value, Connection, Result, params}; +//! # use std::rc::Rc; +//! fn example(db: &Connection) -> Result<()> { +//! // Note: This should be done once (usually when opening the DB). +//! rusqlite::vtab::array::load_module(&db)?; +//! let v = [1i64, 2, 3, 4]; +//! // Note: A `Rc>` must be used as the parameter. +//! let values = Rc::new(v.iter().copied().map(Value::from).collect::>()); +//! let mut stmt = db.prepare("SELECT value from rarray(?1);")?; +//! let rows = stmt.query_map([values], |row| row.get::<_, i64>(0))?; +//! for value in rows { +//! println!("{}", value?); +//! } +//! Ok(()) +//! } +//! ``` + +use std::default::Default; +use std::marker::PhantomData; +use std::os::raw::{c_char, c_int, c_void}; +use std::rc::Rc; + +use crate::ffi; +use crate::types::{ToSql, ToSqlOutput, Value}; +use crate::vtab::{ + eponymous_only_module, Context, IndexConstraintOp, IndexInfo, VTab, VTabConnection, VTabCursor, + Values, +}; +use crate::{Connection, Result}; + +// http://sqlite.org/bindptr.html + +pub(crate) const ARRAY_TYPE: *const c_char = (b"rarray\0" as *const u8).cast::(); + +pub(crate) unsafe extern "C" fn free_array(p: *mut c_void) { + drop(Rc::from_raw(p as *const Vec)); +} + +/// Array parameter / pointer +pub type Array = Rc>; + +impl ToSql for Array { + #[inline] + fn to_sql(&self) -> Result> { + Ok(ToSqlOutput::Array(self.clone())) + } +} + +/// Register the "rarray" module. +pub fn load_module(conn: &Connection) -> Result<()> { + let aux: Option<()> = None; + conn.create_module("rarray", eponymous_only_module::(), aux) +} + +// Column numbers +// const CARRAY_COLUMN_VALUE : c_int = 0; +const CARRAY_COLUMN_POINTER: c_int = 1; + +/// An instance of the Array virtual table +#[repr(C)] +struct ArrayTab { + /// Base class. Must be first + base: ffi::sqlite3_vtab, +} + +unsafe impl<'vtab> VTab<'vtab> for ArrayTab { + type Aux = (); + type Cursor = ArrayTabCursor<'vtab>; + + fn connect( + _: &mut VTabConnection, + _aux: Option<&()>, + _args: &[&[u8]], + ) -> Result<(String, ArrayTab)> { + let vtab = ArrayTab { + base: ffi::sqlite3_vtab::default(), + }; + Ok(("CREATE TABLE x(value,pointer hidden)".to_owned(), vtab)) + } + + fn best_index(&self, info: &mut IndexInfo) -> Result<()> { + // Index of the pointer= constraint + let mut ptr_idx = false; + for (constraint, mut constraint_usage) in info.constraints_and_usages() { + if !constraint.is_usable() { + continue; + } + if constraint.operator() != IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_EQ { + continue; + } + if let CARRAY_COLUMN_POINTER = constraint.column() { + ptr_idx = true; + constraint_usage.set_argv_index(1); + constraint_usage.set_omit(true); + } + } + if ptr_idx { + info.set_estimated_cost(1_f64); + info.set_estimated_rows(100); + info.set_idx_num(1); + } else { + info.set_estimated_cost(2_147_483_647_f64); + info.set_estimated_rows(2_147_483_647); + info.set_idx_num(0); + } + Ok(()) + } + + fn open(&mut self) -> Result> { + Ok(ArrayTabCursor::new()) + } +} + +/// A cursor for the Array virtual table +#[repr(C)] +struct ArrayTabCursor<'vtab> { + /// Base class. Must be first + base: ffi::sqlite3_vtab_cursor, + /// The rowid + row_id: i64, + /// Pointer to the array of values ("pointer") + ptr: Option, + phantom: PhantomData<&'vtab ArrayTab>, +} + +impl ArrayTabCursor<'_> { + fn new<'vtab>() -> ArrayTabCursor<'vtab> { + ArrayTabCursor { + base: ffi::sqlite3_vtab_cursor::default(), + row_id: 0, + ptr: None, + phantom: PhantomData, + } + } + + fn len(&self) -> i64 { + match self.ptr { + Some(ref a) => a.len() as i64, + _ => 0, + } + } +} +unsafe impl VTabCursor for ArrayTabCursor<'_> { + fn filter(&mut self, idx_num: c_int, _idx_str: Option<&str>, args: &Values<'_>) -> Result<()> { + if idx_num > 0 { + self.ptr = args.get_array(0); + } else { + self.ptr = None; + } + self.row_id = 1; + Ok(()) + } + + fn next(&mut self) -> Result<()> { + self.row_id += 1; + Ok(()) + } + + fn eof(&self) -> bool { + self.row_id > self.len() + } + + fn column(&self, ctx: &mut Context, i: c_int) -> Result<()> { + match i { + CARRAY_COLUMN_POINTER => Ok(()), + _ => { + if let Some(ref array) = self.ptr { + let value = &array[(self.row_id - 1) as usize]; + ctx.set_result(&value) + } else { + Ok(()) + } + } + } + } + + fn rowid(&self) -> Result { + Ok(self.row_id) + } +} + +#[cfg(test)] +mod test { + use crate::types::Value; + use crate::vtab::array; + use crate::{Connection, Result}; + use std::rc::Rc; + + #[test] + fn test_array_module() -> Result<()> { + let db = Connection::open_in_memory()?; + array::load_module(&db)?; + + let v = vec![1i64, 2, 3, 4]; + let values: Vec = v.into_iter().map(Value::from).collect(); + let ptr = Rc::new(values); + { + let mut stmt = db.prepare("SELECT value from rarray(?1);")?; + + let rows = stmt.query_map([&ptr], |row| row.get::<_, i64>(0))?; + assert_eq!(2, Rc::strong_count(&ptr)); + let mut count = 0; + for (i, value) in rows.enumerate() { + assert_eq!(i as i64, value? - 1); + count += 1; + } + assert_eq!(4, count); + } + assert_eq!(1, Rc::strong_count(&ptr)); + Ok(()) + } +} diff --git a/third_party/rust/rusqlite/src/vtab/csvtab.rs b/third_party/rust/rusqlite/src/vtab/csvtab.rs new file mode 100644 index 0000000000..acf3cd8180 --- /dev/null +++ b/third_party/rust/rusqlite/src/vtab/csvtab.rs @@ -0,0 +1,387 @@ +//! CSV Virtual Table. +//! +//! Port of [csv](http://www.sqlite.org/cgi/src/finfo?name=ext/misc/csv.c) C +//! extension: `https://www.sqlite.org/csv.html` +//! +//! # Example +//! +//! ```rust,no_run +//! # use rusqlite::{Connection, Result}; +//! fn example() -> Result<()> { +//! // Note: This should be done once (usually when opening the DB). +//! let db = Connection::open_in_memory()?; +//! rusqlite::vtab::csvtab::load_module(&db)?; +//! // Assume my_csv.csv +//! let schema = " +//! CREATE VIRTUAL TABLE my_csv_data +//! USING csv(filename = 'my_csv.csv') +//! "; +//! db.execute_batch(schema)?; +//! // Now the `my_csv_data` (virtual) table can be queried as normal... +//! Ok(()) +//! } +//! ``` +use std::fs::File; +use std::marker::PhantomData; +use std::os::raw::c_int; +use std::path::Path; +use std::str; + +use crate::ffi; +use crate::types::Null; +use crate::vtab::{ + escape_double_quote, parse_boolean, read_only_module, Context, CreateVTab, IndexInfo, VTab, + VTabConfig, VTabConnection, VTabCursor, VTabKind, Values, +}; +use crate::{Connection, Error, Result}; + +/// Register the "csv" module. +/// ```sql +/// CREATE VIRTUAL TABLE vtab USING csv( +/// filename=FILENAME -- Name of file containing CSV content +/// [, schema=SCHEMA] -- Alternative CSV schema. 'CREATE TABLE x(col1 TEXT NOT NULL, col2 INT, ...);' +/// [, header=YES|NO] -- First row of CSV defines the names of columns if "yes". Default "no". +/// [, columns=N] -- Assume the CSV file contains N columns. +/// [, delimiter=C] -- CSV delimiter. Default ','. +/// [, quote=C] -- CSV quote. Default '"'. 0 means no quote. +/// ); +/// ``` +pub fn load_module(conn: &Connection) -> Result<()> { + let aux: Option<()> = None; + conn.create_module("csv", read_only_module::(), aux) +} + +/// An instance of the CSV virtual table +#[repr(C)] +struct CsvTab { + /// Base class. Must be first + base: ffi::sqlite3_vtab, + /// Name of the CSV file + filename: String, + has_headers: bool, + delimiter: u8, + quote: u8, + /// Offset to start of data + offset_first_row: csv::Position, +} + +impl CsvTab { + fn reader(&self) -> Result, csv::Error> { + csv::ReaderBuilder::new() + .has_headers(self.has_headers) + .delimiter(self.delimiter) + .quote(self.quote) + .from_path(&self.filename) + } + + fn parse_byte(arg: &str) -> Option { + if arg.len() == 1 { + arg.bytes().next() + } else { + None + } + } +} + +unsafe impl<'vtab> VTab<'vtab> for CsvTab { + type Aux = (); + type Cursor = CsvTabCursor<'vtab>; + + fn connect( + db: &mut VTabConnection, + _aux: Option<&()>, + args: &[&[u8]], + ) -> Result<(String, CsvTab)> { + if args.len() < 4 { + return Err(Error::ModuleError("no CSV file specified".to_owned())); + } + + let mut vtab = CsvTab { + base: ffi::sqlite3_vtab::default(), + filename: "".to_owned(), + has_headers: false, + delimiter: b',', + quote: b'"', + offset_first_row: csv::Position::new(), + }; + let mut schema = None; + let mut n_col = None; + + let args = &args[3..]; + for c_slice in args { + let (param, value) = super::parameter(c_slice)?; + match param { + "filename" => { + if !Path::new(value).exists() { + return Err(Error::ModuleError(format!("file '{value}' does not exist"))); + } + vtab.filename = value.to_owned(); + } + "schema" => { + schema = Some(value.to_owned()); + } + "columns" => { + if let Ok(n) = value.parse::() { + if n_col.is_some() { + return Err(Error::ModuleError( + "more than one 'columns' parameter".to_owned(), + )); + } else if n == 0 { + return Err(Error::ModuleError( + "must have at least one column".to_owned(), + )); + } + n_col = Some(n); + } else { + return Err(Error::ModuleError(format!( + "unrecognized argument to 'columns': {value}" + ))); + } + } + "header" => { + if let Some(b) = parse_boolean(value) { + vtab.has_headers = b; + } else { + return Err(Error::ModuleError(format!( + "unrecognized argument to 'header': {value}" + ))); + } + } + "delimiter" => { + if let Some(b) = CsvTab::parse_byte(value) { + vtab.delimiter = b; + } else { + return Err(Error::ModuleError(format!( + "unrecognized argument to 'delimiter': {value}" + ))); + } + } + "quote" => { + if let Some(b) = CsvTab::parse_byte(value) { + if b == b'0' { + vtab.quote = 0; + } else { + vtab.quote = b; + } + } else { + return Err(Error::ModuleError(format!( + "unrecognized argument to 'quote': {value}" + ))); + } + } + _ => { + return Err(Error::ModuleError(format!( + "unrecognized parameter '{param}'" + ))); + } + } + } + + if vtab.filename.is_empty() { + return Err(Error::ModuleError("no CSV file specified".to_owned())); + } + + let mut cols: Vec = Vec::new(); + if vtab.has_headers || (n_col.is_none() && schema.is_none()) { + let mut reader = vtab.reader()?; + if vtab.has_headers { + { + let headers = reader.headers()?; + // headers ignored if cols is not empty + if n_col.is_none() && schema.is_none() { + cols = headers + .into_iter() + .map(|header| escape_double_quote(header).into_owned()) + .collect(); + } + } + vtab.offset_first_row = reader.position().clone(); + } else { + let mut record = csv::ByteRecord::new(); + if reader.read_byte_record(&mut record)? { + for (i, _) in record.iter().enumerate() { + cols.push(format!("c{i}")); + } + } + } + } else if let Some(n_col) = n_col { + for i in 0..n_col { + cols.push(format!("c{i}")); + } + } + + if cols.is_empty() && schema.is_none() { + return Err(Error::ModuleError("no column specified".to_owned())); + } + + if schema.is_none() { + let mut sql = String::from("CREATE TABLE x("); + for (i, col) in cols.iter().enumerate() { + sql.push('"'); + sql.push_str(col); + sql.push_str("\" TEXT"); + if i == cols.len() - 1 { + sql.push_str(");"); + } else { + sql.push_str(", "); + } + } + schema = Some(sql); + } + db.config(VTabConfig::DirectOnly)?; + Ok((schema.unwrap(), vtab)) + } + + // Only a forward full table scan is supported. + fn best_index(&self, info: &mut IndexInfo) -> Result<()> { + info.set_estimated_cost(1_000_000.); + Ok(()) + } + + fn open(&mut self) -> Result> { + Ok(CsvTabCursor::new(self.reader()?)) + } +} + +impl CreateVTab<'_> for CsvTab { + const KIND: VTabKind = VTabKind::Default; +} + +/// A cursor for the CSV virtual table +#[repr(C)] +struct CsvTabCursor<'vtab> { + /// Base class. Must be first + base: ffi::sqlite3_vtab_cursor, + /// The CSV reader object + reader: csv::Reader, + /// Current cursor position used as rowid + row_number: usize, + /// Values of the current row + cols: csv::StringRecord, + eof: bool, + phantom: PhantomData<&'vtab CsvTab>, +} + +impl CsvTabCursor<'_> { + fn new<'vtab>(reader: csv::Reader) -> CsvTabCursor<'vtab> { + CsvTabCursor { + base: ffi::sqlite3_vtab_cursor::default(), + reader, + row_number: 0, + cols: csv::StringRecord::new(), + eof: false, + phantom: PhantomData, + } + } + + /// Accessor to the associated virtual table. + fn vtab(&self) -> &CsvTab { + unsafe { &*(self.base.pVtab as *const CsvTab) } + } +} + +unsafe impl VTabCursor for CsvTabCursor<'_> { + // Only a full table scan is supported. So `filter` simply rewinds to + // the beginning. + fn filter( + &mut self, + _idx_num: c_int, + _idx_str: Option<&str>, + _args: &Values<'_>, + ) -> Result<()> { + { + let offset_first_row = self.vtab().offset_first_row.clone(); + self.reader.seek(offset_first_row)?; + } + self.row_number = 0; + self.next() + } + + fn next(&mut self) -> Result<()> { + { + self.eof = self.reader.is_done(); + if self.eof { + return Ok(()); + } + + self.eof = !self.reader.read_record(&mut self.cols)?; + } + + self.row_number += 1; + Ok(()) + } + + fn eof(&self) -> bool { + self.eof + } + + fn column(&self, ctx: &mut Context, col: c_int) -> Result<()> { + if col < 0 || col as usize >= self.cols.len() { + return Err(Error::ModuleError(format!( + "column index out of bounds: {col}" + ))); + } + if self.cols.is_empty() { + return ctx.set_result(&Null); + } + // TODO Affinity + ctx.set_result(&self.cols[col as usize].to_owned()) + } + + fn rowid(&self) -> Result { + Ok(self.row_number as i64) + } +} + +impl From for Error { + #[cold] + fn from(err: csv::Error) -> Error { + Error::ModuleError(err.to_string()) + } +} + +#[cfg(test)] +mod test { + use crate::vtab::csvtab; + use crate::{Connection, Result}; + use fallible_iterator::FallibleIterator; + + #[test] + fn test_csv_module() -> Result<()> { + let db = Connection::open_in_memory()?; + csvtab::load_module(&db)?; + db.execute_batch("CREATE VIRTUAL TABLE vtab USING csv(filename='test.csv', header=yes)")?; + + { + let mut s = db.prepare("SELECT rowid, * FROM vtab")?; + { + let headers = s.column_names(); + assert_eq!(vec!["rowid", "colA", "colB", "colC"], headers); + } + + let ids: Result> = s.query([])?.map(|row| row.get::<_, i32>(0)).collect(); + let sum = ids?.iter().sum::(); + assert_eq!(sum, 15); + } + db.execute_batch("DROP TABLE vtab") + } + + #[test] + fn test_csv_cursor() -> Result<()> { + let db = Connection::open_in_memory()?; + csvtab::load_module(&db)?; + db.execute_batch("CREATE VIRTUAL TABLE vtab USING csv(filename='test.csv', header=yes)")?; + + { + let mut s = db.prepare( + "SELECT v1.rowid, v1.* FROM vtab v1 NATURAL JOIN vtab v2 WHERE \ + v1.rowid < v2.rowid", + )?; + + let mut rows = s.query([])?; + let row = rows.next()?.unwrap(); + assert_eq!(row.get_unwrap::<_, i32>(0), 2); + } + db.execute_batch("DROP TABLE vtab") + } +} diff --git a/third_party/rust/rusqlite/src/vtab/mod.rs b/third_party/rust/rusqlite/src/vtab/mod.rs new file mode 100644 index 0000000000..7e2f5f54f2 --- /dev/null +++ b/third_party/rust/rusqlite/src/vtab/mod.rs @@ -0,0 +1,1333 @@ +//! Create virtual tables. +//! +//! Follow these steps to create your own virtual table: +//! 1. Write implementation of [`VTab`] and [`VTabCursor`] traits. +//! 2. Create an instance of the [`Module`] structure specialized for [`VTab`] +//! impl. from step 1. +//! 3. Register your [`Module`] structure using [`Connection::create_module`]. +//! 4. Run a `CREATE VIRTUAL TABLE` command that specifies the new module in the +//! `USING` clause. +//! +//! (See [SQLite doc](http://sqlite.org/vtab.html)) +use std::borrow::Cow::{self, Borrowed, Owned}; +use std::marker::PhantomData; +use std::marker::Sync; +use std::os::raw::{c_char, c_int, c_void}; +use std::ptr; +use std::slice; + +use crate::context::set_result; +use crate::error::{error_from_sqlite_code, to_sqlite_error}; +use crate::ffi; +pub use crate::ffi::{sqlite3_vtab, sqlite3_vtab_cursor}; +use crate::types::{FromSql, FromSqlError, ToSql, ValueRef}; +use crate::util::alloc; +use crate::{str_to_cstring, Connection, Error, InnerConnection, Result}; + +// let conn: Connection = ...; +// let mod: Module = ...; // VTab builder +// conn.create_module("module", mod); +// +// conn.execute("CREATE VIRTUAL TABLE foo USING module(...)"); +// \-> Module::xcreate +// |-> let vtab: VTab = ...; // on the heap +// \-> conn.declare_vtab("CREATE TABLE foo (...)"); +// conn = Connection::open(...); +// \-> Module::xconnect +// |-> let vtab: VTab = ...; // on the heap +// \-> conn.declare_vtab("CREATE TABLE foo (...)"); +// +// conn.close(); +// \-> vtab.xdisconnect +// conn.execute("DROP TABLE foo"); +// \-> vtab.xDestroy +// +// let stmt = conn.prepare("SELECT ... FROM foo WHERE ..."); +// \-> vtab.xbestindex +// stmt.query().next(); +// \-> vtab.xopen +// |-> let cursor: VTabCursor = ...; // on the heap +// |-> cursor.xfilter or xnext +// |-> cursor.xeof +// \-> if not eof { cursor.column or xrowid } else { cursor.xclose } +// + +// db: *mut ffi::sqlite3 => VTabConnection +// module: *const ffi::sqlite3_module => Module +// aux: *mut c_void => Module::Aux +// ffi::sqlite3_vtab => VTab +// ffi::sqlite3_vtab_cursor => VTabCursor + +/// Virtual table kind +pub enum VTabKind { + /// Non-eponymous + Default, + /// [`create`](CreateVTab::create) == [`connect`](VTab::connect) + /// + /// See [SQLite doc](https://sqlite.org/vtab.html#eponymous_virtual_tables) + Eponymous, + /// No [`create`](CreateVTab::create) / [`destroy`](CreateVTab::destroy) or + /// not used + /// + /// SQLite >= 3.9.0 + /// + /// See [SQLite doc](https://sqlite.org/vtab.html#eponymous_only_virtual_tables) + EponymousOnly, +} + +/// Virtual table module +/// +/// (See [SQLite doc](https://sqlite.org/c3ref/module.html)) +#[repr(transparent)] +pub struct Module<'vtab, T: VTab<'vtab>> { + base: ffi::sqlite3_module, + phantom: PhantomData<&'vtab T>, +} + +unsafe impl<'vtab, T: VTab<'vtab>> Send for Module<'vtab, T> {} +unsafe impl<'vtab, T: VTab<'vtab>> Sync for Module<'vtab, T> {} + +union ModuleZeroHack { + bytes: [u8; std::mem::size_of::()], + module: ffi::sqlite3_module, +} + +// Used as a trailing initializer for sqlite3_module -- this way we avoid having +// the build fail if buildtime_bindgen is on. This is safe, as bindgen-generated +// structs are allowed to be zeroed. +const ZERO_MODULE: ffi::sqlite3_module = unsafe { + ModuleZeroHack { + bytes: [0_u8; std::mem::size_of::()], + } + .module +}; + +macro_rules! module { + ($lt:lifetime, $vt:ty, $ct:ty, $xc:expr, $xd:expr, $xu:expr) => { + #[allow(clippy::needless_update)] + &Module { + base: ffi::sqlite3_module { + // We don't use V3 + iVersion: 2, + xCreate: $xc, + xConnect: Some(rust_connect::<$vt>), + xBestIndex: Some(rust_best_index::<$vt>), + xDisconnect: Some(rust_disconnect::<$vt>), + xDestroy: $xd, + xOpen: Some(rust_open::<$vt>), + xClose: Some(rust_close::<$ct>), + xFilter: Some(rust_filter::<$ct>), + xNext: Some(rust_next::<$ct>), + xEof: Some(rust_eof::<$ct>), + xColumn: Some(rust_column::<$ct>), + xRowid: Some(rust_rowid::<$ct>), // FIXME optional + xUpdate: $xu, + xBegin: None, + xSync: None, + xCommit: None, + xRollback: None, + xFindFunction: None, + xRename: None, + xSavepoint: None, + xRelease: None, + xRollbackTo: None, + ..ZERO_MODULE + }, + phantom: PhantomData::<&$lt $vt>, + } + }; +} + +/// Create an modifiable virtual table implementation. +/// +/// Step 2 of [Creating New Virtual Table Implementations](https://sqlite.org/vtab.html#creating_new_virtual_table_implementations). +#[must_use] +pub fn update_module<'vtab, T: UpdateVTab<'vtab>>() -> &'static Module<'vtab, T> { + match T::KIND { + VTabKind::EponymousOnly => { + module!('vtab, T, T::Cursor, None, None, Some(rust_update::)) + } + VTabKind::Eponymous => { + module!('vtab, T, T::Cursor, Some(rust_connect::), Some(rust_disconnect::), Some(rust_update::)) + } + _ => { + module!('vtab, T, T::Cursor, Some(rust_create::), Some(rust_destroy::), Some(rust_update::)) + } + } +} + +/// Create a read-only virtual table implementation. +/// +/// Step 2 of [Creating New Virtual Table Implementations](https://sqlite.org/vtab.html#creating_new_virtual_table_implementations). +#[must_use] +pub fn read_only_module<'vtab, T: CreateVTab<'vtab>>() -> &'static Module<'vtab, T> { + match T::KIND { + VTabKind::EponymousOnly => eponymous_only_module(), + VTabKind::Eponymous => { + // A virtual table is eponymous if its xCreate method is the exact same function + // as the xConnect method + module!('vtab, T, T::Cursor, Some(rust_connect::), Some(rust_disconnect::), None) + } + _ => { + // The xConnect and xCreate methods may do the same thing, but they must be + // different so that the virtual table is not an eponymous virtual table. + module!('vtab, T, T::Cursor, Some(rust_create::), Some(rust_destroy::), None) + } + } +} + +/// Create an eponymous only virtual table implementation. +/// +/// Step 2 of [Creating New Virtual Table Implementations](https://sqlite.org/vtab.html#creating_new_virtual_table_implementations). +#[must_use] +pub fn eponymous_only_module<'vtab, T: VTab<'vtab>>() -> &'static Module<'vtab, T> { + // For eponymous-only virtual tables, the xCreate method is NULL + module!('vtab, T, T::Cursor, None, None, None) +} + +/// Virtual table configuration options +#[repr(i32)] +#[non_exhaustive] +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum VTabConfig { + /// Equivalent to SQLITE_VTAB_CONSTRAINT_SUPPORT + ConstraintSupport = 1, + /// Equivalent to SQLITE_VTAB_INNOCUOUS + Innocuous = 2, + /// Equivalent to SQLITE_VTAB_DIRECTONLY + DirectOnly = 3, + /// Equivalent to SQLITE_VTAB_USES_ALL_SCHEMAS + UsesAllSchemas = 4, +} + +/// `feature = "vtab"` +pub struct VTabConnection(*mut ffi::sqlite3); + +impl VTabConnection { + /// Configure various facets of the virtual table interface + pub fn config(&mut self, config: VTabConfig) -> Result<()> { + crate::error::check(unsafe { ffi::sqlite3_vtab_config(self.0, config as c_int) }) + } + + // TODO sqlite3_vtab_on_conflict (http://sqlite.org/c3ref/vtab_on_conflict.html) & xUpdate + + /// Get access to the underlying SQLite database connection handle. + /// + /// # Warning + /// + /// You should not need to use this function. If you do need to, please + /// [open an issue on the rusqlite repository](https://github.com/rusqlite/rusqlite/issues) and describe + /// your use case. + /// + /// # Safety + /// + /// This function is unsafe because it gives you raw access + /// to the SQLite connection, and what you do with it could impact the + /// safety of this `Connection`. + pub unsafe fn handle(&mut self) -> *mut ffi::sqlite3 { + self.0 + } +} + +/// Eponymous-only virtual table instance trait. +/// +/// # Safety +/// +/// The first item in a struct implementing `VTab` must be +/// `rusqlite::sqlite3_vtab`, and the struct must be `#[repr(C)]`. +/// +/// ```rust,ignore +/// #[repr(C)] +/// struct MyTab { +/// /// Base class. Must be first +/// base: rusqlite::vtab::sqlite3_vtab, +/// /* Virtual table implementations will typically add additional fields */ +/// } +/// ``` +/// +/// (See [SQLite doc](https://sqlite.org/c3ref/vtab.html)) +pub unsafe trait VTab<'vtab>: Sized { + /// Client data passed to [`Connection::create_module`]. + type Aux; + /// Specific cursor implementation + type Cursor: VTabCursor; + + /// Establish a new connection to an existing virtual table. + /// + /// (See [SQLite doc](https://sqlite.org/vtab.html#the_xconnect_method)) + fn connect( + db: &mut VTabConnection, + aux: Option<&Self::Aux>, + args: &[&[u8]], + ) -> Result<(String, Self)>; + + /// Determine the best way to access the virtual table. + /// (See [SQLite doc](https://sqlite.org/vtab.html#the_xbestindex_method)) + fn best_index(&self, info: &mut IndexInfo) -> Result<()>; + + /// Create a new cursor used for accessing a virtual table. + /// (See [SQLite doc](https://sqlite.org/vtab.html#the_xopen_method)) + fn open(&'vtab mut self) -> Result; +} + +/// Read-only virtual table instance trait. +/// +/// (See [SQLite doc](https://sqlite.org/c3ref/vtab.html)) +pub trait CreateVTab<'vtab>: VTab<'vtab> { + /// For [`EponymousOnly`](VTabKind::EponymousOnly), + /// [`create`](CreateVTab::create) and [`destroy`](CreateVTab::destroy) are + /// not called + const KIND: VTabKind; + /// Create a new instance of a virtual table in response to a CREATE VIRTUAL + /// TABLE statement. The `db` parameter is a pointer to the SQLite + /// database connection that is executing the CREATE VIRTUAL TABLE + /// statement. + /// + /// Call [`connect`](VTab::connect) by default. + /// (See [SQLite doc](https://sqlite.org/vtab.html#the_xcreate_method)) + fn create( + db: &mut VTabConnection, + aux: Option<&Self::Aux>, + args: &[&[u8]], + ) -> Result<(String, Self)> { + Self::connect(db, aux, args) + } + + /// Destroy the underlying table implementation. This method undoes the work + /// of [`create`](CreateVTab::create). + /// + /// Do nothing by default. + /// (See [SQLite doc](https://sqlite.org/vtab.html#the_xdestroy_method)) + fn destroy(&self) -> Result<()> { + Ok(()) + } +} + +/// Writable virtual table instance trait. +/// +/// (See [SQLite doc](https://sqlite.org/vtab.html#xupdate)) +pub trait UpdateVTab<'vtab>: CreateVTab<'vtab> { + /// Delete rowid or PK + fn delete(&mut self, arg: ValueRef<'_>) -> Result<()>; + /// Insert: `args[0] == NULL: old rowid or PK, args[1]: new rowid or PK, + /// args[2]: ...` + /// + /// Return the new rowid. + // TODO Make the distinction between argv[1] == NULL and argv[1] != NULL ? + fn insert(&mut self, args: &Values<'_>) -> Result; + /// Update: `args[0] != NULL: old rowid or PK, args[1]: new row id or PK, + /// args[2]: ...` + fn update(&mut self, args: &Values<'_>) -> Result<()>; +} + +/// Index constraint operator. +/// See [Virtual Table Constraint Operator Codes](https://sqlite.org/c3ref/c_index_constraint_eq.html) for details. +#[derive(Debug, Eq, PartialEq)] +#[allow(non_snake_case, non_camel_case_types, missing_docs)] +#[allow(clippy::upper_case_acronyms)] +pub enum IndexConstraintOp { + SQLITE_INDEX_CONSTRAINT_EQ, + SQLITE_INDEX_CONSTRAINT_GT, + SQLITE_INDEX_CONSTRAINT_LE, + SQLITE_INDEX_CONSTRAINT_LT, + SQLITE_INDEX_CONSTRAINT_GE, + SQLITE_INDEX_CONSTRAINT_MATCH, + SQLITE_INDEX_CONSTRAINT_LIKE, // 3.10.0 + SQLITE_INDEX_CONSTRAINT_GLOB, // 3.10.0 + SQLITE_INDEX_CONSTRAINT_REGEXP, // 3.10.0 + SQLITE_INDEX_CONSTRAINT_NE, // 3.21.0 + SQLITE_INDEX_CONSTRAINT_ISNOT, // 3.21.0 + SQLITE_INDEX_CONSTRAINT_ISNOTNULL, // 3.21.0 + SQLITE_INDEX_CONSTRAINT_ISNULL, // 3.21.0 + SQLITE_INDEX_CONSTRAINT_IS, // 3.21.0 + SQLITE_INDEX_CONSTRAINT_LIMIT, // 3.38.0 + SQLITE_INDEX_CONSTRAINT_OFFSET, // 3.38.0 + SQLITE_INDEX_CONSTRAINT_FUNCTION(u8), // 3.25.0 +} + +impl From for IndexConstraintOp { + fn from(code: u8) -> IndexConstraintOp { + match code { + 2 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_EQ, + 4 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_GT, + 8 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_LE, + 16 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_LT, + 32 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_GE, + 64 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_MATCH, + 65 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_LIKE, + 66 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_GLOB, + 67 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_REGEXP, + 68 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_NE, + 69 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_ISNOT, + 70 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_ISNOTNULL, + 71 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_ISNULL, + 72 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_IS, + 73 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_LIMIT, + 74 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_OFFSET, + v => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_FUNCTION(v), + } + } +} + +bitflags::bitflags! { + /// Virtual table scan flags + /// See [Function Flags](https://sqlite.org/c3ref/c_index_scan_unique.html) for details. + #[repr(C)] + pub struct IndexFlags: ::std::os::raw::c_int { + /// Default + const NONE = 0; + /// Scan visits at most 1 row. + const SQLITE_INDEX_SCAN_UNIQUE = ffi::SQLITE_INDEX_SCAN_UNIQUE; + } +} + +/// Pass information into and receive the reply from the +/// [`VTab::best_index`] method. +/// +/// (See [SQLite doc](http://sqlite.org/c3ref/index_info.html)) +#[derive(Debug)] +pub struct IndexInfo(*mut ffi::sqlite3_index_info); + +impl IndexInfo { + /// Iterate on index constraint and its associated usage. + #[inline] + pub fn constraints_and_usages(&mut self) -> IndexConstraintAndUsageIter<'_> { + let constraints = + unsafe { slice::from_raw_parts((*self.0).aConstraint, (*self.0).nConstraint as usize) }; + let constraint_usages = unsafe { + slice::from_raw_parts_mut((*self.0).aConstraintUsage, (*self.0).nConstraint as usize) + }; + IndexConstraintAndUsageIter { + iter: constraints.iter().zip(constraint_usages.iter_mut()), + } + } + + /// Record WHERE clause constraints. + #[inline] + #[must_use] + pub fn constraints(&self) -> IndexConstraintIter<'_> { + let constraints = + unsafe { slice::from_raw_parts((*self.0).aConstraint, (*self.0).nConstraint as usize) }; + IndexConstraintIter { + iter: constraints.iter(), + } + } + + /// Information about the ORDER BY clause. + #[inline] + #[must_use] + pub fn order_bys(&self) -> OrderByIter<'_> { + let order_bys = + unsafe { slice::from_raw_parts((*self.0).aOrderBy, (*self.0).nOrderBy as usize) }; + OrderByIter { + iter: order_bys.iter(), + } + } + + /// Number of terms in the ORDER BY clause + #[inline] + #[must_use] + pub fn num_of_order_by(&self) -> usize { + unsafe { (*self.0).nOrderBy as usize } + } + + /// Information about what parameters to pass to [`VTabCursor::filter`]. + #[inline] + pub fn constraint_usage(&mut self, constraint_idx: usize) -> IndexConstraintUsage<'_> { + let constraint_usages = unsafe { + slice::from_raw_parts_mut((*self.0).aConstraintUsage, (*self.0).nConstraint as usize) + }; + IndexConstraintUsage(&mut constraint_usages[constraint_idx]) + } + + /// Number used to identify the index + #[inline] + pub fn set_idx_num(&mut self, idx_num: c_int) { + unsafe { + (*self.0).idxNum = idx_num; + } + } + + /// String used to identify the index + pub fn set_idx_str(&mut self, idx_str: &str) { + unsafe { + (*self.0).idxStr = alloc(idx_str); + (*self.0).needToFreeIdxStr = 1; + } + } + + /// True if output is already ordered + #[inline] + pub fn set_order_by_consumed(&mut self, order_by_consumed: bool) { + unsafe { + (*self.0).orderByConsumed = order_by_consumed as c_int; + } + } + + /// Estimated cost of using this index + #[inline] + pub fn set_estimated_cost(&mut self, estimated_ost: f64) { + unsafe { + (*self.0).estimatedCost = estimated_ost; + } + } + + /// Estimated number of rows returned. + #[inline] + pub fn set_estimated_rows(&mut self, estimated_rows: i64) { + unsafe { + (*self.0).estimatedRows = estimated_rows; + } + } + + /// Mask of SQLITE_INDEX_SCAN_* flags. + #[inline] + pub fn set_idx_flags(&mut self, flags: IndexFlags) { + unsafe { (*self.0).idxFlags = flags.bits() }; + } + + /// Mask of columns used by statement + #[inline] + pub fn col_used(&self) -> u64 { + unsafe { (*self.0).colUsed } + } + + /// Determine the collation for a virtual table constraint + #[cfg(feature = "modern_sqlite")] // SQLite >= 3.22.0 + #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))] + pub fn collation(&self, constraint_idx: usize) -> Result<&str> { + use std::ffi::CStr; + let idx = constraint_idx as c_int; + let collation = unsafe { ffi::sqlite3_vtab_collation(self.0, idx) }; + if collation.is_null() { + return Err(Error::SqliteFailure( + ffi::Error::new(ffi::SQLITE_MISUSE), + Some(format!("{constraint_idx} is out of range")), + )); + } + Ok(unsafe { CStr::from_ptr(collation) }.to_str()?) + } + + /*/// Determine if a virtual table query is DISTINCT + #[cfg(feature = "modern_sqlite")] // SQLite >= 3.38.0 + #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))] + pub fn distinct(&self) -> c_int { + unsafe { ffi::sqlite3_vtab_distinct(self.0) } + } + + /// Constraint values + #[cfg(feature = "modern_sqlite")] // SQLite >= 3.38.0 + #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))] + pub fn set_rhs_value(&mut self, constraint_idx: c_int, value: ValueRef) -> Result<()> { + // TODO ValueRef to sqlite3_value + crate::error::check(unsafe { ffi::sqlite3_vtab_rhs_value(self.O, constraint_idx, value) }) + } + + /// Identify and handle IN constraints + #[cfg(feature = "modern_sqlite")] // SQLite >= 3.38.0 + #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))] + pub fn set_in_constraint(&mut self, constraint_idx: c_int, b_handle: c_int) -> bool { + unsafe { ffi::sqlite3_vtab_in(self.0, constraint_idx, b_handle) != 0 } + } // TODO sqlite3_vtab_in_first / sqlite3_vtab_in_next https://sqlite.org/c3ref/vtab_in_first.html + */ +} + +/// Iterate on index constraint and its associated usage. +pub struct IndexConstraintAndUsageIter<'a> { + iter: std::iter::Zip< + slice::Iter<'a, ffi::sqlite3_index_constraint>, + slice::IterMut<'a, ffi::sqlite3_index_constraint_usage>, + >, +} + +impl<'a> Iterator for IndexConstraintAndUsageIter<'a> { + type Item = (IndexConstraint<'a>, IndexConstraintUsage<'a>); + + #[inline] + fn next(&mut self) -> Option<(IndexConstraint<'a>, IndexConstraintUsage<'a>)> { + self.iter + .next() + .map(|raw| (IndexConstraint(raw.0), IndexConstraintUsage(raw.1))) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } +} + +/// `feature = "vtab"` +pub struct IndexConstraintIter<'a> { + iter: slice::Iter<'a, ffi::sqlite3_index_constraint>, +} + +impl<'a> Iterator for IndexConstraintIter<'a> { + type Item = IndexConstraint<'a>; + + #[inline] + fn next(&mut self) -> Option> { + self.iter.next().map(IndexConstraint) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } +} + +/// WHERE clause constraint. +pub struct IndexConstraint<'a>(&'a ffi::sqlite3_index_constraint); + +impl IndexConstraint<'_> { + /// Column constrained. -1 for ROWID + #[inline] + #[must_use] + pub fn column(&self) -> c_int { + self.0.iColumn + } + + /// Constraint operator + #[inline] + #[must_use] + pub fn operator(&self) -> IndexConstraintOp { + IndexConstraintOp::from(self.0.op) + } + + /// True if this constraint is usable + #[inline] + #[must_use] + pub fn is_usable(&self) -> bool { + self.0.usable != 0 + } +} + +/// Information about what parameters to pass to +/// [`VTabCursor::filter`]. +pub struct IndexConstraintUsage<'a>(&'a mut ffi::sqlite3_index_constraint_usage); + +impl IndexConstraintUsage<'_> { + /// if `argv_index` > 0, constraint is part of argv to + /// [`VTabCursor::filter`] + #[inline] + pub fn set_argv_index(&mut self, argv_index: c_int) { + self.0.argvIndex = argv_index; + } + + /// if `omit`, do not code a test for this constraint + #[inline] + pub fn set_omit(&mut self, omit: bool) { + self.0.omit = omit as std::os::raw::c_uchar; + } +} + +/// `feature = "vtab"` +pub struct OrderByIter<'a> { + iter: slice::Iter<'a, ffi::sqlite3_index_orderby>, +} + +impl<'a> Iterator for OrderByIter<'a> { + type Item = OrderBy<'a>; + + #[inline] + fn next(&mut self) -> Option> { + self.iter.next().map(OrderBy) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } +} + +/// A column of the ORDER BY clause. +pub struct OrderBy<'a>(&'a ffi::sqlite3_index_orderby); + +impl OrderBy<'_> { + /// Column number + #[inline] + #[must_use] + pub fn column(&self) -> c_int { + self.0.iColumn + } + + /// True for DESC. False for ASC. + #[inline] + #[must_use] + pub fn is_order_by_desc(&self) -> bool { + self.0.desc != 0 + } +} + +/// Virtual table cursor trait. +/// +/// # Safety +/// +/// Implementations must be like: +/// ```rust,ignore +/// #[repr(C)] +/// struct MyTabCursor { +/// /// Base class. Must be first +/// base: rusqlite::vtab::sqlite3_vtab_cursor, +/// /* Virtual table implementations will typically add additional fields */ +/// } +/// ``` +/// +/// (See [SQLite doc](https://sqlite.org/c3ref/vtab_cursor.html)) +pub unsafe trait VTabCursor: Sized { + /// Begin a search of a virtual table. + /// (See [SQLite doc](https://sqlite.org/vtab.html#the_xfilter_method)) + fn filter(&mut self, idx_num: c_int, idx_str: Option<&str>, args: &Values<'_>) -> Result<()>; + /// Advance cursor to the next row of a result set initiated by + /// [`filter`](VTabCursor::filter). (See [SQLite doc](https://sqlite.org/vtab.html#the_xnext_method)) + fn next(&mut self) -> Result<()>; + /// Must return `false` if the cursor currently points to a valid row of + /// data, or `true` otherwise. + /// (See [SQLite doc](https://sqlite.org/vtab.html#the_xeof_method)) + fn eof(&self) -> bool; + /// Find the value for the `i`-th column of the current row. + /// `i` is zero-based so the first column is numbered 0. + /// May return its result back to SQLite using one of the specified `ctx`. + /// (See [SQLite doc](https://sqlite.org/vtab.html#the_xcolumn_method)) + fn column(&self, ctx: &mut Context, i: c_int) -> Result<()>; + /// Return the rowid of row that the cursor is currently pointing at. + /// (See [SQLite doc](https://sqlite.org/vtab.html#the_xrowid_method)) + fn rowid(&self) -> Result; +} + +/// Context is used by [`VTabCursor::column`] to specify the +/// cell value. +pub struct Context(*mut ffi::sqlite3_context); + +impl Context { + /// Set current cell value + #[inline] + pub fn set_result(&mut self, value: &T) -> Result<()> { + let t = value.to_sql()?; + unsafe { set_result(self.0, &t) }; + Ok(()) + } + + // TODO sqlite3_vtab_nochange (http://sqlite.org/c3ref/vtab_nochange.html) // 3.22.0 & xColumn +} + +/// Wrapper to [`VTabCursor::filter`] arguments, the values +/// requested by [`VTab::best_index`]. +pub struct Values<'a> { + args: &'a [*mut ffi::sqlite3_value], +} + +impl Values<'_> { + /// Returns the number of values. + #[inline] + #[must_use] + pub fn len(&self) -> usize { + self.args.len() + } + + /// Returns `true` if there is no value. + #[inline] + #[must_use] + pub fn is_empty(&self) -> bool { + self.args.is_empty() + } + + /// Returns value at `idx` + pub fn get(&self, idx: usize) -> Result { + let arg = self.args[idx]; + let value = unsafe { ValueRef::from_value(arg) }; + FromSql::column_result(value).map_err(|err| match err { + FromSqlError::InvalidType => Error::InvalidFilterParameterType(idx, value.data_type()), + FromSqlError::Other(err) => { + Error::FromSqlConversionFailure(idx, value.data_type(), err) + } + FromSqlError::InvalidBlobSize { .. } => { + Error::FromSqlConversionFailure(idx, value.data_type(), Box::new(err)) + } + FromSqlError::OutOfRange(i) => Error::IntegralValueOutOfRange(idx, i), + }) + } + + // `sqlite3_value_type` returns `SQLITE_NULL` for pointer. + // So it seems not possible to enhance `ValueRef::from_value`. + #[cfg(feature = "array")] + #[cfg_attr(docsrs, doc(cfg(feature = "array")))] + fn get_array(&self, idx: usize) -> Option { + use crate::types::Value; + let arg = self.args[idx]; + let ptr = unsafe { ffi::sqlite3_value_pointer(arg, array::ARRAY_TYPE) }; + if ptr.is_null() { + None + } else { + Some(unsafe { + let rc = array::Array::from_raw(ptr as *const Vec); + let array = rc.clone(); + array::Array::into_raw(rc); // don't consume it + array + }) + } + } + + /// Turns `Values` into an iterator. + #[inline] + #[must_use] + pub fn iter(&self) -> ValueIter<'_> { + ValueIter { + iter: self.args.iter(), + } + } + // TODO sqlite3_vtab_in_first / sqlite3_vtab_in_next https://sqlite.org/c3ref/vtab_in_first.html & 3.38.0 +} + +impl<'a> IntoIterator for &'a Values<'a> { + type IntoIter = ValueIter<'a>; + type Item = ValueRef<'a>; + + #[inline] + fn into_iter(self) -> ValueIter<'a> { + self.iter() + } +} + +/// [`Values`] iterator. +pub struct ValueIter<'a> { + iter: slice::Iter<'a, *mut ffi::sqlite3_value>, +} + +impl<'a> Iterator for ValueIter<'a> { + type Item = ValueRef<'a>; + + #[inline] + fn next(&mut self) -> Option> { + self.iter + .next() + .map(|&raw| unsafe { ValueRef::from_value(raw) }) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } +} + +impl Connection { + /// Register a virtual table implementation. + /// + /// Step 3 of [Creating New Virtual Table + /// Implementations](https://sqlite.org/vtab.html#creating_new_virtual_table_implementations). + #[inline] + pub fn create_module<'vtab, T: VTab<'vtab>>( + &self, + module_name: &str, + module: &'static Module<'vtab, T>, + aux: Option, + ) -> Result<()> { + self.db.borrow_mut().create_module(module_name, module, aux) + } +} + +impl InnerConnection { + fn create_module<'vtab, T: VTab<'vtab>>( + &mut self, + module_name: &str, + module: &'static Module<'vtab, T>, + aux: Option, + ) -> Result<()> { + use crate::version; + if version::version_number() < 3_009_000 && module.base.xCreate.is_none() { + return Err(Error::ModuleError(format!( + "Eponymous-only virtual table not supported by SQLite version {}", + version::version() + ))); + } + let c_name = str_to_cstring(module_name)?; + let r = match aux { + Some(aux) => { + let boxed_aux: *mut T::Aux = Box::into_raw(Box::new(aux)); + unsafe { + ffi::sqlite3_create_module_v2( + self.db(), + c_name.as_ptr(), + &module.base, + boxed_aux.cast::(), + Some(free_boxed_value::), + ) + } + } + None => unsafe { + ffi::sqlite3_create_module_v2( + self.db(), + c_name.as_ptr(), + &module.base, + ptr::null_mut(), + None, + ) + }, + }; + self.decode_result(r) + } +} + +/// Escape double-quote (`"`) character occurrences by +/// doubling them (`""`). +#[must_use] +pub fn escape_double_quote(identifier: &str) -> Cow<'_, str> { + if identifier.contains('"') { + // escape quote by doubling them + Owned(identifier.replace('"', "\"\"")) + } else { + Borrowed(identifier) + } +} +/// Dequote string +#[must_use] +pub fn dequote(s: &str) -> &str { + if s.len() < 2 { + return s; + } + match s.bytes().next() { + Some(b) if b == b'"' || b == b'\'' => match s.bytes().next_back() { + Some(e) if e == b => &s[1..s.len() - 1], // FIXME handle inner escaped quote(s) + _ => s, + }, + _ => s, + } +} +/// The boolean can be one of: +/// ```text +/// 1 yes true on +/// 0 no false off +/// ``` +#[must_use] +pub fn parse_boolean(s: &str) -> Option { + if s.eq_ignore_ascii_case("yes") + || s.eq_ignore_ascii_case("on") + || s.eq_ignore_ascii_case("true") + || s.eq("1") + { + Some(true) + } else if s.eq_ignore_ascii_case("no") + || s.eq_ignore_ascii_case("off") + || s.eq_ignore_ascii_case("false") + || s.eq("0") + { + Some(false) + } else { + None + } +} + +/// `=['"]?['"]?` => `(, )` +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(p: *mut c_void) { + drop(Box::from_raw(p.cast::())); +} + +unsafe extern "C" fn rust_create<'vtab, T>( + db: *mut ffi::sqlite3, + aux: *mut c_void, + argc: c_int, + argv: *const *const c_char, + pp_vtab: *mut *mut ffi::sqlite3_vtab, + err_msg: *mut *mut c_char, +) -> c_int +where + T: CreateVTab<'vtab>, +{ + use std::ffi::CStr; + + let mut conn = VTabConnection(db); + let aux = aux.cast::(); + let args = slice::from_raw_parts(argv, argc as usize); + let vec = args + .iter() + .map(|&cs| CStr::from_ptr(cs).to_bytes()) // FIXME .to_str() -> Result<&str, Utf8Error> + .collect::>(); + match T::create(&mut conn, aux.as_ref(), &vec[..]) { + Ok((sql, vtab)) => match std::ffi::CString::new(sql) { + Ok(c_sql) => { + let rc = ffi::sqlite3_declare_vtab(db, c_sql.as_ptr()); + if rc == ffi::SQLITE_OK { + let boxed_vtab: *mut T = Box::into_raw(Box::new(vtab)); + *pp_vtab = boxed_vtab.cast::(); + ffi::SQLITE_OK + } else { + let err = error_from_sqlite_code(rc, None); + to_sqlite_error(&err, err_msg) + } + } + Err(err) => { + *err_msg = alloc(&err.to_string()); + ffi::SQLITE_ERROR + } + }, + Err(err) => to_sqlite_error(&err, err_msg), + } +} + +unsafe extern "C" fn rust_connect<'vtab, T>( + db: *mut ffi::sqlite3, + aux: *mut c_void, + argc: c_int, + argv: *const *const c_char, + pp_vtab: *mut *mut ffi::sqlite3_vtab, + err_msg: *mut *mut c_char, +) -> c_int +where + T: VTab<'vtab>, +{ + use std::ffi::CStr; + + let mut conn = VTabConnection(db); + let aux = aux.cast::(); + let args = slice::from_raw_parts(argv, argc as usize); + let vec = args + .iter() + .map(|&cs| CStr::from_ptr(cs).to_bytes()) // FIXME .to_str() -> Result<&str, Utf8Error> + .collect::>(); + match T::connect(&mut conn, aux.as_ref(), &vec[..]) { + Ok((sql, vtab)) => match std::ffi::CString::new(sql) { + Ok(c_sql) => { + let rc = ffi::sqlite3_declare_vtab(db, c_sql.as_ptr()); + if rc == ffi::SQLITE_OK { + let boxed_vtab: *mut T = Box::into_raw(Box::new(vtab)); + *pp_vtab = boxed_vtab.cast::(); + ffi::SQLITE_OK + } else { + let err = error_from_sqlite_code(rc, None); + to_sqlite_error(&err, err_msg) + } + } + Err(err) => { + *err_msg = alloc(&err.to_string()); + ffi::SQLITE_ERROR + } + }, + Err(err) => to_sqlite_error(&err, err_msg), + } +} + +unsafe extern "C" fn rust_best_index<'vtab, T>( + vtab: *mut ffi::sqlite3_vtab, + info: *mut ffi::sqlite3_index_info, +) -> c_int +where + T: VTab<'vtab>, +{ + let vt = vtab.cast::(); + let mut idx_info = IndexInfo(info); + match (*vt).best_index(&mut idx_info) { + Ok(_) => ffi::SQLITE_OK, + Err(Error::SqliteFailure(err, s)) => { + if let Some(err_msg) = s { + set_err_msg(vtab, &err_msg); + } + err.extended_code + } + Err(err) => { + set_err_msg(vtab, &err.to_string()); + ffi::SQLITE_ERROR + } + } +} + +unsafe extern "C" fn rust_disconnect<'vtab, T>(vtab: *mut ffi::sqlite3_vtab) -> c_int +where + T: VTab<'vtab>, +{ + if vtab.is_null() { + return ffi::SQLITE_OK; + } + let vtab = vtab.cast::(); + drop(Box::from_raw(vtab)); + ffi::SQLITE_OK +} + +unsafe extern "C" fn rust_destroy<'vtab, T>(vtab: *mut ffi::sqlite3_vtab) -> c_int +where + T: CreateVTab<'vtab>, +{ + if vtab.is_null() { + return ffi::SQLITE_OK; + } + let vt = vtab.cast::(); + match (*vt).destroy() { + Ok(_) => { + drop(Box::from_raw(vt)); + ffi::SQLITE_OK + } + Err(Error::SqliteFailure(err, s)) => { + if let Some(err_msg) = s { + set_err_msg(vtab, &err_msg); + } + err.extended_code + } + Err(err) => { + set_err_msg(vtab, &err.to_string()); + ffi::SQLITE_ERROR + } + } +} + +unsafe extern "C" fn rust_open<'vtab, T: 'vtab>( + vtab: *mut ffi::sqlite3_vtab, + pp_cursor: *mut *mut ffi::sqlite3_vtab_cursor, +) -> c_int +where + T: VTab<'vtab>, +{ + let vt = vtab.cast::(); + match (*vt).open() { + Ok(cursor) => { + let boxed_cursor: *mut T::Cursor = Box::into_raw(Box::new(cursor)); + *pp_cursor = boxed_cursor.cast::(); + ffi::SQLITE_OK + } + Err(Error::SqliteFailure(err, s)) => { + if let Some(err_msg) = s { + set_err_msg(vtab, &err_msg); + } + err.extended_code + } + Err(err) => { + set_err_msg(vtab, &err.to_string()); + ffi::SQLITE_ERROR + } + } +} + +unsafe extern "C" fn rust_close(cursor: *mut ffi::sqlite3_vtab_cursor) -> c_int +where + C: VTabCursor, +{ + let cr = cursor.cast::(); + drop(Box::from_raw(cr)); + ffi::SQLITE_OK +} + +unsafe extern "C" fn rust_filter( + cursor: *mut ffi::sqlite3_vtab_cursor, + idx_num: c_int, + idx_str: *const c_char, + argc: c_int, + argv: *mut *mut ffi::sqlite3_value, +) -> c_int +where + C: VTabCursor, +{ + use std::ffi::CStr; + use std::str; + let idx_name = if idx_str.is_null() { + None + } else { + let c_slice = CStr::from_ptr(idx_str).to_bytes(); + Some(str::from_utf8_unchecked(c_slice)) + }; + let args = slice::from_raw_parts_mut(argv, argc as usize); + let values = Values { args }; + let cr = cursor as *mut C; + cursor_error(cursor, (*cr).filter(idx_num, idx_name, &values)) +} + +unsafe extern "C" fn rust_next(cursor: *mut ffi::sqlite3_vtab_cursor) -> c_int +where + C: VTabCursor, +{ + let cr = cursor as *mut C; + cursor_error(cursor, (*cr).next()) +} + +unsafe extern "C" fn rust_eof(cursor: *mut ffi::sqlite3_vtab_cursor) -> c_int +where + C: VTabCursor, +{ + let cr = cursor.cast::(); + (*cr).eof() as c_int +} + +unsafe extern "C" fn rust_column( + cursor: *mut ffi::sqlite3_vtab_cursor, + ctx: *mut ffi::sqlite3_context, + i: c_int, +) -> c_int +where + C: VTabCursor, +{ + let cr = cursor.cast::(); + let mut ctxt = Context(ctx); + result_error(ctx, (*cr).column(&mut ctxt, i)) +} + +unsafe extern "C" fn rust_rowid( + cursor: *mut ffi::sqlite3_vtab_cursor, + p_rowid: *mut ffi::sqlite3_int64, +) -> c_int +where + C: VTabCursor, +{ + let cr = cursor.cast::(); + match (*cr).rowid() { + Ok(rowid) => { + *p_rowid = rowid; + ffi::SQLITE_OK + } + err => cursor_error(cursor, err), + } +} + +unsafe extern "C" fn rust_update<'vtab, T: 'vtab>( + vtab: *mut ffi::sqlite3_vtab, + argc: c_int, + argv: *mut *mut ffi::sqlite3_value, + p_rowid: *mut ffi::sqlite3_int64, +) -> c_int +where + T: UpdateVTab<'vtab>, +{ + assert!(argc >= 1); + let args = slice::from_raw_parts_mut(argv, argc as usize); + let vt = vtab.cast::(); + let r = if args.len() == 1 { + (*vt).delete(ValueRef::from_value(args[0])) + } else if ffi::sqlite3_value_type(args[0]) == ffi::SQLITE_NULL { + // TODO Make the distinction between argv[1] == NULL and argv[1] != NULL ? + let values = Values { args }; + match (*vt).insert(&values) { + Ok(rowid) => { + *p_rowid = rowid; + Ok(()) + } + Err(e) => Err(e), + } + } else { + let values = Values { args }; + (*vt).update(&values) + }; + match r { + Ok(_) => ffi::SQLITE_OK, + Err(Error::SqliteFailure(err, s)) => { + if let Some(err_msg) = s { + set_err_msg(vtab, &err_msg); + } + err.extended_code + } + Err(err) => { + set_err_msg(vtab, &err.to_string()); + ffi::SQLITE_ERROR + } + } +} + +/// Virtual table cursors can set an error message by assigning a string to +/// `zErrMsg`. +#[cold] +unsafe fn cursor_error(cursor: *mut ffi::sqlite3_vtab_cursor, result: Result) -> c_int { + match result { + Ok(_) => ffi::SQLITE_OK, + Err(Error::SqliteFailure(err, s)) => { + if let Some(err_msg) = s { + set_err_msg((*cursor).pVtab, &err_msg); + } + err.extended_code + } + Err(err) => { + set_err_msg((*cursor).pVtab, &err.to_string()); + ffi::SQLITE_ERROR + } + } +} + +/// Virtual tables methods can set an error message by assigning a string to +/// `zErrMsg`. +#[cold] +unsafe fn set_err_msg(vtab: *mut ffi::sqlite3_vtab, err_msg: &str) { + if !(*vtab).zErrMsg.is_null() { + ffi::sqlite3_free((*vtab).zErrMsg.cast::()); + } + (*vtab).zErrMsg = alloc(err_msg); +} + +/// To raise an error, the `column` method should use this method to set the +/// error message and return the error code. +#[cold] +unsafe fn result_error(ctx: *mut ffi::sqlite3_context, result: Result) -> c_int { + match result { + Ok(_) => ffi::SQLITE_OK, + Err(Error::SqliteFailure(err, s)) => { + match err.extended_code { + ffi::SQLITE_TOOBIG => { + ffi::sqlite3_result_error_toobig(ctx); + } + ffi::SQLITE_NOMEM => { + ffi::sqlite3_result_error_nomem(ctx); + } + code => { + ffi::sqlite3_result_error_code(ctx, code); + if let Some(Ok(cstr)) = s.map(|s| str_to_cstring(&s)) { + ffi::sqlite3_result_error(ctx, cstr.as_ptr(), -1); + } + } + }; + err.extended_code + } + Err(err) => { + ffi::sqlite3_result_error_code(ctx, ffi::SQLITE_ERROR); + if let Ok(cstr) = str_to_cstring(&err.to_string()) { + ffi::sqlite3_result_error(ctx, cstr.as_ptr(), -1); + } + ffi::SQLITE_ERROR + } + } +} + +#[cfg(feature = "array")] +#[cfg_attr(docsrs, doc(cfg(feature = "array")))] +pub mod array; +#[cfg(feature = "csvtab")] +#[cfg_attr(docsrs, doc(cfg(feature = "csvtab")))] +pub mod csvtab; +#[cfg(feature = "series")] +#[cfg_attr(docsrs, doc(cfg(feature = "series")))] +pub mod series; // SQLite >= 3.9.0 +#[cfg(all(test, feature = "modern_sqlite"))] +mod vtablog; + +#[cfg(test)] +mod test { + #[test] + fn test_dequote() { + assert_eq!("", super::dequote("")); + assert_eq!("'", super::dequote("'")); + assert_eq!("\"", super::dequote("\"")); + assert_eq!("'\"", super::dequote("'\"")); + assert_eq!("", super::dequote("''")); + assert_eq!("", super::dequote("\"\"")); + assert_eq!("x", super::dequote("'x'")); + assert_eq!("x", super::dequote("\"x\"")); + assert_eq!("x", super::dequote("x")); + } + #[test] + fn test_parse_boolean() { + assert_eq!(None, super::parse_boolean("")); + assert_eq!(Some(true), super::parse_boolean("1")); + assert_eq!(Some(true), super::parse_boolean("yes")); + assert_eq!(Some(true), super::parse_boolean("on")); + assert_eq!(Some(true), super::parse_boolean("true")); + assert_eq!(Some(false), super::parse_boolean("0")); + assert_eq!(Some(false), super::parse_boolean("no")); + assert_eq!(Some(false), super::parse_boolean("off")); + assert_eq!(Some(false), super::parse_boolean("false")); + } +} diff --git a/third_party/rust/rusqlite/src/vtab/series.rs b/third_party/rust/rusqlite/src/vtab/series.rs new file mode 100644 index 0000000000..5b67758149 --- /dev/null +++ b/third_party/rust/rusqlite/src/vtab/series.rs @@ -0,0 +1,341 @@ +//! Generate series virtual table. +//! +//! Port of C [generate series +//! "function"](http://www.sqlite.org/cgi/src/finfo?name=ext/misc/series.c): +//! `https://www.sqlite.org/series.html` +use std::default::Default; +use std::marker::PhantomData; +use std::os::raw::c_int; + +use crate::ffi; +use crate::types::Type; +use crate::vtab::{ + eponymous_only_module, Context, IndexConstraintOp, IndexInfo, VTab, VTabConfig, VTabConnection, + VTabCursor, Values, +}; +use crate::{Connection, Error, Result}; + +/// Register the "generate_series" module. +pub fn load_module(conn: &Connection) -> Result<()> { + let aux: Option<()> = None; + conn.create_module("generate_series", eponymous_only_module::(), aux) +} + +// Column numbers +// const SERIES_COLUMN_VALUE : c_int = 0; +const SERIES_COLUMN_START: c_int = 1; +const SERIES_COLUMN_STOP: c_int = 2; +const SERIES_COLUMN_STEP: c_int = 3; + +bitflags::bitflags! { + #[derive(Clone, Copy)] + #[repr(C)] + struct QueryPlanFlags: ::std::os::raw::c_int { + // start = $value -- constraint exists + const START = 1; + // stop = $value -- constraint exists + const STOP = 2; + // step = $value -- constraint exists + const STEP = 4; + // output in descending order + const DESC = 8; + // output in ascending order + const ASC = 16; + // Both start and stop + const BOTH = QueryPlanFlags::START.bits() | QueryPlanFlags::STOP.bits(); + } +} + +/// An instance of the Series virtual table +#[repr(C)] +struct SeriesTab { + /// Base class. Must be first + base: ffi::sqlite3_vtab, +} + +unsafe impl<'vtab> VTab<'vtab> for SeriesTab { + type Aux = (); + type Cursor = SeriesTabCursor<'vtab>; + + fn connect( + db: &mut VTabConnection, + _aux: Option<&()>, + _args: &[&[u8]], + ) -> Result<(String, SeriesTab)> { + let vtab = SeriesTab { + base: ffi::sqlite3_vtab::default(), + }; + db.config(VTabConfig::Innocuous)?; + Ok(( + "CREATE TABLE x(value,start hidden,stop hidden,step hidden)".to_owned(), + vtab, + )) + } + + fn best_index(&self, info: &mut IndexInfo) -> Result<()> { + // The query plan bitmask + let mut idx_num: QueryPlanFlags = QueryPlanFlags::empty(); + // Mask of unusable constraints + let mut unusable_mask: QueryPlanFlags = QueryPlanFlags::empty(); + // Constraints on start, stop, and step + let mut a_idx: [Option; 3] = [None, None, None]; + for (i, constraint) in info.constraints().enumerate() { + if constraint.column() < SERIES_COLUMN_START { + continue; + } + let (i_col, i_mask) = match constraint.column() { + SERIES_COLUMN_START => (0, QueryPlanFlags::START), + SERIES_COLUMN_STOP => (1, QueryPlanFlags::STOP), + SERIES_COLUMN_STEP => (2, QueryPlanFlags::STEP), + _ => { + unreachable!() + } + }; + if !constraint.is_usable() { + unusable_mask |= i_mask; + } else if constraint.operator() == IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_EQ { + idx_num |= i_mask; + a_idx[i_col] = Some(i); + } + } + // Number of arguments that SeriesTabCursor::filter expects + let mut n_arg = 0; + for j in a_idx.iter().flatten() { + n_arg += 1; + let mut constraint_usage = info.constraint_usage(*j); + constraint_usage.set_argv_index(n_arg); + constraint_usage.set_omit(true); + #[cfg(all(test, feature = "modern_sqlite"))] + debug_assert_eq!(Ok("BINARY"), info.collation(*j)); + } + if !(unusable_mask & !idx_num).is_empty() { + return Err(Error::SqliteFailure( + ffi::Error::new(ffi::SQLITE_CONSTRAINT), + None, + )); + } + if idx_num.contains(QueryPlanFlags::BOTH) { + // Both start= and stop= boundaries are available. + #[allow(clippy::bool_to_int_with_if)] + info.set_estimated_cost(f64::from( + 2 - if idx_num.contains(QueryPlanFlags::STEP) { + 1 + } else { + 0 + }, + )); + info.set_estimated_rows(1000); + let order_by_consumed = { + let mut order_bys = info.order_bys(); + if let Some(order_by) = order_bys.next() { + if order_by.column() == 0 { + if order_by.is_order_by_desc() { + idx_num |= QueryPlanFlags::DESC; + } else { + idx_num |= QueryPlanFlags::ASC; + } + true + } else { + false + } + } else { + false + } + }; + if order_by_consumed { + info.set_order_by_consumed(true); + } + } else { + // If either boundary is missing, we have to generate a huge span + // of numbers. Make this case very expensive so that the query + // planner will work hard to avoid it. + info.set_estimated_rows(2_147_483_647); + } + info.set_idx_num(idx_num.bits()); + Ok(()) + } + + fn open(&mut self) -> Result> { + Ok(SeriesTabCursor::new()) + } +} + +/// A cursor for the Series virtual table +#[repr(C)] +struct SeriesTabCursor<'vtab> { + /// Base class. Must be first + base: ffi::sqlite3_vtab_cursor, + /// True to count down rather than up + is_desc: bool, + /// The rowid + row_id: i64, + /// Current value ("value") + value: i64, + /// Minimum value ("start") + min_value: i64, + /// Maximum value ("stop") + max_value: i64, + /// Increment ("step") + step: i64, + phantom: PhantomData<&'vtab SeriesTab>, +} + +impl SeriesTabCursor<'_> { + fn new<'vtab>() -> SeriesTabCursor<'vtab> { + SeriesTabCursor { + base: ffi::sqlite3_vtab_cursor::default(), + is_desc: false, + row_id: 0, + value: 0, + min_value: 0, + max_value: 0, + step: 0, + phantom: PhantomData, + } + } +} +#[allow(clippy::comparison_chain)] +unsafe impl VTabCursor for SeriesTabCursor<'_> { + fn filter(&mut self, idx_num: c_int, _idx_str: Option<&str>, args: &Values<'_>) -> Result<()> { + let mut idx_num = QueryPlanFlags::from_bits_truncate(idx_num); + let mut i = 0; + if idx_num.contains(QueryPlanFlags::START) { + self.min_value = args.get::>(i)?.unwrap_or_default(); + i += 1; + } else { + self.min_value = 0; + } + if idx_num.contains(QueryPlanFlags::STOP) { + self.max_value = args.get::>(i)?.unwrap_or_default(); + i += 1; + } else { + self.max_value = 0xffff_ffff; + } + if idx_num.contains(QueryPlanFlags::STEP) { + self.step = args.get::>(i)?.unwrap_or_default(); + if self.step == 0 { + self.step = 1; + } else if self.step < 0 { + self.step = -self.step; + if !idx_num.contains(QueryPlanFlags::ASC) { + idx_num |= QueryPlanFlags::DESC; + } + } + } else { + self.step = 1; + }; + for arg in args.iter() { + if arg.data_type() == Type::Null { + // If any of the constraints have a NULL value, then return no rows. + self.min_value = 1; + self.max_value = 0; + break; + } + } + self.is_desc = idx_num.contains(QueryPlanFlags::DESC); + if self.is_desc { + self.value = self.max_value; + if self.step > 0 { + self.value -= (self.max_value - self.min_value) % self.step; + } + } else { + self.value = self.min_value; + } + self.row_id = 1; + Ok(()) + } + + fn next(&mut self) -> Result<()> { + if self.is_desc { + self.value -= self.step; + } else { + self.value += self.step; + } + self.row_id += 1; + Ok(()) + } + + fn eof(&self) -> bool { + if self.is_desc { + self.value < self.min_value + } else { + self.value > self.max_value + } + } + + fn column(&self, ctx: &mut Context, i: c_int) -> Result<()> { + let x = match i { + SERIES_COLUMN_START => self.min_value, + SERIES_COLUMN_STOP => self.max_value, + SERIES_COLUMN_STEP => self.step, + _ => self.value, + }; + ctx.set_result(&x) + } + + fn rowid(&self) -> Result { + Ok(self.row_id) + } +} + +#[cfg(test)] +mod test { + use crate::ffi; + use crate::vtab::series; + use crate::{Connection, Result}; + use fallible_iterator::FallibleIterator; + + #[test] + fn test_series_module() -> Result<()> { + let version = unsafe { ffi::sqlite3_libversion_number() }; + if version < 3_008_012 { + return Ok(()); + } + + let db = Connection::open_in_memory()?; + series::load_module(&db)?; + + let mut s = db.prepare("SELECT * FROM generate_series(0,20,5)")?; + + let series = s.query_map([], |row| row.get::<_, i32>(0))?; + + let mut expected = 0; + for value in series { + assert_eq!(expected, value?); + expected += 5; + } + + let mut s = + db.prepare("SELECT * FROM generate_series WHERE start=1 AND stop=9 AND step=2")?; + let series: Vec = s.query([])?.map(|r| r.get(0)).collect()?; + assert_eq!(vec![1, 3, 5, 7, 9], series); + let mut s = db.prepare("SELECT * FROM generate_series LIMIT 5")?; + let series: Vec = s.query([])?.map(|r| r.get(0)).collect()?; + assert_eq!(vec![0, 1, 2, 3, 4], series); + let mut s = db.prepare("SELECT * FROM generate_series(0,32,5) ORDER BY value DESC")?; + let series: Vec = s.query([])?.map(|r| r.get(0)).collect()?; + assert_eq!(vec![30, 25, 20, 15, 10, 5, 0], series); + + let mut s = db.prepare("SELECT * FROM generate_series(NULL)")?; + let series: Vec = s.query([])?.map(|r| r.get(0)).collect()?; + let empty = Vec::::new(); + assert_eq!(empty, series); + let mut s = db.prepare("SELECT * FROM generate_series(5,NULL)")?; + let series: Vec = s.query([])?.map(|r| r.get(0)).collect()?; + assert_eq!(empty, series); + let mut s = db.prepare("SELECT * FROM generate_series(5,10,NULL)")?; + let series: Vec = s.query([])?.map(|r| r.get(0)).collect()?; + assert_eq!(empty, series); + let mut s = db.prepare("SELECT * FROM generate_series(NULL,10,2)")?; + let series: Vec = s.query([])?.map(|r| r.get(0)).collect()?; + assert_eq!(empty, series); + let mut s = db.prepare("SELECT * FROM generate_series(5,NULL,2)")?; + let series: Vec = s.query([])?.map(|r| r.get(0)).collect()?; + assert_eq!(empty, series); + let mut s = db.prepare("SELECT * FROM generate_series(NULL) ORDER BY value DESC")?; + let series: Vec = s.query([])?.map(|r| r.get(0)).collect()?; + assert_eq!(empty, series); + + Ok(()) + } +} diff --git a/third_party/rust/rusqlite/src/vtab/vtablog.rs b/third_party/rust/rusqlite/src/vtab/vtablog.rs new file mode 100644 index 0000000000..e289cfae71 --- /dev/null +++ b/third_party/rust/rusqlite/src/vtab/vtablog.rs @@ -0,0 +1,297 @@ +//! 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::(), 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 '{param}' parameter" + ))); + } + schema = Some(value.to_owned()) + } + "rows" => { + if n_row.is_some() { + return Err(Error::ModuleError(format!( + "more than one '{param}' parameter" + ))); + } + 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.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({}, {arg:?})", self.i_inst); + Ok(()) + } + + fn insert(&mut self, args: &Values<'_>) -> Result { + println!( + "VTabLog::insert({}, {:?})", + self.i_inst, + args.iter().collect::>>() + ); + Ok(self.n_row) + } + + fn update(&mut self, args: &Values<'_>) -> Result<()> { + println!( + "VTabLog::update({}, {:?})", + self.i_inst, + args.iter().collect::>>() + ); + 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 { + 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 rows.next()?.is_some() {} + db.execute("DELETE FROM log WHERE a = ?1", ["a1"])?; + db.execute( + "INSERT INTO log (a, b, c) VALUES (?1, ?2, ?3)", + ["a", "b", "c"], + )?; + db.execute( + "UPDATE log SET b = ?1, c = ?2 WHERE a = ?3", + ["bn", "cn", "a1"], + )?; + Ok(()) + } +} -- cgit v1.2.3