Skip to main content

sqlite3_ext/vtab/
module.rs

1//! Wrappers for creating virtual tables.
2
3use super::*;
4use crate::{ffi, sqlite3_match_version, sqlite3_require_version, Connection};
5use sealed::sealed;
6use std::{ffi::CString, marker::PhantomData};
7
8union ModuleBytes {
9    bytes: [u8; std::mem::size_of::<ffi::sqlite3_module>()],
10    module: ffi::sqlite3_module,
11}
12
13// We use this empty module hack to avoid specifying all of the fields for the module here. In
14// general, we present the most modern API that we can, but use Result types to indicate when a
15// feature is not available due to the runtime SQLite version. When statically linking, we
16// emulate the same behavior, but we have to be a bit more cautious, since we are using the
17// libsqlite3_sys presented API, which might otherwise cause compilation errors.
18const EMPTY_MODULE: ffi::sqlite3_module = unsafe {
19    ModuleBytes {
20        bytes: [0_u8; std::mem::size_of::<ffi::sqlite3_module>()],
21    }
22    .module
23};
24
25#[cfg(modern_sqlite)]
26fn set_version(m: &mut ffi::sqlite3_module, val: i32) {
27    m.iVersion = std::cmp::max(m.iVersion, val);
28}
29
30/// Handle to the module and aux data, so that it can be properly dropped when the module is
31/// unloaded.
32pub(super) struct Handle<'vtab, T: VTab<'vtab>> {
33    pub vtab: ffi::sqlite3_module,
34    pub aux: T::Aux,
35}
36
37impl<'vtab, T: VTab<'vtab>> Handle<'vtab, T> {
38    pub unsafe fn from_ptr<'a>(ptr: *mut c_void) -> &'a Self {
39        &*(ptr as *mut Self)
40    }
41}
42
43/// A virtual table module.
44///
45/// You generally do not need to use this trait directly, see
46/// [sqlite_ext_vtab](::sqlite3_ext_macro::sqlite3_ext_vtab) for details on how to use this.
47#[sealed]
48pub trait Module<'vtab, T: VTab<'vtab> + 'vtab>
49where
50    Self: Sized,
51{
52    #[doc(hidden)]
53    fn module(&mut self) -> &mut ffi::sqlite3_module;
54
55    #[doc(hidden)]
56    fn with_update(mut self) -> Self
57    where
58        T: UpdateVTab<'vtab>,
59    {
60        self.module().xUpdate = Some(stubs::vtab_update::<T>);
61        self
62    }
63
64    #[doc(hidden)]
65    fn with_transactions(mut self) -> Self
66    where
67        T: TransactionVTab<'vtab>,
68    {
69        self.with_initial_transaction();
70        let m = self.module();
71        m.xBegin = Some(stubs::vtab_begin::<T>);
72        m.xSync = Some(stubs::vtab_sync::<T>);
73        m.xCommit = Some(stubs::vtab_commit::<T>);
74        sqlite3_match_version! {
75            3_007_007 => {
76                set_version(m, 2);
77                m.xRollback = Some(stubs::vtab_rollback::<T>);
78                m.xSavepoint = Some(stubs::vtab_savepoint::<T>);
79                m.xRelease = Some(stubs::vtab_release::<T>);
80                m.xRollbackTo = Some(stubs::vtab_rollback_to::<T>);
81            }
82            _ => (),
83        }
84        self
85    }
86
87    #[doc(hidden)]
88    fn with_initial_transaction(&mut self)
89    where
90        T: TransactionVTab<'vtab>;
91
92    #[doc(hidden)]
93    fn with_find_function(mut self) -> Self
94    where
95        T: FindFunctionVTab<'vtab>,
96    {
97        self.module().xFindFunction = Some(stubs::vtab_find_function::<T>);
98        self
99    }
100
101    #[doc(hidden)]
102    fn with_rename(mut self) -> Self
103    where
104        T: RenameVTab<'vtab>,
105    {
106        self.module().xRename = Some(stubs::vtab_rename::<T>);
107        self
108    }
109}
110
111macro_rules! module_base {
112    ($(#[$attr:meta])* $name:ident < $ty:ident > { $($extra:tt)* }) => {
113        $(#[$attr])*
114        pub struct $name<'vtab, T: VTab<'vtab>> {
115            base: ffi::sqlite3_module,
116            phantom: PhantomData<&'vtab T>,
117        }
118
119        #[sealed]
120        impl<'vtab, T: $ty<'vtab>> Module<'vtab, T> for $name<'vtab, T> {
121            fn module(&mut self) -> &mut ffi::sqlite3_module {
122                &mut self.base
123            }
124
125            $($extra)*
126        }
127    };
128}
129
130module_base!(
131    /// Declare a virtual table.
132    ///
133    /// See [sqlite_ext_vtab](::sqlite3_ext_macro::sqlite3_ext_vtab) for details on how to
134    /// use this. The struct implementing the virtual table must implement [VTab] and
135    /// [CreateVTab] at a minimum.
136    StandardModule<CreateVTab> {
137    fn with_initial_transaction(&mut self)
138    where
139        T: TransactionVTab<'vtab>,
140    {
141        // This is a standard table, so we need to override this.
142        self.base.xCreate = Some(stubs::vtab_create_transaction::<T>);
143    }
144});
145
146module_base!(
147    /// Describes an eponymous virtual table.
148    ///
149    /// For this module, the virtual table is available ambiently in the database, but
150    /// CREATE VIRTUAL TABLE can also be used to instantiate the table with alternative
151    /// parameters.
152    ///
153    /// See [sqlite_ext_vtab](::sqlite3_ext_macro::sqlite3_ext_vtab) for details on how to
154    /// use this. The struct implementing the virtual table must implement [VTab] at a
155    /// minimum. Any implementation of [CreateVTab] will be ignored.
156    EponymousModule<VTab> {
157    fn with_initial_transaction(&mut self)
158    where
159        T: TransactionVTab<'vtab>,
160    {
161        // This is an eponymous table, so we need to override both methods together.
162        self.base.xConnect = Some(stubs::vtab_connect_transaction::<T>);
163        self.base.xCreate = Some(stubs::vtab_connect_transaction::<T>);
164    }
165});
166
167module_base!(
168    /// Declare an eponymous-only virtual table.
169    ///
170    /// For this virtual table, CREATE VIRTUAL TABLE is forbidden, but the table is
171    /// ambiently available under the module name.
172    ///
173    /// This feature requires SQLite 3.9.0 or above. Older versions of SQLite do not
174    /// support eponymous virtual tables, meaning they require at least one CREATE VIRTUAL
175    /// TABLE statement to be used. If supporting these versions of SQLite is desired, you
176    /// can use [StandardModule] and return an error if there is an attempt to instantiate
177    /// the virtual table more than once.
178    ///
179    /// See [sqlite_ext_vtab](::sqlite3_ext_macro::sqlite3_ext_vtab) for details on how to
180    /// use this. The struct implementing the virtual table must implement [VTab] at a
181    /// minimum. Any implementation of [CreateVTab] will be ignored.
182    EponymousOnlyModule<VTab> {
183    fn with_initial_transaction(&mut self)
184    where
185        T: TransactionVTab<'vtab>,
186    {
187        // CREATE VIRTUAL TABLE will never be called on this table.
188    }
189});
190
191impl<'vtab, T: CreateVTab<'vtab>> StandardModule<'vtab, T> {
192    #[doc(hidden)]
193    pub fn new() -> Self {
194        #[cfg_attr(not(modern_sqlite), allow(unused_mut))]
195        let mut ret = StandardModule {
196            base: ffi::sqlite3_module {
197                xCreate: Some(stubs::vtab_create::<T>),
198                xConnect: Some(stubs::vtab_connect::<T>),
199                xBestIndex: Some(stubs::vtab_best_index::<T>),
200                xDisconnect: Some(stubs::vtab_disconnect::<T>),
201                xDestroy: Some(stubs::vtab_destroy::<T>),
202                xOpen: Some(stubs::vtab_open::<T>),
203                xClose: Some(stubs::vtab_close::<T>),
204                xFilter: Some(stubs::vtab_filter::<T>),
205                xNext: Some(stubs::vtab_next::<T>),
206                xEof: Some(stubs::vtab_eof::<T>),
207                xColumn: Some(stubs::vtab_column::<T>),
208                xRowid: Some(stubs::vtab_rowid::<T>),
209                ..EMPTY_MODULE
210            },
211            phantom: PhantomData,
212        };
213        sqlite3_match_version! {
214            3_026_000 => {
215                if T::SHADOW_NAMES.len() > 0 {
216                    set_version(&mut ret.base, 3);
217                    ret.base.xShadowName = Some(stubs::vtab_shadow_name::<T>);
218                }
219            }
220            _ => (),
221        }
222        ret
223    }
224}
225
226impl<'vtab, T: VTab<'vtab>> EponymousModule<'vtab, T> {
227    #[doc(hidden)]
228    pub fn new() -> Self {
229        EponymousModule {
230            base: ffi::sqlite3_module {
231                xCreate: Some(stubs::vtab_connect::<T>),
232                xConnect: Some(stubs::vtab_connect::<T>),
233                xBestIndex: Some(stubs::vtab_best_index::<T>),
234                xDisconnect: Some(stubs::vtab_disconnect::<T>),
235                xDestroy: Some(stubs::vtab_disconnect::<T>),
236                xOpen: Some(stubs::vtab_open::<T>),
237                xClose: Some(stubs::vtab_close::<T>),
238                xFilter: Some(stubs::vtab_filter::<T>),
239                xNext: Some(stubs::vtab_next::<T>),
240                xEof: Some(stubs::vtab_eof::<T>),
241                xColumn: Some(stubs::vtab_column::<T>),
242                xRowid: Some(stubs::vtab_rowid::<T>),
243                ..EMPTY_MODULE
244            },
245            phantom: PhantomData,
246        }
247    }
248}
249
250impl<'vtab, T: VTab<'vtab>> EponymousOnlyModule<'vtab, T> {
251    #[doc(hidden)]
252    pub fn new() -> Result<Self> {
253        sqlite3_require_version!(
254            3_009_000,
255            Ok(EponymousOnlyModule {
256                base: ffi::sqlite3_module {
257                    xConnect: Some(stubs::vtab_connect::<T>),
258                    xBestIndex: Some(stubs::vtab_best_index::<T>),
259                    xDisconnect: Some(stubs::vtab_disconnect::<T>),
260                    xDestroy: Some(stubs::vtab_disconnect::<T>),
261                    xOpen: Some(stubs::vtab_open::<T>),
262                    xClose: Some(stubs::vtab_close::<T>),
263                    xFilter: Some(stubs::vtab_filter::<T>),
264                    xNext: Some(stubs::vtab_next::<T>),
265                    xEof: Some(stubs::vtab_eof::<T>),
266                    xColumn: Some(stubs::vtab_column::<T>),
267                    xRowid: Some(stubs::vtab_rowid::<T>),
268                    ..EMPTY_MODULE
269                },
270                phantom: PhantomData,
271            })
272        )
273    }
274}
275
276impl Connection {
277    /// Register the provided virtual table module with this connection.
278    pub fn create_module<'db: 'vtab, 'vtab, T: VTab<'vtab> + 'vtab, M: Module<'vtab, T> + 'vtab>(
279        &'db self,
280        name: &str,
281        mut vtab: M,
282        aux: T::Aux,
283    ) -> Result<()>
284    where
285        T::Aux: 'db,
286    {
287        let name = CString::new(name).unwrap();
288        let vtab = vtab.module().clone();
289        let handle = Box::new(Handle::<'vtab, T> { vtab, aux });
290        let guard = self.lock();
291        Error::from_sqlite_desc(
292            unsafe {
293                ffi::sqlite3_create_module_v2(
294                    self.as_mut_ptr(),
295                    name.as_ptr() as _,
296                    &handle.vtab,
297                    Box::into_raw(handle) as _,
298                    Some(ffi::drop_boxed::<Handle<T>>),
299                )
300            },
301            guard,
302        )
303    }
304}