1 //! Create virtual tables.
2 //!
3 //! Follow these steps to create your own virtual table:
4 //! 1. Write implementation of [`VTab`] and [`VTabCursor`] traits.
5 //! 2. Create an instance of the [`Module`] structure specialized for [`VTab`]
6 //! impl. from step 1.
7 //! 3. Register your [`Module`] structure using [`Connection::create_module`].
8 //! 4. Run a `CREATE VIRTUAL TABLE` command that specifies the new module in the
9 //! `USING` clause.
10 //!
11 //! (See [SQLite doc](http://sqlite.org/vtab.html))
12 use std::borrow::Cow::{self, Borrowed, Owned};
13 use std::marker::PhantomData;
14 use std::marker::Sync;
15 use std::os::raw::{c_char, c_int, c_void};
16 use std::ptr;
17 use std::slice;
18
19 use crate::context::set_result;
20 use crate::error::error_from_sqlite_code;
21 use crate::ffi;
22 pub use crate::ffi::{sqlite3_vtab, sqlite3_vtab_cursor};
23 use crate::types::{FromSql, FromSqlError, ToSql, ValueRef};
24 use crate::{str_to_cstring, Connection, Error, InnerConnection, Result};
25
26 // let conn: Connection = ...;
27 // let mod: Module = ...; // VTab builder
28 // conn.create_module("module", mod);
29 //
30 // conn.execute("CREATE VIRTUAL TABLE foo USING module(...)");
31 // \-> Module::xcreate
32 // |-> let vtab: VTab = ...; // on the heap
33 // \-> conn.declare_vtab("CREATE TABLE foo (...)");
34 // conn = Connection::open(...);
35 // \-> Module::xconnect
36 // |-> let vtab: VTab = ...; // on the heap
37 // \-> conn.declare_vtab("CREATE TABLE foo (...)");
38 //
39 // conn.close();
40 // \-> vtab.xdisconnect
41 // conn.execute("DROP TABLE foo");
42 // \-> vtab.xDestroy
43 //
44 // let stmt = conn.prepare("SELECT ... FROM foo WHERE ...");
45 // \-> vtab.xbestindex
46 // stmt.query().next();
47 // \-> vtab.xopen
48 // |-> let cursor: VTabCursor = ...; // on the heap
49 // |-> cursor.xfilter or xnext
50 // |-> cursor.xeof
51 // \-> if not eof { cursor.column or xrowid } else { cursor.xclose }
52 //
53
54 // db: *mut ffi::sqlite3 => VTabConnection
55 // module: *const ffi::sqlite3_module => Module
56 // aux: *mut c_void => Module::Aux
57 // ffi::sqlite3_vtab => VTab
58 // ffi::sqlite3_vtab_cursor => VTabCursor
59
60 /// Virtual table module
61 ///
62 /// (See [SQLite doc](https://sqlite.org/c3ref/module.html))
63 #[repr(transparent)]
64 pub struct Module<'vtab, T: VTab<'vtab>> {
65 base: ffi::sqlite3_module,
66 phantom: PhantomData<&'vtab T>,
67 }
68
69 unsafe impl<'vtab, T: VTab<'vtab>> Send for Module<'vtab, T> {}
70 unsafe impl<'vtab, T: VTab<'vtab>> Sync for Module<'vtab, T> {}
71
72 union ModuleZeroHack {
73 bytes: [u8; std::mem::size_of::<ffi::sqlite3_module>()],
74 module: ffi::sqlite3_module,
75 }
76
77 // Used as a trailing initializer for sqlite3_module -- this way we avoid having
78 // the build fail if buildtime_bindgen is on. This is safe, as bindgen-generated
79 // structs are allowed to be zeroed.
80 const ZERO_MODULE: ffi::sqlite3_module = unsafe {
81 ModuleZeroHack {
82 bytes: [0_u8; std::mem::size_of::<ffi::sqlite3_module>()],
83 }
84 .module
85 };
86
87 /// Create a read-only virtual table implementation.
88 ///
89 /// Step 2 of [Creating New Virtual Table Implementations](https://sqlite.org/vtab.html#creating_new_virtual_table_implementations).
90 #[must_use]
read_only_module<'vtab, T: CreateVTab<'vtab>>() -> &'static Module<'vtab, T>91 pub fn read_only_module<'vtab, T: CreateVTab<'vtab>>() -> &'static Module<'vtab, T> {
92 // The xConnect and xCreate methods do the same thing, but they must be
93 // different so that the virtual table is not an eponymous virtual table.
94 #[allow(clippy::needless_update)]
95 &Module {
96 base: ffi::sqlite3_module {
97 // We don't use V3
98 iVersion: 2, // We don't use V2 or V3 features in read_only_module types
99 xCreate: Some(rust_create::<T>),
100 xConnect: Some(rust_connect::<T>),
101 xBestIndex: Some(rust_best_index::<T>),
102 xDisconnect: Some(rust_disconnect::<T>),
103 xDestroy: Some(rust_destroy::<T>),
104 xOpen: Some(rust_open::<T>),
105 xClose: Some(rust_close::<T::Cursor>),
106 xFilter: Some(rust_filter::<T::Cursor>),
107 xNext: Some(rust_next::<T::Cursor>),
108 xEof: Some(rust_eof::<T::Cursor>),
109 xColumn: Some(rust_column::<T::Cursor>),
110 xRowid: Some(rust_rowid::<T::Cursor>),
111 xUpdate: None,
112 xBegin: None,
113 xSync: None,
114 xCommit: None,
115 xRollback: None,
116 xFindFunction: None,
117 xRename: None,
118 xSavepoint: None,
119 xRelease: None,
120 xRollbackTo: None,
121 ..ZERO_MODULE
122 },
123 phantom: PhantomData::<&'vtab T>,
124 }
125 }
126
127 /// Create an eponymous only virtual table implementation.
128 ///
129 /// Step 2 of [Creating New Virtual Table Implementations](https://sqlite.org/vtab.html#creating_new_virtual_table_implementations).
130 #[must_use]
eponymous_only_module<'vtab, T: VTab<'vtab>>() -> &'static Module<'vtab, T>131 pub fn eponymous_only_module<'vtab, T: VTab<'vtab>>() -> &'static Module<'vtab, T> {
132 // A virtual table is eponymous if its xCreate method is the exact same function
133 // as the xConnect method For eponymous-only virtual tables, the xCreate
134 // method is NULL
135 #[allow(clippy::needless_update)]
136 &Module {
137 base: ffi::sqlite3_module {
138 // We don't use V3
139 iVersion: 2,
140 xCreate: None,
141 xConnect: Some(rust_connect::<T>),
142 xBestIndex: Some(rust_best_index::<T>),
143 xDisconnect: Some(rust_disconnect::<T>),
144 xDestroy: None,
145 xOpen: Some(rust_open::<T>),
146 xClose: Some(rust_close::<T::Cursor>),
147 xFilter: Some(rust_filter::<T::Cursor>),
148 xNext: Some(rust_next::<T::Cursor>),
149 xEof: Some(rust_eof::<T::Cursor>),
150 xColumn: Some(rust_column::<T::Cursor>),
151 xRowid: Some(rust_rowid::<T::Cursor>),
152 xUpdate: None,
153 xBegin: None,
154 xSync: None,
155 xCommit: None,
156 xRollback: None,
157 xFindFunction: None,
158 xRename: None,
159 xSavepoint: None,
160 xRelease: None,
161 xRollbackTo: None,
162 ..ZERO_MODULE
163 },
164 phantom: PhantomData::<&'vtab T>,
165 }
166 }
167
168 /// `feature = "vtab"`
169 pub struct VTabConnection(*mut ffi::sqlite3);
170
171 impl VTabConnection {
172 // TODO sqlite3_vtab_config (http://sqlite.org/c3ref/vtab_config.html)
173
174 // TODO sqlite3_vtab_on_conflict (http://sqlite.org/c3ref/vtab_on_conflict.html)
175
176 /// Get access to the underlying SQLite database connection handle.
177 ///
178 /// # Warning
179 ///
180 /// You should not need to use this function. If you do need to, please
181 /// [open an issue on the rusqlite repository](https://github.com/rusqlite/rusqlite/issues) and describe
182 /// your use case.
183 ///
184 /// # Safety
185 ///
186 /// This function is unsafe because it gives you raw access
187 /// to the SQLite connection, and what you do with it could impact the
188 /// safety of this `Connection`.
handle(&mut self) -> *mut ffi::sqlite3189 pub unsafe fn handle(&mut self) -> *mut ffi::sqlite3 {
190 self.0
191 }
192 }
193
194 /// Virtual table instance trait.
195 ///
196 /// # Safety
197 ///
198 /// The first item in a struct implementing `VTab` must be
199 /// `rusqlite::sqlite3_vtab`, and the struct must be `#[repr(C)]`.
200 ///
201 /// ```rust,ignore
202 /// #[repr(C)]
203 /// struct MyTab {
204 /// /// Base class. Must be first
205 /// base: rusqlite::vtab::sqlite3_vtab,
206 /// /* Virtual table implementations will typically add additional fields */
207 /// }
208 /// ```
209 ///
210 /// (See [SQLite doc](https://sqlite.org/c3ref/vtab.html))
211 pub unsafe trait VTab<'vtab>: Sized {
212 /// Client data passed to [`Connection::create_module`].
213 type Aux;
214 /// Specific cursor implementation
215 type Cursor: VTabCursor;
216
217 /// Establish a new connection to an existing virtual table.
218 ///
219 /// (See [SQLite doc](https://sqlite.org/vtab.html#the_xconnect_method))
connect( db: &mut VTabConnection, aux: Option<&Self::Aux>, args: &[&[u8]], ) -> Result<(String, Self)>220 fn connect(
221 db: &mut VTabConnection,
222 aux: Option<&Self::Aux>,
223 args: &[&[u8]],
224 ) -> Result<(String, Self)>;
225
226 /// Determine the best way to access the virtual table.
227 /// (See [SQLite doc](https://sqlite.org/vtab.html#the_xbestindex_method))
best_index(&self, info: &mut IndexInfo) -> Result<()>228 fn best_index(&self, info: &mut IndexInfo) -> Result<()>;
229
230 /// Create a new cursor used for accessing a virtual table.
231 /// (See [SQLite doc](https://sqlite.org/vtab.html#the_xopen_method))
open(&'vtab self) -> Result<Self::Cursor>232 fn open(&'vtab self) -> Result<Self::Cursor>;
233 }
234
235 /// Non-eponymous virtual table instance trait.
236 ///
237 /// (See [SQLite doc](https://sqlite.org/c3ref/vtab.html))
238 pub trait CreateVTab<'vtab>: VTab<'vtab> {
239 /// Create a new instance of a virtual table in response to a CREATE VIRTUAL
240 /// TABLE statement. The `db` parameter is a pointer to the SQLite
241 /// database connection that is executing the CREATE VIRTUAL TABLE
242 /// statement.
243 ///
244 /// Call [`connect`](VTab::connect) by default.
245 /// (See [SQLite doc](https://sqlite.org/vtab.html#the_xcreate_method))
create( db: &mut VTabConnection, aux: Option<&Self::Aux>, args: &[&[u8]], ) -> Result<(String, Self)>246 fn create(
247 db: &mut VTabConnection,
248 aux: Option<&Self::Aux>,
249 args: &[&[u8]],
250 ) -> Result<(String, Self)> {
251 Self::connect(db, aux, args)
252 }
253
254 /// Destroy the underlying table implementation. This method undoes the work
255 /// of [`create`](CreateVTab::create).
256 ///
257 /// Do nothing by default.
258 /// (See [SQLite doc](https://sqlite.org/vtab.html#the_xdestroy_method))
destroy(&self) -> Result<()>259 fn destroy(&self) -> Result<()> {
260 Ok(())
261 }
262 }
263
264 /// Index constraint operator.
265 /// See [Virtual Table Constraint Operator Codes](https://sqlite.org/c3ref/c_index_constraint_eq.html) for details.
266 #[derive(Debug, PartialEq)]
267 #[allow(non_snake_case, non_camel_case_types, missing_docs)]
268 #[allow(clippy::upper_case_acronyms)]
269 pub enum IndexConstraintOp {
270 SQLITE_INDEX_CONSTRAINT_EQ,
271 SQLITE_INDEX_CONSTRAINT_GT,
272 SQLITE_INDEX_CONSTRAINT_LE,
273 SQLITE_INDEX_CONSTRAINT_LT,
274 SQLITE_INDEX_CONSTRAINT_GE,
275 SQLITE_INDEX_CONSTRAINT_MATCH,
276 SQLITE_INDEX_CONSTRAINT_LIKE, // 3.10.0
277 SQLITE_INDEX_CONSTRAINT_GLOB, // 3.10.0
278 SQLITE_INDEX_CONSTRAINT_REGEXP, // 3.10.0
279 SQLITE_INDEX_CONSTRAINT_NE, // 3.21.0
280 SQLITE_INDEX_CONSTRAINT_ISNOT, // 3.21.0
281 SQLITE_INDEX_CONSTRAINT_ISNOTNULL, // 3.21.0
282 SQLITE_INDEX_CONSTRAINT_ISNULL, // 3.21.0
283 SQLITE_INDEX_CONSTRAINT_IS, // 3.21.0
284 SQLITE_INDEX_CONSTRAINT_LIMIT, // 3.38.0
285 SQLITE_INDEX_CONSTRAINT_OFFSET, // 3.38.0
286 SQLITE_INDEX_CONSTRAINT_FUNCTION(u8), // 3.25.0
287 }
288
289 impl From<u8> for IndexConstraintOp {
from(code: u8) -> IndexConstraintOp290 fn from(code: u8) -> IndexConstraintOp {
291 match code {
292 2 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_EQ,
293 4 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_GT,
294 8 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_LE,
295 16 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_LT,
296 32 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_GE,
297 64 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_MATCH,
298 65 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_LIKE,
299 66 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_GLOB,
300 67 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_REGEXP,
301 68 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_NE,
302 69 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_ISNOT,
303 70 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_ISNOTNULL,
304 71 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_ISNULL,
305 72 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_IS,
306 73 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_LIMIT,
307 74 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_OFFSET,
308 v => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_FUNCTION(v),
309 }
310 }
311 }
312
313 /// Pass information into and receive the reply from the
314 /// [`VTab::best_index`] method.
315 ///
316 /// (See [SQLite doc](http://sqlite.org/c3ref/index_info.html))
317 pub struct IndexInfo(*mut ffi::sqlite3_index_info);
318
319 impl IndexInfo {
320 /// Iterate on index constraint and its associated usage.
321 #[inline]
constraints_and_usages(&mut self) -> IndexConstraintAndUsageIter<'_>322 pub fn constraints_and_usages(&mut self) -> IndexConstraintAndUsageIter<'_> {
323 let constraints =
324 unsafe { slice::from_raw_parts((*self.0).aConstraint, (*self.0).nConstraint as usize) };
325 let constraint_usages = unsafe {
326 slice::from_raw_parts_mut((*self.0).aConstraintUsage, (*self.0).nConstraint as usize)
327 };
328 IndexConstraintAndUsageIter {
329 iter: constraints.iter().zip(constraint_usages.iter_mut()),
330 }
331 }
332
333 /// Record WHERE clause constraints.
334 #[inline]
335 #[must_use]
constraints(&self) -> IndexConstraintIter<'_>336 pub fn constraints(&self) -> IndexConstraintIter<'_> {
337 let constraints =
338 unsafe { slice::from_raw_parts((*self.0).aConstraint, (*self.0).nConstraint as usize) };
339 IndexConstraintIter {
340 iter: constraints.iter(),
341 }
342 }
343
344 /// Information about the ORDER BY clause.
345 #[inline]
346 #[must_use]
order_bys(&self) -> OrderByIter<'_>347 pub fn order_bys(&self) -> OrderByIter<'_> {
348 let order_bys =
349 unsafe { slice::from_raw_parts((*self.0).aOrderBy, (*self.0).nOrderBy as usize) };
350 OrderByIter {
351 iter: order_bys.iter(),
352 }
353 }
354
355 /// Number of terms in the ORDER BY clause
356 #[inline]
357 #[must_use]
num_of_order_by(&self) -> usize358 pub fn num_of_order_by(&self) -> usize {
359 unsafe { (*self.0).nOrderBy as usize }
360 }
361
362 /// Information about what parameters to pass to [`VTabCursor::filter`].
363 #[inline]
constraint_usage(&mut self, constraint_idx: usize) -> IndexConstraintUsage<'_>364 pub fn constraint_usage(&mut self, constraint_idx: usize) -> IndexConstraintUsage<'_> {
365 let constraint_usages = unsafe {
366 slice::from_raw_parts_mut((*self.0).aConstraintUsage, (*self.0).nConstraint as usize)
367 };
368 IndexConstraintUsage(&mut constraint_usages[constraint_idx])
369 }
370
371 /// Number used to identify the index
372 #[inline]
set_idx_num(&mut self, idx_num: c_int)373 pub fn set_idx_num(&mut self, idx_num: c_int) {
374 unsafe {
375 (*self.0).idxNum = idx_num;
376 }
377 }
378
379 /// True if output is already ordered
380 #[inline]
set_order_by_consumed(&mut self, order_by_consumed: bool)381 pub fn set_order_by_consumed(&mut self, order_by_consumed: bool) {
382 unsafe {
383 (*self.0).orderByConsumed = if order_by_consumed { 1 } else { 0 };
384 }
385 }
386
387 /// Estimated cost of using this index
388 #[inline]
set_estimated_cost(&mut self, estimated_ost: f64)389 pub fn set_estimated_cost(&mut self, estimated_ost: f64) {
390 unsafe {
391 (*self.0).estimatedCost = estimated_ost;
392 }
393 }
394
395 /// Estimated number of rows returned.
396 #[cfg(feature = "modern_sqlite")] // SQLite >= 3.8.2
397 #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))]
398 #[inline]
set_estimated_rows(&mut self, estimated_rows: i64)399 pub fn set_estimated_rows(&mut self, estimated_rows: i64) {
400 unsafe {
401 (*self.0).estimatedRows = estimated_rows;
402 }
403 }
404
405 // TODO idxFlags
406 // TODO colUsed
407
408 // TODO sqlite3_vtab_collation (http://sqlite.org/c3ref/vtab_collation.html)
409 }
410
411 /// Iterate on index constraint and its associated usage.
412 pub struct IndexConstraintAndUsageIter<'a> {
413 iter: std::iter::Zip<
414 slice::Iter<'a, ffi::sqlite3_index_constraint>,
415 slice::IterMut<'a, ffi::sqlite3_index_constraint_usage>,
416 >,
417 }
418
419 impl<'a> Iterator for IndexConstraintAndUsageIter<'a> {
420 type Item = (IndexConstraint<'a>, IndexConstraintUsage<'a>);
421
422 #[inline]
next(&mut self) -> Option<(IndexConstraint<'a>, IndexConstraintUsage<'a>)>423 fn next(&mut self) -> Option<(IndexConstraint<'a>, IndexConstraintUsage<'a>)> {
424 self.iter
425 .next()
426 .map(|raw| (IndexConstraint(raw.0), IndexConstraintUsage(raw.1)))
427 }
428
429 #[inline]
size_hint(&self) -> (usize, Option<usize>)430 fn size_hint(&self) -> (usize, Option<usize>) {
431 self.iter.size_hint()
432 }
433 }
434
435 /// `feature = "vtab"`
436 pub struct IndexConstraintIter<'a> {
437 iter: slice::Iter<'a, ffi::sqlite3_index_constraint>,
438 }
439
440 impl<'a> Iterator for IndexConstraintIter<'a> {
441 type Item = IndexConstraint<'a>;
442
443 #[inline]
next(&mut self) -> Option<IndexConstraint<'a>>444 fn next(&mut self) -> Option<IndexConstraint<'a>> {
445 self.iter.next().map(IndexConstraint)
446 }
447
448 #[inline]
size_hint(&self) -> (usize, Option<usize>)449 fn size_hint(&self) -> (usize, Option<usize>) {
450 self.iter.size_hint()
451 }
452 }
453
454 /// WHERE clause constraint.
455 pub struct IndexConstraint<'a>(&'a ffi::sqlite3_index_constraint);
456
457 impl IndexConstraint<'_> {
458 /// Column constrained. -1 for ROWID
459 #[inline]
460 #[must_use]
column(&self) -> c_int461 pub fn column(&self) -> c_int {
462 self.0.iColumn
463 }
464
465 /// Constraint operator
466 #[inline]
467 #[must_use]
operator(&self) -> IndexConstraintOp468 pub fn operator(&self) -> IndexConstraintOp {
469 IndexConstraintOp::from(self.0.op)
470 }
471
472 /// True if this constraint is usable
473 #[inline]
474 #[must_use]
is_usable(&self) -> bool475 pub fn is_usable(&self) -> bool {
476 self.0.usable != 0
477 }
478 }
479
480 /// Information about what parameters to pass to
481 /// [`VTabCursor::filter`].
482 pub struct IndexConstraintUsage<'a>(&'a mut ffi::sqlite3_index_constraint_usage);
483
484 impl IndexConstraintUsage<'_> {
485 /// if `argv_index` > 0, constraint is part of argv to
486 /// [`VTabCursor::filter`]
487 #[inline]
set_argv_index(&mut self, argv_index: c_int)488 pub fn set_argv_index(&mut self, argv_index: c_int) {
489 self.0.argvIndex = argv_index;
490 }
491
492 /// if `omit`, do not code a test for this constraint
493 #[inline]
set_omit(&mut self, omit: bool)494 pub fn set_omit(&mut self, omit: bool) {
495 self.0.omit = if omit { 1 } else { 0 };
496 }
497 }
498
499 /// `feature = "vtab"`
500 pub struct OrderByIter<'a> {
501 iter: slice::Iter<'a, ffi::sqlite3_index_info_sqlite3_index_orderby>,
502 }
503
504 impl<'a> Iterator for OrderByIter<'a> {
505 type Item = OrderBy<'a>;
506
507 #[inline]
next(&mut self) -> Option<OrderBy<'a>>508 fn next(&mut self) -> Option<OrderBy<'a>> {
509 self.iter.next().map(OrderBy)
510 }
511
512 #[inline]
size_hint(&self) -> (usize, Option<usize>)513 fn size_hint(&self) -> (usize, Option<usize>) {
514 self.iter.size_hint()
515 }
516 }
517
518 /// A column of the ORDER BY clause.
519 pub struct OrderBy<'a>(&'a ffi::sqlite3_index_info_sqlite3_index_orderby);
520
521 impl OrderBy<'_> {
522 /// Column number
523 #[inline]
524 #[must_use]
column(&self) -> c_int525 pub fn column(&self) -> c_int {
526 self.0.iColumn
527 }
528
529 /// True for DESC. False for ASC.
530 #[inline]
531 #[must_use]
is_order_by_desc(&self) -> bool532 pub fn is_order_by_desc(&self) -> bool {
533 self.0.desc != 0
534 }
535 }
536
537 /// Virtual table cursor trait.
538 ///
539 /// # Safety
540 ///
541 /// Implementations must be like:
542 /// ```rust,ignore
543 /// #[repr(C)]
544 /// struct MyTabCursor {
545 /// /// Base class. Must be first
546 /// base: rusqlite::vtab::sqlite3_vtab_cursor,
547 /// /* Virtual table implementations will typically add additional fields */
548 /// }
549 /// ```
550 ///
551 /// (See [SQLite doc](https://sqlite.org/c3ref/vtab_cursor.html))
552 pub unsafe trait VTabCursor: Sized {
553 /// Begin a search of a virtual table.
554 /// (See [SQLite doc](https://sqlite.org/vtab.html#the_xfilter_method))
filter(&mut self, idx_num: c_int, idx_str: Option<&str>, args: &Values<'_>) -> Result<()>555 fn filter(&mut self, idx_num: c_int, idx_str: Option<&str>, args: &Values<'_>) -> Result<()>;
556 /// Advance cursor to the next row of a result set initiated by
557 /// [`filter`](VTabCursor::filter). (See [SQLite doc](https://sqlite.org/vtab.html#the_xnext_method))
next(&mut self) -> Result<()>558 fn next(&mut self) -> Result<()>;
559 /// Must return `false` if the cursor currently points to a valid row of
560 /// data, or `true` otherwise.
561 /// (See [SQLite doc](https://sqlite.org/vtab.html#the_xeof_method))
eof(&self) -> bool562 fn eof(&self) -> bool;
563 /// Find the value for the `i`-th column of the current row.
564 /// `i` is zero-based so the first column is numbered 0.
565 /// May return its result back to SQLite using one of the specified `ctx`.
566 /// (See [SQLite doc](https://sqlite.org/vtab.html#the_xcolumn_method))
column(&self, ctx: &mut Context, i: c_int) -> Result<()>567 fn column(&self, ctx: &mut Context, i: c_int) -> Result<()>;
568 /// Return the rowid of row that the cursor is currently pointing at.
569 /// (See [SQLite doc](https://sqlite.org/vtab.html#the_xrowid_method))
rowid(&self) -> Result<i64>570 fn rowid(&self) -> Result<i64>;
571 }
572
573 /// Context is used by [`VTabCursor::column`] to specify the
574 /// cell value.
575 pub struct Context(*mut ffi::sqlite3_context);
576
577 impl Context {
578 /// Set current cell value
579 #[inline]
set_result<T: ToSql>(&mut self, value: &T) -> Result<()>580 pub fn set_result<T: ToSql>(&mut self, value: &T) -> Result<()> {
581 let t = value.to_sql()?;
582 unsafe { set_result(self.0, &t) };
583 Ok(())
584 }
585
586 // TODO sqlite3_vtab_nochange (http://sqlite.org/c3ref/vtab_nochange.html)
587 }
588
589 /// Wrapper to [`VTabCursor::filter`] arguments, the values
590 /// requested by [`VTab::best_index`].
591 pub struct Values<'a> {
592 args: &'a [*mut ffi::sqlite3_value],
593 }
594
595 impl Values<'_> {
596 /// Returns the number of values.
597 #[inline]
598 #[must_use]
len(&self) -> usize599 pub fn len(&self) -> usize {
600 self.args.len()
601 }
602
603 /// Returns `true` if there is no value.
604 #[inline]
605 #[must_use]
is_empty(&self) -> bool606 pub fn is_empty(&self) -> bool {
607 self.args.is_empty()
608 }
609
610 /// Returns value at `idx`
get<T: FromSql>(&self, idx: usize) -> Result<T>611 pub fn get<T: FromSql>(&self, idx: usize) -> Result<T> {
612 let arg = self.args[idx];
613 let value = unsafe { ValueRef::from_value(arg) };
614 FromSql::column_result(value).map_err(|err| match err {
615 FromSqlError::InvalidType => Error::InvalidFilterParameterType(idx, value.data_type()),
616 FromSqlError::Other(err) => {
617 Error::FromSqlConversionFailure(idx, value.data_type(), err)
618 }
619 FromSqlError::InvalidBlobSize { .. } => {
620 Error::FromSqlConversionFailure(idx, value.data_type(), Box::new(err))
621 }
622 FromSqlError::OutOfRange(i) => Error::IntegralValueOutOfRange(idx, i),
623 })
624 }
625
626 // `sqlite3_value_type` returns `SQLITE_NULL` for pointer.
627 // So it seems not possible to enhance `ValueRef::from_value`.
628 #[cfg(feature = "array")]
629 #[cfg_attr(docsrs, doc(cfg(feature = "array")))]
get_array(&self, idx: usize) -> Option<array::Array>630 fn get_array(&self, idx: usize) -> Option<array::Array> {
631 use crate::types::Value;
632 let arg = self.args[idx];
633 let ptr = unsafe { ffi::sqlite3_value_pointer(arg, array::ARRAY_TYPE) };
634 if ptr.is_null() {
635 None
636 } else {
637 Some(unsafe {
638 let rc = array::Array::from_raw(ptr as *const Vec<Value>);
639 let array = rc.clone();
640 array::Array::into_raw(rc); // don't consume it
641 array
642 })
643 }
644 }
645
646 /// Turns `Values` into an iterator.
647 #[inline]
648 #[must_use]
iter(&self) -> ValueIter<'_>649 pub fn iter(&self) -> ValueIter<'_> {
650 ValueIter {
651 iter: self.args.iter(),
652 }
653 }
654 }
655
656 impl<'a> IntoIterator for &'a Values<'a> {
657 type IntoIter = ValueIter<'a>;
658 type Item = ValueRef<'a>;
659
660 #[inline]
into_iter(self) -> ValueIter<'a>661 fn into_iter(self) -> ValueIter<'a> {
662 self.iter()
663 }
664 }
665
666 /// [`Values`] iterator.
667 pub struct ValueIter<'a> {
668 iter: slice::Iter<'a, *mut ffi::sqlite3_value>,
669 }
670
671 impl<'a> Iterator for ValueIter<'a> {
672 type Item = ValueRef<'a>;
673
674 #[inline]
next(&mut self) -> Option<ValueRef<'a>>675 fn next(&mut self) -> Option<ValueRef<'a>> {
676 self.iter
677 .next()
678 .map(|&raw| unsafe { ValueRef::from_value(raw) })
679 }
680
681 #[inline]
size_hint(&self) -> (usize, Option<usize>)682 fn size_hint(&self) -> (usize, Option<usize>) {
683 self.iter.size_hint()
684 }
685 }
686
687 impl Connection {
688 /// Register a virtual table implementation.
689 ///
690 /// Step 3 of [Creating New Virtual Table
691 /// Implementations](https://sqlite.org/vtab.html#creating_new_virtual_table_implementations).
692 #[inline]
create_module<'vtab, T: VTab<'vtab>>( &self, module_name: &str, module: &'static Module<'vtab, T>, aux: Option<T::Aux>, ) -> Result<()>693 pub fn create_module<'vtab, T: VTab<'vtab>>(
694 &self,
695 module_name: &str,
696 module: &'static Module<'vtab, T>,
697 aux: Option<T::Aux>,
698 ) -> Result<()> {
699 self.db.borrow_mut().create_module(module_name, module, aux)
700 }
701 }
702
703 impl InnerConnection {
create_module<'vtab, T: VTab<'vtab>>( &mut self, module_name: &str, module: &'static Module<'vtab, T>, aux: Option<T::Aux>, ) -> Result<()>704 fn create_module<'vtab, T: VTab<'vtab>>(
705 &mut self,
706 module_name: &str,
707 module: &'static Module<'vtab, T>,
708 aux: Option<T::Aux>,
709 ) -> Result<()> {
710 let c_name = str_to_cstring(module_name)?;
711 let r = match aux {
712 Some(aux) => {
713 let boxed_aux: *mut T::Aux = Box::into_raw(Box::new(aux));
714 unsafe {
715 ffi::sqlite3_create_module_v2(
716 self.db(),
717 c_name.as_ptr(),
718 &module.base,
719 boxed_aux.cast::<c_void>(),
720 Some(free_boxed_value::<T::Aux>),
721 )
722 }
723 }
724 None => unsafe {
725 ffi::sqlite3_create_module_v2(
726 self.db(),
727 c_name.as_ptr(),
728 &module.base,
729 ptr::null_mut(),
730 None,
731 )
732 },
733 };
734 self.decode_result(r)
735 }
736 }
737
738 /// Escape double-quote (`"`) character occurrences by
739 /// doubling them (`""`).
740 #[must_use]
escape_double_quote(identifier: &str) -> Cow<'_, str>741 pub fn escape_double_quote(identifier: &str) -> Cow<'_, str> {
742 if identifier.contains('"') {
743 // escape quote by doubling them
744 Owned(identifier.replace('"', "\"\""))
745 } else {
746 Borrowed(identifier)
747 }
748 }
749 /// Dequote string
750 #[must_use]
dequote(s: &str) -> &str751 pub fn dequote(s: &str) -> &str {
752 if s.len() < 2 {
753 return s;
754 }
755 match s.bytes().next() {
756 Some(b) if b == b'"' || b == b'\'' => match s.bytes().rev().next() {
757 Some(e) if e == b => &s[1..s.len() - 1],
758 _ => s,
759 },
760 _ => s,
761 }
762 }
763 /// The boolean can be one of:
764 /// ```text
765 /// 1 yes true on
766 /// 0 no false off
767 /// ```
768 #[must_use]
parse_boolean(s: &str) -> Option<bool>769 pub fn parse_boolean(s: &str) -> Option<bool> {
770 if s.eq_ignore_ascii_case("yes")
771 || s.eq_ignore_ascii_case("on")
772 || s.eq_ignore_ascii_case("true")
773 || s.eq("1")
774 {
775 Some(true)
776 } else if s.eq_ignore_ascii_case("no")
777 || s.eq_ignore_ascii_case("off")
778 || s.eq_ignore_ascii_case("false")
779 || s.eq("0")
780 {
781 Some(false)
782 } else {
783 None
784 }
785 }
786
787 // FIXME copy/paste from function.rs
free_boxed_value<T>(p: *mut c_void)788 unsafe extern "C" fn free_boxed_value<T>(p: *mut c_void) {
789 drop(Box::from_raw(p.cast::<T>()));
790 }
791
rust_create<'vtab, T>( db: *mut ffi::sqlite3, aux: *mut c_void, argc: c_int, argv: *const *const c_char, pp_vtab: *mut *mut ffi::sqlite3_vtab, err_msg: *mut *mut c_char, ) -> c_int where T: CreateVTab<'vtab>,792 unsafe extern "C" fn rust_create<'vtab, T>(
793 db: *mut ffi::sqlite3,
794 aux: *mut c_void,
795 argc: c_int,
796 argv: *const *const c_char,
797 pp_vtab: *mut *mut ffi::sqlite3_vtab,
798 err_msg: *mut *mut c_char,
799 ) -> c_int
800 where
801 T: CreateVTab<'vtab>,
802 {
803 use std::ffi::CStr;
804
805 let mut conn = VTabConnection(db);
806 let aux = aux.cast::<T::Aux>();
807 let args = slice::from_raw_parts(argv, argc as usize);
808 let vec = args
809 .iter()
810 .map(|&cs| CStr::from_ptr(cs).to_bytes()) // FIXME .to_str() -> Result<&str, Utf8Error>
811 .collect::<Vec<_>>();
812 match T::create(&mut conn, aux.as_ref(), &vec[..]) {
813 Ok((sql, vtab)) => match ::std::ffi::CString::new(sql) {
814 Ok(c_sql) => {
815 let rc = ffi::sqlite3_declare_vtab(db, c_sql.as_ptr());
816 if rc == ffi::SQLITE_OK {
817 let boxed_vtab: *mut T = Box::into_raw(Box::new(vtab));
818 *pp_vtab = boxed_vtab.cast::<ffi::sqlite3_vtab>();
819 ffi::SQLITE_OK
820 } else {
821 let err = error_from_sqlite_code(rc, None);
822 *err_msg = alloc(&err.to_string());
823 rc
824 }
825 }
826 Err(err) => {
827 *err_msg = alloc(&err.to_string());
828 ffi::SQLITE_ERROR
829 }
830 },
831 Err(Error::SqliteFailure(err, s)) => {
832 if let Some(s) = s {
833 *err_msg = alloc(&s);
834 }
835 err.extended_code
836 }
837 Err(err) => {
838 *err_msg = alloc(&err.to_string());
839 ffi::SQLITE_ERROR
840 }
841 }
842 }
843
rust_connect<'vtab, T>( db: *mut ffi::sqlite3, aux: *mut c_void, argc: c_int, argv: *const *const c_char, pp_vtab: *mut *mut ffi::sqlite3_vtab, err_msg: *mut *mut c_char, ) -> c_int where T: VTab<'vtab>,844 unsafe extern "C" fn rust_connect<'vtab, T>(
845 db: *mut ffi::sqlite3,
846 aux: *mut c_void,
847 argc: c_int,
848 argv: *const *const c_char,
849 pp_vtab: *mut *mut ffi::sqlite3_vtab,
850 err_msg: *mut *mut c_char,
851 ) -> c_int
852 where
853 T: VTab<'vtab>,
854 {
855 use std::ffi::CStr;
856
857 let mut conn = VTabConnection(db);
858 let aux = aux.cast::<T::Aux>();
859 let args = slice::from_raw_parts(argv, argc as usize);
860 let vec = args
861 .iter()
862 .map(|&cs| CStr::from_ptr(cs).to_bytes()) // FIXME .to_str() -> Result<&str, Utf8Error>
863 .collect::<Vec<_>>();
864 match T::connect(&mut conn, aux.as_ref(), &vec[..]) {
865 Ok((sql, vtab)) => match ::std::ffi::CString::new(sql) {
866 Ok(c_sql) => {
867 let rc = ffi::sqlite3_declare_vtab(db, c_sql.as_ptr());
868 if rc == ffi::SQLITE_OK {
869 let boxed_vtab: *mut T = Box::into_raw(Box::new(vtab));
870 *pp_vtab = boxed_vtab.cast::<ffi::sqlite3_vtab>();
871 ffi::SQLITE_OK
872 } else {
873 let err = error_from_sqlite_code(rc, None);
874 *err_msg = alloc(&err.to_string());
875 rc
876 }
877 }
878 Err(err) => {
879 *err_msg = alloc(&err.to_string());
880 ffi::SQLITE_ERROR
881 }
882 },
883 Err(Error::SqliteFailure(err, s)) => {
884 if let Some(s) = s {
885 *err_msg = alloc(&s);
886 }
887 err.extended_code
888 }
889 Err(err) => {
890 *err_msg = alloc(&err.to_string());
891 ffi::SQLITE_ERROR
892 }
893 }
894 }
895
rust_best_index<'vtab, T>( vtab: *mut ffi::sqlite3_vtab, info: *mut ffi::sqlite3_index_info, ) -> c_int where T: VTab<'vtab>,896 unsafe extern "C" fn rust_best_index<'vtab, T>(
897 vtab: *mut ffi::sqlite3_vtab,
898 info: *mut ffi::sqlite3_index_info,
899 ) -> c_int
900 where
901 T: VTab<'vtab>,
902 {
903 let vt = vtab.cast::<T>();
904 let mut idx_info = IndexInfo(info);
905 match (*vt).best_index(&mut idx_info) {
906 Ok(_) => ffi::SQLITE_OK,
907 Err(Error::SqliteFailure(err, s)) => {
908 if let Some(err_msg) = s {
909 set_err_msg(vtab, &err_msg);
910 }
911 err.extended_code
912 }
913 Err(err) => {
914 set_err_msg(vtab, &err.to_string());
915 ffi::SQLITE_ERROR
916 }
917 }
918 }
919
rust_disconnect<'vtab, T>(vtab: *mut ffi::sqlite3_vtab) -> c_int where T: VTab<'vtab>,920 unsafe extern "C" fn rust_disconnect<'vtab, T>(vtab: *mut ffi::sqlite3_vtab) -> c_int
921 where
922 T: VTab<'vtab>,
923 {
924 if vtab.is_null() {
925 return ffi::SQLITE_OK;
926 }
927 let vtab = vtab.cast::<T>();
928 drop(Box::from_raw(vtab));
929 ffi::SQLITE_OK
930 }
931
rust_destroy<'vtab, T>(vtab: *mut ffi::sqlite3_vtab) -> c_int where T: CreateVTab<'vtab>,932 unsafe extern "C" fn rust_destroy<'vtab, T>(vtab: *mut ffi::sqlite3_vtab) -> c_int
933 where
934 T: CreateVTab<'vtab>,
935 {
936 if vtab.is_null() {
937 return ffi::SQLITE_OK;
938 }
939 let vt = vtab.cast::<T>();
940 match (*vt).destroy() {
941 Ok(_) => {
942 drop(Box::from_raw(vt));
943 ffi::SQLITE_OK
944 }
945 Err(Error::SqliteFailure(err, s)) => {
946 if let Some(err_msg) = s {
947 set_err_msg(vtab, &err_msg);
948 }
949 err.extended_code
950 }
951 Err(err) => {
952 set_err_msg(vtab, &err.to_string());
953 ffi::SQLITE_ERROR
954 }
955 }
956 }
957
rust_open<'vtab, T: 'vtab>( vtab: *mut ffi::sqlite3_vtab, pp_cursor: *mut *mut ffi::sqlite3_vtab_cursor, ) -> c_int where T: VTab<'vtab>,958 unsafe extern "C" fn rust_open<'vtab, T: 'vtab>(
959 vtab: *mut ffi::sqlite3_vtab,
960 pp_cursor: *mut *mut ffi::sqlite3_vtab_cursor,
961 ) -> c_int
962 where
963 T: VTab<'vtab>,
964 {
965 let vt = vtab.cast::<T>();
966 match (*vt).open() {
967 Ok(cursor) => {
968 let boxed_cursor: *mut T::Cursor = Box::into_raw(Box::new(cursor));
969 *pp_cursor = boxed_cursor.cast::<ffi::sqlite3_vtab_cursor>();
970 ffi::SQLITE_OK
971 }
972 Err(Error::SqliteFailure(err, s)) => {
973 if let Some(err_msg) = s {
974 set_err_msg(vtab, &err_msg);
975 }
976 err.extended_code
977 }
978 Err(err) => {
979 set_err_msg(vtab, &err.to_string());
980 ffi::SQLITE_ERROR
981 }
982 }
983 }
984
rust_close<C>(cursor: *mut ffi::sqlite3_vtab_cursor) -> c_int where C: VTabCursor,985 unsafe extern "C" fn rust_close<C>(cursor: *mut ffi::sqlite3_vtab_cursor) -> c_int
986 where
987 C: VTabCursor,
988 {
989 let cr = cursor.cast::<C>();
990 drop(Box::from_raw(cr));
991 ffi::SQLITE_OK
992 }
993
rust_filter<C>( cursor: *mut ffi::sqlite3_vtab_cursor, idx_num: c_int, idx_str: *const c_char, argc: c_int, argv: *mut *mut ffi::sqlite3_value, ) -> c_int where C: VTabCursor,994 unsafe extern "C" fn rust_filter<C>(
995 cursor: *mut ffi::sqlite3_vtab_cursor,
996 idx_num: c_int,
997 idx_str: *const c_char,
998 argc: c_int,
999 argv: *mut *mut ffi::sqlite3_value,
1000 ) -> c_int
1001 where
1002 C: VTabCursor,
1003 {
1004 use std::ffi::CStr;
1005 use std::str;
1006 let idx_name = if idx_str.is_null() {
1007 None
1008 } else {
1009 let c_slice = CStr::from_ptr(idx_str).to_bytes();
1010 Some(str::from_utf8_unchecked(c_slice))
1011 };
1012 let args = slice::from_raw_parts_mut(argv, argc as usize);
1013 let values = Values { args };
1014 let cr = cursor as *mut C;
1015 cursor_error(cursor, (*cr).filter(idx_num, idx_name, &values))
1016 }
1017
rust_next<C>(cursor: *mut ffi::sqlite3_vtab_cursor) -> c_int where C: VTabCursor,1018 unsafe extern "C" fn rust_next<C>(cursor: *mut ffi::sqlite3_vtab_cursor) -> c_int
1019 where
1020 C: VTabCursor,
1021 {
1022 let cr = cursor as *mut C;
1023 cursor_error(cursor, (*cr).next())
1024 }
1025
rust_eof<C>(cursor: *mut ffi::sqlite3_vtab_cursor) -> c_int where C: VTabCursor,1026 unsafe extern "C" fn rust_eof<C>(cursor: *mut ffi::sqlite3_vtab_cursor) -> c_int
1027 where
1028 C: VTabCursor,
1029 {
1030 let cr = cursor.cast::<C>();
1031 (*cr).eof() as c_int
1032 }
1033
rust_column<C>( cursor: *mut ffi::sqlite3_vtab_cursor, ctx: *mut ffi::sqlite3_context, i: c_int, ) -> c_int where C: VTabCursor,1034 unsafe extern "C" fn rust_column<C>(
1035 cursor: *mut ffi::sqlite3_vtab_cursor,
1036 ctx: *mut ffi::sqlite3_context,
1037 i: c_int,
1038 ) -> c_int
1039 where
1040 C: VTabCursor,
1041 {
1042 let cr = cursor.cast::<C>();
1043 let mut ctxt = Context(ctx);
1044 result_error(ctx, (*cr).column(&mut ctxt, i))
1045 }
1046
rust_rowid<C>( cursor: *mut ffi::sqlite3_vtab_cursor, p_rowid: *mut ffi::sqlite3_int64, ) -> c_int where C: VTabCursor,1047 unsafe extern "C" fn rust_rowid<C>(
1048 cursor: *mut ffi::sqlite3_vtab_cursor,
1049 p_rowid: *mut ffi::sqlite3_int64,
1050 ) -> c_int
1051 where
1052 C: VTabCursor,
1053 {
1054 let cr = cursor.cast::<C>();
1055 match (*cr).rowid() {
1056 Ok(rowid) => {
1057 *p_rowid = rowid;
1058 ffi::SQLITE_OK
1059 }
1060 err => cursor_error(cursor, err),
1061 }
1062 }
1063
1064 /// Virtual table cursors can set an error message by assigning a string to
1065 /// `zErrMsg`.
1066 #[cold]
cursor_error<T>(cursor: *mut ffi::sqlite3_vtab_cursor, result: Result<T>) -> c_int1067 unsafe fn cursor_error<T>(cursor: *mut ffi::sqlite3_vtab_cursor, result: Result<T>) -> c_int {
1068 match result {
1069 Ok(_) => ffi::SQLITE_OK,
1070 Err(Error::SqliteFailure(err, s)) => {
1071 if let Some(err_msg) = s {
1072 set_err_msg((*cursor).pVtab, &err_msg);
1073 }
1074 err.extended_code
1075 }
1076 Err(err) => {
1077 set_err_msg((*cursor).pVtab, &err.to_string());
1078 ffi::SQLITE_ERROR
1079 }
1080 }
1081 }
1082
1083 /// Virtual tables methods can set an error message by assigning a string to
1084 /// `zErrMsg`.
1085 #[cold]
set_err_msg(vtab: *mut ffi::sqlite3_vtab, err_msg: &str)1086 unsafe fn set_err_msg(vtab: *mut ffi::sqlite3_vtab, err_msg: &str) {
1087 if !(*vtab).zErrMsg.is_null() {
1088 ffi::sqlite3_free((*vtab).zErrMsg.cast::<c_void>());
1089 }
1090 (*vtab).zErrMsg = alloc(err_msg);
1091 }
1092
1093 /// To raise an error, the `column` method should use this method to set the
1094 /// error message and return the error code.
1095 #[cold]
result_error<T>(ctx: *mut ffi::sqlite3_context, result: Result<T>) -> c_int1096 unsafe fn result_error<T>(ctx: *mut ffi::sqlite3_context, result: Result<T>) -> c_int {
1097 match result {
1098 Ok(_) => ffi::SQLITE_OK,
1099 Err(Error::SqliteFailure(err, s)) => {
1100 match err.extended_code {
1101 ffi::SQLITE_TOOBIG => {
1102 ffi::sqlite3_result_error_toobig(ctx);
1103 }
1104 ffi::SQLITE_NOMEM => {
1105 ffi::sqlite3_result_error_nomem(ctx);
1106 }
1107 code => {
1108 ffi::sqlite3_result_error_code(ctx, code);
1109 if let Some(Ok(cstr)) = s.map(|s| str_to_cstring(&s)) {
1110 ffi::sqlite3_result_error(ctx, cstr.as_ptr(), -1);
1111 }
1112 }
1113 };
1114 err.extended_code
1115 }
1116 Err(err) => {
1117 ffi::sqlite3_result_error_code(ctx, ffi::SQLITE_ERROR);
1118 if let Ok(cstr) = str_to_cstring(&err.to_string()) {
1119 ffi::sqlite3_result_error(ctx, cstr.as_ptr(), -1);
1120 }
1121 ffi::SQLITE_ERROR
1122 }
1123 }
1124 }
1125
1126 // Space to hold this string must be obtained
1127 // from an SQLite memory allocation function
alloc(s: &str) -> *mut c_char1128 fn alloc(s: &str) -> *mut c_char {
1129 crate::util::SqliteMallocString::from_str(s).into_raw()
1130 }
1131
1132 #[cfg(feature = "array")]
1133 #[cfg_attr(docsrs, doc(cfg(feature = "array")))]
1134 pub mod array;
1135 #[cfg(feature = "csvtab")]
1136 #[cfg_attr(docsrs, doc(cfg(feature = "csvtab")))]
1137 pub mod csvtab;
1138 #[cfg(feature = "series")]
1139 #[cfg_attr(docsrs, doc(cfg(feature = "series")))]
1140 pub mod series; // SQLite >= 3.9.0
1141
1142 #[cfg(test)]
1143 mod test {
1144 #[test]
test_dequote()1145 fn test_dequote() {
1146 assert_eq!("", super::dequote(""));
1147 assert_eq!("'", super::dequote("'"));
1148 assert_eq!("\"", super::dequote("\""));
1149 assert_eq!("'\"", super::dequote("'\""));
1150 assert_eq!("", super::dequote("''"));
1151 assert_eq!("", super::dequote("\"\""));
1152 assert_eq!("x", super::dequote("'x'"));
1153 assert_eq!("x", super::dequote("\"x\""));
1154 assert_eq!("x", super::dequote("x"));
1155 }
1156 #[test]
test_parse_boolean()1157 fn test_parse_boolean() {
1158 assert_eq!(None, super::parse_boolean(""));
1159 assert_eq!(Some(true), super::parse_boolean("1"));
1160 assert_eq!(Some(true), super::parse_boolean("yes"));
1161 assert_eq!(Some(true), super::parse_boolean("on"));
1162 assert_eq!(Some(true), super::parse_boolean("true"));
1163 assert_eq!(Some(false), super::parse_boolean("0"));
1164 assert_eq!(Some(false), super::parse_boolean("no"));
1165 assert_eq!(Some(false), super::parse_boolean("off"));
1166 assert_eq!(Some(false), super::parse_boolean("false"));
1167 }
1168 }
1169