rusqlite 0.40.0

Ergonomic wrapper for SQLite
Documentation
//! Array Virtual Table.
//!
//! Note: `rarray`, not `carray` is the name of the table valued function we
//! define.
//!
//! Port of [carray](https://sqlite.org/src/file/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::borrow::Cow;
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::{
    Context, Filters, IndexConstraintOp, IndexInfo, Module, VTab, VTabConnection, VTabCursor,
};
use crate::{Connection, Result};

// http://sqlite.org/bindptr.html

const ARRAY_TYPE: &CStr = c"rarray";
const MODULE_NAME: &CStr = ARRAY_TYPE;

/// 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<()> {
    const MODULE: Module<ArrayTab> = Module::eponymous_only_module();
    let aux: Option<()> = None;
    conn.create_module(MODULE_NAME, &MODULE, aux)
}

// Column numbers
// const CARRAY_COLUMN_VALUE : c_int = 0;
const CARRAY_COLUMN_POINTER: c_int = 1;

/// An instance of the Array virtual table
#[repr(C)]
struct ArrayTab {
    /// Base class. Must be first
    base: ffi::sqlite3_vtab,
}

unsafe impl<'vtab> VTab<'vtab> for ArrayTab {
    type Aux = ();
    type Cursor = ArrayTabCursor<'vtab>;

    fn connect(
        _: &mut VTabConnection,
        aux: Option<&()>,
        module_name: &[u8],
        _database_name: &[u8],
        table_name: &[u8],
        args: &[&[u8]],
    ) -> Result<(Cow<'static, CStr>, Self)> {
        debug_assert_eq!(aux, None);
        debug_assert_eq!(module_name, MODULE_NAME.to_bytes());
        debug_assert_eq!(table_name, MODULE_NAME.to_bytes());
        debug_assert_eq!(args.len(), 0);
        let vtab = Self {
            base: ffi::sqlite3_vtab::default(),
        };
        Ok((Cow::Borrowed(c"CREATE TABLE x(value,pointer hidden)"), vtab))
    }

    fn best_index(&self, info: &mut IndexInfo) -> Result<bool> {
        // 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(true)
    }

    fn open(&mut self) -> Result<ArrayTabCursor<'_>> {
        Ok(ArrayTabCursor::default())
    }
}

/// A cursor for the Array virtual table
#[derive(Default)]
#[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 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(all(test, not(miri)))]
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(())
    }
}