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