rusqlite/vtab/
array.rs

1//! Array Virtual Table.
2//!
3//! Note: `rarray`, not `carray` is the name of the table valued function we
4//! define.
5//!
6//! Port of [carray](http://www.sqlite.org/cgi/src/finfo?name=ext/misc/carray.c)
7//! C extension: `https://www.sqlite.org/carray.html`
8//!
9//! # Example
10//!
11//! ```rust,no_run
12//! # use rusqlite::{types::Value, Connection, Result, params};
13//! # use std::rc::Rc;
14//! fn example(db: &Connection) -> Result<()> {
15//!     // Note: This should be done once (usually when opening the DB).
16//!     rusqlite::vtab::array::load_module(&db)?;
17//!     let v = [1i64, 2, 3, 4];
18//!     // Note: A `Rc<Vec<Value>>` must be used as the parameter.
19//!     let values = Rc::new(v.iter().copied().map(Value::from).collect::<Vec<Value>>());
20//!     let mut stmt = db.prepare("SELECT value from rarray(?1);")?;
21//!     let rows = stmt.query_map([values], |row| row.get::<_, i64>(0))?;
22//!     for value in rows {
23//!         println!("{}", value?);
24//!     }
25//!     Ok(())
26//! }
27//! ```
28
29use std::default::Default;
30use std::marker::PhantomData;
31use std::os::raw::{c_char, c_int, c_void};
32use std::rc::Rc;
33
34use crate::ffi;
35use crate::types::{ToSql, ToSqlOutput, Value};
36use crate::vtab::{
37    eponymous_only_module, Context, IndexConstraintOp, IndexInfo, VTab, VTabConnection, VTabCursor,
38    Values,
39};
40use crate::{Connection, Result};
41
42// http://sqlite.org/bindptr.html
43
44pub(crate) const ARRAY_TYPE: *const c_char = (b"rarray\0" as *const u8).cast::<c_char>();
45
46pub(crate) unsafe extern "C" fn free_array(p: *mut c_void) {
47    drop(Rc::from_raw(p as *const Vec<Value>));
48}
49
50/// Array parameter / pointer
51pub type Array = Rc<Vec<Value>>;
52
53impl ToSql for Array {
54    #[inline]
55    fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
56        Ok(ToSqlOutput::Array(self.clone()))
57    }
58}
59
60/// Register the "rarray" module.
61pub fn load_module(conn: &Connection) -> Result<()> {
62    let aux: Option<()> = None;
63    conn.create_module("rarray", eponymous_only_module::<ArrayTab>(), aux)
64}
65
66// Column numbers
67// const CARRAY_COLUMN_VALUE : c_int = 0;
68const CARRAY_COLUMN_POINTER: c_int = 1;
69
70/// An instance of the Array virtual table
71#[repr(C)]
72struct ArrayTab {
73    /// Base class. Must be first
74    base: ffi::sqlite3_vtab,
75}
76
77unsafe impl<'vtab> VTab<'vtab> for ArrayTab {
78    type Aux = ();
79    type Cursor = ArrayTabCursor<'vtab>;
80
81    fn connect(
82        _: &mut VTabConnection,
83        _aux: Option<&()>,
84        _args: &[&[u8]],
85    ) -> Result<(String, ArrayTab)> {
86        let vtab = ArrayTab {
87            base: ffi::sqlite3_vtab::default(),
88        };
89        Ok(("CREATE TABLE x(value,pointer hidden)".to_owned(), vtab))
90    }
91
92    fn best_index(&self, info: &mut IndexInfo) -> Result<()> {
93        // Index of the pointer= constraint
94        let mut ptr_idx = false;
95        for (constraint, mut constraint_usage) in info.constraints_and_usages() {
96            if !constraint.is_usable() {
97                continue;
98            }
99            if constraint.operator() != IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_EQ {
100                continue;
101            }
102            if let CARRAY_COLUMN_POINTER = constraint.column() {
103                ptr_idx = true;
104                constraint_usage.set_argv_index(1);
105                constraint_usage.set_omit(true);
106            }
107        }
108        if ptr_idx {
109            info.set_estimated_cost(1_f64);
110            info.set_estimated_rows(100);
111            info.set_idx_num(1);
112        } else {
113            info.set_estimated_cost(2_147_483_647_f64);
114            info.set_estimated_rows(2_147_483_647);
115            info.set_idx_num(0);
116        }
117        Ok(())
118    }
119
120    fn open(&mut self) -> Result<ArrayTabCursor<'_>> {
121        Ok(ArrayTabCursor::new())
122    }
123}
124
125/// A cursor for the Array virtual table
126#[repr(C)]
127struct ArrayTabCursor<'vtab> {
128    /// Base class. Must be first
129    base: ffi::sqlite3_vtab_cursor,
130    /// The rowid
131    row_id: i64,
132    /// Pointer to the array of values ("pointer")
133    ptr: Option<Array>,
134    phantom: PhantomData<&'vtab ArrayTab>,
135}
136
137impl ArrayTabCursor<'_> {
138    fn new<'vtab>() -> ArrayTabCursor<'vtab> {
139        ArrayTabCursor {
140            base: ffi::sqlite3_vtab_cursor::default(),
141            row_id: 0,
142            ptr: None,
143            phantom: PhantomData,
144        }
145    }
146
147    fn len(&self) -> i64 {
148        match self.ptr {
149            Some(ref a) => a.len() as i64,
150            _ => 0,
151        }
152    }
153}
154unsafe impl VTabCursor for ArrayTabCursor<'_> {
155    fn filter(&mut self, idx_num: c_int, _idx_str: Option<&str>, args: &Values<'_>) -> Result<()> {
156        if idx_num > 0 {
157            self.ptr = args.get_array(0);
158        } else {
159            self.ptr = None;
160        }
161        self.row_id = 1;
162        Ok(())
163    }
164
165    fn next(&mut self) -> Result<()> {
166        self.row_id += 1;
167        Ok(())
168    }
169
170    fn eof(&self) -> bool {
171        self.row_id > self.len()
172    }
173
174    fn column(&self, ctx: &mut Context, i: c_int) -> Result<()> {
175        match i {
176            CARRAY_COLUMN_POINTER => Ok(()),
177            _ => {
178                if let Some(ref array) = self.ptr {
179                    let value = &array[(self.row_id - 1) as usize];
180                    ctx.set_result(&value)
181                } else {
182                    Ok(())
183                }
184            }
185        }
186    }
187
188    fn rowid(&self) -> Result<i64> {
189        Ok(self.row_id)
190    }
191}
192
193#[cfg(test)]
194mod test {
195    use crate::types::Value;
196    use crate::vtab::array;
197    use crate::{Connection, Result};
198    use std::rc::Rc;
199
200    #[test]
201    fn test_array_module() -> Result<()> {
202        let db = Connection::open_in_memory()?;
203        array::load_module(&db)?;
204
205        let v = vec![1i64, 2, 3, 4];
206        let values: Vec<Value> = v.into_iter().map(Value::from).collect();
207        let ptr = Rc::new(values);
208        {
209            let mut stmt = db.prepare("SELECT value from rarray(?1);")?;
210
211            let rows = stmt.query_map([&ptr], |row| row.get::<_, i64>(0))?;
212            assert_eq!(2, Rc::strong_count(&ptr));
213            let mut count = 0;
214            for (i, value) in rows.enumerate() {
215                assert_eq!(i as i64, value? - 1);
216                count += 1;
217            }
218            assert_eq!(4, count);
219        }
220        assert_eq!(1, Rc::strong_count(&ptr));
221        Ok(())
222    }
223}