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};
const ARRAY_TYPE: &CStr = c"rarray";
const MODULE_NAME: &CStr = ARRAY_TYPE;
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))
}
}
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)
}
const CARRAY_COLUMN_POINTER: c_int = 1;
#[repr(C)]
struct ArrayTab {
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> {
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())
}
}
#[derive(Default)]
#[repr(C)]
struct ArrayTabCursor<'vtab> {
base: ffi::sqlite3_vtab_cursor,
row_id: i64,
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(())
}
}