1use std::any::Any;
16
17use fsqlite_error::{FrankenError, Result};
18use fsqlite_types::SqliteValue;
19use fsqlite_types::cx::Cx;
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
27pub enum ConstraintOp {
28 Eq,
29 Gt,
30 Le,
31 Lt,
32 Ge,
33 Match,
34 Like,
35 Glob,
36 Regexp,
37 Ne,
38 IsNot,
39 IsNotNull,
40 IsNull,
41 Is,
42}
43
44#[derive(Debug, Clone)]
46pub struct IndexConstraint {
47 pub column: i32,
49 pub op: ConstraintOp,
51 pub usable: bool,
53}
54
55#[derive(Debug, Clone)]
57pub struct IndexOrderBy {
58 pub column: i32,
60 pub desc: bool,
62}
63
64#[derive(Debug, Clone, Default)]
66pub struct IndexConstraintUsage {
67 pub argv_index: i32,
70 pub omit: bool,
73}
74
75#[derive(Debug, Clone)]
82pub struct IndexInfo {
83 pub constraints: Vec<IndexConstraint>,
85 pub order_by: Vec<IndexOrderBy>,
87 pub constraint_usage: Vec<IndexConstraintUsage>,
89 pub idx_num: i32,
91 pub idx_str: Option<String>,
93 pub order_by_consumed: bool,
95 pub estimated_cost: f64,
97 pub estimated_rows: i64,
99}
100
101impl IndexInfo {
102 #[must_use]
104 pub fn new(constraints: Vec<IndexConstraint>, order_by: Vec<IndexOrderBy>) -> Self {
105 let usage_len = constraints.len();
106 Self {
107 constraints,
108 order_by,
109 constraint_usage: vec![IndexConstraintUsage::default(); usage_len],
110 idx_num: 0,
111 idx_str: None,
112 order_by_consumed: false,
113 estimated_cost: 1_000_000.0,
114 estimated_rows: 1_000_000,
115 }
116 }
117}
118
119#[derive(Debug, Default)]
128pub struct ColumnContext {
129 value: Option<SqliteValue>,
130}
131
132impl ColumnContext {
133 #[must_use]
135 pub fn new() -> Self {
136 Self { value: None }
137 }
138
139 pub fn set_value(&mut self, val: SqliteValue) {
141 self.value = Some(val);
142 }
143
144 pub fn take_value(&mut self) -> Option<SqliteValue> {
146 self.value.take()
147 }
148}
149
150#[derive(Debug, Clone)]
156pub struct TransactionalVtabState<S: Clone> {
157 base_snapshot: Option<S>,
158 savepoints: Vec<(i32, S)>,
159}
160
161impl<S: Clone> Default for TransactionalVtabState<S> {
162 fn default() -> Self {
163 Self {
164 base_snapshot: None,
165 savepoints: Vec::new(),
166 }
167 }
168}
169
170impl<S: Clone> TransactionalVtabState<S> {
171 pub fn begin(&mut self, snapshot: S) {
173 if self.base_snapshot.is_none() {
174 self.base_snapshot = Some(snapshot);
175 self.savepoints.clear();
176 }
177 }
178
179 pub fn commit(&mut self) {
181 self.base_snapshot = None;
182 self.savepoints.clear();
183 }
184
185 pub fn rollback(&mut self) -> Option<S> {
187 let snapshot = self.base_snapshot.take();
188 self.savepoints.clear();
189 snapshot
190 }
191
192 pub fn savepoint(&mut self, level: i32, snapshot: S) {
194 if self.base_snapshot.is_none() {
195 return;
196 }
197 self.savepoints.retain(|(existing, _)| *existing < level);
198 self.savepoints.push((level, snapshot));
199 }
200
201 pub fn release(&mut self, level: i32) {
203 if self.base_snapshot.is_none() {
204 return;
205 }
206 self.savepoints.retain(|(existing, _)| *existing < level);
207 }
208
209 pub fn rollback_to(&mut self, level: i32) -> Option<S> {
217 self.base_snapshot.as_ref()?;
218 let snapshot = self
219 .savepoints
220 .iter()
221 .rfind(|(existing, _)| *existing == level)
222 .map(|(_, snapshot)| snapshot.clone())
223 .or_else(|| self.base_snapshot.clone());
224 if snapshot.is_some() {
225 self.savepoints.retain(|(existing, _)| *existing <= level);
226 }
227 snapshot
228 }
229}
230
231#[allow(clippy::missing_errors_doc)]
250pub trait VirtualTable: Send + Sync {
251 type Cursor: VirtualTableCursor;
253
254 fn create(cx: &Cx, args: &[&str]) -> Result<Self>
259 where
260 Self: Sized,
261 {
262 Self::connect(cx, args)
263 }
264
265 fn connect(cx: &Cx, args: &[&str]) -> Result<Self>
267 where
268 Self: Sized;
269
270 fn best_index(&self, info: &mut IndexInfo) -> Result<()>;
272
273 fn open(&self) -> Result<Self::Cursor>;
275
276 fn disconnect(&mut self, _cx: &Cx) -> Result<()> {
278 Ok(())
279 }
280
281 fn destroy(&mut self, cx: &Cx) -> Result<()> {
285 self.disconnect(cx)
286 }
287
288 fn update(&mut self, _cx: &Cx, _args: &[SqliteValue]) -> Result<Option<i64>> {
298 Err(FrankenError::ReadOnly)
299 }
300
301 fn begin(&mut self, _cx: &Cx) -> Result<()> {
303 Ok(())
304 }
305
306 fn sync_txn(&mut self, _cx: &Cx) -> Result<()> {
308 Ok(())
309 }
310
311 fn commit(&mut self, _cx: &Cx) -> Result<()> {
313 Ok(())
314 }
315
316 fn rollback(&mut self, _cx: &Cx) -> Result<()> {
318 Ok(())
319 }
320
321 fn rename(&mut self, _cx: &Cx, _new_name: &str) -> Result<()> {
325 Err(FrankenError::Unsupported)
326 }
327
328 fn savepoint(&mut self, _cx: &Cx, _n: i32) -> Result<()> {
330 Ok(())
331 }
332
333 fn release(&mut self, _cx: &Cx, _n: i32) -> Result<()> {
335 Ok(())
336 }
337
338 fn rollback_to(&mut self, _cx: &Cx, _n: i32) -> Result<()> {
340 Ok(())
341 }
342}
343
344#[allow(clippy::missing_errors_doc)]
359pub trait VirtualTableCursor: Send {
360 fn filter(
362 &mut self,
363 cx: &Cx,
364 idx_num: i32,
365 idx_str: Option<&str>,
366 args: &[SqliteValue],
367 ) -> Result<()>;
368
369 fn next(&mut self, cx: &Cx) -> Result<()>;
371
372 fn eof(&self) -> bool;
374
375 fn column(&self, ctx: &mut ColumnContext, col: i32) -> Result<()>;
377
378 fn rowid(&self) -> Result<i64>;
380}
381
382#[allow(clippy::missing_errors_doc)]
392pub trait VtabModuleFactory: Send + Sync {
393 fn create(&self, cx: &Cx, args: &[&str]) -> Result<Box<dyn ErasedVtabInstance>>;
395
396 fn connect(&self, cx: &Cx, args: &[&str]) -> Result<Box<dyn ErasedVtabInstance>> {
398 self.create(cx, args)
399 }
400
401 fn column_info(&self, _args: &[&str]) -> Vec<(String, char)> {
403 Vec::new()
404 }
405}
406
407#[allow(clippy::missing_errors_doc)]
409pub trait ErasedVtabInstance: Send + Sync {
410 fn as_any(&self) -> &dyn Any;
412 fn as_any_mut(&mut self) -> &mut dyn Any;
414 fn open_cursor(&self) -> Result<Box<dyn ErasedVtabCursor>>;
416 fn update(&mut self, cx: &Cx, args: &[SqliteValue]) -> Result<Option<i64>>;
418 fn begin(&mut self, cx: &Cx) -> Result<()>;
420 fn sync_txn(&mut self, cx: &Cx) -> Result<()>;
422 fn commit(&mut self, cx: &Cx) -> Result<()>;
424 fn rollback(&mut self, cx: &Cx) -> Result<()>;
426 fn savepoint(&mut self, cx: &Cx, n: i32) -> Result<()>;
428 fn release(&mut self, cx: &Cx, n: i32) -> Result<()>;
430 fn rollback_to(&mut self, cx: &Cx, n: i32) -> Result<()>;
432 fn destroy(&mut self, cx: &Cx) -> Result<()>;
434 fn rename(&mut self, cx: &Cx, new_name: &str) -> Result<()>;
436 fn best_index(&self, info: &mut IndexInfo) -> Result<()>;
438}
439
440#[allow(clippy::missing_errors_doc)]
442pub trait ErasedVtabCursor: Send {
443 fn erased_filter(
445 &mut self,
446 cx: &Cx,
447 idx_num: i32,
448 idx_str: Option<&str>,
449 args: &[SqliteValue],
450 ) -> Result<()>;
451 fn erased_next(&mut self, cx: &Cx) -> Result<()>;
453 fn erased_eof(&self) -> bool;
455 fn erased_column(&self, ctx: &mut ColumnContext, col: i32) -> Result<()>;
457 fn erased_rowid(&self) -> Result<i64>;
459}
460
461impl<C: VirtualTableCursor + 'static> ErasedVtabCursor for C {
463 fn erased_filter(
464 &mut self,
465 cx: &Cx,
466 idx_num: i32,
467 idx_str: Option<&str>,
468 args: &[SqliteValue],
469 ) -> Result<()> {
470 VirtualTableCursor::filter(self, cx, idx_num, idx_str, args)
471 }
472 fn erased_next(&mut self, cx: &Cx) -> Result<()> {
473 VirtualTableCursor::next(self, cx)
474 }
475 fn erased_eof(&self) -> bool {
476 VirtualTableCursor::eof(self)
477 }
478 fn erased_column(&self, ctx: &mut ColumnContext, col: i32) -> Result<()> {
479 VirtualTableCursor::column(self, ctx, col)
480 }
481 fn erased_rowid(&self) -> Result<i64> {
482 VirtualTableCursor::rowid(self)
483 }
484}
485
486impl<T: VirtualTable + 'static> ErasedVtabInstance for T
488where
489 T::Cursor: 'static,
490{
491 fn as_any(&self) -> &dyn Any {
492 self
493 }
494
495 fn as_any_mut(&mut self) -> &mut dyn Any {
496 self
497 }
498
499 fn open_cursor(&self) -> Result<Box<dyn ErasedVtabCursor>> {
500 let cursor = VirtualTable::open(self)?;
501 Ok(Box::new(cursor))
502 }
503 fn update(&mut self, cx: &Cx, args: &[SqliteValue]) -> Result<Option<i64>> {
504 VirtualTable::update(self, cx, args)
505 }
506 fn begin(&mut self, cx: &Cx) -> Result<()> {
507 VirtualTable::begin(self, cx)
508 }
509 fn sync_txn(&mut self, cx: &Cx) -> Result<()> {
510 VirtualTable::sync_txn(self, cx)
511 }
512 fn commit(&mut self, cx: &Cx) -> Result<()> {
513 VirtualTable::commit(self, cx)
514 }
515 fn rollback(&mut self, cx: &Cx) -> Result<()> {
516 VirtualTable::rollback(self, cx)
517 }
518 fn savepoint(&mut self, cx: &Cx, n: i32) -> Result<()> {
519 VirtualTable::savepoint(self, cx, n)
520 }
521 fn release(&mut self, cx: &Cx, n: i32) -> Result<()> {
522 VirtualTable::release(self, cx, n)
523 }
524 fn rollback_to(&mut self, cx: &Cx, n: i32) -> Result<()> {
525 VirtualTable::rollback_to(self, cx, n)
526 }
527 fn destroy(&mut self, cx: &Cx) -> Result<()> {
528 VirtualTable::destroy(self, cx)
529 }
530 fn rename(&mut self, cx: &Cx, new_name: &str) -> Result<()> {
531 VirtualTable::rename(self, cx, new_name)
532 }
533 fn best_index(&self, info: &mut IndexInfo) -> Result<()> {
534 VirtualTable::best_index(self, info)
535 }
536}
537
538pub fn module_factory_from<T>() -> impl VtabModuleFactory
540where
541 T: VirtualTable + 'static,
542 T::Cursor: 'static,
543{
544 struct Factory<T: Send + Sync>(std::marker::PhantomData<T>);
545
546 impl<T: VirtualTable + 'static> VtabModuleFactory for Factory<T>
547 where
548 T::Cursor: 'static,
549 {
550 fn create(&self, cx: &Cx, args: &[&str]) -> Result<Box<dyn ErasedVtabInstance>> {
551 let vtab = T::create(cx, args)?;
552 Ok(Box::new(vtab))
553 }
554 fn connect(&self, cx: &Cx, args: &[&str]) -> Result<Box<dyn ErasedVtabInstance>> {
555 let vtab = T::connect(cx, args)?;
556 Ok(Box::new(vtab))
557 }
558 }
559
560 Factory::<T>(std::marker::PhantomData)
561}
562
563#[cfg(test)]
568#[allow(clippy::too_many_lines)]
569mod tests {
570 use super::*;
571
572 struct GenerateSeries {
575 destroyed: bool,
576 }
577
578 struct GenerateSeriesCursor {
579 start: i64,
580 stop: i64,
581 current: i64,
582 }
583
584 impl VirtualTable for GenerateSeries {
585 type Cursor = GenerateSeriesCursor;
586
587 fn connect(_cx: &Cx, _args: &[&str]) -> Result<Self> {
588 Ok(Self { destroyed: false })
589 }
590
591 fn best_index(&self, info: &mut IndexInfo) -> Result<()> {
592 info.estimated_cost = 10.0;
593 info.estimated_rows = 100;
594 info.idx_num = 1;
595
596 if !info.constraints.is_empty() && info.constraints[0].usable {
598 info.constraint_usage[0].argv_index = 1;
599 info.constraint_usage[0].omit = true;
600 }
601 Ok(())
602 }
603
604 fn open(&self) -> Result<GenerateSeriesCursor> {
605 Ok(GenerateSeriesCursor {
606 start: 0,
607 stop: 0,
608 current: 0,
609 })
610 }
611
612 fn destroy(&mut self, _cx: &Cx) -> Result<()> {
613 self.destroyed = true;
614 Ok(())
615 }
616 }
617
618 impl VirtualTableCursor for GenerateSeriesCursor {
619 fn filter(
620 &mut self,
621 _cx: &Cx,
622 _idx_num: i32,
623 _idx_str: Option<&str>,
624 args: &[SqliteValue],
625 ) -> Result<()> {
626 self.start = args.first().map_or(1, SqliteValue::to_integer);
627 self.stop = args.get(1).map_or(10, SqliteValue::to_integer);
628 self.current = self.start;
629 Ok(())
630 }
631
632 fn next(&mut self, _cx: &Cx) -> Result<()> {
633 self.current += 1;
634 Ok(())
635 }
636
637 fn eof(&self) -> bool {
638 self.current > self.stop
639 }
640
641 fn column(&self, ctx: &mut ColumnContext, _col: i32) -> Result<()> {
642 ctx.set_value(SqliteValue::Integer(self.current));
643 Ok(())
644 }
645
646 fn rowid(&self) -> Result<i64> {
647 Ok(self.current)
648 }
649 }
650
651 struct ReadOnlyVtab;
654
655 struct ReadOnlyCursor;
656
657 impl VirtualTable for ReadOnlyVtab {
658 type Cursor = ReadOnlyCursor;
659
660 fn connect(_cx: &Cx, _args: &[&str]) -> Result<Self> {
661 Ok(Self)
662 }
663
664 fn best_index(&self, _info: &mut IndexInfo) -> Result<()> {
665 Ok(())
666 }
667
668 fn open(&self) -> Result<ReadOnlyCursor> {
669 Ok(ReadOnlyCursor)
670 }
671 }
672
673 impl VirtualTableCursor for ReadOnlyCursor {
674 fn filter(
675 &mut self,
676 _cx: &Cx,
677 _idx_num: i32,
678 _idx_str: Option<&str>,
679 _args: &[SqliteValue],
680 ) -> Result<()> {
681 Ok(())
682 }
683
684 fn next(&mut self, _cx: &Cx) -> Result<()> {
685 Ok(())
686 }
687
688 fn eof(&self) -> bool {
689 true
690 }
691
692 fn column(&self, _ctx: &mut ColumnContext, _col: i32) -> Result<()> {
693 Ok(())
694 }
695
696 fn rowid(&self) -> Result<i64> {
697 Ok(0)
698 }
699 }
700
701 struct WritableVtab {
704 rows: Vec<(i64, Vec<SqliteValue>)>,
705 next_rowid: i64,
706 }
707
708 struct WritableCursor {
709 rows: Vec<(i64, Vec<SqliteValue>)>,
710 pos: usize,
711 }
712
713 impl VirtualTable for WritableVtab {
714 type Cursor = WritableCursor;
715
716 fn connect(_cx: &Cx, _args: &[&str]) -> Result<Self> {
717 Ok(Self {
718 rows: Vec::new(),
719 next_rowid: 1,
720 })
721 }
722
723 fn best_index(&self, _info: &mut IndexInfo) -> Result<()> {
724 Ok(())
725 }
726
727 fn open(&self) -> Result<WritableCursor> {
728 Ok(WritableCursor {
729 rows: self.rows.clone(),
730 pos: 0,
731 })
732 }
733
734 fn update(&mut self, _cx: &Cx, args: &[SqliteValue]) -> Result<Option<i64>> {
735 if args[0].is_null() {
737 let rowid = self.next_rowid;
739 self.next_rowid += 1;
740 let cols: Vec<SqliteValue> = args[2..].to_vec();
741 self.rows.push((rowid, cols));
742 return Ok(Some(rowid));
743 }
744 Ok(None)
745 }
746 }
747
748 impl VirtualTableCursor for WritableCursor {
749 fn filter(
750 &mut self,
751 _cx: &Cx,
752 _idx_num: i32,
753 _idx_str: Option<&str>,
754 _args: &[SqliteValue],
755 ) -> Result<()> {
756 self.pos = 0;
757 Ok(())
758 }
759
760 fn next(&mut self, _cx: &Cx) -> Result<()> {
761 self.pos += 1;
762 Ok(())
763 }
764
765 fn eof(&self) -> bool {
766 self.pos >= self.rows.len()
767 }
768
769 fn column(&self, ctx: &mut ColumnContext, col: i32) -> Result<()> {
770 #[allow(clippy::cast_sign_loss)]
771 let col_idx = col as usize;
772 if let Some((_, cols)) = self.rows.get(self.pos) {
773 if let Some(val) = cols.get(col_idx) {
774 ctx.set_value(val.clone());
775 }
776 }
777 Ok(())
778 }
779
780 fn rowid(&self) -> Result<i64> {
781 self.rows
782 .get(self.pos)
783 .map_or(Ok(0), |(rowid, _)| Ok(*rowid))
784 }
785 }
786
787 #[derive(Debug, Clone, PartialEq, Eq)]
788 struct HookSnapshot {
789 version: i32,
790 }
791
792 struct HookAwareVtab {
793 version: i32,
794 syncs: usize,
795 tx_state: TransactionalVtabState<HookSnapshot>,
796 }
797
798 impl VirtualTable for HookAwareVtab {
799 type Cursor = ReadOnlyCursor;
800
801 fn connect(_cx: &Cx, _args: &[&str]) -> Result<Self> {
802 Ok(Self {
803 version: 7,
804 syncs: 0,
805 tx_state: TransactionalVtabState::default(),
806 })
807 }
808
809 fn best_index(&self, _info: &mut IndexInfo) -> Result<()> {
810 Ok(())
811 }
812
813 fn open(&self) -> Result<Self::Cursor> {
814 Ok(ReadOnlyCursor)
815 }
816
817 fn begin(&mut self, _cx: &Cx) -> Result<()> {
818 self.tx_state.begin(HookSnapshot {
819 version: self.version,
820 });
821 Ok(())
822 }
823
824 fn sync_txn(&mut self, _cx: &Cx) -> Result<()> {
825 self.syncs += 1;
826 Ok(())
827 }
828
829 fn savepoint(&mut self, _cx: &Cx, n: i32) -> Result<()> {
830 self.tx_state.savepoint(
831 n,
832 HookSnapshot {
833 version: self.version,
834 },
835 );
836 Ok(())
837 }
838
839 fn release(&mut self, _cx: &Cx, n: i32) -> Result<()> {
840 self.tx_state.release(n);
841 Ok(())
842 }
843
844 fn rollback_to(&mut self, _cx: &Cx, n: i32) -> Result<()> {
845 if let Some(snapshot) = self.tx_state.rollback_to(n) {
846 self.version = snapshot.version;
847 }
848 Ok(())
849 }
850
851 fn commit(&mut self, _cx: &Cx) -> Result<()> {
852 self.tx_state.commit();
853 Ok(())
854 }
855
856 fn rollback(&mut self, _cx: &Cx) -> Result<()> {
857 if let Some(snapshot) = self.tx_state.rollback() {
858 self.version = snapshot.version;
859 }
860 Ok(())
861 }
862 }
863
864 #[test]
867 fn test_vtab_create_vs_connect() {
868 let cx = Cx::new();
869
870 let vtab = GenerateSeries::create(&cx, &[]).unwrap();
872 assert!(!vtab.destroyed);
873
874 let vtab2 = GenerateSeries::connect(&cx, &[]).unwrap();
876 assert!(!vtab2.destroyed);
877 }
878
879 #[test]
880 fn test_vtab_best_index_populates_info() {
881 let cx = Cx::new();
882 let vtab = GenerateSeries::connect(&cx, &[]).unwrap();
883
884 let mut info = IndexInfo::new(
885 vec![IndexConstraint {
886 column: 0,
887 op: ConstraintOp::Gt,
888 usable: true,
889 }],
890 vec![],
891 );
892
893 VirtualTable::best_index(&vtab, &mut info).unwrap();
894
895 assert_eq!(info.idx_num, 1);
896 assert!((info.estimated_cost - 10.0).abs() < f64::EPSILON);
897 assert_eq!(info.estimated_rows, 100);
898 assert_eq!(info.constraint_usage[0].argv_index, 1);
899 assert!(info.constraint_usage[0].omit);
900 }
901
902 #[test]
903 fn test_vtab_cursor_filter_next_eof() {
904 let cx = Cx::new();
905 let vtab = GenerateSeries::connect(&cx, &[]).unwrap();
906 let mut cursor = vtab.open().unwrap();
907
908 cursor
909 .filter(
910 &cx,
911 0,
912 None,
913 &[SqliteValue::Integer(1), SqliteValue::Integer(3)],
914 )
915 .unwrap();
916
917 let mut values = Vec::new();
918 while !cursor.eof() {
919 let mut ctx = ColumnContext::new();
920 cursor.column(&mut ctx, 0).unwrap();
921 let rowid = cursor.rowid().unwrap();
922 values.push((rowid, ctx.take_value().unwrap()));
923 cursor.next(&cx).unwrap();
924 }
925
926 assert_eq!(values.len(), 3);
927 assert_eq!(values[0], (1, SqliteValue::Integer(1)));
928 assert_eq!(values[1], (2, SqliteValue::Integer(2)));
929 assert_eq!(values[2], (3, SqliteValue::Integer(3)));
930 }
931
932 #[test]
933 fn test_vtab_update_insert() {
934 let cx = Cx::new();
935 let mut vtab = WritableVtab::connect(&cx, &[]).unwrap();
936
937 let result = VirtualTable::update(
940 &mut vtab,
941 &cx,
942 &[
943 SqliteValue::Null,
944 SqliteValue::Null,
945 SqliteValue::Text("hello".into()),
946 ],
947 )
948 .unwrap();
949
950 assert_eq!(result, Some(1));
951 assert_eq!(vtab.rows.len(), 1);
952 assert_eq!(vtab.rows[0].0, 1);
953 }
954
955 #[test]
956 fn test_vtab_update_readonly_default() {
957 let cx = Cx::new();
958 let mut vtab = ReadOnlyVtab::connect(&cx, &[]).unwrap();
959 let err = VirtualTable::update(&mut vtab, &cx, &[SqliteValue::Null]).unwrap_err();
960 assert!(matches!(err, FrankenError::ReadOnly));
961 }
962
963 #[test]
964 fn test_vtab_destroy_vs_disconnect() {
965 let cx = Cx::new();
966
967 let mut vtab = ReadOnlyVtab::connect(&cx, &[]).unwrap();
969 VirtualTable::disconnect(&mut vtab, &cx).unwrap();
970 VirtualTable::destroy(&mut vtab, &cx).unwrap();
971
972 let mut vtab = GenerateSeries::connect(&cx, &[]).unwrap();
974 assert!(!vtab.destroyed);
975 VirtualTable::destroy(&mut vtab, &cx).unwrap();
976 assert!(vtab.destroyed);
977 }
978
979 #[test]
980 fn test_vtab_cursor_send_but_not_sync() {
981 fn assert_send<T: Send>() {}
982 assert_send::<GenerateSeriesCursor>();
983
984 }
991
992 #[test]
993 fn test_column_context_lifecycle() {
994 let mut ctx = ColumnContext::new();
995 assert!(ctx.take_value().is_none());
996
997 ctx.set_value(SqliteValue::Integer(42));
998 assert_eq!(ctx.take_value(), Some(SqliteValue::Integer(42)));
999
1000 assert!(ctx.take_value().is_none());
1002 }
1003
1004 #[test]
1005 fn test_index_info_new() {
1006 let info = IndexInfo::new(
1007 vec![
1008 IndexConstraint {
1009 column: 0,
1010 op: ConstraintOp::Eq,
1011 usable: true,
1012 },
1013 IndexConstraint {
1014 column: 1,
1015 op: ConstraintOp::Gt,
1016 usable: false,
1017 },
1018 ],
1019 vec![IndexOrderBy {
1020 column: 0,
1021 desc: false,
1022 }],
1023 );
1024
1025 assert_eq!(info.constraints.len(), 2);
1026 assert_eq!(info.order_by.len(), 1);
1027 assert_eq!(info.constraint_usage.len(), 2);
1028 assert_eq!(info.idx_num, 0);
1029 assert!(info.idx_str.is_none());
1030 assert!(!info.order_by_consumed);
1031 }
1032
1033 #[test]
1034 fn test_transactional_vtab_state_tracks_savepoints() {
1035 let mut state = TransactionalVtabState::default();
1036
1037 state.begin(1_i32);
1038 state.savepoint(0, 2);
1039 state.savepoint(1, 3);
1040 assert_eq!(state.rollback_to(1), Some(3));
1041 state.release(1);
1042 assert_eq!(state.rollback(), Some(1));
1043 assert_eq!(state.rollback(), None);
1044 }
1045
1046 #[test]
1047 fn test_transactional_vtab_state_uses_base_for_late_enlistment() {
1048 let mut state = TransactionalVtabState::default();
1049
1050 state.begin(7_i32);
1051 state.savepoint(2, 9);
1052
1053 assert_eq!(state.rollback_to(1), Some(7));
1054 assert_eq!(state.rollback(), Some(7));
1055 }
1056
1057 #[test]
1058 fn test_erased_vtab_instance_forwards_transaction_hooks() {
1059 let cx = Cx::new();
1060 let mut erased: Box<dyn ErasedVtabInstance> =
1061 Box::new(HookAwareVtab::connect(&cx, &[]).unwrap());
1062
1063 erased.begin(&cx).unwrap();
1064 {
1065 let hook = erased
1066 .as_any_mut()
1067 .downcast_mut::<HookAwareVtab>()
1068 .expect("hook-aware vtab");
1069 hook.version = 9;
1070 }
1071 erased.savepoint(&cx, 0).unwrap();
1072 {
1073 let hook = erased
1074 .as_any_mut()
1075 .downcast_mut::<HookAwareVtab>()
1076 .expect("hook-aware vtab");
1077 hook.version = 11;
1078 }
1079 erased.rollback_to(&cx, 0).unwrap();
1080 erased.release(&cx, 0).unwrap();
1081 erased.sync_txn(&cx).unwrap();
1082 erased.rollback(&cx).unwrap();
1083
1084 let hook = erased
1085 .as_any_mut()
1086 .downcast_mut::<HookAwareVtab>()
1087 .expect("hook-aware vtab");
1088 assert_eq!(hook.version, 7);
1089 assert_eq!(hook.syncs, 1);
1090 }
1091}