story-kit: merge 260_refactor_upgrade_libsqlite3_sys
This commit is contained in:
221
vendor/rusqlite/src/vtab/array.rs
vendored
Normal file
221
vendor/rusqlite/src/vtab/array.rs
vendored
Normal file
@@ -0,0 +1,221 @@
|
||||
//! 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(?1);")?;
|
||||
//! let rows = stmt.query_map([values], |row| row.get::<_, i64>(0))?;
|
||||
//! for value in rows {
|
||||
//! println!("{}", value?);
|
||||
//! }
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
use std::ffi::{c_int, CStr};
|
||||
use std::marker::PhantomData;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::ffi;
|
||||
use crate::types::{ToSql, ToSqlOutput, Value};
|
||||
use crate::vtab::{
|
||||
eponymous_only_module, Context, Filters, IndexConstraintOp, IndexInfo, VTab, VTabConnection,
|
||||
VTabCursor,
|
||||
};
|
||||
use crate::{Connection, Result};
|
||||
|
||||
// http://sqlite.org/bindptr.html
|
||||
|
||||
const ARRAY_TYPE: &CStr = c"rarray";
|
||||
|
||||
/// Array parameter / pointer
|
||||
pub type Array = Rc<Vec<Value>>;
|
||||
|
||||
impl ToSql for Array {
|
||||
#[inline]
|
||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||
Ok(ToSqlOutput::from_rc(self.clone(), ARRAY_TYPE))
|
||||
}
|
||||
}
|
||||
|
||||
/// Register the "rarray" module.
|
||||
pub fn load_module(conn: &Connection) -> Result<()> {
|
||||
let aux: Option<()> = None;
|
||||
conn.create_module(c"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, Self)> {
|
||||
let vtab = Self {
|
||||
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<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<&'vtab Vec<Value>>,
|
||||
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(a) => a.len() as i64,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
unsafe impl VTabCursor for ArrayTabCursor<'_> {
|
||||
fn filter(&mut self, idx_num: c_int, _idx_str: Option<&str>, args: &Filters<'_>) -> Result<()> {
|
||||
if idx_num > 0 {
|
||||
self.ptr = unsafe { args.get_pointer(0, ARRAY_TYPE) };
|
||||
} 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(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 {
|
||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
||||
use wasm_bindgen_test::wasm_bindgen_test as 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<Value> = 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(())
|
||||
}
|
||||
}
|
||||
400
vendor/rusqlite/src/vtab/csvtab.rs
vendored
Normal file
400
vendor/rusqlite/src/vtab/csvtab.rs
vendored
Normal file
@@ -0,0 +1,400 @@
|
||||
//! 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::ffi::c_int;
|
||||
use std::fs::File;
|
||||
use std::marker::PhantomData;
|
||||
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, Filters, IndexInfo,
|
||||
VTab, VTabConfig, VTabConnection, VTabCursor, VTabKind,
|
||||
};
|
||||
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(c"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 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(
|
||||
db: &mut VTabConnection,
|
||||
_aux: Option<&()>,
|
||||
args: &[&[u8]],
|
||||
) -> Result<(String, Self)> {
|
||||
if args.len() < 4 {
|
||||
return Err(Error::ModuleError("no CSV file specified".to_owned()));
|
||||
}
|
||||
|
||||
let mut vtab = Self {
|
||||
base: ffi::sqlite3_vtab::default(),
|
||||
filename: String::new(),
|
||||
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")));
|
||||
}
|
||||
value.clone_into(&mut vtab.filename);
|
||||
}
|
||||
"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) = Self::parse_byte(value) {
|
||||
vtab.delimiter = b;
|
||||
} else {
|
||||
return Err(Error::ModuleError(format!(
|
||||
"unrecognized argument to 'delimiter': {value}"
|
||||
)));
|
||||
}
|
||||
}
|
||||
"quote" => {
|
||||
if let Some(b) = Self::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);
|
||||
}
|
||||
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<CsvTabCursor<'_>> {
|
||||
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<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: &Filters<'_>,
|
||||
) -> 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 {
|
||||
#[cold]
|
||||
fn from(err: csv::Error) -> Self {
|
||||
Self::ModuleError(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
||||
|
||||
use crate::vtab::csvtab;
|
||||
use crate::{Connection, Result};
|
||||
use fallible_iterator::FallibleIterator;
|
||||
|
||||
#[cfg_attr(
|
||||
all(target_family = "wasm", target_os = "unknown"),
|
||||
ignore = "no filesystem on this platform"
|
||||
)]
|
||||
#[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<Vec<i32>> = s.query([])?.map(|row| row.get::<_, i32>(0)).collect();
|
||||
let sum = ids?.iter().sum::<i32>();
|
||||
assert_eq!(sum, 15);
|
||||
}
|
||||
db.execute_batch("DROP TABLE vtab")
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
all(target_family = "wasm", target_os = "unknown"),
|
||||
ignore = "no filesystem on this platform"
|
||||
)]
|
||||
#[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")
|
||||
}
|
||||
}
|
||||
1574
vendor/rusqlite/src/vtab/mod.rs
vendored
Normal file
1574
vendor/rusqlite/src/vtab/mod.rs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
344
vendor/rusqlite/src/vtab/series.rs
vendored
Normal file
344
vendor/rusqlite/src/vtab/series.rs
vendored
Normal file
@@ -0,0 +1,344 @@
|
||||
//! 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::ffi::c_int;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use crate::ffi;
|
||||
use crate::types::Type;
|
||||
use crate::vtab::{
|
||||
eponymous_only_module, Context, Filters, IndexConstraintOp, IndexInfo, VTab, VTabConfig,
|
||||
VTabConnection, VTabCursor,
|
||||
};
|
||||
use crate::{error::error_from_sqlite_code, Connection, Result};
|
||||
|
||||
/// Register the `generate_series` module.
|
||||
pub fn load_module(conn: &Connection) -> Result<()> {
|
||||
let aux: Option<()> = None;
|
||||
conn.create_module(
|
||||
c"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! {
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(C)]
|
||||
struct QueryPlanFlags: 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, Self)> {
|
||||
let vtab = Self {
|
||||
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<usize>; 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_from_sqlite_code(ffi::SQLITE_CONSTRAINT, None));
|
||||
}
|
||||
if idx_num.contains(QueryPlanFlags::BOTH) {
|
||||
// Both start= and stop= boundaries are available.
|
||||
#[expect(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<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,
|
||||
/// 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl VTabCursor for SeriesTabCursor<'_> {
|
||||
fn filter(&mut self, idx_num: c_int, _idx_str: Option<&str>, args: &Filters<'_>) -> 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::<Option<_>>(i)?.unwrap_or_default();
|
||||
i += 1;
|
||||
} else {
|
||||
self.min_value = 0;
|
||||
}
|
||||
if idx_num.contains(QueryPlanFlags::STOP) {
|
||||
self.max_value = args.get::<Option<_>>(i)?.unwrap_or_default();
|
||||
i += 1;
|
||||
} else {
|
||||
self.max_value = 0xffff_ffff;
|
||||
}
|
||||
if idx_num.contains(QueryPlanFlags::STEP) {
|
||||
self.step = args.get::<Option<_>>(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<i64> {
|
||||
Ok(self.row_id)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
||||
use wasm_bindgen_test::wasm_bindgen_test as 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<i32> = 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<i32> = 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<i32> = 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<i32> = s.query([])?.map(|r| r.get(0)).collect()?;
|
||||
let empty = Vec::<i32>::new();
|
||||
assert_eq!(empty, series);
|
||||
let mut s = db.prepare("SELECT * FROM generate_series(5,NULL)")?;
|
||||
let series: Vec<i32> = 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<i32> = 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<i32> = 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<i32> = 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<i32> = s.query([])?.map(|r| r.get(0)).collect()?;
|
||||
assert_eq!(empty, series);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
385
vendor/rusqlite/src/vtab/vtablog.rs
vendored
Normal file
385
vendor/rusqlite/src/vtab/vtablog.rs
vendored
Normal file
@@ -0,0 +1,385 @@
|
||||
//! Port of C [vtablog](http://www.sqlite.org/cgi/src/finfo?name=ext/misc/vtablog.c)
|
||||
use std::ffi::c_int;
|
||||
use std::marker::PhantomData;
|
||||
use std::str::FromStr;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
use fallible_iterator::FallibleIterator;
|
||||
|
||||
use crate::types::Type;
|
||||
use crate::vtab::{
|
||||
update_module_with_tx, Context, CreateVTab, Filters, IndexInfo, Inserts, TransactionVTab,
|
||||
UpdateVTab, Updates, VTab, VTabConnection, VTabCursor, VTabKind,
|
||||
};
|
||||
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(c"vtablog", update_module_with_tx::<VTabLog>(), aux)
|
||||
}
|
||||
|
||||
/// An instance of the vtablog virtual table
|
||||
#[repr(C)]
|
||||
struct VTabLog {
|
||||
/// Base class. Must be first
|
||||
base: ffi::sqlite3_vtab,
|
||||
/// Associated connection
|
||||
db: *mut ffi::sqlite3,
|
||||
/// 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(
|
||||
db: &mut VTabConnection,
|
||||
_: Option<&()>,
|
||||
args: &[&[u8]],
|
||||
is_create: bool,
|
||||
) -> Result<(String, Self)> {
|
||||
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.iter().map(|b| str::from_utf8(b)).collect::<Vec<_>>(),
|
||||
);
|
||||
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 = Self {
|
||||
base: ffi::sqlite3_vtab::default(),
|
||||
db: unsafe { db.handle() },
|
||||
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)> {
|
||||
Self::connect_create(db, aux, args, false)
|
||||
}
|
||||
|
||||
fn best_index(&self, info: &mut IndexInfo) -> Result<()> {
|
||||
println!(
|
||||
"VTabLog::best_index({}, num_of_order_by: {}, col_used: {}, distinct: {:?})",
|
||||
self.i_inst,
|
||||
info.num_of_order_by(),
|
||||
info.col_used(),
|
||||
info.distinct()
|
||||
);
|
||||
let mut in_constraint = None;
|
||||
for (i, constraint) in info.constraints().enumerate() {
|
||||
println!(
|
||||
" constraint[{}]: col={}, usable={}, op={:?}, rhs={:?}, in={:?}",
|
||||
i,
|
||||
constraint.column(),
|
||||
constraint.is_usable(),
|
||||
constraint.operator(),
|
||||
info.rhs_value(i),
|
||||
info.is_in_constraint(i),
|
||||
);
|
||||
if info.is_in_constraint(i)? {
|
||||
in_constraint = Some(i);
|
||||
}
|
||||
}
|
||||
info.set_estimated_cost(500.);
|
||||
info.set_estimated_rows(500);
|
||||
info.set_idx_str("idx");
|
||||
info.set_idx_cstr(c"idx");
|
||||
if let Some(idx) = in_constraint {
|
||||
info.set_in_constraint(idx, true)?;
|
||||
info.constraint_usage(idx).set_argv_index(1);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn open(&'vtab mut self) -> Result<Self::Cursor> {
|
||||
self.n_cursor += 1;
|
||||
println!(
|
||||
"VTabLog::open(tab={}, cursor={})",
|
||||
self.i_inst, self.n_cursor
|
||||
);
|
||||
Ok(VTabLogCursor {
|
||||
base: ffi::sqlite3_vtab_cursor::default(),
|
||||
i_cursor: self.n_cursor,
|
||||
row_id: 0,
|
||||
phantom: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl CreateVTab<'_> for VTabLog {
|
||||
const KIND: VTabKind = VTabKind::Default;
|
||||
|
||||
fn create(
|
||||
db: &mut VTabConnection,
|
||||
aux: Option<&Self::Aux>,
|
||||
args: &[&[u8]],
|
||||
) -> Result<(String, Self)> {
|
||||
Self::connect_create(db, aux, args, true)
|
||||
}
|
||||
|
||||
fn destroy(&self) -> Result<()> {
|
||||
println!("VTabLog::destroy({})", self.i_inst);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl UpdateVTab<'_> for VTabLog {
|
||||
fn delete(&mut self, arg: ValueRef<'_>) -> Result<()> {
|
||||
println!("VTabLog::delete({}, {arg:?})", self.i_inst);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn insert(&mut self, args: &Inserts<'_>) -> Result<i64> {
|
||||
println!(
|
||||
"VTabLog::insert({}, on_conflict:{:?}, {:?})",
|
||||
self.i_inst,
|
||||
unsafe { args.on_conflict(self.db) },
|
||||
args.iter().collect::<Vec<ValueRef<'_>>>()
|
||||
);
|
||||
Ok(self.n_row)
|
||||
}
|
||||
|
||||
fn update(&mut self, args: &Updates<'_>) -> Result<()> {
|
||||
println!(
|
||||
"VTabLog::update({}, on_conflict:{:?}, {:?})",
|
||||
self.i_inst,
|
||||
unsafe { args.on_conflict(self.db) },
|
||||
args.iter()
|
||||
.enumerate()
|
||||
.map(|(i, v)| (v, args.no_change(i)))
|
||||
.collect::<Vec<(ValueRef<'_>, bool)>>()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TransactionVTab<'_> for VTabLog {
|
||||
fn begin(&mut self) -> Result<()> {
|
||||
println!("VTabLog::begin({})", self.i_inst);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn sync(&mut self) -> Result<()> {
|
||||
println!("VTabLog::sync({})", self.i_inst);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn commit(&mut self) -> Result<()> {
|
||||
println!("VTabLog::commit({})", self.i_inst);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn rollback(&mut self) -> Result<()> {
|
||||
println!("VTabLog::rollback({})", self.i_inst);
|
||||
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, idx_num: c_int, idx_str: Option<&str>, args: &Filters<'_>) -> Result<()> {
|
||||
println!(
|
||||
"VTabLogCursor::filter(tab={}, cursor={}, idx_num={idx_num}, idx_str={idx_str:?}, args={})",
|
||||
self.vtab().i_inst,
|
||||
self.i_cursor,
|
||||
args.len()
|
||||
);
|
||||
for (i, arg) in args.iter().enumerate() {
|
||||
if arg.data_type() == Type::Null {
|
||||
println!(
|
||||
" in_values[{}]: {:?}",
|
||||
i,
|
||||
args.in_values(i)?.collect::<Vec<ValueRef>>()
|
||||
);
|
||||
}
|
||||
}
|
||||
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<()> {
|
||||
if ctx.no_change() {
|
||||
println!(
|
||||
"VTabLogCursor::column(tab={}, cursor={}, i={}): no change",
|
||||
self.vtab().i_inst,
|
||||
self.i_cursor,
|
||||
i,
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
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,
|
||||
);
|
||||
if i == 0 {
|
||||
println!(" db busy: {:?}", unsafe {
|
||||
ctx.get_connection().map(|c| c.is_busy())
|
||||
})
|
||||
}
|
||||
ctx.set_result(&value)
|
||||
}
|
||||
|
||||
fn rowid(&self) -> Result<i64> {
|
||||
println!(
|
||||
"VTabLogCursor::rowid(tab={}, cursor={}): {}",
|
||||
self.vtab().i_inst,
|
||||
self.i_cursor,
|
||||
self.row_id,
|
||||
);
|
||||
Ok(self.row_id)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
||||
use wasm_bindgen_test::wasm_bindgen_test as 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=3
|
||||
);",
|
||||
)?;
|
||||
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"],
|
||||
)?;
|
||||
db.query_one("SELECT b, c FROM log WHERE a = 'a1'", [], |_| Ok(0))?;
|
||||
db.execute("UPDATE log SET b = '' WHERE a IN (?1, ?2)", ["a1", "a2"])?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user