summaryrefslogtreecommitdiffstats
path: root/third_party/rust/rusqlite/src/vtab
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /third_party/rust/rusqlite/src/vtab
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/rusqlite/src/vtab')
-rw-r--r--third_party/rust/rusqlite/src/vtab/array.rs224
-rw-r--r--third_party/rust/rusqlite/src/vtab/csvtab.rs414
-rw-r--r--third_party/rust/rusqlite/src/vtab/mod.rs1076
-rw-r--r--third_party/rust/rusqlite/src/vtab/series.rs298
4 files changed, 2012 insertions, 0 deletions
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..644b4687b6
--- /dev/null
+++ b/third_party/rust/rusqlite/src/vtab/array.rs
@@ -0,0 +1,224 @@
+//! `feature = "array"` 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<Vec<Value>>` must be used as the parameter.
+//! let values = Rc::new(v.iter().copied().map(Value::from).collect::<Vec<Value>>());
+//! let mut stmt = db.prepare("SELECT value from rarray(?);")?;
+//! let rows = stmt.query_map(params![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 as *const c_char;
+
+pub(crate) unsafe extern "C" fn free_array(p: *mut c_void) {
+ let _: Array = Rc::from_raw(p as *const Vec<Value>);
+}
+
+/// Array parameter / pointer
+pub type Array = Rc<Vec<Value>>;
+
+impl ToSql for Array {
+ fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
+ Ok(ToSqlOutput::Array(self.clone()))
+ }
+}
+
+/// `feature = "array"` Register the "rarray" module.
+pub fn load_module(conn: &Connection) -> Result<()> {
+ let aux: Option<()> = None;
+ conn.create_module("rarray", eponymous_only_module::<ArrayTab>(), 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 = None;
+ for (i, constraint) in info.constraints().enumerate() {
+ if !constraint.is_usable() {
+ continue;
+ }
+ if constraint.operator() != IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_EQ {
+ continue;
+ }
+ if let CARRAY_COLUMN_POINTER = constraint.column() {
+ ptr_idx = Some(i);
+ }
+ }
+ if let Some(ptr_idx) = ptr_idx {
+ {
+ let mut constraint_usage = info.constraint_usage(ptr_idx);
+ constraint_usage.set_argv_index(1);
+ constraint_usage.set_omit(true);
+ }
+ info.set_estimated_cost(1f64);
+ info.set_estimated_rows(100);
+ info.set_idx_num(1);
+ } else {
+ info.set_estimated_cost(2_147_483_647f64);
+ info.set_estimated_rows(2_147_483_647);
+ info.set_idx_num(0);
+ }
+ Ok(())
+ }
+
+ fn open(&self) -> Result<ArrayTabCursor<'_>> {
+ 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<Array>,
+ 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<i64> {
+ Ok(self.row_id)
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use crate::types::Value;
+ use crate::vtab::array;
+ use crate::Connection;
+ use std::rc::Rc;
+
+ #[test]
+ fn test_array_module() {
+ let db = Connection::open_in_memory().unwrap();
+ array::load_module(&db).unwrap();
+
+ let v = vec![1i64, 2, 3, 4];
+ let values: Vec<Value> = v.into_iter().map(Value::from).collect();
+ let ptr = Rc::new(values);
+ {
+ let mut stmt = db.prepare("SELECT value from rarray(?);").unwrap();
+
+ let rows = stmt.query_map(&[&ptr], |row| row.get::<_, i64>(0)).unwrap();
+ assert_eq!(2, Rc::strong_count(&ptr));
+ let mut count = 0;
+ for (i, value) in rows.enumerate() {
+ assert_eq!(i as i64, value.unwrap() - 1);
+ count += 1;
+ }
+ assert_eq!(4, count);
+ }
+ assert_eq!(1, Rc::strong_count(&ptr));
+ }
+}
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..79ec5dab34
--- /dev/null
+++ b/third_party/rust/rusqlite/src/vtab/csvtab.rs
@@ -0,0 +1,414 @@
+//! `feature = "csvtab"` 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)?;
+//! // Assum3e 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::{
+ dequote, escape_double_quote, parse_boolean, read_only_module, Context, CreateVTab, IndexInfo,
+ VTab, VTabConnection, VTabCursor, Values,
+};
+use crate::{Connection, Error, Result};
+
+/// `feature = "csvtab"` 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::<CSVTab>(), 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::Reader<File>, csv::Error> {
+ csv::ReaderBuilder::new()
+ .has_headers(self.has_headers)
+ .delimiter(self.delimiter)
+ .quote(self.quote)
+ .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> {
+ if arg.len() == 1 {
+ arg.bytes().next()
+ } else {
+ None
+ }
+ }
+}
+
+unsafe impl<'vtab> VTab<'vtab> for CSVTab {
+ type Aux = ();
+ type Cursor = CSVTabCursor<'vtab>;
+
+ fn connect(
+ _: &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) = CSVTab::parameter(c_slice)?;
+ match param {
+ "filename" => {
+ if !Path::new(value).exists() {
+ return Err(Error::ModuleError(format!(
+ "file '{}' does not exist",
+ value
+ )));
+ }
+ vtab.filename = value.to_owned();
+ }
+ "schema" => {
+ schema = Some(value.to_owned());
+ }
+ "columns" => {
+ if let Ok(n) = value.parse::<u16>() {
+ 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<String> = 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);
+ }
+
+ 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(&self) -> Result<CSVTabCursor<'_>> {
+ Ok(CSVTabCursor::new(self.reader()?))
+ }
+}
+
+impl CreateVTab<'_> for CSVTab {}
+
+/// 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<File>,
+ /// 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<File>) -> 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<i64> {
+ Ok(self.row_number as i64)
+ }
+}
+
+impl From<csv::Error> for Error {
+ fn from(err: csv::Error) -> Error {
+ Error::ModuleError(err.to_string())
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use crate::vtab::csvtab;
+ use crate::{Connection, Result, NO_PARAMS};
+ use fallible_iterator::FallibleIterator;
+
+ #[test]
+ fn test_csv_module() {
+ let db = Connection::open_in_memory().unwrap();
+ csvtab::load_module(&db).unwrap();
+ db.execute_batch("CREATE VIRTUAL TABLE vtab USING csv(filename='test.csv', header=yes)")
+ .unwrap();
+
+ {
+ let mut s = db.prepare("SELECT rowid, * FROM vtab").unwrap();
+ {
+ let headers = s.column_names();
+ assert_eq!(vec!["rowid", "colA", "colB", "colC"], headers);
+ }
+
+ let ids: Result<Vec<i32>> = s
+ .query(NO_PARAMS)
+ .unwrap()
+ .map(|row| row.get::<_, i32>(0))
+ .collect();
+ let sum = ids.unwrap().iter().sum::<i32>();
+ assert_eq!(sum, 15);
+ }
+ db.execute_batch("DROP TABLE vtab").unwrap();
+ }
+
+ #[test]
+ fn test_csv_cursor() {
+ let db = Connection::open_in_memory().unwrap();
+ csvtab::load_module(&db).unwrap();
+ db.execute_batch("CREATE VIRTUAL TABLE vtab USING csv(filename='test.csv', header=yes)")
+ .unwrap();
+
+ {
+ let mut s = db
+ .prepare(
+ "SELECT v1.rowid, v1.* FROM vtab v1 NATURAL JOIN vtab v2 WHERE \
+ v1.rowid < v2.rowid",
+ )
+ .unwrap();
+
+ let mut rows = s.query(NO_PARAMS).unwrap();
+ let row = rows.next().unwrap().unwrap();
+ assert_eq!(row.get_unwrap::<_, i32>(0), 2);
+ }
+ db.execute_batch("DROP TABLE vtab").unwrap();
+ }
+}
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..dc3bda6481
--- /dev/null
+++ b/third_party/rust/rusqlite/src/vtab/mod.rs
@@ -0,0 +1,1076 @@
+//! `feature = "vtab"` Create virtual tables.
+//!
+//! Follow these steps to create your own virtual table:
+//! 1. Write implemenation 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;
+use crate::ffi;
+pub use crate::ffi::{sqlite3_vtab, sqlite3_vtab_cursor};
+use crate::types::{FromSql, FromSqlError, ToSql, ValueRef};
+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
+
+/// `feature = "vtab"` 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::<ffi::sqlite3_module>()],
+ 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: [0u8; std::mem::size_of::<ffi::sqlite3_module>()],
+ }
+ .module
+};
+
+/// `feature = "vtab"` 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).
+pub fn read_only_module<'vtab, T: CreateVTab<'vtab>>() -> &'static Module<'vtab, T> {
+ // The xConnect and xCreate methods do the same thing, but they must be
+ // different so that the virtual table is not an eponymous virtual table.
+ &Module {
+ base: ffi::sqlite3_module {
+ // We don't use V3
+ iVersion: 2, // We don't use V2 or V3 features in read_only_module types
+ xCreate: Some(rust_create::<T>),
+ xConnect: Some(rust_connect::<T>),
+ xBestIndex: Some(rust_best_index::<T>),
+ xDisconnect: Some(rust_disconnect::<T>),
+ xDestroy: Some(rust_destroy::<T>),
+ xOpen: Some(rust_open::<T>),
+ xClose: Some(rust_close::<T::Cursor>),
+ xFilter: Some(rust_filter::<T::Cursor>),
+ xNext: Some(rust_next::<T::Cursor>),
+ xEof: Some(rust_eof::<T::Cursor>),
+ xColumn: Some(rust_column::<T::Cursor>),
+ xRowid: Some(rust_rowid::<T::Cursor>),
+ xUpdate: None,
+ xBegin: None,
+ xSync: None,
+ xCommit: None,
+ xRollback: None,
+ xFindFunction: None,
+ xRename: None,
+ xSavepoint: None,
+ xRelease: None,
+ xRollbackTo: None,
+ ..ZERO_MODULE
+ },
+ phantom: PhantomData::<&'vtab T>,
+ }
+}
+
+/// `feature = "vtab"` 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).
+pub fn eponymous_only_module<'vtab, T: VTab<'vtab>>() -> &'static Module<'vtab, T> {
+ // A virtual table is eponymous if its xCreate method is the exact same function
+ // as the xConnect method For eponymous-only virtual tables, the xCreate
+ // method is NULL
+ &Module {
+ base: ffi::sqlite3_module {
+ // We don't use V3
+ iVersion: 2,
+ xCreate: None,
+ xConnect: Some(rust_connect::<T>),
+ xBestIndex: Some(rust_best_index::<T>),
+ xDisconnect: Some(rust_disconnect::<T>),
+ xDestroy: None,
+ xOpen: Some(rust_open::<T>),
+ xClose: Some(rust_close::<T::Cursor>),
+ xFilter: Some(rust_filter::<T::Cursor>),
+ xNext: Some(rust_next::<T::Cursor>),
+ xEof: Some(rust_eof::<T::Cursor>),
+ xColumn: Some(rust_column::<T::Cursor>),
+ xRowid: Some(rust_rowid::<T::Cursor>),
+ xUpdate: None,
+ xBegin: None,
+ xSync: None,
+ xCommit: None,
+ xRollback: None,
+ xFindFunction: None,
+ xRename: None,
+ xSavepoint: None,
+ xRelease: None,
+ xRollbackTo: None,
+ ..ZERO_MODULE
+ },
+ phantom: PhantomData::<&'vtab T>,
+ }
+}
+
+/// `feature = "vtab"`
+pub struct VTabConnection(*mut ffi::sqlite3);
+
+impl VTabConnection {
+ // TODO sqlite3_vtab_config (http://sqlite.org/c3ref/vtab_config.html)
+
+ // TODO sqlite3_vtab_on_conflict (http://sqlite.org/c3ref/vtab_on_conflict.html)
+
+ /// 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
+ }
+}
+
+/// `feature = "vtab"` 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: ffi::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 self) -> Result<Self::Cursor>;
+}
+
+/// `feature = "vtab"` Non-eponymous virtual table instance trait.
+///
+/// (See [SQLite doc](https://sqlite.org/c3ref/vtab.html))
+pub trait CreateVTab<'vtab>: VTab<'vtab> {
+ /// 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` 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`.
+ ///
+ /// Do nothing by default.
+ /// (See [SQLite doc](https://sqlite.org/vtab.html#the_xdestroy_method))
+ fn destroy(&self) -> Result<()> {
+ Ok(())
+ }
+}
+
+/// `feature = "vtab"` Index constraint operator.
+/// See [Virtual Table Constraint Operator Codes](https://sqlite.org/c3ref/c_index_constraint_eq.html) for details.
+#[derive(Debug, PartialEq)]
+#[allow(non_snake_case, non_camel_case_types, missing_docs)]
+#[non_exhaustive]
+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_FUNCTION(u8), // 3.25.0
+}
+
+impl From<u8> 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,
+ v => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_FUNCTION(v),
+ }
+ }
+}
+
+/// `feature = "vtab"` Pass information into and receive the reply from the
+/// `VTab.best_index` method.
+///
+/// (See [SQLite doc](http://sqlite.org/c3ref/index_info.html))
+pub struct IndexInfo(*mut ffi::sqlite3_index_info);
+
+impl IndexInfo {
+ /// Record WHERE clause constraints.
+ 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.
+ 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
+ pub fn num_of_order_by(&self) -> usize {
+ unsafe { (*self.0).nOrderBy as usize }
+ }
+
+ /// Information about what parameters to pass to `VTabCursor.filter`.
+ 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
+ pub fn set_idx_num(&mut self, idx_num: c_int) {
+ unsafe {
+ (*self.0).idxNum = idx_num;
+ }
+ }
+
+ /// True if output is already ordered
+ pub fn set_order_by_consumed(&mut self, order_by_consumed: bool) {
+ unsafe {
+ (*self.0).orderByConsumed = if order_by_consumed { 1 } else { 0 };
+ }
+ }
+
+ /// Estimated cost of using this index
+ pub fn set_estimated_cost(&mut self, estimated_ost: f64) {
+ unsafe {
+ (*self.0).estimatedCost = estimated_ost;
+ }
+ }
+
+ /// Estimated number of rows returned.
+ #[cfg(feature = "modern_sqlite")] // SQLite >= 3.8.2
+ pub fn set_estimated_rows(&mut self, estimated_rows: i64) {
+ unsafe {
+ (*self.0).estimatedRows = estimated_rows;
+ }
+ }
+
+ // TODO idxFlags
+ // TODO colUsed
+
+ // TODO sqlite3_vtab_collation (http://sqlite.org/c3ref/vtab_collation.html)
+}
+
+/// `feature = "vtab"`
+pub struct IndexConstraintIter<'a> {
+ iter: slice::Iter<'a, ffi::sqlite3_index_constraint>,
+}
+
+impl<'a> Iterator for IndexConstraintIter<'a> {
+ type Item = IndexConstraint<'a>;
+
+ fn next(&mut self) -> Option<IndexConstraint<'a>> {
+ self.iter.next().map(|raw| IndexConstraint(raw))
+ }
+
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ self.iter.size_hint()
+ }
+}
+
+/// `feature = "vtab"` WHERE clause constraint.
+pub struct IndexConstraint<'a>(&'a ffi::sqlite3_index_constraint);
+
+impl IndexConstraint<'_> {
+ /// Column constrained. -1 for ROWID
+ pub fn column(&self) -> c_int {
+ self.0.iColumn
+ }
+
+ /// Constraint operator
+ pub fn operator(&self) -> IndexConstraintOp {
+ IndexConstraintOp::from(self.0.op)
+ }
+
+ /// True if this constraint is usable
+ pub fn is_usable(&self) -> bool {
+ self.0.usable != 0
+ }
+}
+
+/// `feature = "vtab"` 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`
+ 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
+ pub fn set_omit(&mut self, omit: bool) {
+ self.0.omit = if omit { 1 } else { 0 };
+ }
+}
+
+/// `feature = "vtab"`
+pub struct OrderByIter<'a> {
+ iter: slice::Iter<'a, ffi::sqlite3_index_info_sqlite3_index_orderby>,
+}
+
+impl<'a> Iterator for OrderByIter<'a> {
+ type Item = OrderBy<'a>;
+
+ fn next(&mut self) -> Option<OrderBy<'a>> {
+ self.iter.next().map(|raw| OrderBy(raw))
+ }
+
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ self.iter.size_hint()
+ }
+}
+
+/// `feature = "vtab"` A column of the ORDER BY clause.
+pub struct OrderBy<'a>(&'a ffi::sqlite3_index_info_sqlite3_index_orderby);
+
+impl OrderBy<'_> {
+ /// Column number
+ pub fn column(&self) -> c_int {
+ self.0.iColumn
+ }
+
+ /// True for DESC. False for ASC.
+ pub fn is_order_by_desc(&self) -> bool {
+ self.0.desc != 0
+ }
+}
+
+/// `feature = "vtab"` Virtual table cursor trait.
+///
+/// Implementations must be like:
+/// ```rust,ignore
+/// #[repr(C)]
+/// struct MyTabCursor {
+/// /// Base class. Must be first
+/// base: ffi::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`.
+ /// (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<i64>;
+}
+
+/// `feature = "vtab"` Context is used by `VTabCursor.column` to specify the
+/// cell value.
+pub struct Context(*mut ffi::sqlite3_context);
+
+impl Context {
+ /// Set current cell value
+ pub fn set_result<T: ToSql>(&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)
+}
+
+/// `feature = "vtab"` 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.
+ pub fn len(&self) -> usize {
+ self.args.len()
+ }
+
+ /// Returns `true` if there is no value.
+ pub fn is_empty(&self) -> bool {
+ self.args.is_empty()
+ }
+
+ /// Returns value at `idx`
+ pub fn get<T: FromSql>(&self, idx: usize) -> Result<T> {
+ 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::OutOfRange(i) => Error::IntegralValueOutOfRange(idx, i),
+ #[cfg(feature = "i128_blob")]
+ FromSqlError::InvalidI128Size(_) => {
+ Error::InvalidColumnType(idx, idx.to_string(), value.data_type())
+ }
+ #[cfg(feature = "uuid")]
+ FromSqlError::InvalidUuidSize(_) => {
+ Error::FromSqlConversionFailure(idx, value.data_type(), Box::new(err))
+ }
+ })
+ }
+
+ // `sqlite3_value_type` returns `SQLITE_NULL` for pointer.
+ // So it seems not possible to enhance `ValueRef::from_value`.
+ #[cfg(feature = "array")]
+ fn get_array(&self, idx: usize) -> Result<Option<array::Array>> {
+ use crate::types::Value;
+ let arg = self.args[idx];
+ let ptr = unsafe { ffi::sqlite3_value_pointer(arg, array::ARRAY_TYPE) };
+ if ptr.is_null() {
+ Ok(None)
+ } else {
+ Ok(Some(unsafe {
+ let rc = array::Array::from_raw(ptr as *const Vec<Value>);
+ let array = rc.clone();
+ array::Array::into_raw(rc); // don't consume it
+ array
+ }))
+ }
+ }
+
+ /// Turns `Values` into an iterator.
+ pub fn iter(&self) -> ValueIter<'_> {
+ ValueIter {
+ iter: self.args.iter(),
+ }
+ }
+}
+
+impl<'a> IntoIterator for &'a Values<'a> {
+ type IntoIter = ValueIter<'a>;
+ type Item = ValueRef<'a>;
+
+ 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>;
+
+ fn next(&mut self) -> Option<ValueRef<'a>> {
+ self.iter
+ .next()
+ .map(|&raw| unsafe { ValueRef::from_value(raw) })
+ }
+
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ self.iter.size_hint()
+ }
+}
+
+impl Connection {
+ /// `feature = "vtab"` Register a virtual table implementation.
+ ///
+ /// Step 3 of [Creating New Virtual Table
+ /// Implementations](https://sqlite.org/vtab.html#creating_new_virtual_table_implementations).
+ pub fn create_module<'vtab, T: VTab<'vtab>>(
+ &self,
+ module_name: &str,
+ module: &'static Module<'vtab, T>,
+ aux: Option<T::Aux>,
+ ) -> 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<T::Aux>,
+ ) -> Result<()> {
+ 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 as *mut c_void,
+ Some(free_boxed_value::<T::Aux>),
+ )
+ }
+ }
+ None => unsafe {
+ ffi::sqlite3_create_module_v2(
+ self.db(),
+ c_name.as_ptr(),
+ &module.base,
+ ptr::null_mut(),
+ None,
+ )
+ },
+ };
+ self.decode_result(r)
+ }
+}
+
+/// `feature = "vtab"` Escape double-quote (`"`) character occurences by
+/// doubling them (`""`).
+pub fn escape_double_quote(identifier: &str) -> Cow<'_, str> {
+ if identifier.contains('"') {
+ // escape quote by doubling them
+ Owned(identifier.replace("\"", "\"\""))
+ } else {
+ Borrowed(identifier)
+ }
+}
+/// `feature = "vtab"` Dequote string
+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().rev().next() {
+ Some(e) if e == b => &s[1..s.len() - 1],
+ _ => s,
+ },
+ _ => s,
+ }
+}
+/// `feature = "vtab"` The boolean can be one of:
+/// ```text
+/// 1 yes true on
+/// 0 no false off
+/// ```
+pub fn parse_boolean(s: &str) -> Option<bool> {
+ 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
+ }
+}
+
+// FIXME copy/paste from function.rs
+unsafe extern "C" fn free_boxed_value<T>(p: *mut c_void) {
+ let _: Box<T> = Box::from_raw(p as *mut T);
+}
+
+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 as *mut T::Aux;
+ 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::<Vec<_>>();
+ 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 as *mut ffi::sqlite3_vtab;
+ ffi::SQLITE_OK
+ } else {
+ let err = error_from_sqlite_code(rc, None);
+ *err_msg = alloc(&err.to_string());
+ rc
+ }
+ }
+ Err(err) => {
+ *err_msg = alloc(&err.to_string());
+ ffi::SQLITE_ERROR
+ }
+ },
+ Err(Error::SqliteFailure(err, s)) => {
+ if let Some(s) = s {
+ *err_msg = alloc(&s);
+ }
+ err.extended_code
+ }
+ Err(err) => {
+ *err_msg = alloc(&err.to_string());
+ ffi::SQLITE_ERROR
+ }
+ }
+}
+
+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 as *mut T::Aux;
+ 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::<Vec<_>>();
+ 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 as *mut ffi::sqlite3_vtab;
+ ffi::SQLITE_OK
+ } else {
+ let err = error_from_sqlite_code(rc, None);
+ *err_msg = alloc(&err.to_string());
+ rc
+ }
+ }
+ Err(err) => {
+ *err_msg = alloc(&err.to_string());
+ ffi::SQLITE_ERROR
+ }
+ },
+ Err(Error::SqliteFailure(err, s)) => {
+ if let Some(s) = s {
+ *err_msg = alloc(&s);
+ }
+ err.extended_code
+ }
+ Err(err) => {
+ *err_msg = alloc(&err.to_string());
+ ffi::SQLITE_ERROR
+ }
+ }
+}
+
+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 as *mut T;
+ 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 as *mut T;
+ let _: Box<T> = 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 as *mut T;
+ match (*vt).destroy() {
+ Ok(_) => {
+ let _: Box<T> = 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 as *mut T;
+ match (*vt).open() {
+ Ok(cursor) => {
+ let boxed_cursor: *mut T::Cursor = Box::into_raw(Box::new(cursor));
+ *pp_cursor = boxed_cursor as *mut ffi::sqlite3_vtab_cursor;
+ 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<C>(cursor: *mut ffi::sqlite3_vtab_cursor) -> c_int
+where
+ C: VTabCursor,
+{
+ let cr = cursor as *mut C;
+ let _: Box<C> = Box::from_raw(cr);
+ ffi::SQLITE_OK
+}
+
+unsafe extern "C" fn rust_filter<C>(
+ 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<C>(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<C>(cursor: *mut ffi::sqlite3_vtab_cursor) -> c_int
+where
+ C: VTabCursor,
+{
+ let cr = cursor as *mut C;
+ (*cr).eof() as c_int
+}
+
+unsafe extern "C" fn rust_column<C>(
+ cursor: *mut ffi::sqlite3_vtab_cursor,
+ ctx: *mut ffi::sqlite3_context,
+ i: c_int,
+) -> c_int
+where
+ C: VTabCursor,
+{
+ let cr = cursor as *mut C;
+ let mut ctxt = Context(ctx);
+ result_error(ctx, (*cr).column(&mut ctxt, i))
+}
+
+unsafe extern "C" fn rust_rowid<C>(
+ cursor: *mut ffi::sqlite3_vtab_cursor,
+ p_rowid: *mut ffi::sqlite3_int64,
+) -> c_int
+where
+ C: VTabCursor,
+{
+ let cr = cursor as *mut C;
+ match (*cr).rowid() {
+ Ok(rowid) => {
+ *p_rowid = rowid;
+ ffi::SQLITE_OK
+ }
+ err => cursor_error(cursor, err),
+ }
+}
+
+/// Virtual table cursors can set an error message by assigning a string to
+/// `zErrMsg`.
+unsafe fn cursor_error<T>(cursor: *mut ffi::sqlite3_vtab_cursor, result: Result<T>) -> 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`.
+unsafe fn set_err_msg(vtab: *mut ffi::sqlite3_vtab, err_msg: &str) {
+ if !(*vtab).zErrMsg.is_null() {
+ ffi::sqlite3_free((*vtab).zErrMsg as *mut c_void);
+ }
+ (*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.
+unsafe fn result_error<T>(ctx: *mut ffi::sqlite3_context, result: Result<T>) -> 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
+ }
+ }
+}
+
+// Space to hold this string must be obtained
+// from an SQLite memory allocation function
+fn alloc(s: &str) -> *mut c_char {
+ crate::util::SqliteMallocString::from_str(s).into_raw()
+}
+
+#[cfg(feature = "array")]
+pub mod array;
+#[cfg(feature = "csvtab")]
+pub mod csvtab;
+#[cfg(feature = "series")]
+pub mod series; // SQLite >= 3.9.0
+
+#[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..ed67f16597
--- /dev/null
+++ b/third_party/rust/rusqlite/src/vtab/series.rs
@@ -0,0 +1,298 @@
+//! `feature = "series"` 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, VTabConnection, VTabCursor,
+ Values,
+};
+use crate::{Connection, Result};
+
+/// `feature = "series"` Register the "generate_series" module.
+pub fn load_module(conn: &Connection) -> Result<()> {
+ let aux: Option<()> = None;
+ conn.create_module("generate_series", eponymous_only_module::<SeriesTab>(), 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! {
+ #[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;
+ // 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(
+ _: &mut VTabConnection,
+ _aux: Option<&()>,
+ _args: &[&[u8]],
+ ) -> Result<(String, SeriesTab)> {
+ let vtab = SeriesTab {
+ base: ffi::sqlite3_vtab::default(),
+ };
+ 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();
+ // Index of the start= constraint
+ let mut start_idx = None;
+ // Index of the stop= constraint
+ let mut stop_idx = None;
+ // Index of the step= constraint
+ let mut step_idx = None;
+ for (i, constraint) in info.constraints().enumerate() {
+ if !constraint.is_usable() {
+ continue;
+ }
+ if constraint.operator() != IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_EQ {
+ continue;
+ }
+ match constraint.column() {
+ SERIES_COLUMN_START => {
+ start_idx = Some(i);
+ idx_num |= QueryPlanFlags::START;
+ }
+ SERIES_COLUMN_STOP => {
+ stop_idx = Some(i);
+ idx_num |= QueryPlanFlags::STOP;
+ }
+ SERIES_COLUMN_STEP => {
+ step_idx = Some(i);
+ idx_num |= QueryPlanFlags::STEP;
+ }
+ _ => {}
+ };
+ }
+
+ let mut num_of_arg = 0;
+ if let Some(start_idx) = start_idx {
+ num_of_arg += 1;
+ let mut constraint_usage = info.constraint_usage(start_idx);
+ constraint_usage.set_argv_index(num_of_arg);
+ constraint_usage.set_omit(true);
+ }
+ if let Some(stop_idx) = stop_idx {
+ num_of_arg += 1;
+ let mut constraint_usage = info.constraint_usage(stop_idx);
+ constraint_usage.set_argv_index(num_of_arg);
+ constraint_usage.set_omit(true);
+ }
+ if let Some(step_idx) = step_idx {
+ num_of_arg += 1;
+ let mut constraint_usage = info.constraint_usage(step_idx);
+ constraint_usage.set_argv_index(num_of_arg);
+ constraint_usage.set_omit(true);
+ }
+ if idx_num.contains(QueryPlanFlags::BOTH) {
+ // Both start= and stop= boundaries are available.
+ 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.is_order_by_desc() {
+ idx_num |= QueryPlanFlags::DESC;
+ }
+ true
+ } else {
+ false
+ }
+ };
+ if order_by_consumed {
+ info.set_order_by_consumed(true);
+ }
+ } else {
+ info.set_estimated_cost(2_147_483_647f64);
+ info.set_estimated_rows(2_147_483_647);
+ }
+ info.set_idx_num(idx_num.bits());
+ Ok(())
+ }
+
+ fn open(&self) -> Result<SeriesTabCursor<'_>> {
+ 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,
+ /// Mimimum 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,
+ }
+ }
+}
+unsafe impl VTabCursor for SeriesTabCursor<'_> {
+ fn filter(&mut self, idx_num: c_int, _idx_str: Option<&str>, args: &Values<'_>) -> Result<()> {
+ let idx_num = QueryPlanFlags::from_bits_truncate(idx_num);
+ let mut i = 0;
+ if idx_num.contains(QueryPlanFlags::START) {
+ self.min_value = args.get(i)?;
+ i += 1;
+ } else {
+ self.min_value = 0;
+ }
+ if idx_num.contains(QueryPlanFlags::STOP) {
+ self.max_value = args.get(i)?;
+ i += 1;
+ } else {
+ self.max_value = 0xffff_ffff;
+ }
+ if idx_num.contains(QueryPlanFlags::STEP) {
+ self.step = args.get(i)?;
+ if self.step < 1 {
+ self.step = 1;
+ }
+ } 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<i64> {
+ Ok(self.row_id)
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use crate::ffi;
+ use crate::vtab::series;
+ use crate::{Connection, NO_PARAMS};
+
+ #[test]
+ fn test_series_module() {
+ let version = unsafe { ffi::sqlite3_libversion_number() };
+ if version < 3_008_012 {
+ return;
+ }
+
+ let db = Connection::open_in_memory().unwrap();
+ series::load_module(&db).unwrap();
+
+ let mut s = db.prepare("SELECT * FROM generate_series(0,20,5)").unwrap();
+
+ let series = s.query_map(NO_PARAMS, |row| row.get::<_, i32>(0)).unwrap();
+
+ let mut expected = 0;
+ for value in series {
+ assert_eq!(expected, value.unwrap());
+ expected += 5;
+ }
+ }
+}