Skip to main content

rusqlite/vtab/
array.rs

1//! `feature = "array"` 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(?);")?;
21//!     let rows = stmt.query_map(params![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 as *const c_char;
45
46pub(crate) unsafe extern "C" fn free_array(p: *mut c_void) {
47    let _: Array = 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/// `feature = "array"` 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 = None;
95        for (i, constraint) in info.constraints().enumerate() {
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 = Some(i);
104            }
105        }
106        if let Some(ptr_idx) = ptr_idx {
107            {
108                let mut constraint_usage = info.constraint_usage(ptr_idx);
109                constraint_usage.set_argv_index(1);
110                constraint_usage.set_omit(true);
111            }
112            info.set_estimated_cost(1f64);
113            info.set_estimated_rows(100);
114            info.set_idx_num(1);
115        } else {
116            info.set_estimated_cost(2_147_483_647f64);
117            info.set_estimated_rows(2_147_483_647);
118            info.set_idx_num(0);
119        }
120        Ok(())
121    }
122
123    fn open(&self) -> Result<ArrayTabCursor<'_>> {
124        Ok(ArrayTabCursor::new())
125    }
126}
127
128/// A cursor for the Array virtual table
129#[repr(C)]
130struct ArrayTabCursor<'vtab> {
131    /// Base class. Must be first
132    base: ffi::sqlite3_vtab_cursor,
133    /// The rowid
134    row_id: i64,
135    /// Pointer to the array of values ("pointer")
136    ptr: Option<Array>,
137    phantom: PhantomData<&'vtab ArrayTab>,
138}
139
140impl ArrayTabCursor<'_> {
141    fn new<'vtab>() -> ArrayTabCursor<'vtab> {
142        ArrayTabCursor {
143            base: ffi::sqlite3_vtab_cursor::default(),
144            row_id: 0,
145            ptr: None,
146            phantom: PhantomData,
147        }
148    }
149
150    fn len(&self) -> i64 {
151        match self.ptr {
152            Some(ref a) => a.len() as i64,
153            _ => 0,
154        }
155    }
156}
157unsafe impl VTabCursor for ArrayTabCursor<'_> {
158    fn filter(&mut self, idx_num: c_int, _idx_str: Option<&str>, args: &Values<'_>) -> Result<()> {
159        if idx_num > 0 {
160            self.ptr = args.get_array(0)?;
161        } else {
162            self.ptr = None;
163        }
164        self.row_id = 1;
165        Ok(())
166    }
167
168    fn next(&mut self) -> Result<()> {
169        self.row_id += 1;
170        Ok(())
171    }
172
173    fn eof(&self) -> bool {
174        self.row_id > self.len()
175    }
176
177    fn column(&self, ctx: &mut Context, i: c_int) -> Result<()> {
178        match i {
179            CARRAY_COLUMN_POINTER => Ok(()),
180            _ => {
181                if let Some(ref array) = self.ptr {
182                    let value = &array[(self.row_id - 1) as usize];
183                    ctx.set_result(&value)
184                } else {
185                    Ok(())
186                }
187            }
188        }
189    }
190
191    fn rowid(&self) -> Result<i64> {
192        Ok(self.row_id)
193    }
194}
195
196#[cfg(test)]
197mod test {
198    use crate::types::Value;
199    use crate::vtab::array;
200    use crate::{Connection, Result};
201    use std::rc::Rc;
202
203    #[test]
204    fn test_array_module() -> Result<()> {
205        let db = Connection::open_in_memory()?;
206        array::load_module(&db)?;
207
208        let v = vec![1i64, 2, 3, 4];
209        let values: Vec<Value> = v.into_iter().map(Value::from).collect();
210        let ptr = Rc::new(values);
211        {
212            let mut stmt = db.prepare("SELECT value from rarray(?);")?;
213
214            let rows = stmt.query_map(&[&ptr], |row| row.get::<_, i64>(0))?;
215            assert_eq!(2, Rc::strong_count(&ptr));
216            let mut count = 0;
217            for (i, value) in rows.enumerate() {
218                assert_eq!(i as i64, value? - 1);
219                count += 1;
220            }
221            assert_eq!(4, count);
222        }
223        assert_eq!(1, Rc::strong_count(&ptr));
224        Ok(())
225    }
226}