1use crate::alloc::TryClone;
2use crate::error::io_error;
3#[cfg(any(clt_turso_tests, injected_yields))]
4use crate::mvcc::yield_points::{FailureInjector, YieldInjector};
5use crate::statement::StatementOrigin;
6use crate::storage::{journal_mode, pager::SavepointResult};
7use crate::sync::{
8 atomic::{
9 AtomicBool, AtomicI32, AtomicI64, AtomicIsize, AtomicU16, AtomicU64, AtomicU8, Ordering,
10 },
11 Arc, RwLock,
12};
13#[cfg(all(clt_turso_feature = "fs", clt_turso_feature = "conn_raw_api"))]
14use crate::types::{WalFrameInfo, WalState};
15#[cfg(clt_turso_feature = "fs")]
16use crate::util::{OpenMode, OpenOptions};
17#[cfg(all(clt_turso_feature = "fs", clt_turso_feature = "conn_raw_api"))]
18use crate::Page;
19use crate::{
20 ast, function,
21 io::{MemoryIO, IO},
22 progress::{ProgressHandler, ProgressHandlerCallback},
23 translate,
24 translate::collate::CollationSeq,
25 util::IOExt,
26 vdbe, AllViewsTxState, AtomicCipherMode, AtomicSyncMode, AtomicTempStore, BusyHandler,
27 BusyHandlerCallback, CaptureDataChangesInfo, CheckpointMode, CheckpointResult, CipherMode, Cmd,
28 Completion, ConnectionMetrics, Database, DatabaseCatalog, DatabaseOpts, Duration,
29 EncryptionKey, EncryptionOpts, IOResult, IndexMethod, LimboError, MvStore, OpenFlags, PageSize,
30 Pager, Parser, Program, QueryMode, QueryRunner, Result, Schema, Statement, SyncMode,
31 TransactionMode, Trigger, Value, VirtualTable, WalAutoActions,
32};
33use crate::{is_memory_like, turso_assert};
34use crate::{MAIN_DB_ID, TEMP_DB_ID};
35use arc_swap::ArcSwap;
36use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
37use smallvec::SmallVec;
38use std::cmp::Ordering as CmpOrdering;
39use std::fmt::Display;
40use std::ops::Deref;
41#[cfg(clt_turso_feature = "simulator")]
42use std::path::Path;
43#[cfg(not(target_family = "wasm"))]
44use tempfile::TempDir;
45use tracing::{instrument, Level};
46use turso_macros::{turso_assert_ne, AtomicEnum};
47
48#[cfg(clt_turso_feature = "simulator")]
49fn db_identity_for_testing(db_path: &Path) -> Result<(u32, u32)> {
50 let bytes =
51 std::fs::read(db_path).map_err(|e| io_error(e, "read db header for simulator testing"))?;
52 let db_header_size = crate::storage::sqlite3_ondisk::DatabaseHeader::SIZE;
53 if bytes.len() < db_header_size {
54 return Err(LimboError::InternalError(format!(
55 "database file is smaller than the header: got {}, need at least {}",
56 bytes.len(),
57 db_header_size
58 )));
59 }
60 let db_size_pages = u32::from_be_bytes(bytes[28..32].try_into().unwrap());
61 let crc = crc32c::crc32c(&bytes[..db_header_size]);
62 Ok((db_size_pages, crc))
63}
64
65#[derive(Clone, AtomicEnum, Copy, PartialEq, Eq, Debug)]
66pub(crate) enum TransactionState {
67 Write {
68 schema_did_change: bool,
69 },
70 Read,
71 PendingUpgrade {
75 has_read_txn: bool,
76 },
77 None,
78}
79
80pub(crate) struct TempDatabase {
81 pub(crate) db: Arc<Database>,
82 pub(crate) pager: Arc<Pager>,
83 #[cfg(not(target_family = "wasm"))]
84 _temp_dir: Option<TempDir>,
85}
86
87pub(crate) struct TempDbContext {
96 pub(crate) database: RwLock<Option<TempDatabase>>,
99 pub(crate) committed_schema: RwLock<Option<Arc<Schema>>>,
106 pub(crate) schema_did_change: AtomicBool,
111}
112
113impl TempDbContext {
114 pub(crate) fn new() -> Self {
115 Self {
116 database: RwLock::new(None),
117 committed_schema: RwLock::new(None),
118 schema_did_change: AtomicBool::new(false),
119 }
120 }
121}
122
123#[derive(Debug, Clone)]
124pub(crate) struct NamedSavepointFrame {
125 pub(crate) name: String,
126 pub(crate) starts_transaction: bool,
127 pub(crate) deferred_fk_violations: isize,
128 pub(crate) main_schema_snapshot: Arc<Schema>,
139 pub(crate) temp_schema_snapshot: Option<Arc<Schema>>,
144 pub(crate) staged_schema_snapshot: HashMap<usize, Arc<Schema>>,
148}
149
150pub(crate) struct RollbackFrameInfo {
153 pub(crate) main_schema_snapshot: Arc<Schema>,
154 pub(crate) temp_schema_snapshot: Option<Arc<Schema>>,
155 pub(crate) staged_schema_snapshot: HashMap<usize, Arc<Schema>>,
156}
157
158struct SchemaReparseGuard {
159 connection: Arc<Connection>,
160}
161
162impl Drop for SchemaReparseGuard {
163 fn drop(&mut self) {
164 self.connection
165 .schema_reparse_in_progress
166 .store(false, Ordering::SeqCst);
167 }
168}
169
170#[derive(Default)]
177pub enum ReparseSchemaState {
178 #[default]
179 Start,
180 Building(Box<ReparseSchemaInner>),
181}
182
183pub struct ReparseSchemaInner {
184 _guard: SchemaReparseGuard,
187 fresh: Schema,
188 tvfs: Vec<Arc<crate::vtab::VirtualTable>>,
191 preserved_sequences: Option<rustc_hash::FxHashMap<String, Arc<crate::schema::Sequence>>>,
195 phase: ReparsePhase,
196}
197
198enum ReparsePhase {
199 ParseSchema {
201 parse: Box<crate::util::ParseSchemaRowsState>,
202 },
203 PopulateSequences {
208 pending: Option<crate::alloc::Vec<(String, String)>>,
211 idx: usize,
213 stmt: Option<Box<Statement>>,
215 meta: Option<(i64, i64, i64, i64, bool)>,
217 seq: Option<crate::schema::Sequence>,
220 watermark_stmt: Option<Box<Statement>>,
222 watermark_row: Option<(i64, bool)>,
224 },
225 LoadTypes {
227 stmt: Box<Statement>,
228 type_rows: Vec<String>,
229 },
230 RefreshStats {
232 stats: crate::stats::RefreshAnalyzeStatsState,
233 },
234}
235
236#[cfg(not(clt_turso_feature = "fs"))]
237#[derive(Default)]
238pub(crate) enum AttachDatabaseState {
239 #[default]
240 Start,
241}
242
243#[cfg(clt_turso_feature = "fs")]
244#[derive(Default)]
245pub(crate) enum AttachDatabaseState {
246 #[default]
247 Start,
248 Init(Box<AttachDatabaseInitState>),
249 Bootstrap(Box<AttachDatabaseBootstrapState>),
250 Publish {
251 alias: String,
252 db: Arc<Database>,
253 pager: Arc<Pager>,
254 },
255 Done,
256}
257
258#[cfg(clt_turso_feature = "fs")]
259pub(crate) struct AttachDatabaseInitState {
260 alias: String,
261 reserved_space: Option<u8>,
262 db: Arc<Database>,
263 attached_is_fresh: bool,
264 encryption_key: Option<EncryptionKey>,
265 init_st: crate::InitState,
266}
267
268#[cfg(clt_turso_feature = "fs")]
269pub(crate) struct AttachDatabaseBootstrapState {
270 alias: String,
271 db: Arc<Database>,
272 pager: Arc<Pager>,
273 encryption_key: Option<EncryptionKey>,
274 bootstrap_conn: Option<Arc<Connection>>,
275 bootstrap_st: crate::mvcc::database::BootstrapState,
276}
277
278#[derive(Default)]
283pub enum LoadSequenceDescriptorsState {
284 #[default]
285 Start,
286 Reading {
287 pending: crate::alloc::Vec<(String, String)>,
289 idx: usize,
291 stmt: Option<Box<Statement>>,
293 meta: Option<(i64, i64, i64, i64, bool)>,
295 seq: Option<crate::schema::Sequence>,
298 watermark_stmt: Option<Box<Statement>>,
300 watermark_row: Option<(i64, bool)>,
302 },
303}
304
305#[derive(Default)]
308pub enum SyncAutoincrementState {
309 #[default]
310 Start,
311 ReadSeqRows {
313 stmt: Box<Statement>,
314 rows: Vec<(String, i64)>,
315 },
316 Process {
318 rows: Vec<(String, i64)>,
319 idx: usize,
320 sub: SyncRowStep,
321 },
322}
323
324#[derive(Default)]
326pub enum SyncRowStep {
327 #[default]
329 Start,
330 ReadMax {
332 backing_table_name: String,
333 stmt: Box<Statement>,
334 current_max: Option<i64>,
335 },
336 Upsert { stmt: Box<Statement> },
338}
339
340pub struct Connection {
346 pub(crate) db: Arc<Database>,
347 pub(crate) pager: ArcSwap<Pager>,
348 pub(crate) schema: RwLock<Arc<Schema>>,
349 pub(super) database_schemas: RwLock<HashMap<usize, Arc<Schema>>>,
352 pub(crate) auto_commit: AtomicBool,
354 pub(super) transaction_state: AtomicTransactionState,
355 pub(crate) poisoned_tx: AtomicBool,
359 pub(super) last_insert_rowid: AtomicI64,
360 pub(crate) changes: AtomicI64,
361 pub(crate) total_changes: AtomicI64,
362 pub(crate) syms: parking_lot::RwLock<SymbolTable>,
363 pub(super) _shared_cache: bool,
364 pub(super) cache_size: AtomicI32,
365 pub(super) page_size: AtomicU16,
368 pub(super) wal_auto_actions: AtomicU8,
376 #[cfg(clt_turso_feature = "conn_raw_api")]
382 pub(super) portable_logical_changes_enabled: AtomicBool,
383 #[cfg(clt_turso_feature = "conn_raw_api")]
384 pub(super) mvcc_log_metadata: RwLock<HashMap<String, String>>,
385 pub(super) capture_data_changes: RwLock<Option<CaptureDataChangesInfo>>,
386 pub(crate) cdc_transaction_id: AtomicI64,
389 pub(super) closed: AtomicBool,
390 pub(crate) temp: TempDbContext,
393 pub(super) attached_databases: RwLock<DatabaseCatalog>,
395 pub(super) query_only: AtomicBool,
396 pub(super) vdbe_trace: AtomicBool,
397 pub(super) dml_require_where: AtomicBool,
399 pub(super) dqs_dml: AtomicBool,
402 pub(super) full_column_names: AtomicBool,
404 pub(super) short_column_names: AtomicBool,
406 pub(super) enable_load_extension: AtomicBool,
408 pub(crate) sequence_inner_retries: AtomicU64,
419 pub(crate) mv_tx: RwLock<Option<(crate::mvcc::database::TxID, TransactionMode)>>,
420 pub(crate) attached_mv_txs:
423 RwLock<HashMap<usize, (crate::mvcc::database::TxID, TransactionMode)>>,
424 #[cfg(any(clt_turso_tests, injected_yields))]
425 pub(super) yield_injector: RwLock<Option<Arc<dyn YieldInjector>>>,
426 #[cfg(any(clt_turso_tests, injected_yields))]
427 pub(super) failure_injector: RwLock<Option<Arc<dyn FailureInjector>>>,
428 #[cfg(any(clt_turso_tests, injected_yields))]
429 pub(super) yield_instance_id_counter: AtomicU64,
430
431 pub(crate) view_transaction_states: AllViewsTxState,
434 pub metrics: RwLock<ConnectionMetrics>,
436 pub(super) nestedness: AtomicI32,
443 pub(super) compiling_triggers: RwLock<Vec<Arc<Trigger>>>,
445 pub(super) executing_triggers: RwLock<Vec<Arc<Trigger>>>,
448 pub(crate) encryption_key: RwLock<Option<EncryptionKey>>,
449 pub(super) encryption_cipher_mode: AtomicCipherMode,
450 pub(super) sync_mode: AtomicSyncMode,
451 pub(super) temp_store: AtomicTempStore,
452 pub(super) data_sync_retry: AtomicBool,
453 pub(super) busy_handler: RwLock<BusyHandler>,
456 pub(super) progress_handler: ProgressHandler,
458 pub(super) query_timeout_ms: AtomicU64,
461 pub(super) interrupt_requested: AtomicBool,
463 pub(super) is_mvcc_bootstrap_connection: AtomicBool,
465 pub(super) fk_pragma: AtomicBool,
467 pub(crate) fk_deferred_violations: AtomicIsize,
468 pub(crate) n_active_writes: AtomicI32,
473 pub(crate) n_active_root_statements: AtomicI32,
478 pub(super) check_constraints_pragma: AtomicBool,
480 pub(crate) vtab_txn_states: RwLock<HashSet<u64>>,
482 pub(crate) named_savepoints: RwLock<Vec<NamedSavepointFrame>>,
485 pub(crate) schema_reparse_in_progress: AtomicBool,
489 pub(crate) prepare_context_generation: AtomicU64,
496 pub(crate) sequence_currvals: RwLock<HashMap<String, i64>>,
498}
499
500crate::assert::assert_send_sync!(Connection);
503
504impl Drop for Connection {
505 fn drop(&mut self) {
506 if !self.is_closed() {
507 if let Some(mv_store) = self.db.get_mv_store().as_ref() {
512 if let Some(tx_id) = self.get_mv_tx_id() {
513 let pager = self.pager.load();
514 if mv_store.is_tx_rollbackable(tx_id) {
515 mv_store.rollback_tx(tx_id, pager.clone(), self, MAIN_DB_ID);
516 } else {
517 self.set_mv_tx(None);
518 }
519 pager.end_read_tx();
520 }
521 }
522 self.rollback_attached_mvcc_txs(false);
523
524 let pager = self.pager.load();
528 if let Some(wal) = &pager.wal {
529 if wal.holds_write_lock() {
530 wal.end_write_tx();
531 }
532 if wal.holds_read_lock() {
533 wal.end_read_tx();
534 }
535 }
536
537 self.with_all_attached_pagers_with_index(|attached_pagers| {
539 for (_, attached_pager) in attached_pagers {
540 if let Some(wal) = &attached_pager.wal {
541 if wal.holds_write_lock() {
542 wal.end_write_tx();
543 }
544 if wal.holds_read_lock() {
545 wal.end_read_tx();
546 }
547 }
548 }
549 });
550
551 self.db
553 .n_connections
554 .fetch_sub(1, crate::sync::atomic::Ordering::SeqCst);
555 }
556 }
557}
558
559impl Connection {
560 fn schema_reparse_guard(self: &Arc<Connection>) -> SchemaReparseGuard {
561 let was_reparsing = self.schema_reparse_in_progress.swap(true, Ordering::SeqCst);
562 turso_assert!(
563 !was_reparsing,
564 "schema reparse must not recurse on the same connection"
565 );
566 SchemaReparseGuard {
567 connection: self.clone(),
568 }
569 }
570
571 pub(crate) fn schema_reparse_in_progress(&self) -> bool {
572 self.schema_reparse_in_progress.load(Ordering::Acquire)
573 }
574
575 pub(crate) fn empty_temp_schema(&self) -> Arc<Schema> {
576 let mut schema = Schema::with_options(self.db.experimental_custom_types_enabled())
578 .expect("built-in type definitions are malformed");
579 schema.generated_columns_enabled = self.db.experimental_generated_columns_enabled();
580 Arc::new(schema)
581 }
582
583 fn make_temp_database_opts(&self) -> DatabaseOpts {
584 DatabaseOpts::new()
585 .with_views(self.db.experimental_views_enabled())
586 .with_custom_types(self.db.experimental_custom_types_enabled())
587 .with_index_method(self.db.experimental_index_method_enabled())
588 .with_vacuum(self.db.experimental_vacuum_enabled())
589 .with_generated_columns(self.db.experimental_generated_columns_enabled())
590 .with_without_rowid(self.db.experimental_without_rowid_enabled())
591 }
592
593 fn effective_temp_store(&self) -> crate::TempStore {
594 let temp_store = self.get_temp_store();
595 #[cfg(clt_turso_feature = "fs")]
596 {
597 temp_store
598 }
599 #[cfg(not(clt_turso_feature = "fs"))]
600 {
601 let _ = temp_store;
602 crate::TempStore::Memory
603 }
604 }
605
606 #[cfg(clt_turso_feature = "fs")]
607 fn create_temp_database(&self) -> Result<TempDatabase> {
608 let temp_store = self.effective_temp_store();
609 let db_opts = self.make_temp_database_opts();
610 let page_size = self.get_page_size();
611
612 if matches!(temp_store, crate::TempStore::Memory) {
613 let io: Arc<dyn IO> = Arc::new(MemoryIO::new());
614 let db = Database::open_file_with_flags(
615 io,
616 crate::util::MEMORY_PATH,
617 OpenFlags::Create,
618 db_opts,
619 None,
620 )?;
621 let pager = Arc::new(db._init(None)?);
622 pager.set_initial_page_size(page_size)?;
623 return Ok(TempDatabase {
624 db,
625 pager,
626 #[cfg(not(target_family = "wasm"))]
627 _temp_dir: None,
628 });
629 }
630
631 #[cfg(not(target_family = "wasm"))]
632 {
633 let temp_dir = self.create_tempdir()?;
634 let temp_path = temp_dir.path().join("tursodb-temp.db");
635 let temp_path_str = temp_path.to_str().ok_or_else(|| {
636 LimboError::InternalError("temp db path is not valid UTF-8".into())
637 })?;
638 let io = Database::io_for_path(temp_path_str)?;
644 let db = Database::open_file_with_flags(
645 io,
646 temp_path_str,
647 OpenFlags::Create,
648 db_opts,
649 None,
650 )?;
651 let pager = Arc::new(db._init(None)?);
652 pager.set_initial_page_size(page_size)?;
653 Ok(TempDatabase {
654 db,
655 pager,
656 _temp_dir: Some(temp_dir),
657 })
658 }
659
660 #[cfg(target_family = "wasm")]
661 {
662 let io: Arc<dyn IO> = Arc::new(MemoryIO::new());
663 let db = Database::open_file_with_flags(
664 io,
665 crate::util::MEMORY_PATH,
666 OpenFlags::Create,
667 db_opts,
668 None,
669 )?;
670 let pager = Arc::new(db._init(None)?);
671 pager.set_initial_page_size(page_size)?;
672 Ok(TempDatabase { db, pager })
673 }
674 }
675
676 #[cfg(not(clt_turso_feature = "fs"))]
677 fn create_temp_database(&self) -> Result<TempDatabase> {
678 let io: Arc<dyn IO> = Arc::new(MemoryIO::new());
679 let db = Database::open_file_with_flags(
680 io,
681 crate::util::MEMORY_PATH,
682 OpenFlags::Create,
683 self.make_temp_database_opts(),
684 None,
685 )?;
686 let pager = Arc::new(db._init(None)?);
687 pager.set_initial_page_size(self.get_page_size())?;
688 Ok(TempDatabase {
689 db,
690 pager,
691 #[cfg(not(target_family = "wasm"))]
692 _temp_dir: None,
693 })
694 }
695
696 pub(crate) fn ensure_temp_database(&self) -> Result<()> {
697 if self.temp.database.read().is_some() {
698 return Ok(());
699 }
700
701 let temp_db = self.create_temp_database()?;
702 let mut guard = self.temp.database.write();
703 if guard.is_none() {
704 *guard = Some(temp_db);
705 }
706 Ok(())
707 }
708
709 fn reset_temp_database(&self) {
715 if let Some(temp_db) = self.temp.database.write().take() {
716 temp_db.pager.rollback_attached();
717 }
718 *self.temp.committed_schema.write() = None;
719 self.temp.schema_did_change.store(false, Ordering::Release);
720 }
721
722 pub(crate) fn mark_temp_schema_did_change(&self) {
726 turso_assert!(
730 self.temp.database.read().is_some(),
731 "mark_temp_schema_did_change called without an initialized temp database"
732 );
733 self.temp.schema_did_change.store(true, Ordering::Release);
734 }
735
736 pub(crate) fn commit_temp_schema(&self) {
740 if !self.temp.schema_did_change.load(Ordering::Acquire) {
741 return;
742 }
743 let guard = self.temp.database.read();
748 turso_assert!(
749 guard.is_some(),
750 "commit_temp_schema: schema_did_change set but temp is uninitialized"
751 );
752 let snap = guard
753 .as_ref()
754 .expect("asserted above")
755 .db
756 .schema
757 .lock()
758 .clone();
759 drop(guard);
760 *self.temp.committed_schema.write() = Some(snap);
762 self.temp.schema_did_change.store(false, Ordering::Release);
763 }
764
765 pub(crate) fn rollback_temp_schema(&self) {
769 if !self.temp.schema_did_change.load(Ordering::Acquire) {
770 return;
771 }
772 let committed = self.temp.committed_schema.read().clone();
775 {
776 let guard = self.temp.database.read();
777 turso_assert!(
778 guard.is_some(),
779 "rollback_temp_schema: schema_did_change set but temp is uninitialized"
780 );
781 let temp_db = guard.as_ref().expect("asserted above");
782 match committed {
783 Some(snap) => *temp_db.db.schema.lock() = snap,
784 None => *temp_db.db.schema.lock() = self.empty_temp_schema(),
785 }
786 }
787 self.temp.schema_did_change.store(false, Ordering::Release);
788 self.bump_prepare_context_generation();
789 }
790
791 #[inline]
795 pub(crate) fn bump_prepare_context_generation(&self) {
796 self.prepare_context_generation
797 .fetch_add(1, Ordering::Release);
798 }
799
800 #[inline]
801 pub(crate) fn prepare_context_generation(&self) -> u64 {
802 self.prepare_context_generation.load(Ordering::Acquire)
803 }
804
805 pub fn is_nested_stmt(&self) -> bool {
807 self.nestedness.load(Ordering::SeqCst) > 0
808 }
809 pub fn start_nested(&self) {
811 self.nestedness.fetch_add(1, Ordering::SeqCst);
812 }
813 pub fn end_nested(&self) {
815 self.nestedness.fetch_add(-1, Ordering::SeqCst);
816 }
817
818 pub fn trigger_is_compiling(&self, trigger: &Arc<Trigger>) -> bool {
820 let compiling = self.compiling_triggers.read();
821 if let Some(trigger) = compiling.iter().find(|t| Arc::ptr_eq(t, trigger)) {
822 tracing::debug!("Trigger is already compiling: {}", trigger.name);
823 return true;
824 }
825 false
826 }
827
828 pub fn start_trigger_compilation(&self, trigger: Arc<Trigger>) {
829 tracing::debug!("Starting trigger compilation: {}", trigger.name);
830 self.compiling_triggers.write().push(trigger);
831 }
832
833 pub fn end_trigger_compilation(&self) {
834 tracing::debug!(
835 "Ending trigger compilation: {:?}",
836 self.compiling_triggers.read().last().map(|t| &t.name)
837 );
838 self.compiling_triggers.write().pop();
839 }
840
841 pub fn is_trigger_executing(&self, trigger: &Arc<Trigger>) -> bool {
843 let executing = self.executing_triggers.read();
844 if let Some(active_trigger) = executing.iter().find(|t| Arc::ptr_eq(t, trigger)) {
845 tracing::debug!("Trigger is already executing: {}", trigger.name);
846 debug_assert!(Arc::ptr_eq(active_trigger, trigger));
847 return true;
848 }
849 false
850 }
851
852 pub fn start_trigger_execution(&self, trigger: Arc<Trigger>) {
853 tracing::debug!("Starting trigger execution: {}", trigger.name);
854 self.executing_triggers.write().push(trigger);
855 }
856
857 pub fn end_trigger_execution(&self) {
858 tracing::debug!(
859 "Ending trigger execution: {:?}",
860 self.executing_triggers.read().last().map(|t| &t.name)
861 );
862 self.executing_triggers.write().pop();
863 }
864
865 fn should_retry_cross_process_schema_lookup(
866 self: &Arc<Connection>,
867 err: &LimboError,
868 ) -> Result<bool> {
869 let LimboError::ParseError(msg) = err else {
870 return Ok(false);
871 };
872 if !msg.contains("no such table") && !msg.contains("table not found") {
873 return Ok(false);
874 }
875 if self.get_tx_state() != TransactionState::None {
876 return Ok(false);
877 }
878 if self.db.shared_wal_coordination()?.is_none() {
879 return Ok(false);
880 }
881 self.maybe_reparse_schema()?;
882 Ok(true)
883 }
884
885 #[turso_macros::trace_stack]
886 fn compile_cmd(
887 self: &Arc<Connection>,
888 cmd: Cmd,
889 input: &str,
890 ) -> Result<(Program, Arc<Pager>, QueryMode)> {
891 self.maybe_update_schema();
892
893 let syms = self.syms.read();
894 let pager = self.pager.load().clone();
895 let mode = QueryMode::new(&cmd);
896 let (Cmd::Stmt(stmt) | Cmd::Explain(stmt) | Cmd::ExplainQueryPlan(stmt)) = cmd;
897 let schema = self.schema.read().clone();
898 match translate::translate(
899 &schema,
900 stmt,
901 pager.clone(),
902 self.clone(),
903 &syms,
904 mode,
905 input,
906 ) {
907 Ok(program) => Ok((program, pager, mode)),
908 Err(err) if self.should_retry_cross_process_schema_lookup(&err)? => {
909 drop(syms);
913 let cmd = {
914 crate::stack::trace_stack!("schema_retry_parse");
915 let mut parser = Parser::new(input.as_bytes());
916 let Some(cmd) = parser.next_cmd()? else {
917 return Err(err);
918 };
919 cmd
920 };
921 self.maybe_update_schema();
922 let syms = self.syms.read();
923 let pager = self.pager.load().clone();
924 let mode = QueryMode::new(&cmd);
925 let (Cmd::Stmt(stmt) | Cmd::Explain(stmt) | Cmd::ExplainQueryPlan(stmt)) = cmd;
926 let schema = self.schema.read().clone();
927 translate::translate(
928 &schema,
929 stmt,
930 pager.clone(),
931 self.clone(),
932 &syms,
933 mode,
934 input,
935 )
936 .map(|program| (program, pager, mode))
937 }
938 Err(err) => Err(err),
939 }
940 }
941
942 pub fn prepare(self: &Arc<Connection>, sql: impl AsRef<str>) -> Result<Statement> {
943 self._prepare(sql)
944 }
945
946 pub(crate) fn prepare_internal(
947 self: &Arc<Connection>,
948 sql: impl AsRef<str>,
949 ) -> Result<Statement> {
950 self.prepare_with_origin(sql, StatementOrigin::InternalHelper)
951 }
952
953 #[instrument(skip_all, level = Level::DEBUG)]
954 pub fn _prepare(self: &Arc<Connection>, sql: impl AsRef<str>) -> Result<Statement> {
955 self.prepare_with_origin(sql, StatementOrigin::Root)
956 }
957
958 #[turso_macros::trace_stack]
959 fn prepare_with_origin(
960 self: &Arc<Connection>,
961 sql: impl AsRef<str>,
962 origin: StatementOrigin,
963 ) -> Result<Statement> {
964 if self.is_closed() {
965 return Err(LimboError::InternalError("Connection closed".to_string()));
966 }
967 if sql.as_ref().is_empty() {
968 return Err(LimboError::InvalidArgument(
969 "The supplied SQL string contains no statements".to_string(),
970 ));
971 }
972
973 let needs_nested_guard = origin.needs_nested_guard();
974 if needs_nested_guard {
975 self.start_nested();
976 }
977 let result = (|| {
978 let sql = sql.as_ref();
979 tracing::debug!("Preparing: {}", sql);
980 let (cmd, byte_offset_end) = {
981 crate::stack::trace_stack!("parse");
982 let mut parser = Parser::new(sql.as_bytes());
983 let cmd = match parser.next_cmd()? {
984 Some(cmd) => cmd,
985 None => {
986 return Err(LimboError::InvalidArgument(
987 "The supplied SQL string contains no statements".to_string(),
988 ));
989 }
990 };
991 (cmd, parser.offset())
992 };
993 let input = str::from_utf8(&sql.as_bytes()[..byte_offset_end])
994 .unwrap()
995 .trim();
996 let (program, pager, mode) = self.compile_cmd(cmd, input)?;
997
998 Ok(Statement::new_with_origin(
999 program,
1000 pager,
1001 mode,
1002 byte_offset_end,
1003 origin,
1004 needs_nested_guard,
1005 ))
1006 })();
1007 if result.is_err() && needs_nested_guard {
1008 self.end_nested();
1009 }
1010 result
1011 }
1012
1013 pub fn prepare_stmt(self: &Arc<Connection>, stmt: ast::Stmt) -> Result<Statement> {
1016 self.prepare_stmt_with_origin(stmt, StatementOrigin::Root)
1017 }
1018
1019 #[turso_macros::trace_stack]
1020 fn prepare_stmt_with_origin(
1021 self: &Arc<Connection>,
1022 stmt: ast::Stmt,
1023 origin: StatementOrigin,
1024 ) -> Result<Statement> {
1025 if self.is_closed() {
1026 return Err(LimboError::InternalError("Connection closed".to_string()));
1027 }
1028 let needs_nested_guard = origin.needs_nested_guard();
1029 if needs_nested_guard {
1030 self.start_nested();
1031 }
1032 let result = (|| {
1033 self.maybe_update_schema();
1034 let syms = self.syms.read();
1035 let pager = self.pager.load().clone();
1036 let mode = QueryMode::Normal;
1037 let schema = self.schema.read().clone();
1038 let program = translate::translate(
1039 &schema,
1040 stmt,
1041 pager.clone(),
1042 self.clone(),
1043 &syms,
1044 mode,
1045 "<ast>", )?;
1047 Ok(Statement::new_with_origin(
1048 program,
1049 pager,
1050 mode,
1051 0,
1052 origin,
1053 needs_nested_guard,
1054 ))
1055 })();
1056 if result.is_err() && needs_nested_guard {
1057 self.end_nested();
1058 }
1059 result
1060 }
1061
1062 pub fn is_mvcc_bootstrap_connection(&self) -> bool {
1064 self.is_mvcc_bootstrap_connection.load(Ordering::SeqCst)
1065 }
1066
1067 pub fn promote_to_regular_connection(&self) {
1069 assert!(self.is_mvcc_bootstrap_connection.load(Ordering::SeqCst));
1070 self.is_mvcc_bootstrap_connection
1071 .store(false, Ordering::SeqCst);
1072 }
1073
1074 pub fn demote_to_mvcc_connection(&self) {
1076 assert!(!self.is_mvcc_bootstrap_connection.load(Ordering::SeqCst));
1077 self.is_mvcc_bootstrap_connection
1078 .store(true, Ordering::SeqCst);
1079 }
1080
1081 pub fn maybe_reparse_schema(self: &Arc<Connection>) -> Result<()> {
1085 let pager = self.pager.load().clone();
1086 let mv_store = self.mv_store();
1087
1088 if self.get_tx_state() != TransactionState::None {
1092 return Ok(());
1093 }
1094 let had_main_mv_tx = self.get_mv_tx().is_some();
1095
1096 if self.db.shared_wal_coordination()?.is_some() {
1097 pager.clear_page_cache(false);
1101 pager.set_schema_cookie(None);
1102 }
1103
1104 let on_disk_schema_version = if mv_store.as_ref().is_some() {
1105 self.read_current_schema_cookie().or_else(|err| match err {
1106 LimboError::Page1NotAlloc => Ok(0),
1107 other => Err(other),
1108 })?
1109 } else {
1110 pager.begin_read_tx()?;
1112 let on_disk_schema_version = pager
1113 .io
1114 .block(|| pager.with_header(|header| header.schema_cookie));
1115
1116 let on_disk_schema_version = match on_disk_schema_version {
1117 Ok(db_schema_version) => db_schema_version.get(),
1118 Err(LimboError::Page1NotAlloc) => {
1119 0
1121 }
1122 Err(err) => {
1123 pager.end_read_tx();
1124 return Err(err);
1125 }
1126 };
1127 pager.end_read_tx();
1128 on_disk_schema_version
1129 };
1130
1131 let db_schema_version = self.db.schema.lock().schema_version;
1132 tracing::debug!(
1133 "path: {}, db_schema_version={} vs on_disk_schema_version={}",
1134 self.db.path,
1135 db_schema_version,
1136 on_disk_schema_version
1137 );
1138 if db_schema_version == on_disk_schema_version {
1140 return Ok(());
1141 }
1142
1143 pager.begin_read_tx()?;
1149 self.set_tx_state(TransactionState::Read);
1150
1151 let reparse_result = self.reparse_schema();
1152
1153 let previous = self.transaction_state.swap(TransactionState::None);
1154 turso_assert!(
1155 matches!(previous, TransactionState::None | TransactionState::Read),
1156 "unexpected end transaction state"
1157 );
1158 if previous == TransactionState::Read {
1161 pager.end_read_tx();
1162 }
1163 if !had_main_mv_tx {
1164 self.clear_internal_main_mvcc_tx(&pager);
1165 }
1166
1167 reparse_result?;
1168
1169 let schema = self.schema.read().clone();
1170 self.db.update_schema_if_newer(schema);
1171 Ok(())
1172 }
1173
1174 #[cfg(clt_turso_feature = "conn_raw_api")]
1180 pub fn force_reparse_schema(self: &Arc<Connection>) -> Result<()> {
1181 self.force_reparse_schema_inner(true)
1182 }
1183
1184 pub fn force_reparse_schema_without_publish(self: &Arc<Connection>) -> Result<()> {
1190 self.force_reparse_schema_inner(false)
1191 }
1192
1193 fn force_reparse_schema_inner(self: &Arc<Connection>, publish: bool) -> Result<()> {
1194 if self.get_tx_state() != TransactionState::None {
1195 return Err(LimboError::Busy);
1196 }
1197 if self.get_mv_tx().is_some() || self.next_attached_mv_tx().is_some() {
1198 return Err(LimboError::Busy);
1199 }
1200
1201 let pager = self.pager.load().clone();
1202 pager.clear_page_cache(false);
1203 pager.set_schema_cookie(None);
1204 pager.begin_read_tx()?;
1205 self.set_tx_state(TransactionState::Read);
1206
1207 let reparse_result = self.reparse_schema();
1208
1209 let previous = self.transaction_state.swap(TransactionState::None);
1210 turso_assert!(
1211 matches!(previous, TransactionState::None | TransactionState::Read),
1212 "unexpected end transaction state"
1213 );
1214 if previous == TransactionState::Read {
1215 pager.end_read_tx();
1216 }
1217 self.clear_internal_main_mvcc_tx(&pager);
1218
1219 reparse_result?;
1220
1221 if publish {
1222 let schema = self.schema.read().clone();
1223 self.db.update_schema_if_newer(schema);
1224 }
1225 Ok(())
1226 }
1227
1228 fn clear_internal_main_mvcc_tx(&self, pager: &Arc<Pager>) {
1229 let Some(tx_id) = self.get_mv_tx_id() else {
1230 return;
1231 };
1232 if let Some(mv_store) = self.mv_store().as_ref() {
1233 if mv_store.is_tx_rollbackable(tx_id) {
1234 mv_store.rollback_tx(tx_id, pager.clone(), self, MAIN_DB_ID);
1235 } else {
1236 self.set_mv_tx(None);
1237 }
1238 } else {
1239 self.set_mv_tx(None);
1240 }
1241 pager.cleanup_read_tx();
1242 }
1243
1244 pub(crate) fn reparse_schema(self: &Arc<Connection>) -> Result<()> {
1249 let io = self.pager.load().io.clone();
1250 let mut state = ReparseSchemaState::default();
1251 io.block(|| self.reparse_schema_nonblock(&mut state))
1252 }
1253
1254 pub(crate) fn reparse_schema_with_cookie_keeping_sequences(
1266 self: &Arc<Connection>,
1267 cookie: u32,
1268 sequences: rustc_hash::FxHashMap<String, Arc<crate::schema::Sequence>>,
1269 ) -> Result<()> {
1270 let io = self.pager.load().io.clone();
1271 let mut state = ReparseSchemaState::default();
1272 let mut preserved = Some(sequences);
1276 io.block(|| {
1277 if matches!(state, ReparseSchemaState::Start) {
1278 state = ReparseSchemaState::Building(Box::new(
1279 self.init_reparse_building(cookie, preserved.take())?,
1280 ));
1281 }
1282 self.drive_reparse_building(&mut state)
1283 })
1284 }
1285
1286 pub(crate) fn reparse_schema_nonblock(
1289 self: &Arc<Connection>,
1290 state: &mut ReparseSchemaState,
1291 ) -> Result<crate::types::IOResult<()>> {
1292 use crate::types::IOResult;
1293 if matches!(state, ReparseSchemaState::Start) {
1294 let cookie = crate::return_if_io!(self.read_current_schema_cookie_nonblock());
1297 *state =
1298 ReparseSchemaState::Building(Box::new(self.init_reparse_building(cookie, None)?));
1299 }
1300 self.drive_reparse_building(state)
1301 }
1302
1303 fn init_reparse_building(
1309 self: &Arc<Connection>,
1310 cookie: u32,
1311 preserved_sequences: Option<rustc_hash::FxHashMap<String, Arc<crate::schema::Sequence>>>,
1312 ) -> Result<ReparseSchemaInner> {
1313 let guard = self.schema_reparse_guard();
1314 self.pager.load().set_schema_cookie(Some(cookie));
1315 let mut fresh = Schema::with_options(self.experimental_custom_types_enabled())?;
1317 fresh.generated_columns_enabled = self.db.experimental_generated_columns_enabled();
1318 fresh.schema_version = cookie;
1319
1320 let tvfs: Vec<Arc<crate::vtab::VirtualTable>> = self
1324 .schema
1325 .read()
1326 .tables
1327 .values()
1328 .filter_map(|table| match table.as_ref() {
1329 crate::schema::Table::Virtual(vtab)
1330 if matches!(vtab.kind, turso_ext::VTabKind::TableValuedFunction) =>
1331 {
1332 Some(vtab.clone())
1333 }
1334 _ => None,
1335 })
1336 .collect();
1337
1338 self.with_schema_mut(|schema| {
1344 *schema = fresh.try_clone()?;
1345 Ok::<_, crate::alloc::TryReserveError>(())
1346 })??;
1347
1348 let stmt = self.prepare("SELECT * FROM sqlite_schema")?;
1349
1350 let mv_tx = if self.is_mvcc_bootstrap_connection() {
1352 None
1353 } else {
1354 self.get_mv_tx()
1355 };
1356 Ok(ReparseSchemaInner {
1357 _guard: guard,
1358 fresh,
1359 tvfs,
1360 preserved_sequences,
1361 phase: ReparsePhase::ParseSchema {
1362 parse: Box::new(crate::util::ParseSchemaRowsState::new(stmt, mv_tx)),
1363 },
1364 })
1365 }
1366
1367 fn drive_reparse_building(
1370 self: &Arc<Connection>,
1371 state: &mut ReparseSchemaState,
1372 ) -> Result<crate::types::IOResult<()>> {
1373 use crate::types::IOResult;
1374 loop {
1375 let ReparseSchemaState::Building(inner) = state else {
1376 unreachable!("drive_reparse_building requires Building state");
1377 };
1378 match &mut inner.phase {
1379 ReparsePhase::ParseSchema { parse } => {
1380 let attached_resolver = |name: &str| -> Option<usize> {
1383 self.attached_databases
1384 .read()
1385 .get_database_by_name(&crate::util::normalize_ident(name))
1386 .map(|(idx, _)| idx)
1387 };
1388 crate::return_if_io!(crate::util::parse_schema_rows(
1389 parse,
1390 &mut inner.fresh,
1391 &self.syms.read(),
1392 &attached_resolver,
1393 ));
1394
1395 for vtab in &inner.tvfs {
1397 let normalized = crate::util::normalize_ident(&vtab.name);
1398 inner.fresh.tables.entry(normalized).or_insert_with(|| {
1399 Arc::new(crate::schema::Table::Virtual(vtab.clone()))
1400 });
1401 }
1402
1403 inner.phase = ReparsePhase::PopulateSequences {
1405 pending: None,
1406 idx: 0,
1407 stmt: None,
1408 meta: None,
1409 seq: None,
1410 watermark_stmt: None,
1411 watermark_row: None,
1412 };
1413 }
1414 ReparsePhase::PopulateSequences {
1415 pending,
1416 idx,
1417 stmt,
1418 meta,
1419 seq,
1420 watermark_stmt,
1421 watermark_row,
1422 } => {
1423 if pending.is_none() {
1428 if let Some(sequences) = inner.preserved_sequences.take() {
1429 inner.fresh.sequences = sequences;
1430 *pending = Some(crate::alloc::vec![]);
1431 } else {
1432 let work = inner.fresh.sequence_backing_table_names();
1433 if !work.is_empty() {
1434 self.with_schema_mut(|schema| {
1435 *schema = inner.fresh.try_clone()?;
1436 Ok::<_, crate::alloc::TryReserveError>(())
1437 })??;
1438 }
1439 *pending = Some(work);
1440 }
1441 }
1442
1443 loop {
1449 let entry = {
1450 let work = pending.as_ref().expect("pending initialized above");
1451 if *idx >= work.len() {
1452 break;
1453 }
1454 work[*idx].clone()
1455 };
1456 let (backing_table_name, seq_name) = entry;
1457 let normalized = crate::util::normalize_ident(&seq_name);
1458 if inner.fresh.sequences.contains_key(&normalized) {
1459 *idx += 1;
1460 *stmt = None;
1461 *meta = None;
1462 *seq = None;
1463 *watermark_stmt = None;
1464 *watermark_row = None;
1465 continue;
1466 }
1467 if seq.is_none() {
1468 crate::return_if_io!(self.read_seq_descriptor_row_nonblock(
1469 &backing_table_name,
1470 &seq_name,
1471 stmt,
1472 meta,
1473 ));
1474 *seq = Some(Self::sequence_from_descriptor_meta(
1475 &seq_name,
1476 &backing_table_name,
1477 *meta,
1478 )?);
1479 *stmt = None;
1480 *meta = None;
1481 }
1482 let sequence = seq.as_ref().expect("sequence set above");
1483 crate::return_if_io!(self.read_sequence_watermark_row_nonblock(
1484 &backing_table_name,
1485 sequence,
1486 watermark_stmt,
1487 watermark_row,
1488 ));
1489 let watermark = Self::sequence_watermark_from_row(
1490 &backing_table_name,
1491 sequence,
1492 *watermark_row,
1493 )?;
1494 if let Some(mv_store) = self.db.get_mv_store().as_ref() {
1495 mv_store.set_sequence_watermark(&normalized, watermark);
1496 }
1497 let sequence = seq.take().expect("sequence set above");
1498 inner.fresh.sequences.insert(normalized, Arc::new(sequence));
1499 *idx += 1;
1500 *stmt = None;
1501 *meta = None;
1502 *watermark_stmt = None;
1503 *watermark_row = None;
1504 }
1505
1506 if self.experimental_custom_types_enabled()
1508 && inner
1509 .fresh
1510 .tables
1511 .contains_key(crate::schema::TURSO_TYPES_TABLE_NAME)
1512 {
1513 self.with_schema_mut(|schema| {
1515 *schema = inner.fresh.try_clone()?;
1516 Ok::<_, crate::alloc::TryReserveError>(())
1517 })??;
1518 let stmt = self.prepare_internal(format!(
1519 "SELECT name, sql FROM {}",
1520 crate::schema::TURSO_TYPES_TABLE_NAME
1521 ))?;
1522 inner.phase = ReparsePhase::LoadTypes {
1523 stmt: Box::new(stmt),
1524 type_rows: Vec::new(),
1525 };
1526 } else {
1527 inner.phase = ReparsePhase::RefreshStats {
1528 stats: Default::default(),
1529 };
1530 }
1531 }
1532 ReparsePhase::LoadTypes { stmt, type_rows } => {
1533 let scan = (|| -> Result<IOResult<()>> {
1535 crate::return_if_io!(stmt.run_with_row_callback_nonblock(|row| {
1536 type_rows.push(row.get::<&str>(1)?.to_string());
1537 Ok(())
1538 }));
1539 Ok(IOResult::Done(()))
1540 })();
1541 match scan {
1542 Ok(IOResult::IO(io)) => return Ok(IOResult::IO(io)),
1543 Ok(IOResult::Done(())) => {
1544 let type_rows = std::mem::take(type_rows);
1545 if let Err(e) = inner.fresh.load_type_definitions(&type_rows) {
1546 tracing::warn!("Failed to load custom types: {}", e);
1547 }
1548 inner.phase = ReparsePhase::RefreshStats {
1549 stats: Default::default(),
1550 };
1551 }
1552 Err(e) => {
1553 tracing::warn!("Failed to load custom types: {}", e);
1554 inner.phase = ReparsePhase::RefreshStats {
1555 stats: Default::default(),
1556 };
1557 }
1558 }
1559 }
1560 ReparsePhase::RefreshStats { stats } => {
1561 crate::return_if_io!(crate::stats::refresh_analyze_stats_nonblock(self, stats));
1563
1564 let ReparseSchemaState::Building(inner) = std::mem::take(state) else {
1567 unreachable!("state is Building");
1568 };
1569 let fresh = inner.fresh;
1570 tracing::debug!(
1571 "reparse_schema: schema_version={}, tables={:?}",
1572 fresh.schema_version,
1573 fresh.tables.keys()
1574 );
1575 self.with_schema_mut(|schema| {
1576 *schema = fresh;
1577 })?;
1578 return Ok(IOResult::Done(()));
1579 }
1580 }
1581 }
1582 }
1583
1584 pub(crate) fn read_current_schema_cookie(&self) -> Result<u32> {
1585 if let Some(mv_store) = self.mv_store().as_ref() {
1586 let tx_id = self.get_mv_tx_id();
1587 mv_store.with_header(|header| header.schema_cookie.get(), tx_id.as_ref())
1588 } else {
1589 let pager = self.pager.load();
1590 pager
1591 .io
1592 .block(|| pager.with_header(|header| header.schema_cookie))
1593 .map(|cookie| cookie.get())
1594 }
1595 }
1596
1597 pub(crate) fn read_current_schema_cookie_nonblock(
1601 &self,
1602 ) -> Result<crate::types::IOResult<u32>> {
1603 use crate::types::IOResult;
1604 if let Some(mv_store) = self.mv_store().as_ref() {
1605 let tx_id = self.get_mv_tx_id();
1606 let cookie =
1607 mv_store.with_header(|header| header.schema_cookie.get(), tx_id.as_ref())?;
1608 Ok(crate::types::IOResult::Done(cookie))
1609 } else {
1610 let pager = self.pager.load();
1611 let cookie = crate::return_if_io!(pager.with_header(|header| header.schema_cookie));
1612 Ok(crate::types::IOResult::Done(cookie.get()))
1613 }
1614 }
1615
1616 #[instrument(skip_all, level = Level::DEBUG)]
1617 pub fn prepare_execute_batch(self: &Arc<Connection>, sql: impl AsRef<str>) -> Result<()> {
1618 if self.is_closed() {
1619 return Err(LimboError::InternalError("Connection closed".to_string()));
1620 }
1621 if sql.as_ref().is_empty() {
1622 return Err(LimboError::InvalidArgument(
1623 "The supplied SQL string contains no statements".to_string(),
1624 ));
1625 }
1626 let sql = sql.as_ref();
1627 tracing::trace!("Preparing and executing batch: {}", sql);
1628 let mut parser = Parser::new(sql.as_bytes());
1629 while let Some(cmd) = parser.next_cmd()? {
1630 let byte_offset_end = parser.offset();
1631 let input = str::from_utf8(&sql.as_bytes()[..byte_offset_end])
1632 .unwrap()
1633 .trim();
1634 let (program, pager, mode) = self.compile_cmd(cmd, input)?;
1635 Statement::new(program, pager.clone(), mode, 0).run_ignore_rows()?;
1636 }
1637 Ok(())
1638 }
1639
1640 #[instrument(skip_all, level = Level::DEBUG)]
1641 pub fn query(self: &Arc<Connection>, sql: impl AsRef<str>) -> Result<Option<Statement>> {
1642 if self.is_closed() {
1643 return Err(LimboError::InternalError("Connection closed".to_string()));
1644 }
1645 let sql = sql.as_ref();
1646 tracing::trace!("Querying: {}", sql);
1647 let mut parser = Parser::new(sql.as_bytes());
1648 let cmd = parser.next_cmd()?;
1649 let byte_offset_end = parser.offset();
1650 let input = str::from_utf8(&sql.as_bytes()[..byte_offset_end])
1651 .unwrap()
1652 .trim();
1653 match cmd {
1654 Some(cmd) => self.run_cmd(cmd, input),
1655 None => Ok(None),
1656 }
1657 }
1658
1659 #[instrument(skip_all, level = Level::DEBUG)]
1660 pub(crate) fn run_cmd(
1661 self: &Arc<Connection>,
1662 cmd: Cmd,
1663 input: &str,
1664 ) -> Result<Option<Statement>> {
1665 if self.is_closed() {
1666 return Err(LimboError::InternalError("Connection closed".to_string()));
1667 }
1668 let (program, pager, mode) = self.compile_cmd(cmd, input)?;
1669 let stmt = Statement::new(program, pager, mode, 0);
1670 Ok(Some(stmt))
1671 }
1672
1673 pub fn query_runner<'a>(self: &'a Arc<Connection>, sql: &'a [u8]) -> QueryRunner<'a> {
1674 QueryRunner::new(self, sql)
1675 }
1676
1677 #[instrument(skip_all, level = Level::DEBUG)]
1680 #[turso_macros::trace_stack]
1681 pub fn execute(self: &Arc<Connection>, sql: impl AsRef<str>) -> Result<()> {
1682 if self.is_closed() {
1683 return Err(LimboError::InternalError("Connection closed".to_string()));
1684 }
1685 let sql = sql.as_ref();
1686 let mut parser = Parser::new(sql.as_bytes());
1687 while let Some(cmd) = parser.next_cmd()? {
1688 let byte_offset_end = parser.offset();
1689 let input = str::from_utf8(&sql.as_bytes()[..byte_offset_end])
1690 .unwrap()
1691 .trim();
1692 let (program, pager, mode) = self.compile_cmd(cmd, input)?;
1693 {
1694 crate::stack::trace_stack!("run");
1695 Statement::new(program, pager.clone(), mode, 0).run_ignore_rows()?;
1696 }
1697 }
1698 Ok(())
1699 }
1700
1701 #[instrument(skip_all, level = Level::DEBUG)]
1702 pub fn consume_stmt(
1703 self: &Arc<Connection>,
1704 sql: impl AsRef<str>,
1705 ) -> Result<Option<(Statement, usize)>> {
1706 let mut parser = Parser::new(sql.as_ref().as_bytes());
1707 let Some(cmd) = parser.next_cmd()? else {
1708 return Ok(None);
1709 };
1710 let byte_offset_end = parser.offset();
1711 let input = str::from_utf8(&sql.as_ref().as_bytes()[..byte_offset_end])
1712 .unwrap()
1713 .trim();
1714 let (program, pager, mode) = self.compile_cmd(cmd, input)?;
1715 let stmt = Statement::new(program, pager, mode, 0);
1716 Ok(Some((stmt, parser.offset())))
1717 }
1718
1719 #[cfg(clt_turso_feature = "fs")]
1720 pub fn from_uri(uri: &str, db_opts: DatabaseOpts) -> Result<(Arc<dyn IO>, Arc<Connection>)> {
1721 use crate::util::MEMORY_PATH;
1722 let opts = OpenOptions::parse(uri)?;
1723 let flags = opts.get_flags()?;
1724 if opts.path == MEMORY_PATH || matches!(opts.mode, OpenMode::Memory) {
1725 let io = Arc::new(MemoryIO::new());
1726 let db = Database::open_file_with_flags(io.clone(), MEMORY_PATH, flags, db_opts, None)?;
1727 let conn = db.connect()?;
1728 return Ok((io, conn));
1729 }
1730 let encryption_opts = match (opts.cipher.clone(), opts.hexkey.clone()) {
1731 (Some(cipher), Some(hexkey)) => Some(EncryptionOpts { cipher, hexkey }),
1732 (Some(_), None) => {
1733 return Err(LimboError::InvalidArgument(
1734 "hexkey is required when cipher is provided".to_string(),
1735 ));
1736 }
1737 (None, Some(_)) => {
1738 return Err(LimboError::InvalidArgument(
1739 "cipher is required when hexkey is provided".to_string(),
1740 ));
1741 }
1742 (None, None) => None,
1743 };
1744 let (io, db) = Database::open_new(
1745 &opts.path,
1746 opts.vfs.as_ref(),
1747 flags,
1748 db_opts,
1749 encryption_opts,
1750 )?;
1751 if let Some(modeof) = opts.modeof {
1752 let perms = std::fs::metadata(modeof).map_err(|e| io_error(e, "metadata"))?;
1753 std::fs::set_permissions(&opts.path, perms.permissions())
1754 .map_err(|e| io_error(e, "set_permissions"))?;
1755 }
1756 let conn = db.connect()?;
1757 if let Some(cipher) = opts.cipher {
1758 let _ = conn.pragma_update("cipher", format!("'{cipher}'"));
1759 }
1760 if let Some(hexkey) = opts.hexkey {
1761 let _ = conn.pragma_update("hexkey", format!("'{hexkey}'"));
1762 }
1763 Ok((io, conn))
1764 }
1765
1766 #[cfg(clt_turso_feature = "fs")]
1767 fn from_uri_attached(
1768 uri: &str,
1769 mut db_opts: DatabaseOpts,
1770 main_db_flags: OpenFlags,
1771 io: Arc<dyn IO>,
1772 ) -> Result<(Arc<Database>, Option<EncryptionOpts>)> {
1773 let opts = OpenOptions::parse(uri)?;
1774 let mut flags = opts.get_flags()?;
1775 if main_db_flags.contains(OpenFlags::ReadOnly) {
1776 flags |= OpenFlags::ReadOnly;
1777 }
1778 let encryption_opts = match (opts.cipher.clone(), opts.hexkey.clone()) {
1779 (Some(cipher), Some(hexkey)) => Some(EncryptionOpts { cipher, hexkey }),
1780 (Some(_), None) => {
1781 return Err(LimboError::InvalidArgument(
1782 "hexkey is required when cipher is provided".to_string(),
1783 ));
1784 }
1785 (None, Some(_)) => {
1786 return Err(LimboError::InvalidArgument(
1787 "cipher is required when hexkey is provided".to_string(),
1788 ));
1789 }
1790 (None, None) => None,
1791 };
1792 if encryption_opts.is_some() {
1793 db_opts = db_opts.with_encryption(true);
1794 }
1795 let io = opts.vfs.map(Database::io_for_vfs).unwrap_or(Ok(io))?;
1796 let db = Database::open_file_with_flags(
1797 io.clone(),
1798 &opts.path,
1799 flags,
1800 db_opts,
1801 encryption_opts.clone(),
1802 )?;
1803 if let Some(modeof) = opts.modeof {
1804 let perms = std::fs::metadata(modeof).map_err(|e| io_error(e, "metadata"))?;
1805 std::fs::set_permissions(&opts.path, perms.permissions())
1806 .map_err(|e| io_error(e, "set_permissions"))?;
1807 }
1808 Ok((db, encryption_opts))
1809 }
1810
1811 pub fn set_foreign_keys_enabled(&self, enable: bool) {
1812 self.fk_pragma.store(enable, Ordering::Release);
1813 self.bump_prepare_context_generation();
1814 }
1815
1816 pub fn foreign_keys_enabled(&self) -> bool {
1817 self.fk_pragma.load(Ordering::Acquire)
1818 }
1819
1820 pub fn set_check_constraints_ignored(&self, ignore: bool) {
1821 self.check_constraints_pragma
1822 .store(ignore, Ordering::Release);
1823 }
1824
1825 pub fn check_constraints_ignored(&self) -> bool {
1826 self.check_constraints_pragma.load(Ordering::Acquire)
1827 }
1828
1829 pub(crate) fn clear_deferred_foreign_key_violations(&self) -> isize {
1830 self.fk_deferred_violations.swap(0, Ordering::Release)
1831 }
1832
1833 pub(crate) fn get_deferred_foreign_key_violations(&self) -> isize {
1834 self.fk_deferred_violations.load(Ordering::Acquire)
1835 }
1836
1837 pub(crate) fn increment_deferred_foreign_key_violations(&self, v: isize) {
1838 self.fk_deferred_violations.fetch_add(v, Ordering::AcqRel);
1839 }
1840
1841 pub(crate) fn query_stored_type_definitions(self: &Arc<Connection>) -> Result<Vec<String>> {
1846 let has_types_table = {
1847 let s = self.schema.read();
1848 s.tables.contains_key(crate::schema::TURSO_TYPES_TABLE_NAME)
1849 };
1850 if !has_types_table {
1851 return Ok(Vec::new());
1852 }
1853 let mut type_stmt = self.prepare_internal(format!(
1854 "SELECT name, sql FROM {}",
1855 crate::schema::TURSO_TYPES_TABLE_NAME
1856 ))?;
1857 let mut type_rows = Vec::new();
1858 type_stmt.run_with_row_callback(|row| {
1859 type_rows.push(row.get::<&str>(1)?.to_string());
1860 Ok(())
1861 })?;
1862 Ok(type_rows)
1863 }
1864
1865 pub fn maybe_update_schema(&self) {
1866 if self.schema_reparse_in_progress() {
1867 return;
1868 }
1869 let current_schema = self.schema.read().clone();
1870 let schema = self.db.schema.lock();
1871 if self.has_no_open_transaction_state()
1876 && (current_schema.schema_version != schema.schema_version
1877 || self
1878 .has_mvcc_schema_snapshot_changed_with_same_version(¤t_schema, &schema))
1879 {
1880 let mut adopted = schema.clone();
1881 let mv_store_guard = self.db.get_mv_store();
1884 if let Some(mv_store) = mv_store_guard.as_ref() {
1885 if let Ok(schema) = Schema::try_make_mut(&mut adopted) {
1886 mv_store.resolve_schema_negative_roots(schema);
1887 }
1888 }
1889 *self.schema.write() = adopted;
1890 self.bump_prepare_context_generation();
1891 }
1892 }
1893
1894 fn has_no_open_transaction_state(&self) -> bool {
1895 matches!(self.get_tx_state(), TransactionState::None)
1896 && self.get_mv_tx().is_none()
1897 && self.next_attached_mv_tx().is_none()
1898 }
1899
1900 fn has_mvcc_schema_snapshot_changed_with_same_version(
1901 &self,
1902 current_schema: &Arc<Schema>,
1903 schema: &Arc<Schema>,
1904 ) -> bool {
1905 self.mvcc_enabled()
1906 && current_schema.schema_version == schema.schema_version
1907 && !Arc::ptr_eq(current_schema, schema)
1908 }
1909
1910 pub(crate) fn mvcc_schema_requires_reprepare_before_tx(&self) -> bool {
1911 if !self.has_no_open_transaction_state() {
1912 return false;
1913 }
1914 let current_schema = self.schema.read().clone();
1915 let schema = self.db.schema.lock();
1916 self.has_mvcc_schema_snapshot_changed_with_same_version(¤t_schema, &schema)
1917 }
1918
1919 pub(crate) fn mvcc_begin_schema_generation(&self) -> Result<Option<u64>> {
1926 let mv_guard = self.db.get_mv_store();
1927 let Some(mv) = mv_guard.as_ref() else {
1928 return Ok(None);
1929 };
1930 if !self.has_no_open_transaction_state() {
1934 return Ok(None);
1935 }
1936 let generation = mv.schema_generation();
1940 let current_schema = self.schema.read().clone();
1941 let schema = self.db.schema.lock();
1942 if self.has_mvcc_schema_snapshot_changed_with_same_version(¤t_schema, &schema) {
1943 return Err(LimboError::SchemaUpdated);
1944 }
1945 Ok(Some(generation))
1946 }
1947
1948 pub(crate) fn refresh_schema_from_shared_for_reprepare(&self) {
1949 let current_schema = self.schema.read().clone();
1950 let schema = self.db.schema.lock().clone();
1951 if current_schema.schema_version < schema.schema_version
1952 || (self.has_no_open_transaction_state()
1953 && self
1954 .has_mvcc_schema_snapshot_changed_with_same_version(¤t_schema, &schema))
1955 {
1956 *self.schema.write() = schema;
1957 self.bump_prepare_context_generation();
1958 }
1959 }
1960
1961 #[cfg(all(clt_turso_feature = "fs", clt_turso_feature = "conn_raw_api"))]
1963 pub fn read_schema_version(&self) -> Result<u32> {
1964 let pager = self.pager.load();
1965 pager
1966 .io
1967 .block(|| pager.with_header(|header| header.schema_cookie))
1968 .map(|version| version.get())
1969 }
1970
1971 #[cfg(all(clt_turso_feature = "fs", clt_turso_feature = "conn_raw_api"))]
1976 pub fn write_schema_version(self: &Arc<Connection>, version: u32) -> Result<()> {
1977 let TransactionState::Write { .. } = self.get_tx_state() else {
1978 return Err(LimboError::InternalError(
1979 "write_schema_version must be called from within Write transaction".to_string(),
1980 ));
1981 };
1982 let pager = self.pager.load();
1983 pager.io.block(|| {
1984 pager.with_header_mut(|header| {
1985 turso_assert!(
1986 header.schema_cookie.get() < version,
1987 "cookie can't go back in time"
1988 );
1989 self.with_schema_mut(|schema| schema.schema_version = version)
1990 .map(|()| {
1991 self.set_tx_state(TransactionState::Write {
1992 schema_did_change: true,
1993 });
1994 header.schema_cookie = version.into();
1995 })
1996 })
1997 })??;
1998 self.reparse_schema()?;
1999 Ok(())
2000 }
2001
2002 #[cfg(all(clt_turso_feature = "fs", clt_turso_feature = "conn_raw_api"))]
2005 pub fn try_wal_watermark_read_page(
2006 &self,
2007 page_idx: u32,
2008 page: &mut [u8],
2009 frame_watermark: Option<u64>,
2010 ) -> Result<bool> {
2011 let Some((page_ref, c)) =
2012 self.try_wal_watermark_read_page_begin(page_idx, frame_watermark)?
2013 else {
2014 return Ok(false);
2015 };
2016 match self.get_pager().io.wait_for_completion(c) {
2017 Err(LimboError::CompletionError(err))
2018 if Self::wal_watermark_read_error_is_absent_page(&err) =>
2019 {
2020 return Ok(false);
2021 }
2022 Err(e) => return Err(e),
2023 _ => {}
2024 }
2025
2026 self.try_wal_watermark_read_page_end(page, page_ref)
2027 }
2028
2029 #[cfg(all(clt_turso_feature = "fs", clt_turso_feature = "conn_raw_api"))]
2036 pub fn wal_watermark_read_error_is_absent_page(err: &crate::error::CompletionError) -> bool {
2037 #[cfg(all(target_os = "windows", clt_turso_feature = "experimental_win_iocp"))]
2038 {
2039 matches!(
2040 err,
2041 crate::error::CompletionError::IOError(std::io::ErrorKind::UnexpectedEof, _)
2042 )
2043 }
2044 #[cfg(not(all(target_os = "windows", clt_turso_feature = "experimental_win_iocp")))]
2045 {
2046 let _ = err;
2047 false
2048 }
2049 }
2050
2051 #[cfg(all(clt_turso_feature = "fs", clt_turso_feature = "conn_raw_api"))]
2052 pub fn try_wal_watermark_read_page_begin(
2053 &self,
2054 page_idx: u32,
2055 frame_watermark: Option<u64>,
2056 ) -> Result<Option<(Arc<Page>, Completion)>> {
2057 let pager = self.pager.load();
2058 let (page_ref, c) = match pager.read_page_no_cache(page_idx as i64, frame_watermark, true) {
2059 Ok(result) => result,
2060 #[cfg(target_os = "windows")]
2062 Err(LimboError::CompletionError(crate::error::CompletionError::IOError(
2063 std::io::ErrorKind::UnexpectedEof,
2064 _,
2065 ))) => return Ok(None),
2066 Err(err) => return Err(err),
2067 };
2068
2069 Ok(Some((page_ref, c)))
2070 }
2071
2072 #[cfg(all(clt_turso_feature = "fs", clt_turso_feature = "conn_raw_api"))]
2073 pub fn try_wal_watermark_read_page_end(
2074 &self,
2075 page: &mut [u8],
2076 page_ref: Arc<Page>,
2077 ) -> Result<bool> {
2078 let content = page_ref.get_contents();
2079 if content.buffer.as_ref().is_none_or(|b| b.is_empty()) {
2081 return Ok(false);
2082 }
2083 page.copy_from_slice(content.as_ptr());
2084 Ok(true)
2085 }
2086
2087 #[cfg(all(clt_turso_feature = "fs", clt_turso_feature = "conn_raw_api"))]
2090 pub fn wal_changed_pages_after(&self, frame_watermark: u64) -> Result<Vec<u32>> {
2091 self.pager.load().wal_changed_pages_after(frame_watermark)
2092 }
2093
2094 #[cfg(all(clt_turso_feature = "fs", clt_turso_feature = "conn_raw_api"))]
2095 pub fn wal_state(&self) -> Result<WalState> {
2096 self.pager.load().wal_state()
2097 }
2098
2099 #[cfg(all(clt_turso_feature = "fs", clt_turso_feature = "conn_raw_api"))]
2100 pub fn wal_get_frame(&self, frame_no: u64, frame: &mut [u8]) -> Result<WalFrameInfo> {
2101 use crate::storage::sqlite3_ondisk::parse_wal_frame_header;
2102
2103 let c = self.pager.load().wal_get_frame(frame_no, frame)?;
2104 self.db.io.wait_for_completion(c)?;
2105 let (header, _) = parse_wal_frame_header(frame);
2106 Ok(WalFrameInfo {
2107 page_no: header.page_number,
2108 db_size: header.db_size,
2109 })
2110 }
2111
2112 #[cfg(all(clt_turso_feature = "fs", clt_turso_feature = "conn_raw_api"))]
2116 pub fn wal_insert_frame(&self, frame_no: u64, frame: &[u8]) -> Result<WalFrameInfo> {
2117 self.pager.load().wal_insert_frame(frame_no, frame)
2118 }
2119
2120 #[cfg(all(clt_turso_feature = "fs", clt_turso_feature = "conn_raw_api"))]
2122 pub fn wal_insert_begin(&self) -> Result<()> {
2123 let pager = self.pager.load();
2124 pager.begin_read_tx()?;
2125 pager
2130 .io
2131 .block(|| pager.begin_write_tx(WalAutoActions::empty()))
2132 .inspect_err(|_| {
2133 pager.end_read_tx();
2134 })?;
2135
2136 self.set_tx_state(TransactionState::Write {
2138 schema_did_change: false,
2139 });
2140 self.auto_commit.store(false, Ordering::SeqCst);
2141
2142 Ok(())
2143 }
2144
2145 #[cfg(all(clt_turso_feature = "fs", clt_turso_feature = "conn_raw_api"))]
2148 pub fn wal_insert_end(self: &Arc<Connection>, force_commit: bool) -> Result<()> {
2149 use crate::{return_if_io, types::IOResult};
2150
2151 {
2152 let pager = self.pager.load();
2153
2154 let Some(wal) = pager.wal.as_ref() else {
2155 return Err(LimboError::InternalError(
2156 "wal_insert_end called without a wal".to_string(),
2157 ));
2158 };
2159
2160 let commit_err = if force_commit {
2161 pager
2162 .io
2163 .block(|| {
2164 return_if_io!(pager.commit_wal(
2165 WalAutoActions::empty(),
2166 self.get_sync_mode(),
2167 self.get_data_sync_retry(),
2168 ));
2169 pager.commit_wal_end();
2170 Ok(IOResult::Done(()))
2171 })
2172 .err()
2173 } else {
2174 None
2175 };
2176
2177 self.auto_commit.store(true, Ordering::SeqCst);
2178 self.set_tx_state(TransactionState::None);
2179 wal.end_write_tx();
2180 wal.end_read_tx();
2181
2182 if !force_commit {
2183 if let Some(mv_store) = self.mv_store().as_ref() {
2185 if let Some(tx_id) = self.get_mv_tx_id() {
2186 mv_store.rollback_tx(tx_id, pager.clone(), self, MAIN_DB_ID);
2187 }
2188 }
2189 pager.rollback(false, self, true);
2190 }
2191 if let Some(err) = commit_err {
2192 return Err(err);
2193 }
2194 }
2195
2196 self.maybe_reparse_schema()?;
2198 Ok(())
2199 }
2200
2201 pub fn cacheflush(&self) -> Result<Vec<Completion>> {
2203 if self.is_closed() {
2204 return Err(LimboError::InternalError("Connection closed".to_string()));
2205 }
2206 let pager = self.pager.load();
2207 pager.io.block(|| pager.cacheflush())
2208 }
2209
2210 pub fn checkpoint(self: &Arc<Self>, mode: CheckpointMode) -> Result<CheckpointResult> {
2211 use crate::mvcc::database::CheckpointStateMachine;
2212 use crate::state_machine::{StateTransition, TransitionResult};
2213 if self.is_closed() {
2214 return Err(LimboError::InternalError("Connection closed".to_string()));
2215 }
2216 if let Some(mv_store) = self.mv_store().as_ref() {
2217 let pager = self.pager.load().clone();
2218 let io = pager.io.clone();
2219 let mut ckpt_sm = CheckpointStateMachine::new(
2220 pager,
2221 mv_store.clone(),
2222 self.clone(),
2223 true,
2224 self.get_sync_mode(),
2225 MAIN_DB_ID,
2226 crate::storage::wal::CheckpointMode::Truncate {
2228 upper_bound_inclusive: None,
2229 },
2230 );
2231 loop {
2232 match ckpt_sm.step(&()) {
2233 Ok(TransitionResult::Continue) => {}
2234 Ok(TransitionResult::Done(result)) => return Ok(result),
2235 Ok(TransitionResult::Io(iocompletions)) => {
2236 if let Err(err) = iocompletions.wait(io.as_ref()) {
2237 ckpt_sm.cleanup_after_external_io_error(err.clone())?;
2238 return Err(err);
2239 }
2240 }
2241 Err(err) => return Err(err),
2242 }
2243 }
2244 } else {
2245 self.pager
2246 .load()
2247 .blocking_checkpoint(mode, self.get_sync_mode())
2248 }
2249 }
2250
2251 pub fn close(&self) -> Result<()> {
2253 if self.is_closed() {
2254 return Ok(());
2255 }
2256 self.closed.store(true, Ordering::SeqCst);
2257 let pager = self.pager.load();
2258
2259 match self.get_tx_state() {
2260 TransactionState::None => {
2261 }
2263 _ => {
2264 if self.mvcc_enabled() {
2265 if let Some(mv_store) = self.mv_store().as_ref() {
2266 if let Some(tx_id) = self.get_mv_tx_id() {
2267 mv_store.rollback_tx(tx_id, pager.clone(), self, MAIN_DB_ID);
2268 }
2269 }
2270 pager.end_read_tx();
2271 } else {
2272 pager.rollback_tx(self);
2273 }
2274 self.rollback_attached_mvcc_txs(false);
2278 self.rollback_attached_wal_txns();
2279 self.set_tx_state(TransactionState::None);
2280 }
2281 }
2282 self.clear_mvcc_log_meta();
2283
2284 let is_memory_db = is_memory_like(&self.db.path);
2285 let should_checkpoint_on_close = pager
2286 .wal
2287 .as_ref()
2288 .is_none_or(|wal| wal.should_checkpoint_on_close());
2289 if self.db.n_connections.fetch_sub(1, Ordering::SeqCst).eq(&1)
2290 && !self.db.is_readonly()
2291 && !is_memory_db
2292 && should_checkpoint_on_close
2293 {
2294 self.pager
2295 .load()
2296 .checkpoint_shutdown(self.wal_auto_actions(), self.get_sync_mode())?;
2297 };
2298 Ok(())
2299 }
2300
2301 pub fn wal_auto_actions_disable(&self) {
2305 self.wal_auto_actions
2306 .store(WalAutoActions::empty().bits(), Ordering::SeqCst);
2307 }
2308
2309 pub fn wal_auto_actions(&self) -> WalAutoActions {
2313 if self.db.get_mv_store().is_some() {
2314 return WalAutoActions::empty();
2315 }
2316 WalAutoActions::from_bits_truncate(self.wal_auto_actions.load(Ordering::SeqCst))
2317 }
2318
2319 pub fn publish_schema_if_newer(&self) {
2322 let schema = self.schema.read().clone();
2323 self.db.update_schema_if_newer(schema);
2324 }
2325
2326 #[cfg(clt_turso_feature = "conn_raw_api")]
2333 pub fn publish_schema_after_external_restore(&self) -> Result<()> {
2334 if self.get_tx_state() != TransactionState::None {
2335 return Err(LimboError::Busy);
2336 }
2337 if self.get_mv_tx().is_some() || self.next_attached_mv_tx().is_some() {
2338 return Err(LimboError::Busy);
2339 }
2340
2341 let schema = self.schema.read().clone();
2342 self.db.with_schema_mut(|current| {
2343 *current = schema.as_ref().try_clone()?;
2344 Ok(())
2345 })?;
2346 Ok(())
2347 }
2348
2349 #[cfg(all(clt_turso_feature = "fs", clt_turso_feature = "conn_raw_api"))]
2352 pub fn reset_main_mvcc_tx_for_wal_session(&self) {
2353 let mv_store = self.mv_store();
2354 let Some(mv_store) = mv_store.as_ref() else {
2355 return;
2356 };
2357 let Some(tx_id) = self.get_mv_tx_id() else {
2358 return;
2359 };
2360 let pager = self.pager.load();
2361 mv_store.rollback_tx(tx_id, pager.clone(), self, MAIN_DB_ID);
2362 }
2363
2364 #[cfg(all(clt_turso_feature = "fs", clt_turso_feature = "conn_raw_api"))]
2367 pub fn discard_main_mvcc_tx_after_external_restore(&self) {
2368 let pager = self.pager.load();
2369 self.clear_internal_main_mvcc_tx(&pager);
2370 }
2371
2372 #[cfg(all(clt_turso_feature = "fs", clt_turso_feature = "conn_raw_api"))]
2374 pub fn has_main_mvcc_tx_for_wal_session(&self) -> bool {
2375 self.get_mv_tx_id().is_some()
2376 }
2377
2378 #[cfg(all(clt_turso_feature = "fs", clt_turso_feature = "conn_raw_api"))]
2381 pub fn commit_main_mvcc_tx_for_wal_session(self: &Arc<Self>) -> Result<()> {
2382 let mv_store_handle = self.mv_store();
2383 let Some(mv_store) = mv_store_handle.as_ref() else {
2384 return Ok(());
2385 };
2386 let Some(tx_id) = self.get_mv_tx_id() else {
2387 return Ok(());
2388 };
2389
2390 let mut state_machine = mv_store.commit_tx(tx_id, self, MAIN_DB_ID)?;
2391 while let IOResult::IO(io) = state_machine.step(mv_store)? {
2392 io.wait(self.db.io.as_ref())?;
2393 }
2394 assert!(state_machine.is_finalized());
2395 self.set_mv_tx(None);
2396 self.publish_schema_if_newer();
2397 Ok(())
2398 }
2399
2400 #[cfg(clt_turso_feature = "conn_raw_api")]
2401 pub fn reload_wal_after_external_restore(&self) -> Result<()> {
2402 self.db.reload_wal_after_external_restore()
2403 }
2404
2405 pub fn set_portable_logical_changes_enabled(&self, enabled: bool) {
2408 #[cfg(clt_turso_feature = "conn_raw_api")]
2409 {
2410 self.portable_logical_changes_enabled
2411 .store(enabled, Ordering::Release);
2412 }
2413 let _ = enabled;
2414 }
2415
2416 pub fn portable_logical_changes_enabled(&self) -> bool {
2417 #[cfg(clt_turso_feature = "conn_raw_api")]
2418 {
2419 self.portable_logical_changes_enabled
2420 .load(Ordering::Acquire)
2421 }
2422 #[cfg(not(clt_turso_feature = "conn_raw_api"))]
2423 {
2424 false
2425 }
2426 }
2427
2428 pub fn set_mvcc_log_meta(&self, key: String, value: Option<String>) {
2429 #[cfg(clt_turso_feature = "conn_raw_api")]
2430 {
2431 let mut metadata = self.mvcc_log_metadata.write();
2432 match value {
2433 Some(value) => {
2434 metadata.insert(key, value);
2435 }
2436 None => {
2437 metadata.remove(&key);
2438 }
2439 }
2440 }
2441 #[cfg(not(clt_turso_feature = "conn_raw_api"))]
2442 {
2443 let _ = (key, value);
2444 }
2445 }
2446
2447 #[cfg(clt_turso_feature = "conn_raw_api")]
2448 pub(crate) fn mvcc_log_meta_snapshot(&self) -> HashMap<String, String> {
2449 self.mvcc_log_metadata.read().clone()
2450 }
2451
2452 pub(crate) fn clear_mvcc_log_meta(&self) {
2453 #[cfg(clt_turso_feature = "conn_raw_api")]
2454 {
2455 self.mvcc_log_metadata.write().clear();
2456 }
2457 }
2458
2459 pub fn mvcc_log_meta(&self, key: &str) -> Option<String> {
2460 #[cfg(clt_turso_feature = "conn_raw_api")]
2461 {
2462 return self.mvcc_log_metadata.read().get(key).cloned();
2463 }
2464 #[cfg(not(clt_turso_feature = "conn_raw_api"))]
2465 {
2466 let _ = key;
2467 None
2468 }
2469 }
2470
2471 #[cfg(clt_turso_feature = "simulator")]
2472 pub fn checkpoint_for_testing(&self, mode: CheckpointMode) -> Result<CheckpointResult> {
2473 let pager = self.pager.load();
2474 pager
2475 .io
2476 .block(|| pager.checkpoint(mode, SyncMode::Full, true))
2477 }
2478
2479 #[cfg(all(
2480 clt_turso_feature = "simulator",
2481 target_pointer_width = "64",
2482 host_shared_wal
2483 ))]
2484 pub fn install_unpublished_backfill_proof_for_testing(
2485 &self,
2486 upper_bound_inclusive: u64,
2487 ) -> Result<()> {
2488 let pager = self.pager.load();
2489 let proof_nbackfills =
2490 pager.run_checkpoint_until_post_sync_gap_for_testing(CheckpointMode::Passive {
2491 upper_bound_inclusive: Some(upper_bound_inclusive),
2492 })?;
2493 let authority = self.db.shared_wal_coordination()?.ok_or_else(|| {
2494 LimboError::InternalError("shared WAL authority is unavailable".into())
2495 })?;
2496 let snapshot_before_publish = authority.snapshot();
2497 if snapshot_before_publish.nbackfills != 0 {
2498 return Err(LimboError::InternalError(
2499 "unpublished-proof setup requires nbackfills to remain unpublished".into(),
2500 ));
2501 }
2502
2503 let (db_size_pages, db_header_crc32c) = db_identity_for_testing(Path::new(&self.db.path))?;
2504 authority.install_backfill_proof(
2505 crate::storage::shared_wal_coordination::SharedWalCoordinationHeader {
2506 nbackfills: proof_nbackfills,
2507 ..snapshot_before_publish
2508 },
2509 db_size_pages,
2510 db_header_crc32c,
2511 );
2512 Ok(())
2513 }
2514
2515 pub fn last_insert_rowid(&self) -> i64 {
2516 self.last_insert_rowid.load(Ordering::SeqCst)
2517 }
2518
2519 pub(crate) fn update_last_rowid(&self, rowid: i64) {
2520 self.last_insert_rowid.store(rowid, Ordering::SeqCst);
2521 }
2522
2523 pub(crate) fn add_total_changes(&self, num_changes: i64) {
2524 self.total_changes.fetch_add(num_changes, Ordering::SeqCst);
2525 }
2526
2527 pub fn set_changes(&self, num_changes: i64) {
2528 self.changes.store(num_changes, Ordering::SeqCst);
2529 }
2530
2531 pub fn changes(&self) -> i64 {
2532 self.changes.load(Ordering::SeqCst)
2533 }
2534
2535 pub fn total_changes(&self) -> i64 {
2536 self.total_changes.load(Ordering::SeqCst)
2537 }
2538
2539 pub fn get_cache_size(&self) -> i32 {
2540 self.cache_size.load(Ordering::SeqCst)
2541 }
2542 pub fn set_cache_size(&self, size: i32) {
2543 self.cache_size.store(size, Ordering::SeqCst);
2544 self.bump_prepare_context_generation();
2545 }
2546
2547 pub fn get_capture_data_changes_info(
2548 &self,
2549 ) -> crate::sync::RwLockReadGuard<'_, Option<CaptureDataChangesInfo>> {
2550 self.capture_data_changes.read()
2551 }
2552 pub fn set_capture_data_changes_info(&self, opts: Option<CaptureDataChangesInfo>) {
2553 *self.capture_data_changes.write() = opts;
2554 self.bump_prepare_context_generation();
2555 }
2556 pub fn get_cdc_transaction_id(&self) -> i64 {
2557 self.cdc_transaction_id.load(Ordering::SeqCst)
2558 }
2559 pub fn set_cdc_transaction_id(&self, id: i64) {
2560 self.cdc_transaction_id.store(id, Ordering::SeqCst);
2561 }
2562 pub fn get_page_size(&self) -> PageSize {
2563 let value = self.page_size.load(Ordering::SeqCst);
2564 PageSize::new_from_header_u16(value).unwrap_or_default()
2565 }
2566
2567 pub fn is_closed(&self) -> bool {
2568 self.closed.load(Ordering::SeqCst)
2569 }
2570
2571 pub fn is_query_only(&self) -> bool {
2572 self.query_only.load(Ordering::SeqCst)
2573 }
2574
2575 pub fn get_database_canonical_path(&self) -> String {
2576 self.db.get_database_canonical_path()
2577 }
2578
2579 pub fn is_readonly(&self, index: usize) -> bool {
2581 match index {
2582 crate::MAIN_DB_ID => self.db.is_readonly(),
2583 crate::TEMP_DB_ID => self
2584 .temp
2585 .database
2586 .read()
2587 .as_ref()
2588 .is_some_and(|temp_db| temp_db.db.is_readonly()),
2589 _ => {
2590 let db = self.attached_databases.read().get_database_by_index(index);
2591 db.expect("Should never have called this without being sure the database exists")
2592 .is_readonly()
2593 }
2594 }
2595 }
2596
2597 pub fn reset_page_size(&self, size: u32) -> Result<()> {
2604 if self.db.initialized() {
2605 return Ok(());
2606 }
2607 let Some(size) = PageSize::new(size) else {
2608 return Ok(());
2609 };
2610
2611 self.page_size.store(size.get_raw(), Ordering::SeqCst);
2612 self.pager.load().set_initial_page_size(size)?;
2613 if let Some(mv_store) = self.db.get_mv_store().as_ref() {
2620 mv_store.set_global_page_size(size);
2621 }
2622 self.bump_prepare_context_generation();
2623
2624 Ok(())
2625 }
2626
2627 #[cfg(clt_turso_feature = "fs")]
2628 pub fn open_new(&self, path: &str, vfs: &str) -> Result<(Arc<dyn IO>, Arc<Database>)> {
2629 Database::open_with_vfs(&self.db, path, vfs)
2630 }
2631
2632 pub fn list_vfs(&self) -> Vec<String> {
2633 #[allow(unused_mut)]
2634 let mut all_vfs = vec![String::from("memory")];
2635 #[cfg(clt_turso_feature = "fs")]
2636 {
2637 #[cfg(target_family = "unix")]
2638 {
2639 all_vfs.push("syscall".to_string());
2640 }
2641 #[cfg(all(target_os = "linux", clt_turso_feature = "io_uring"))]
2642 {
2643 all_vfs.push("io_uring".to_string());
2644 }
2645 #[cfg(all(target_os = "windows", clt_turso_feature = "experimental_win_iocp"))]
2646 {
2647 all_vfs.push("experimental_win_iocp".to_string());
2648 }
2649 all_vfs.extend(crate::ext::list_vfs_modules());
2650 }
2651 all_vfs
2652 }
2653
2654 pub fn get_auto_commit(&self) -> bool {
2655 self.auto_commit.load(Ordering::SeqCst)
2656 }
2657
2658 pub(crate) fn mark_tx_poisoned(&self) {
2663 self.poisoned_tx.store(true, Ordering::SeqCst);
2664 }
2665
2666 pub(crate) fn tx_is_poisoned(&self) -> bool {
2668 self.poisoned_tx.load(Ordering::SeqCst)
2669 }
2670
2671 pub(crate) fn clear_tx_poison(&self) {
2673 self.poisoned_tx.store(false, Ordering::SeqCst);
2674 }
2675
2676 pub fn set_load_extension_enabled(&self, enabled: bool) {
2677 self.enable_load_extension.store(enabled, Ordering::Release);
2678 }
2679
2680 pub(crate) fn can_load_extensions(&self) -> bool {
2681 self.enable_load_extension.load(Ordering::Acquire)
2682 }
2683
2684 pub fn reparse_schema_after_extension_load(self: &Arc<Connection>) -> Result<()> {
2685 if self.is_closed() {
2686 return Err(LimboError::InternalError("Connection closed".to_string()));
2687 }
2688 let mut rows_data: Vec<(String, String, String, i64, Option<String>)> = Vec::new();
2693 {
2694 let mut rows = self
2695 .query("SELECT * FROM sqlite_schema")?
2696 .expect("query must be parsed to statement");
2697 rows.run_with_row_callback(|row| {
2698 let ty = row.get::<&str>(0)?.to_string();
2699 let name = row.get::<&str>(1)?.to_string();
2700 let table_name = row.get::<&str>(2)?.to_string();
2701 let root_page = row.get::<i64>(3)?;
2702 let sql = row.get::<&str>(4).ok().map(|s| s.to_string());
2703 rows_data.push((ty, name, table_name, root_page, sql));
2704 Ok(())
2705 })?;
2706 } let syms = self.syms.read();
2709 self.with_schema_mut(|schema| -> Result<()> {
2710 let mut from_sql_indexes = crate::alloc::vec![];
2715 let mut automatic_indices = HashMap::default();
2716 let mut dbsp_state_roots = HashMap::default();
2717 let mut dbsp_state_index_roots = HashMap::default();
2718 let mut materialized_view_info = HashMap::default();
2719
2720 let attached_resolver = |name: &str| -> Option<usize> {
2721 self.attached_databases
2722 .read()
2723 .get_database_by_name(&crate::util::normalize_ident(name))
2724 .map(|(idx, _)| idx)
2725 };
2726 for (ty, name, table_name, root_page, sql) in &rows_data {
2727 match schema.handle_schema_row(
2728 ty,
2729 name,
2730 table_name,
2731 *root_page,
2732 sql.as_deref(),
2733 &syms,
2734 &mut from_sql_indexes,
2735 &mut automatic_indices,
2736 &mut dbsp_state_roots,
2737 &mut dbsp_state_index_roots,
2738 &mut materialized_view_info,
2739 &attached_resolver,
2740 ) {
2741 Ok(()) => {}
2742 Err(LimboError::ParseError(msg)) if msg.contains("already exists") => {}
2743 Err(LimboError::ExtensionError(msg)) => {
2744 eprintln!("Warning: {msg}");
2745 }
2746 Err(e) => return Err(e),
2747 }
2748 }
2749
2750 match schema.populate_indices(&syms, from_sql_indexes, automatic_indices, false) {
2751 Ok(()) => {}
2752 Err(LimboError::ParseError(msg)) if msg.contains("already exists") => {}
2753 Err(LimboError::ExtensionError(msg)) => eprintln!("Warning: {msg}"),
2754 Err(e) => return Err(e),
2755 }
2756 match schema.populate_materialized_views(
2757 materialized_view_info,
2758 dbsp_state_roots,
2759 dbsp_state_index_roots,
2760 ) {
2761 Ok(()) => {}
2762 Err(LimboError::ExtensionError(msg)) => eprintln!("Warning: {msg}"),
2763 Err(e) => return Err(e),
2764 }
2765 Ok(())
2766 })?
2767 }
2768
2769 pub fn pragma_query(self: &Arc<Connection>, pragma_name: &str) -> Result<Vec<Vec<Value>>> {
2772 if self.is_closed() {
2773 return Err(LimboError::InternalError("Connection closed".to_string()));
2774 }
2775 let pragma = format!("PRAGMA {pragma_name}");
2776 let mut stmt = self.prepare(pragma)?;
2777 stmt.run_collect_rows()
2778 }
2779
2780 pub fn pragma_update<V: Display>(
2785 self: &Arc<Connection>,
2786 pragma_name: &str,
2787 pragma_value: V,
2788 ) -> Result<Vec<Vec<Value>>> {
2789 if self.is_closed() {
2790 return Err(LimboError::InternalError("Connection closed".to_string()));
2791 }
2792 let pragma = format!("PRAGMA {pragma_name} = {pragma_value}");
2793 let mut stmt = self.prepare(pragma)?;
2794 stmt.run_collect_rows()
2795 }
2796
2797 pub fn experimental_views_enabled(&self) -> bool {
2798 self.db.experimental_views_enabled()
2799 }
2800
2801 pub fn experimental_index_method_enabled(&self) -> bool {
2802 self.db.experimental_index_method_enabled()
2803 }
2804
2805 pub fn experimental_custom_types_enabled(&self) -> bool {
2806 self.db.experimental_custom_types_enabled()
2807 }
2808
2809 pub fn experimental_attach_enabled(&self) -> bool {
2810 self.db.experimental_attach_enabled()
2811 }
2812
2813 pub fn experimental_vacuum_enabled(&self) -> bool {
2814 self.db.experimental_vacuum_enabled()
2815 }
2816
2817 pub fn experimental_mvcc_passive_checkpoint_enabled(&self) -> bool {
2818 self.db.experimental_mvcc_passive_checkpoint_enabled()
2819 }
2820
2821 pub fn experimental_multiprocess_wal_enabled(&self) -> bool {
2822 self.db.experimental_multiprocess_wal_enabled()
2823 }
2824
2825 pub fn experimental_generated_columns_enabled(&self) -> bool {
2826 self.db.experimental_generated_columns_enabled()
2827 }
2828
2829 pub fn experimental_without_rowid_enabled(&self) -> bool {
2830 self.db.experimental_without_rowid_enabled()
2831 }
2832
2833 pub fn mvcc_enabled(&self) -> bool {
2834 self.db.mvcc_enabled()
2835 }
2836
2837 pub fn mv_store(&self) -> impl Deref<Target = Option<Arc<MvStore>>> {
2838 struct TransparentWrapper<T>(T);
2839
2840 impl<T> Deref for TransparentWrapper<T> {
2841 type Target = T;
2842
2843 fn deref(&self) -> &Self::Target {
2844 &self.0
2845 }
2846 }
2847
2848 if !self.is_mvcc_bootstrap_connection() {
2850 either::Left(self.db.get_mv_store())
2851 } else {
2852 either::Right(TransparentWrapper(None))
2853 }
2854 }
2855
2856 #[cfg(any(clt_turso_tests, injected_yields))]
2857 pub fn set_yield_injector(&self, injector: Option<Arc<dyn YieldInjector>>) {
2858 let mut slot = self.yield_injector.write();
2859 match injector {
2860 Some(injector) => {
2861 turso_assert!(
2862 slot.is_none(),
2863 "yield injector should be empty before installing a new one"
2864 );
2865 *slot = Some(injector);
2866 }
2867 None => {
2868 turso_assert!(
2869 slot.is_some(),
2870 "yield injector should be installed before it is cleared"
2871 );
2872 *slot = None;
2873 }
2874 }
2875 }
2876
2877 #[cfg(any(clt_turso_tests, injected_yields))]
2878 pub(crate) fn yield_injector(&self) -> Option<Arc<dyn YieldInjector>> {
2879 self.yield_injector.read().clone()
2880 }
2881
2882 #[cfg(any(clt_turso_tests, injected_yields))]
2883 pub fn set_failure_injector(&self, injector: Option<Arc<dyn FailureInjector>>) {
2884 let mut slot = self.failure_injector.write();
2885 match injector {
2886 Some(injector) => {
2887 turso_assert!(
2888 slot.is_none(),
2889 "failure injector should be empty before installing a new one"
2890 );
2891 *slot = Some(injector);
2892 }
2893 None => {
2894 turso_assert!(
2895 slot.is_some(),
2896 "failure injector should be installed before it is cleared"
2897 );
2898 *slot = None;
2899 }
2900 }
2901 }
2902
2903 #[cfg(any(clt_turso_tests, injected_yields))]
2904 pub(crate) fn failure_injector(&self) -> Option<Arc<dyn FailureInjector>> {
2905 self.failure_injector.read().clone()
2906 }
2907
2908 #[cfg(any(clt_turso_tests, injected_yields))]
2909 #[inline(always)]
2910 pub(crate) fn next_yield_instance_id(&self) -> u64 {
2911 self.yield_instance_id_counter
2912 .fetch_add(1, Ordering::Relaxed)
2913 }
2914
2915 pub fn pragma<V: Display>(
2922 self: &Arc<Connection>,
2923 pragma_name: &str,
2924 pragma_value: V,
2925 ) -> Result<Vec<Vec<Value>>> {
2926 if self.is_closed() {
2927 return Err(LimboError::InternalError("Connection closed".to_string()));
2928 }
2929 let pragma = format!("PRAGMA {pragma_name}({pragma_value})");
2930 let mut stmt = self.prepare(pragma)?;
2931 let mut results = Vec::new();
2932 loop {
2933 match stmt.step()? {
2934 vdbe::StepResult::Row => {
2935 let row: Vec<Value> = stmt.row().unwrap().get_values().cloned().collect();
2936 results.push(row);
2937 }
2938 vdbe::StepResult::Interrupt | vdbe::StepResult::Busy => {
2939 return Err(LimboError::Busy);
2940 }
2941 _ => break,
2942 }
2943 }
2944
2945 Ok(results)
2946 }
2947
2948 #[inline]
2949 pub fn with_schema_mut<T>(&self, f: impl FnOnce(&mut Schema) -> T) -> Result<T> {
2950 let mut schema_ref = self.schema.write();
2951 let schema = Schema::try_make_mut(&mut schema_ref)?;
2952 Ok(f(schema))
2953 }
2954
2955 pub(crate) fn with_database_schema_mut<T>(
2957 &self,
2958 database_id: usize,
2959 f: impl FnOnce(&mut Schema) -> T,
2960 ) -> Result<T> {
2961 match database_id {
2962 crate::MAIN_DB_ID => self.with_schema_mut(f),
2963 crate::TEMP_DB_ID => {
2964 let temp_db_guard = self.temp.database.read();
2968 let temp_db = temp_db_guard
2969 .as_ref()
2970 .expect("temp database should be initialized before schema mutation");
2971 let mut schema_guard = temp_db.db.schema.lock();
2972 let schema = Schema::try_make_mut(&mut schema_guard)?;
2973 let result = f(schema);
2974 self.bump_prepare_context_generation();
2975 Ok(result)
2976 }
2977 _ => {
2978 let mut schemas = self.database_schemas.write();
2983 let schema_arc = schemas.entry(database_id).or_insert_with(|| {
2984 let attached_dbs = self.attached_databases.read();
2985 let (db, _pager) = attached_dbs
2986 .index_to_data
2987 .get(&database_id)
2988 .expect("Database ID should be valid");
2989 let schema = db.schema.lock().clone();
2990 schema
2991 });
2992 let schema = Schema::try_make_mut(schema_arc)?;
2993 let result = f(schema);
2994 self.bump_prepare_context_generation();
2995 Ok(result)
2996 }
2997 }
2998 }
2999
3000 pub fn is_db_initialized(&self) -> bool {
3001 self.db.initialized()
3002 }
3003
3004 pub(crate) fn get_pager_from_database_index(&self, index: &usize) -> Result<Arc<Pager>> {
3005 match *index {
3006 crate::MAIN_DB_ID => Ok(self.pager.load().clone()),
3007 crate::TEMP_DB_ID => {
3008 if self.temp.database.read().is_none() {
3010 self.ensure_temp_database()?;
3011 }
3012 Ok(self
3013 .temp
3014 .database
3015 .read()
3016 .as_ref()
3017 .map(|temp_db| temp_db.pager.clone())
3018 .expect("temp database should be initialized after ensure_temp_database"))
3019 }
3020 _ => Ok(self.attached_databases.read().get_pager_by_index(index)),
3021 }
3022 }
3023
3024 pub(crate) fn get_database_name_by_index(&self, index: usize) -> Option<String> {
3027 match index {
3028 MAIN_DB_ID => Some("main".to_string()),
3029 TEMP_DB_ID => Some("temp".to_string()),
3030 _ => self.attached_databases.read().get_name_by_index(index),
3031 }
3032 }
3033
3034 pub(crate) fn get_database_id_by_name(&self, name: &str) -> Result<usize> {
3036 let normalized: String = crate::util::normalize_ident(name);
3037 match normalized.as_str() {
3038 "main" => Ok(MAIN_DB_ID),
3039 "temp" => Ok(TEMP_DB_ID),
3040 _ => self
3041 .attached_databases
3042 .read()
3043 .get_database_by_name(&normalized)
3044 .map(|(idx, _)| idx)
3045 .ok_or_else(|| LimboError::InvalidArgument(format!("no such database: {name}"))),
3046 }
3047 }
3048
3049 pub(crate) fn get_source_database(&self, database_id: usize) -> Arc<Database> {
3051 match database_id {
3052 MAIN_DB_ID => self.db.clone(),
3053 TEMP_DB_ID => self
3054 .temp
3055 .database
3056 .read()
3057 .as_ref()
3058 .map(|temp_db| temp_db.db.clone())
3059 .unwrap_or_else(|| self.db.clone()),
3060 _ => self
3061 .attached_databases
3062 .read()
3063 .get_database_by_index(database_id)
3064 .expect("database index should be valid"),
3065 }
3066 }
3067
3068 fn is_attached(&self, alias: &str) -> bool {
3069 self.attached_databases
3070 .read()
3071 .name_to_index
3072 .contains_key(alias)
3073 }
3074
3075 fn inherited_reserved_space_for_fresh_attach(&self) -> u8 {
3078 let pager = self.pager.load();
3079 pager
3080 .get_reserved_space()
3081 .unwrap_or_else(|| pager.io_ctx.read().get_reserved_space_bytes())
3082 }
3083
3084 fn minimum_reserved_space_for_fresh_attach(pager: &Pager) -> u8 {
3087 pager
3088 .get_reserved_space()
3089 .unwrap_or(0)
3090 .max(pager.io_ctx.read().get_reserved_space_bytes())
3091 }
3092
3093 fn database_has_existing_wal_state(db: &Database) -> bool {
3094 let shared_wal = db.shared_wal.read();
3095 shared_wal.page_size() != 0 || shared_wal.last_checksum_and_max_frame().1 != 0
3096 }
3097
3098 fn install_database_wal_on_pager(db: &Arc<Database>, pager: &mut Arc<Pager>) {
3099 let shared_wal = db.shared_wal.clone();
3100 let last_checksum_and_max_frame = shared_wal.read().last_checksum_and_max_frame();
3101 let wal = Arc::new(crate::storage::wal::WalFile::new(
3102 db.io.clone(),
3103 shared_wal,
3104 last_checksum_and_max_frame,
3105 db.buffer_pool.clone(),
3106 ));
3107
3108 let pager = Arc::get_mut(pager)
3109 .expect("fresh attached pager must not be shared before bootstrap or publication");
3110 pager.set_wal(wal);
3111 }
3112
3113 fn set_mvcc_journal_mode_fresh_db(pager: &Pager) -> Result<()> {
3114 turso_assert!(!pager.db_initialized());
3115 pager.set_initial_journal_version(crate::storage::sqlite3_ondisk::Version::Mvcc)
3116 }
3117
3118 fn validate_attach_target(db: &Database, is_fresh: bool, alias: &str) -> Result<()> {
3119 if is_fresh && Self::database_has_existing_wal_state(db) {
3120 return Err(LimboError::InvalidArgument(format!(
3121 "cannot attach database '{alias}': main database file is uninitialized but WAL state exists"
3122 )));
3123 }
3124
3125 if is_fresh && db.is_readonly() {
3126 return Err(LimboError::InvalidArgument(format!(
3127 "cannot attach database '{alias}': fresh read-only databases cannot be initialized during attach"
3128 )));
3129 }
3130 Ok(())
3131 }
3132
3133 fn apply_page_layout_to_fresh_attach_db(
3134 &self,
3135 alias: &str,
3136 attached_db_pager: &Pager,
3137 reserved_space: Option<u8>,
3138 ) -> Result<()> {
3139 let target_page_size = self.get_page_size();
3140 let attached_min_reserved_space =
3141 Self::minimum_reserved_space_for_fresh_attach(attached_db_pager);
3142 let target_reserved_space = match reserved_space {
3143 Some(space) => {
3144 if space < attached_min_reserved_space {
3146 return Err(LimboError::InvalidArgument(format!(
3147 "cannot attach database '{alias}': reserved space {space} is smaller than attached database minimum {attached_min_reserved_space}"
3148 )));
3149 }
3150 Some(space)
3151 }
3152 None => Some(
3153 self.inherited_reserved_space_for_fresh_attach()
3154 .max(attached_min_reserved_space),
3155 ),
3156 };
3157
3158 attached_db_pager.set_initial_page_size(target_page_size)?;
3159 if let Some(reserved_space) = target_reserved_space {
3160 attached_db_pager.set_reserved_space_bytes(reserved_space);
3161 }
3162 Ok(())
3163 }
3164
3165 fn reject_initialized_attach_mismatches(
3166 &self,
3167 alias: &str,
3168 db: &Database,
3169 pager: &Pager,
3170 ) -> Result<()> {
3171 if self.mvcc_enabled() != db.mvcc_enabled() {
3174 let main_mode = if self.mvcc_enabled() { "MVCC" } else { "WAL" };
3175 let attached_mode = if db.mvcc_enabled() { "MVCC" } else { "WAL" };
3176 return Err(LimboError::InvalidArgument(format!(
3177 "cannot attach database '{alias}': main database uses {main_mode} journal mode \
3178 but attached database uses {attached_mode}. Both must use the same journal mode."
3179 )));
3180 }
3181
3182 let main_pager = self.pager.load();
3185 if let (Some(main_ps), Some(attached_ps)) =
3186 (main_pager.get_page_size(), pager.get_page_size())
3187 {
3188 if main_ps != attached_ps {
3189 return Err(LimboError::InvalidArgument(format!(
3190 "cannot attach database '{alias}': page size mismatch \
3191 (main={main_ps:?}, attached={attached_ps:?})"
3192 )));
3193 }
3194 }
3195
3196 Ok(())
3197 }
3198
3199 fn reject_unsupported_fresh_mvcc_attach_durable_storage(
3200 &self,
3201 alias: &str,
3202 db: &Database,
3203 attached_is_fresh: bool,
3204 ) -> Result<()> {
3205 if attached_is_fresh
3206 && self.mvcc_enabled()
3207 && self.db.durable_storage.is_some()
3208 && db.durable_storage.is_none()
3209 {
3210 return Err(LimboError::InvalidArgument(format!(
3211 "cannot attach database '{alias}': fresh MVCC attach does not support inheriting custom durable storage"
3212 )));
3213 }
3214
3215 Ok(())
3216 }
3217
3218 #[cfg(not(clt_turso_feature = "fs"))]
3220 pub(crate) fn attach_database(
3221 &self,
3222 _path: &str,
3223 _alias: &str,
3224 _state: &mut AttachDatabaseState,
3225 ) -> Result<IOResult<()>> {
3226 Err(LimboError::InvalidArgument(
3227 "attach not available in this build (no-fs)".to_string(),
3228 ))
3229 }
3230
3231 #[cfg(not(clt_turso_feature = "fs"))]
3232 pub(crate) fn attach_database_with_config(
3233 &self,
3234 _path: &str,
3235 _alias: &str,
3236 _reserved_space: Option<u8>,
3237 _state: &mut AttachDatabaseState,
3238 ) -> Result<IOResult<()>> {
3239 self.attach_database(_path, _alias, _state)
3242 }
3243
3244 #[cfg(clt_turso_feature = "fs")]
3246 pub(crate) fn attach_database(
3247 &self,
3248 path: &str,
3249 alias: &str,
3250 state: &mut AttachDatabaseState,
3251 ) -> Result<IOResult<()>> {
3252 self.attach_database_with_config(path, alias, None, state)
3253 }
3254
3255 #[cfg(clt_turso_feature = "fs")]
3257 #[cfg_attr(not(clt_turso_tests), allow(dead_code))]
3258 pub(crate) fn attach_database_with_config(
3259 &self,
3260 path: &str,
3261 alias: &str,
3262 reserved_space: Option<u8>,
3263 state: &mut AttachDatabaseState,
3264 ) -> Result<IOResult<()>> {
3265 loop {
3266 match state {
3267 AttachDatabaseState::Start => {
3268 if self.is_closed() {
3269 return Err(LimboError::InternalError("Connection closed".to_string()));
3270 }
3271
3272 if self.is_attached(alias) {
3273 return Err(LimboError::InvalidArgument(format!(
3274 "database {alias} is already in use"
3275 )));
3276 }
3277
3278 if alias.eq_ignore_ascii_case("main") || alias.eq_ignore_ascii_case("temp") {
3279 return Err(LimboError::InvalidArgument(format!(
3280 "reserved name {alias} is already in use"
3281 )));
3282 }
3283
3284 let db_opts = DatabaseOpts::new()
3285 .with_views(self.db.experimental_views_enabled())
3286 .with_custom_types(self.db.experimental_custom_types_enabled())
3287 .with_index_method(self.db.experimental_index_method_enabled())
3288 .with_vacuum(self.db.experimental_vacuum_enabled())
3289 .with_generated_columns(self.db.experimental_generated_columns_enabled())
3290 .with_without_rowid(self.db.experimental_without_rowid_enabled());
3291 let is_memory_db = is_memory_like(path);
3292 let io: Arc<dyn IO> = if is_memory_db {
3293 Arc::new(MemoryIO::new())
3294 } else if self.db.is_in_memory_db() {
3295 Database::io_for_path(path)?
3296 } else {
3297 self.db.io.clone()
3298 };
3299 let main_db_flags = self.db.open_flags;
3300 let (db, encryption_opts) =
3301 Self::from_uri_attached(path, db_opts, main_db_flags, io)?;
3302 let attached_is_fresh = !db.initialized();
3303 if !is_memory_db {
3304 Self::validate_attach_target(&db, attached_is_fresh, alias)?;
3305 }
3306 self.reject_unsupported_fresh_mvcc_attach_durable_storage(
3307 alias,
3308 &db,
3309 attached_is_fresh,
3310 )?;
3311
3312 let encryption_key = if let Some(ref enc) = encryption_opts {
3313 Some(EncryptionKey::from_hex_string(&enc.hexkey)?)
3314 } else {
3315 None
3316 };
3317
3318 *state = AttachDatabaseState::Init(Box::new(AttachDatabaseInitState {
3319 alias: alias.to_string(),
3320 reserved_space,
3321 db,
3322 attached_is_fresh,
3323 encryption_key,
3324 init_st: crate::InitState::default(),
3325 }));
3326 }
3327 AttachDatabaseState::Init(init) => {
3328 let mut pager = Arc::new(crate::return_if_io!(init
3329 .db
3330 ._init_nonblock(&mut init.init_st, init.encryption_key.as_ref(),)));
3331
3332 if !init.attached_is_fresh {
3333 self.reject_initialized_attach_mismatches(&init.alias, &init.db, &pager)?;
3334 *state = AttachDatabaseState::Publish {
3335 alias: init.alias.clone(),
3336 db: init.db.clone(),
3337 pager,
3338 };
3339 continue;
3340 }
3341
3342 self.apply_page_layout_to_fresh_attach_db(
3343 &init.alias,
3344 &pager,
3345 init.reserved_space,
3346 )?;
3347
3348 if self.mvcc_enabled() && !init.db.mvcc_enabled() {
3349 Self::set_mvcc_journal_mode_fresh_db(&pager)?;
3350 Self::install_database_wal_on_pager(&init.db, &mut pager);
3351 let enc_ctx = pager.io_ctx.read().encryption_context().cloned();
3352 let mv_store = journal_mode::open_mv_store(
3353 init.db.io.clone(),
3354 &init.db.path,
3355 init.db.open_flags,
3356 init.db.durable_storage.clone(),
3357 enc_ctx,
3358 init.db.mv_store_allocator.clone(),
3359 init.db.experimental_mvcc_passive_checkpoint_enabled(),
3360 )?;
3361 init.db.mv_store.store(Some(mv_store));
3362 *state = AttachDatabaseState::Bootstrap(Box::new(
3363 AttachDatabaseBootstrapState {
3364 alias: init.alias.clone(),
3365 db: init.db.clone(),
3366 pager,
3367 encryption_key: init.encryption_key.take(),
3368 bootstrap_conn: None,
3369 bootstrap_st: crate::mvcc::database::BootstrapState::default(),
3370 },
3371 ));
3372 } else {
3373 *state = AttachDatabaseState::Publish {
3374 alias: init.alias.clone(),
3375 db: init.db.clone(),
3376 pager,
3377 };
3378 }
3379 }
3380 AttachDatabaseState::Bootstrap(bootstrap) => {
3381 if bootstrap.bootstrap_conn.is_none() {
3382 let default_cache_size = match bootstrap
3383 .pager
3384 .with_header(|header| header.default_page_cache_size)
3385 {
3386 Ok(IOResult::Done(default_cache_size)) => default_cache_size.get(),
3387 Ok(IOResult::IO(io)) => return Ok(IOResult::IO(io)),
3388 Err(_) => 0,
3389 };
3390 bootstrap.bootstrap_conn =
3391 Some(bootstrap.db._connect_with_pager_and_default_cache_size(
3392 true,
3393 bootstrap.pager.clone(),
3394 bootstrap.encryption_key.take(),
3395 default_cache_size,
3396 )?);
3397 }
3398
3399 let mv_store_guard = bootstrap.db.get_mv_store();
3400 let Some(mv_store) = mv_store_guard.as_ref() else {
3401 return Err(LimboError::InternalError(
3402 "fresh MVCC attach missing MV store".to_string(),
3403 ));
3404 };
3405 crate::return_if_io!(mv_store.bootstrap_nonblock(
3406 bootstrap
3407 .bootstrap_conn
3408 .as_ref()
3409 .expect("bootstrap connection initialized above"),
3410 &mut bootstrap.bootstrap_st,
3411 ));
3412
3413 *state = AttachDatabaseState::Publish {
3414 alias: bootstrap.alias.clone(),
3415 db: bootstrap.db.clone(),
3416 pager: bootstrap.pager.clone(),
3417 };
3418 }
3419 AttachDatabaseState::Publish { alias, db, pager } => {
3420 self.attached_databases
3421 .write()
3422 .insert(alias.as_str(), (db.clone(), pager.clone()));
3423 self.bump_prepare_context_generation();
3424 *state = AttachDatabaseState::Done;
3425 return Ok(IOResult::Done(()));
3426 }
3427 AttachDatabaseState::Done => {
3428 return Err(LimboError::InternalError(
3429 "attach_database called after completion".to_string(),
3430 ));
3431 }
3432 }
3433 }
3434 }
3435
3436 pub(crate) fn detach_database(&self, alias: &str) -> Result<()> {
3438 if self.is_closed() {
3439 return Err(LimboError::InternalError("Connection closed".to_string()));
3440 }
3441
3442 if alias == "main" || alias == "temp" {
3443 return Err(LimboError::InvalidArgument(format!(
3444 "cannot detach database: {alias}"
3445 )));
3446 }
3447
3448 let database_id = {
3453 let attached_dbs = self.attached_databases.read();
3454 match attached_dbs.name_to_index.get(alias).copied() {
3455 Some(id) => id,
3456 None => {
3457 return Err(LimboError::InvalidArgument(format!(
3458 "no such database: {alias}"
3459 )));
3460 }
3461 }
3462 };
3463
3464 let pager = self
3468 .get_pager_from_database_index(&database_id)
3469 .expect("attached database should always have a pager");
3470
3471 if pager.holds_read_lock() || pager.holds_write_lock() {
3472 return Err(LimboError::InvalidArgument(format!(
3473 "database {alias} is locked"
3474 )));
3475 }
3476
3477 if let Some((tx_id, _mode)) = self.get_mv_tx_for_db(database_id) {
3478 if let Some(mv_store) = self.mv_store_for_db(database_id) {
3479 mv_store.rollback_tx(tx_id, pager.clone(), self, database_id);
3480 pager.end_read_tx();
3481 }
3482 self.set_mv_tx_for_db(database_id, None);
3483 } else {
3484 pager.rollback_attached();
3486 }
3487
3488 {
3492 let mut attached_dbs = self.attached_databases.write();
3493 attached_dbs.remove(alias);
3494 }
3495
3496 self.database_schemas.write().remove(&database_id);
3499 self.bump_prepare_context_generation();
3500
3501 Ok(())
3502 }
3503
3504 pub fn list_attached_databases(&self) -> Vec<String> {
3506 self.attached_databases
3507 .read()
3508 .name_to_index
3509 .keys()
3510 .cloned()
3511 .collect()
3512 }
3513
3514 pub(crate) fn with_all_attached_pagers_with_index<F, R>(&self, f: F) -> R
3519 where
3520 F: FnOnce(&[(usize, Arc<Pager>)]) -> R,
3521 {
3522 let mut pagers: SmallVec<[(usize, Arc<Pager>); 8]> = SmallVec::new();
3523 if let Some(temp_db) = self.temp.database.read().as_ref() {
3524 pagers.push((crate::TEMP_DB_ID, temp_db.pager.clone()));
3525 }
3526 {
3527 let catalog = self.attached_databases.read();
3528 for (&idx, (_db, pager)) in catalog.index_to_data.iter() {
3529 pagers.push((idx, pager.clone()));
3530 }
3531 }
3532 f(&pagers)
3533 }
3534
3535 pub(crate) fn database_schemas(&self) -> &RwLock<HashMap<usize, Arc<Schema>>> {
3536 &self.database_schemas
3537 }
3538
3539 fn cached_non_main_schema(&self, database_id: usize) -> Arc<Schema> {
3540 turso_assert_ne!(database_id, crate::MAIN_DB_ID);
3541 if database_id == crate::TEMP_DB_ID {
3545 return self
3546 .temp
3547 .database
3548 .read()
3549 .as_ref()
3550 .map(|temp_db| temp_db.db.schema.lock().clone())
3551 .unwrap_or_else(|| self.empty_temp_schema());
3552 }
3553 if let Some(schema) = self.database_schemas.read().get(&database_id).cloned() {
3554 return schema;
3555 }
3556
3557 let attached_dbs = self.attached_databases.read();
3558 let (db, _pager) = attached_dbs
3559 .index_to_data
3560 .get(&database_id)
3561 .expect("Database ID should be valid after resolve_database_id");
3562 let schema = db.schema.lock().clone();
3563 schema
3564 }
3565
3566 pub(crate) fn publish_database_schema(&self, database_id: usize) {
3574 if database_id == crate::TEMP_DB_ID {
3575 return;
3576 }
3577 let mut schemas = self.database_schemas.write();
3578 if let Some(local_schema) = schemas.remove(&database_id) {
3579 let attached_dbs = self.attached_databases.read();
3580 if let Some((db, _pager)) = attached_dbs.index_to_data.get(&database_id) {
3581 *db.schema.lock() = local_schema;
3582 }
3583 self.bump_prepare_context_generation();
3584 }
3585 }
3586
3587 pub(crate) fn attached_databases(&self) -> &RwLock<DatabaseCatalog> {
3588 &self.attached_databases
3589 }
3590
3591 pub(crate) fn with_schema<T>(&self, database_id: usize, f: impl FnOnce(&Schema) -> T) -> T {
3593 match database_id {
3594 crate::MAIN_DB_ID => {
3595 let schema = self.schema.read();
3596 f(&schema)
3597 }
3598 _ => {
3599 let schema = self.cached_non_main_schema(database_id);
3600 f(&schema)
3601 }
3602 }
3603 }
3604
3605 pub(crate) fn clone_shared_schema(&self, database_id: usize) -> Arc<Schema> {
3615 if database_id == crate::MAIN_DB_ID {
3616 self.db.clone_schema()
3617 } else {
3618 self.attached_databases
3619 .read()
3620 .index_to_data
3621 .get(&database_id)
3622 .map(|(db, _)| db.schema.lock().clone())
3623 .unwrap_or_else(|| self.db.clone_schema())
3624 }
3625 }
3626
3627 fn get_canonical_path_for_database(db: &Database) -> String {
3629 if db.is_in_memory_db() {
3630 String::new()
3632 } else {
3633 match std::fs::canonicalize(&db.path) {
3635 Ok(abs_path) => abs_path.to_string_lossy().to_string(),
3636 Err(_) => db.path.to_string(),
3637 }
3638 }
3639 }
3640
3641 pub fn list_all_databases(&self) -> Vec<(usize, String, String)> {
3644 let mut databases = Vec::new();
3645
3646 let main_path = Self::get_canonical_path_for_database(&self.db);
3648 databases.push((MAIN_DB_ID, "main".to_string(), main_path));
3649
3650 if self.temp.database.read().is_some() {
3654 databases.push((crate::TEMP_DB_ID, "temp".to_string(), String::new()));
3655 }
3656
3657 let attached_dbs = self.attached_databases.read();
3659 for (alias, &seq_number) in attached_dbs.name_to_index.iter() {
3660 let file_path = if let Some((db, _pager)) = attached_dbs.index_to_data.get(&seq_number)
3661 {
3662 Self::get_canonical_path_for_database(db)
3663 } else {
3664 String::new()
3665 };
3666 databases.push((seq_number, alias.clone(), file_path));
3667 }
3668
3669 databases.sort_by_key(|&(seq, _, _)| seq);
3671 databases
3672 }
3673
3674 pub fn get_pager(&self) -> Arc<Pager> {
3675 self.pager.load().clone()
3676 }
3677
3678 pub fn get_query_only(&self) -> bool {
3679 self.is_query_only()
3680 }
3681
3682 pub fn set_query_only(&self, value: bool) {
3683 self.query_only.store(value, Ordering::SeqCst);
3684 self.bump_prepare_context_generation();
3685 }
3686
3687 pub fn set_vdbe_trace(&self, value: bool) {
3688 self.vdbe_trace.store(value, Ordering::SeqCst);
3689 }
3690
3691 pub fn get_vdbe_trace(&self) -> bool {
3692 self.vdbe_trace.load(Ordering::SeqCst)
3693 }
3694
3695 pub fn get_dml_require_where(&self) -> bool {
3696 self.dml_require_where.load(Ordering::SeqCst)
3697 }
3698
3699 pub fn set_dml_require_where(&self, value: bool) {
3700 self.dml_require_where.store(value, Ordering::SeqCst);
3701 }
3702
3703 pub fn get_dqs_dml(&self) -> bool {
3704 self.dqs_dml.load(Ordering::SeqCst)
3705 }
3706
3707 pub fn set_dqs_dml(&self, value: bool) {
3708 self.dqs_dml.store(value, Ordering::SeqCst);
3709 self.bump_prepare_context_generation();
3710 }
3711
3712 pub fn get_full_column_names(&self) -> bool {
3713 self.full_column_names.load(Ordering::SeqCst)
3714 }
3715
3716 pub fn set_full_column_names(&self, value: bool) {
3717 self.full_column_names.store(value, Ordering::SeqCst);
3718 self.bump_prepare_context_generation();
3719 }
3720
3721 pub fn get_short_column_names(&self) -> bool {
3722 self.short_column_names.load(Ordering::SeqCst)
3723 }
3724
3725 pub fn set_short_column_names(&self, value: bool) {
3726 self.short_column_names.store(value, Ordering::SeqCst);
3727 self.bump_prepare_context_generation();
3728 }
3729
3730 pub fn get_sync_mode(&self) -> SyncMode {
3731 self.sync_mode.get()
3732 }
3733
3734 pub fn set_sync_mode(&self, mode: SyncMode) {
3735 self.sync_mode.set(mode);
3736 self.bump_prepare_context_generation();
3737 }
3738
3739 pub fn get_temp_store(&self) -> crate::TempStore {
3740 self.temp_store.get()
3741 }
3742
3743 pub fn set_temp_store(&self, value: crate::TempStore) {
3744 if self.temp_store.get() == value {
3745 return;
3746 }
3747 self.reset_temp_database();
3748 self.temp_store.set(value);
3749 self.bump_prepare_context_generation();
3750 }
3751
3752 pub fn find_sequence(&self, name: &str) -> Result<Arc<crate::schema::Sequence>> {
3757 let (db_id, seq_name) = if let Some((schema, seq)) = name.split_once('.') {
3758 let db_id = self.get_database_id_by_name(schema)?;
3759 (db_id, crate::util::normalize_ident(seq))
3760 } else {
3761 (MAIN_DB_ID, crate::util::normalize_ident(name))
3762 };
3763
3764 self.with_schema(db_id, |schema| {
3765 schema.get_sequence(&seq_name).map(Arc::clone)
3766 })
3767 .ok_or_else(|| LimboError::ParseError(format!("sequence \"{name}\" does not exist")))
3768 }
3769
3770 pub fn set_sequence_currval(&self, name: &str, value: i64) {
3772 let normalized = crate::util::normalize_ident(name);
3773 self.sequence_currvals.write().insert(normalized, value);
3774 }
3775
3776 pub fn get_sequence_currval(&self, name: &str) -> Option<i64> {
3778 let normalized = crate::util::normalize_ident(name);
3779 self.sequence_currvals.read().get(&normalized).copied()
3780 }
3781
3782 pub fn clear_sequence_currval(&self, name: &str) {
3789 let normalized = crate::util::normalize_ident(name);
3790 self.sequence_currvals.write().remove(&normalized);
3791 }
3792
3793 pub fn sequence_inner_retries(&self) -> u64 {
3800 self.sequence_inner_retries
3801 .load(std::sync::atomic::Ordering::Relaxed)
3802 }
3803
3804 #[doc(hidden)]
3808 pub fn reset_sequence_inner_retries(&self) {
3809 self.sequence_inner_retries
3810 .store(0, std::sync::atomic::Ordering::Relaxed);
3811 }
3812
3813 pub(crate) fn load_sequence_descriptors_via_sql_nonblock(
3824 self: &Arc<Connection>,
3825 state: &mut LoadSequenceDescriptorsState,
3826 ) -> Result<crate::types::IOResult<()>> {
3827 use crate::types::IOResult;
3828 loop {
3829 match state {
3830 LoadSequenceDescriptorsState::Start => {
3831 let pending =
3837 self.with_schema(MAIN_DB_ID, |s| s.sequence_backing_table_names());
3838 *state = LoadSequenceDescriptorsState::Reading {
3839 pending,
3840 idx: 0,
3841 stmt: None,
3842 meta: None,
3843 seq: None,
3844 watermark_stmt: None,
3845 watermark_row: None,
3846 };
3847 }
3848 LoadSequenceDescriptorsState::Reading {
3849 pending,
3850 idx,
3851 stmt,
3852 meta,
3853 seq,
3854 watermark_stmt,
3855 watermark_row,
3856 } => loop {
3857 let entry = {
3858 if *idx >= pending.len() {
3859 return Ok(IOResult::Done(()));
3860 }
3861 pending[*idx].clone()
3862 };
3863 let (backing_table_name, seq_name) = entry;
3864 let normalized = crate::util::normalize_ident(&seq_name);
3865 let already_present =
3866 self.with_schema(MAIN_DB_ID, |s| s.get_sequence(&normalized).is_some());
3867 if already_present {
3868 *idx += 1;
3869 *stmt = None;
3870 *meta = None;
3871 *seq = None;
3872 *watermark_stmt = None;
3873 *watermark_row = None;
3874 continue;
3875 }
3876 if seq.is_none() {
3877 crate::return_if_io!(self.read_seq_descriptor_row_nonblock(
3878 &backing_table_name,
3879 &seq_name,
3880 stmt,
3881 meta,
3882 ));
3883 *seq = Some(Self::sequence_from_descriptor_meta(
3884 &seq_name,
3885 &backing_table_name,
3886 *meta,
3887 )?);
3888 *stmt = None;
3889 *meta = None;
3890 }
3891 let sequence = seq.as_ref().expect("sequence set above");
3892 crate::return_if_io!(self.read_sequence_watermark_row_nonblock(
3893 &backing_table_name,
3894 sequence,
3895 watermark_stmt,
3896 watermark_row,
3897 ));
3898 let watermark = Self::sequence_watermark_from_row(
3899 &backing_table_name,
3900 sequence,
3901 *watermark_row,
3902 )?;
3903 if let Some(mv_store) = self.db.get_mv_store().as_ref() {
3904 mv_store.set_sequence_watermark(&normalized, watermark);
3905 }
3906 let sequence = seq.take().expect("sequence set above");
3907 self.with_database_schema_mut(MAIN_DB_ID, |schema| {
3908 schema
3909 .sequences
3910 .insert(normalized.clone(), Arc::new(sequence));
3911 })?;
3912 *idx += 1;
3913 *stmt = None;
3914 *meta = None;
3915 *watermark_stmt = None;
3916 *watermark_row = None;
3917 },
3918 }
3919 }
3920 }
3921
3922 fn read_seq_descriptor_row_nonblock(
3931 self: &Arc<Connection>,
3932 backing_table_name: &str,
3933 seq_name: &str,
3934 stmt: &mut Option<Box<Statement>>,
3935 meta: &mut Option<(i64, i64, i64, i64, bool)>,
3936 ) -> Result<crate::types::IOResult<()>> {
3937 use crate::types::IOResult;
3938 if stmt.is_none() {
3939 let escaped = backing_table_name.replace('"', "\"\"");
3940 let sql = format!("SELECT start, inc, min, max, cycle FROM \"{escaped}\" LIMIT 1");
3941 let prepared = self.prepare_internal(sql).map_err(|err| {
3942 LimboError::Corrupt(format!(
3943 "internal sequence backing table \"{backing_table_name}\" for sequence \
3944 \"{seq_name}\": cannot prepare descriptor SELECT: {err}"
3945 ))
3946 })?;
3947 *stmt = Some(Box::new(prepared));
3948 *meta = None;
3952 }
3953 let s = stmt.as_mut().expect("stmt set above");
3954 match s.run_with_row_callback_nonblock(|row| {
3955 *meta = Some((
3956 row.get::<i64>(0)?,
3957 row.get::<i64>(1)?,
3958 row.get::<i64>(2)?,
3959 row.get::<i64>(3)?,
3960 row.get::<i64>(4)? != 0,
3961 ));
3962 Ok(())
3963 }) {
3964 Ok(IOResult::IO(io)) => Ok(IOResult::IO(io)),
3965 Ok(IOResult::Done(())) => Ok(IOResult::Done(())),
3966 Err(err) => Err(LimboError::Corrupt(format!(
3967 "internal sequence backing table \"{backing_table_name}\" for sequence \
3968 \"{seq_name}\": descriptor row read failed: {err}"
3969 ))),
3970 }
3971 }
3972
3973 fn sequence_from_descriptor_meta(
3977 seq_name: &str,
3978 backing_table_name: &str,
3979 meta: Option<(i64, i64, i64, i64, bool)>,
3980 ) -> Result<crate::schema::Sequence> {
3981 let (start, inc, min, max, cycle) = meta.ok_or_else(|| {
3982 LimboError::Corrupt(format!(
3983 "internal sequence backing table \"{backing_table_name}\" for sequence \
3984 \"{seq_name}\" is empty; the descriptor metadata row must always be present"
3985 ))
3986 })?;
3987 crate::schema::Sequence::new(
3988 seq_name.to_string(),
3989 Some(start),
3990 Some(inc),
3991 Some(min),
3992 Some(max),
3993 cycle,
3994 )
3995 .map_err(|err| {
3996 LimboError::Corrupt(format!(
3997 "internal sequence backing table \"{backing_table_name}\" for sequence \
3998 \"{seq_name}\" descriptor is invalid: {err}"
3999 ))
4000 })
4001 }
4002
4003 fn read_sequence_watermark_row_nonblock(
4008 self: &Arc<Connection>,
4009 backing_table_name: &str,
4010 seq: &crate::schema::Sequence,
4011 stmt: &mut Option<Box<Statement>>,
4012 row: &mut Option<(i64, bool)>,
4013 ) -> Result<crate::types::IOResult<()>> {
4014 use crate::types::IOResult;
4015 if stmt.is_none() {
4016 let escaped = backing_table_name.replace('"', "\"\"");
4017 let direction = if seq.increment_by >= 0 { "DESC" } else { "ASC" };
4018 let sql = format!(
4019 "SELECT value, is_called FROM \"{escaped}\" ORDER BY value {direction} LIMIT 1"
4020 );
4021 let prepared = self.prepare_internal(sql).map_err(|err| {
4022 LimboError::Corrupt(format!(
4023 "internal sequence backing table \"{backing_table_name}\" for sequence \
4024 \"{}\": cannot prepare watermark SELECT: {err}",
4025 seq.name
4026 ))
4027 })?;
4028 *stmt = Some(Box::new(prepared));
4029 *row = None;
4030 }
4031 let s = stmt.as_mut().expect("stmt set above");
4032 match s.run_with_row_callback_nonblock(|r| {
4033 let value = r.get::<i64>(0)?;
4034 let is_called = r.get::<i64>(1)? != 0;
4035 *row = Some((value, is_called));
4036 Ok(())
4037 }) {
4038 Ok(IOResult::IO(io)) => Ok(IOResult::IO(io)),
4039 Ok(IOResult::Done(())) => Ok(IOResult::Done(())),
4040 Err(err) => Err(LimboError::Corrupt(format!(
4041 "internal sequence backing table \"{backing_table_name}\" for sequence \
4042 \"{}\": watermark row read failed: {err}",
4043 seq.name
4044 ))),
4045 }
4046 }
4047
4048 fn sequence_watermark_from_row(
4049 backing_table_name: &str,
4050 seq: &crate::schema::Sequence,
4051 row: Option<(i64, bool)>,
4052 ) -> Result<i64> {
4053 let (value, is_called) = row.ok_or_else(|| {
4054 LimboError::Corrupt(format!(
4055 "internal sequence backing table \"{backing_table_name}\" for sequence \
4056 \"{}\" is empty; cannot derive sequence watermark",
4057 seq.name
4058 ))
4059 })?;
4060 Ok(crate::mvcc::database::first_unsafe_sequence_watermark(
4061 seq, value, is_called,
4062 ))
4063 }
4064
4065 pub(crate) fn sync_autoincrement_backing_tables_from_sqlite_sequence_nonblock(
4095 self: &Arc<Connection>,
4096 state: &mut SyncAutoincrementState,
4097 ) -> Result<crate::types::IOResult<()>> {
4098 use crate::schema::{autoincrement_sequence_name, SQLITE_SEQUENCE_TABLE_NAME};
4099 use crate::translate::sequence::sequence_backing_table_name;
4100 use crate::types::IOResult;
4101
4102 loop {
4103 match state {
4104 SyncAutoincrementState::Start => {
4105 let has_seq_table = self.with_schema(MAIN_DB_ID, |s| {
4106 s.get_btree_table(SQLITE_SEQUENCE_TABLE_NAME).is_some()
4107 });
4108 if !has_seq_table {
4109 return Ok(IOResult::Done(()));
4110 }
4111 let stmt = self.prepare_internal(format!(
4112 "SELECT name, seq FROM {SQLITE_SEQUENCE_TABLE_NAME}"
4113 ))?;
4114 *state = SyncAutoincrementState::ReadSeqRows {
4115 stmt: Box::new(stmt),
4116 rows: Vec::new(),
4117 };
4118 }
4119 SyncAutoincrementState::ReadSeqRows { stmt, rows } => {
4120 crate::return_if_io!(stmt.run_with_row_callback_nonblock(|row| {
4121 let name = row.get::<&str>(0)?.to_string();
4122 let seq = row.get::<i64>(1)?;
4123 rows.push((name, seq));
4124 Ok(())
4125 }));
4126 let rows = std::mem::take(rows);
4127 *state = SyncAutoincrementState::Process {
4128 rows,
4129 idx: 0,
4130 sub: SyncRowStep::Start,
4131 };
4132 }
4133 SyncAutoincrementState::Process { rows, idx, sub } => {
4134 if *idx >= rows.len() {
4135 return Ok(IOResult::Done(()));
4136 }
4137 match sub {
4138 SyncRowStep::Start => {
4139 let backing_table_name = sequence_backing_table_name(
4140 &autoincrement_sequence_name(&rows[*idx].0),
4141 );
4142 let has_backing = self.with_schema(MAIN_DB_ID, |s| {
4143 s.get_btree_table(&backing_table_name).is_some()
4144 });
4145 if !has_backing {
4146 *idx += 1;
4147 continue;
4148 }
4149 let escaped = backing_table_name.replace('"', "\"\"");
4152 let stmt = self.prepare_internal(format!(
4153 "SELECT MAX(value) FROM \"{escaped}\""
4154 ))?;
4155 *sub = SyncRowStep::ReadMax {
4156 backing_table_name,
4157 stmt: Box::new(stmt),
4158 current_max: None,
4159 };
4160 }
4161 SyncRowStep::ReadMax {
4162 backing_table_name,
4163 stmt,
4164 current_max,
4165 } => {
4166 crate::return_if_io!(stmt.run_with_row_callback_nonblock(|row| {
4167 if let crate::Value::Numeric(crate::Numeric::Integer(v)) =
4168 row.get_value(0)
4169 {
4170 *current_max = Some(*v);
4171 }
4172 Ok(())
4173 }));
4174 let watermark = rows[*idx].1;
4175 if matches!(*current_max, Some(c) if c > watermark) {
4184 *idx += 1;
4185 *sub = SyncRowStep::Start;
4186 continue;
4187 }
4188 let escaped = backing_table_name.replace('"', "\"\"");
4193 let insert_sql = format!(
4194 "INSERT OR REPLACE INTO \"{escaped}\"\
4195 (value, is_called, start, inc, min, max, cycle) \
4196 VALUES ({watermark}, 1, 1, 1, 1, {}, 0)",
4197 i64::MAX
4198 );
4199 let stmt = self.prepare_internal(insert_sql)?;
4200 *sub = SyncRowStep::Upsert {
4201 stmt: Box::new(stmt),
4202 };
4203 }
4204 SyncRowStep::Upsert { stmt } => {
4205 crate::return_if_io!(stmt.run_with_row_callback_nonblock(|_| Ok(())));
4206 if let Some(mv_store) = self.db.get_mv_store().as_ref() {
4207 let watermark = rows[*idx].1;
4208 let first_unsafe = watermark.checked_add(1).unwrap_or(watermark);
4209 mv_store.set_sequence_watermark(
4210 &autoincrement_sequence_name(&rows[*idx].0),
4211 first_unsafe,
4212 );
4213 }
4214 *idx += 1;
4215 *sub = SyncRowStep::Start;
4216 }
4217 }
4218 }
4219 }
4220 }
4221 }
4222
4223 #[cfg(not(target_family = "wasm"))]
4230 pub(crate) fn create_tempdir(&self) -> Result<TempDir> {
4231 let res = if let Some(d) = std::env::var_os("TURSO_TMPDIR") {
4232 tempfile::tempdir_in(d)
4233 } else if let Some(d) = std::env::var_os("SQLITE_TMPDIR") {
4234 tempfile::tempdir_in(d)
4235 } else {
4236 tempfile::tempdir()
4237 };
4238 res.map_err(|e| io_error(e, "tempdir"))
4239 }
4240
4241 pub fn get_data_sync_retry(&self) -> bool {
4242 self.data_sync_retry
4243 .load(crate::sync::atomic::Ordering::SeqCst)
4244 }
4245
4246 pub fn set_data_sync_retry(&self, value: bool) {
4247 self.data_sync_retry
4248 .store(value, crate::sync::atomic::Ordering::SeqCst);
4249 self.bump_prepare_context_generation();
4250 }
4251
4252 pub fn get_sync_type(&self) -> crate::io::FileSyncType {
4254 self.pager.load().get_sync_type()
4255 }
4256
4257 pub fn set_sync_type(&self, value: crate::io::FileSyncType) {
4259 self.pager.load().set_sync_type(value);
4260 }
4261
4262 pub fn get_syms_vtab_mods(&self) -> HashSet<String> {
4264 self.syms.read().vtab_modules.keys().cloned().collect()
4265 }
4266
4267 pub fn get_syms_functions(&self) -> Vec<(String, bool, i32, bool)> {
4269 self.syms
4270 .read()
4271 .functions
4272 .values()
4273 .map(|f| {
4274 let is_agg = f.func.is_aggregate();
4275 let argc = match &f.func {
4276 function::ExtFunc::Aggregate { argc, .. } => *argc,
4277 function::ExtFunc::Scalar { argc, .. } => *argc,
4278 };
4279 (
4280 f.name.clone(),
4281 is_agg,
4282 argc,
4283 function::Deterministic::is_deterministic(f.as_ref()),
4284 )
4285 })
4286 .collect()
4287 }
4288
4289 pub fn register_external_collation(
4290 &self,
4291 name: String,
4292 context: usize,
4293 callback: crate::ContextCollationFunction,
4294 context_destructor: Option<crate::ContextDestructor>,
4295 ) {
4296 let collation = CollationSeq::custom(&name);
4297 let normalized_name = crate::util::normalize_ident(&name);
4298 self.syms.write().collations.insert(
4299 collation.id(),
4300 Arc::new(function::ExternalCollation::new(
4301 normalized_name,
4302 context,
4303 callback,
4304 context_destructor,
4305 )),
4306 );
4307 self.bump_prepare_context_generation();
4308 }
4309
4310 pub fn unregister_external_collation(&self, name: &str) {
4311 if let Some(collation) = CollationSeq::known_custom(name) {
4312 if self
4313 .syms
4314 .write()
4315 .collations
4316 .remove(&collation.id())
4317 .is_some()
4318 {
4319 self.bump_prepare_context_generation();
4320 }
4321 }
4322 }
4323
4324 pub(crate) fn get_external_collation(
4325 &self,
4326 collation: CollationSeq,
4327 ) -> Result<Arc<function::ExternalCollation>> {
4328 self.syms
4329 .read()
4330 .collations
4331 .get(&collation.id())
4332 .cloned()
4333 .ok_or_else(|| {
4334 LimboError::ParseError(format!("no such collation sequence: {}", collation.name()))
4335 })
4336 }
4337
4338 pub(crate) fn custom_collation_compare(
4339 external: &function::ExternalCollation,
4340 left: &str,
4341 right: &str,
4342 ) -> CmpOrdering {
4343 let result = unsafe {
4344 (external.callback)(
4345 external.context,
4346 left.as_ptr(),
4347 left.len(),
4348 right.as_ptr(),
4349 right.len(),
4350 )
4351 };
4352 result.cmp(&0)
4353 }
4354
4355 pub(crate) fn external_collation_comparator(
4356 external: Arc<function::ExternalCollation>,
4357 ) -> crate::vdbe::sorter::SortComparator {
4358 Arc::new(move |left, right| {
4359 Ok(match (left, right) {
4360 (crate::ValueRef::Text(left), crate::ValueRef::Text(right)) => {
4361 Self::custom_collation_compare(&external, left.as_str(), right.as_str())
4362 }
4363 _ => left.partial_cmp(right).unwrap_or(CmpOrdering::Equal),
4364 })
4365 })
4366 }
4367
4368 pub(crate) fn make_collation_comparator(
4369 &self,
4370 collation: CollationSeq,
4371 ) -> Result<crate::vdbe::sorter::SortComparator> {
4372 let external = self.get_external_collation(collation)?;
4373 Ok(Self::external_collation_comparator(external))
4374 }
4375
4376 pub(crate) fn compare_external_collation(
4377 &self,
4378 collation: CollationSeq,
4379 left: &str,
4380 right: &str,
4381 ) -> Result<CmpOrdering> {
4382 let external = self.get_external_collation(collation)?;
4383 Ok(Self::custom_collation_compare(&external, left, right))
4384 }
4385
4386 pub(crate) fn database_ptr(&self) -> usize {
4387 Arc::as_ptr(&self.db) as usize
4388 }
4389
4390 pub fn set_encryption_key(&self, key: EncryptionKey) -> Result<()> {
4391 tracing::trace!("setting encryption key for connection");
4392 self.ensure_can_change_encryption_settings()?;
4393 *self.encryption_key.write() = Some(key);
4394 self.bump_prepare_context_generation();
4395 self.set_encryption_context()
4396 }
4397
4398 pub fn set_encryption_cipher(&self, cipher_mode: CipherMode) -> Result<()> {
4399 tracing::trace!("setting encryption cipher for connection");
4400 self.ensure_can_change_encryption_settings()?;
4401 self.encryption_cipher_mode.set(cipher_mode);
4402 self.bump_prepare_context_generation();
4403 self.set_encryption_context()
4404 }
4405
4406 pub fn set_reserved_bytes(&self, reserved_bytes: u8) -> Result<()> {
4407 let pager = self.pager.load();
4408 pager.set_reserved_space_bytes(reserved_bytes);
4409 Ok(())
4410 }
4411
4412 pub fn get_reserved_bytes(&self) -> Option<u8> {
4415 let pager = self.pager.load();
4416 pager.get_reserved_space()
4417 }
4418
4419 pub fn get_encryption_cipher_mode(&self) -> Option<CipherMode> {
4420 match self.encryption_cipher_mode.get() {
4421 CipherMode::None => None,
4422 mode => Some(mode),
4423 }
4424 }
4425
4426 fn ensure_can_change_encryption_settings(&self) -> Result<()> {
4427 let pager = self.pager.load();
4428 if pager.is_encryption_ctx_set() {
4429 return Err(LimboError::InvalidArgument(
4430 "cannot reset encryption attributes if already set in the session".to_string(),
4431 ));
4432 }
4433 if self.db.get_mv_store().is_some() {
4434 return Err(LimboError::InvalidArgument(
4435 "cannot enable encryption after MVCC is active; configure encryption before PRAGMA journal_mode='mvcc'"
4436 .to_string(),
4437 ));
4438 }
4439 Ok(())
4440 }
4441
4442 fn set_encryption_context(&self) -> Result<()> {
4444 let key_guard = self.encryption_key.read();
4445 let Some(key) = key_guard.as_ref() else {
4446 return Ok(());
4447 };
4448 let cipher_mode = self.get_encryption_cipher_mode();
4449 let Some(cipher_mode) = cipher_mode else {
4450 return Ok(());
4451 };
4452 tracing::trace!("setting encryption ctx for connection");
4453 let pager = self.pager.load();
4454 pager.set_encryption_context(cipher_mode, key)
4455 }
4456
4457 pub fn set_busy_handler(&self, handler: Option<BusyHandlerCallback>) {
4459 *self.busy_handler.write() = match handler {
4460 Some(callback) => BusyHandler::Custom { callback },
4461 None => BusyHandler::None,
4462 };
4463 self.bump_prepare_context_generation();
4464 }
4465
4466 pub fn set_busy_timeout(&self, duration: Duration) {
4468 *self.busy_handler.write() = if duration.is_zero() {
4469 BusyHandler::None
4470 } else {
4471 BusyHandler::Timeout(duration)
4472 };
4473 self.bump_prepare_context_generation();
4474 }
4475
4476 pub fn get_busy_timeout(&self) -> Duration {
4478 match &*self.busy_handler.read() {
4479 BusyHandler::Timeout(d) => *d,
4480 _ => Duration::ZERO,
4481 }
4482 }
4483
4484 pub fn set_query_timeout(&self, duration: Duration) {
4487 let millis = duration.as_millis().min(u128::from(u64::MAX)) as u64;
4488 self.query_timeout_ms.store(millis, Ordering::SeqCst);
4489 }
4490
4491 pub fn get_query_timeout(&self) -> Duration {
4493 Duration::from_millis(self.query_timeout_ms.load(Ordering::SeqCst))
4494 }
4495
4496 pub fn get_busy_handler(&self) -> crate::sync::RwLockReadGuard<'_, BusyHandler> {
4498 self.busy_handler.read()
4499 }
4500
4501 pub fn set_progress_handler(&self, ops: u64, handler: Option<ProgressHandlerCallback>) {
4504 self.progress_handler.set(ops, handler);
4505 }
4506
4507 pub fn should_interrupt_for_progress(&self, vm_steps: u64) -> bool {
4509 self.progress_handler.should_interrupt(vm_steps)
4510 }
4511
4512 pub fn interrupt(&self) {
4515 if self.n_active_root_statements.load(Ordering::SeqCst) > 0 {
4516 self.interrupt_requested.store(true, Ordering::SeqCst);
4517 }
4518 }
4519
4520 pub fn is_interrupted(&self) -> bool {
4522 self.interrupt_requested.load(Ordering::SeqCst)
4523 }
4524
4525 pub(crate) fn clear_interrupt_if_idle(&self) {
4527 if self.n_active_root_statements.load(Ordering::SeqCst) == 0 {
4528 self.interrupt_requested.store(false, Ordering::SeqCst);
4529 }
4530 }
4531
4532 pub(crate) fn set_tx_state(&self, state: TransactionState) {
4533 self.transaction_state.set(state);
4534 }
4535
4536 pub(crate) fn get_tx_state(&self) -> TransactionState {
4537 self.transaction_state.get()
4538 }
4539
4540 pub fn is_in_write_tx(&self) -> bool {
4543 matches!(self.get_tx_state(), TransactionState::Write { .. })
4544 }
4545
4546 pub(crate) fn get_mv_tx_id(&self) -> Option<u64> {
4547 self.mv_tx.read().map(|(tx_id, _)| tx_id)
4548 }
4549
4550 pub(crate) fn get_mv_tx(&self) -> Option<(u64, TransactionMode)> {
4551 *self.mv_tx.read()
4552 }
4553
4554 #[inline(always)]
4555 pub(crate) fn set_mv_tx(&self, tx_id_and_mode: Option<(u64, TransactionMode)>) {
4556 tracing::debug!("set_mv_tx: {:?}", tx_id_and_mode);
4557 *self.mv_tx.write() = tx_id_and_mode;
4558 }
4559
4560 pub(crate) fn get_mv_tx_id_for_db(&self, db: usize) -> Option<u64> {
4563 if db == crate::MAIN_DB_ID {
4564 self.get_mv_tx_id()
4565 } else {
4566 self.attached_mv_txs
4567 .read()
4568 .get(&db)
4569 .map(|(tx_id, _)| *tx_id)
4570 }
4571 }
4572
4573 pub(crate) fn get_mv_tx_for_db(&self, db: usize) -> Option<(u64, TransactionMode)> {
4575 if db == crate::MAIN_DB_ID {
4576 self.get_mv_tx()
4577 } else {
4578 self.attached_mv_txs.read().get(&db).copied()
4579 }
4580 }
4581
4582 pub(crate) fn set_mv_tx_for_db(&self, db: usize, val: Option<(u64, TransactionMode)>) {
4584 if db == crate::MAIN_DB_ID {
4585 self.set_mv_tx(val);
4586 } else {
4587 let mut txs = self.attached_mv_txs.write();
4588 match val {
4589 Some(v) => {
4590 txs.insert(db, v);
4591 }
4592 None => {
4593 txs.remove(&db);
4594 }
4595 }
4596 }
4597 }
4598
4599 pub(crate) fn rollback_attached_mvcc_txs(&self, clear_schemas: bool) {
4608 let txs: HashMap<usize, _> = self.attached_mv_txs.read().clone();
4609 let mut cleared_any_schema = false;
4610 for (&db_id, &(tx_id, _mode)) in &txs {
4611 if let Some(attached_mv_store) = self.mv_store_for_db(db_id) {
4612 let attached_pager = self
4613 .get_pager_from_database_index(&db_id)
4614 .expect("attached MVCC transaction should always have a pager");
4615 if attached_mv_store.is_tx_rollbackable(tx_id) {
4616 attached_mv_store.rollback_tx(tx_id, attached_pager.clone(), self, db_id);
4617 } else {
4618 self.set_mv_tx_for_db(db_id, None);
4619 }
4620 if clear_schemas {
4621 self.database_schemas().write().remove(&db_id);
4622 cleared_any_schema = true;
4623 }
4624 attached_pager.end_read_tx();
4625 }
4626 }
4627 self.attached_mv_txs.write().clear();
4628 if cleared_any_schema {
4629 self.bump_prepare_context_generation();
4630 }
4631 }
4632
4633 pub(crate) fn rollback_attached_wal_txns(&self) {
4637 self.with_all_attached_pagers_with_index(|pagers| {
4638 let mut wal_indices: SmallVec<[usize; 4]> = SmallVec::new();
4642 for (i, (db_id, _)) in pagers.iter().enumerate() {
4643 if self.mv_store_for_db(*db_id).is_none() {
4644 wal_indices.push(i);
4645 }
4646 }
4647 if wal_indices.is_empty() {
4648 return;
4649 }
4650 {
4651 let mut schemas = self.database_schemas().write();
4652 for &i in &wal_indices {
4653 schemas.remove(&pagers[i].0);
4654 }
4655 }
4656 self.bump_prepare_context_generation();
4657 for &i in &wal_indices {
4658 pagers[i].1.rollback_attached();
4659 }
4660 });
4661 }
4662
4663 pub(crate) fn with_named_savepoints<F, T>(&self, f: F) -> T
4664 where
4665 F: FnOnce(&[NamedSavepointFrame]) -> T,
4666 {
4667 let savepoints = self.named_savepoints.read();
4668 f(&savepoints)
4669 }
4670
4671 pub(crate) fn push_named_savepoint(&self, frame: NamedSavepointFrame) {
4672 self.named_savepoints.write().push(frame);
4673 }
4674
4675 pub(crate) fn with_savepoint_schema_snapshot<F, T>(&self, f: F) -> T
4681 where
4682 F: FnOnce(Arc<Schema>, Option<Arc<Schema>>, HashMap<usize, Arc<Schema>>) -> T,
4683 {
4684 let main_schema_snapshot = self.schema.read().clone();
4685 let temp_schema_snapshot = self
4686 .temp
4687 .database
4688 .read()
4689 .as_ref()
4690 .map(|temp_db| temp_db.db.schema.lock().clone());
4691 let staged_schema_snapshot = self.database_schemas.read().clone();
4692 f(
4693 main_schema_snapshot,
4694 temp_schema_snapshot,
4695 staged_schema_snapshot,
4696 )
4697 }
4698
4699 pub(crate) fn release_named_savepoint_frame(&self, name: &str) -> SavepointResult {
4700 let mut savepoints = self.named_savepoints.write();
4701 let Some(target_idx) = savepoints
4702 .iter()
4703 .rposition(|savepoint| savepoint.name == name)
4704 else {
4705 return SavepointResult::NotFound;
4706 };
4707 if savepoints[target_idx].starts_transaction && target_idx == 0 {
4708 return SavepointResult::Commit;
4709 }
4710 savepoints.truncate(target_idx);
4711 SavepointResult::Release
4712 }
4713
4714 pub(crate) fn rollback_named_savepoint_frame(&self, name: &str) -> Option<RollbackFrameInfo> {
4715 let mut savepoints = self.named_savepoints.write();
4716 let target_idx = savepoints
4717 .iter()
4718 .rposition(|savepoint| savepoint.name == name)?;
4719 let frame = &savepoints[target_idx];
4720 let info = RollbackFrameInfo {
4721 main_schema_snapshot: frame.main_schema_snapshot.clone(),
4722 temp_schema_snapshot: frame.temp_schema_snapshot.clone(),
4723 staged_schema_snapshot: frame.staged_schema_snapshot.clone(),
4724 };
4725 savepoints.truncate(target_idx + 1);
4728 Some(info)
4729 }
4730
4731 pub(crate) fn clear_named_savepoints(&self) {
4732 self.named_savepoints.write().clear();
4733 }
4734
4735 pub(crate) fn rollback_current_txn_state(
4738 &self,
4739 pager: &Arc<Pager>,
4740 clear_attached_schemas: bool,
4741 ) {
4742 if let Some(mv_store) = self.mv_store().as_ref() {
4743 if let Some(tx_id) = self.get_mv_tx_id() {
4744 self.auto_commit.store(true, Ordering::SeqCst);
4745 if mv_store.is_tx_rollbackable(tx_id) {
4746 mv_store.rollback_tx(tx_id, pager.clone(), self, crate::MAIN_DB_ID);
4747 } else {
4748 self.set_mv_tx(None);
4749 }
4750 }
4751 pager.end_read_tx();
4752 self.rollback_attached_mvcc_txs(clear_attached_schemas);
4753 } else {
4754 pager.rollback_tx(self);
4755 self.auto_commit.store(true, Ordering::SeqCst);
4756 }
4757 self.rollback_attached_wal_txns();
4758 self.set_tx_state(TransactionState::None);
4759 self.clear_tx_poison();
4760 }
4761
4762 pub(crate) fn rollback_manual_txn_cleanup(
4769 &self,
4770 pager: &Arc<Pager>,
4771 clear_attached_schemas: bool,
4772 ) {
4773 let main_has_implicit_state = self.get_tx_state() != TransactionState::None
4774 || self.get_mv_tx().is_some()
4775 || pager.holds_read_lock()
4776 || pager.holds_write_lock();
4777
4778 if main_has_implicit_state {
4779 self.rollback_current_txn_state(pager, clear_attached_schemas);
4780 } else {
4781 if self.next_attached_mv_tx().is_some() {
4782 self.rollback_attached_mvcc_txs(clear_attached_schemas);
4783 }
4784 self.rollback_attached_wal_txns();
4785 self.set_tx_state(TransactionState::None);
4786 self.auto_commit.store(true, Ordering::SeqCst);
4787 }
4788
4789 self.rollback_temp_schema();
4790 self.clear_tx_poison();
4791 self.set_cdc_transaction_id(-1);
4792 self.clear_named_savepoints();
4793 self.clear_deferred_foreign_key_violations();
4794 }
4795
4796 pub(crate) fn for_each_attached_mv_tx(&self, mut f: impl FnMut(usize, u64)) {
4798 let txs = self.attached_mv_txs.read();
4799 for (&db_id, &(tx_id, _)) in txs.iter() {
4800 f(db_id, tx_id);
4801 }
4802 }
4803
4804 pub(crate) fn next_attached_mv_tx(&self) -> Option<(usize, u64, TransactionMode)> {
4807 self.attached_mv_txs
4808 .read()
4809 .iter()
4810 .next()
4811 .map(|(&db_id, &(tx_id, mode))| (db_id, tx_id, mode))
4812 }
4813
4814 pub(crate) fn mv_store_for_db(&self, db: usize) -> Option<Arc<MvStore>> {
4817 if self.is_mvcc_bootstrap_connection() {
4818 return None;
4819 }
4820 match db {
4821 crate::MAIN_DB_ID => self.db.get_mv_store().as_ref().cloned(),
4822 crate::TEMP_DB_ID => None,
4823 _ => {
4824 let catalog = self.attached_databases.read();
4825 catalog
4826 .index_to_data
4827 .get(&db)
4828 .and_then(|(db, _)| db.get_mv_store().as_ref().cloned())
4829 }
4830 }
4831 }
4832
4833 pub(crate) fn set_mvcc_checkpoint_threshold(&self, threshold: i64) -> Result<()> {
4834 match self.db.get_mv_store().as_ref() {
4835 Some(mv_store) => {
4836 mv_store.set_checkpoint_threshold(threshold);
4837 self.bump_prepare_context_generation();
4838 Ok(())
4839 }
4840 None => Err(LimboError::InternalError("MVCC not enabled".into())),
4841 }
4842 }
4843
4844 pub(crate) fn mvcc_checkpoint_threshold(&self) -> Result<i64> {
4845 match self.db.get_mv_store().as_ref() {
4846 Some(mv_store) => Ok(mv_store.checkpoint_threshold()),
4847 None => Err(LimboError::InternalError("MVCC not enabled".into())),
4848 }
4849 }
4850
4851 pub(crate) fn set_mvcc_gc_threshold(&self, threshold: i64) -> Result<()> {
4852 match self.db.get_mv_store().as_ref() {
4853 Some(mv_store) => {
4854 mv_store.set_gc_threshold(threshold);
4855 self.bump_prepare_context_generation();
4856 Ok(())
4857 }
4858 None => Err(LimboError::InternalError("MVCC not enabled".into())),
4859 }
4860 }
4861
4862 pub(crate) fn mvcc_gc_threshold(&self) -> Result<i64> {
4863 match self.db.get_mv_store().as_ref() {
4864 Some(mv_store) => Ok(mv_store.gc_threshold()),
4865 None => Err(LimboError::InternalError("MVCC not enabled".into())),
4866 }
4867 }
4868
4869 pub(crate) fn mvcc_tx_should_abort(&self) -> bool {
4870 match (self.db.get_mv_store().clone(), self.get_mv_tx_id()) {
4871 (Some(mv_store), Some(tx_id)) => mv_store.tx_should_abort(tx_id),
4872 _ => false,
4873 }
4874 }
4875}
4876
4877pub type Row = vdbe::Row;
4878
4879pub type StepResult = vdbe::StepResult;
4880
4881#[derive(Default)]
4882pub struct SymbolTable {
4883 pub functions: HashMap<String, Arc<function::ExternalFunc>>,
4884 pub collations: HashMap<u32, Arc<function::ExternalCollation>>,
4885 pub vtabs: HashMap<String, Arc<VirtualTable>>,
4886 pub vtab_modules: HashMap<String, Arc<crate::ext::VTabImpl>>,
4887 pub index_methods: HashMap<String, Arc<dyn IndexMethod>>,
4888}
4889
4890impl std::fmt::Debug for SymbolTable {
4891 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4892 f.debug_struct("SymbolTable")
4893 .field("functions", &self.functions)
4894 .field("collations", &self.collations)
4895 .finish()
4896 }
4897}
4898
4899fn is_shared_library(path: &std::path::Path) -> bool {
4900 path.extension()
4901 .is_some_and(|ext| ext == "so" || ext == "dylib" || ext == "dll")
4902}
4903
4904pub fn resolve_ext_path(extpath: &str) -> Result<std::path::PathBuf> {
4905 let path = std::path::Path::new(extpath);
4906 if !path.exists() {
4907 if is_shared_library(path) {
4908 return Err(LimboError::ExtensionError(format!(
4909 "Extension file not found: {extpath}"
4910 )));
4911 };
4912 let maybe = path.with_extension(std::env::consts::DLL_EXTENSION);
4913 maybe.exists().then_some(maybe).ok_or_else(|| {
4914 LimboError::ExtensionError(format!("Extension file not found: {extpath}"))
4915 })
4916 } else {
4917 Ok(path.to_path_buf())
4918 }
4919}
4920
4921impl SymbolTable {
4922 pub fn new() -> Self {
4923 Self {
4924 functions: HashMap::default(),
4925 collations: HashMap::default(),
4926 vtabs: HashMap::default(),
4927 vtab_modules: HashMap::default(),
4928 index_methods: HashMap::default(),
4929 }
4930 }
4931 pub fn resolve_function(
4932 &self,
4933 name: &str,
4934 arg_count: usize,
4935 ) -> Option<Arc<function::ExternalFunc>> {
4936 self.functions
4937 .get(name)
4938 .cloned()
4939 .or_else(|| {
4940 self.functions
4941 .get(&crate::util::normalize_ident(name))
4942 .cloned()
4943 })
4944 .filter(|func| func.func.matches_arg_count(arg_count))
4945 }
4946
4947 pub fn resolve_collation(&self, name: &str) -> Option<CollationSeq> {
4948 let collation = CollationSeq::known_custom(name)?;
4949 self.collations
4950 .contains_key(&collation.id())
4951 .then_some(collation)
4952 }
4953
4954 pub fn extend(&mut self, other: &SymbolTable) {
4955 for (name, func) in &other.functions {
4956 self.functions.insert(name.clone(), func.clone());
4957 }
4958 for (id, collation) in &other.collations {
4959 self.collations.insert(*id, collation.clone());
4960 }
4961 for (name, vtab) in &other.vtabs {
4962 self.vtabs.insert(name.clone(), vtab.clone());
4963 }
4964 for (name, module) in &other.vtab_modules {
4965 self.vtab_modules.insert(name.clone(), module.clone());
4966 }
4967 for (name, module) in &other.index_methods {
4968 self.index_methods.insert(name.clone(), module.clone());
4969 }
4970 }
4971}
4972
4973#[cfg(all(clt_turso_tests, clt_turso_feature = "fs"))]
4974mod tests {
4975 use super::*;
4976 use tempfile::TempDir;
4977
4978 fn open_connection_with_opts(path: &std::path::Path, opts: DatabaseOpts) -> Arc<Connection> {
4979 let io: Arc<dyn IO> = Arc::new(crate::PlatformIO::new().unwrap());
4980 let db = Database::open_file_with_flags(
4981 io,
4982 path.to_str().unwrap(),
4983 OpenFlags::default(),
4984 opts,
4985 None,
4986 )
4987 .unwrap();
4988 db.connect().unwrap()
4989 }
4990
4991 fn open_connection(path: &std::path::Path) -> Arc<Connection> {
4992 open_connection_with_opts(path, DatabaseOpts::new())
4993 }
4994
4995 fn drive_attach(conn: &Arc<Connection>, path: &str, alias: &str) -> Result<()> {
4996 let mut state = AttachDatabaseState::default();
4997 loop {
4998 match conn.attach_database(path, alias, &mut state)? {
4999 IOResult::Done(()) => return Ok(()),
5000 IOResult::IO(io) => io.wait(conn.db.io.as_ref())?,
5001 }
5002 }
5003 }
5004
5005 fn drive_attach_with_config(
5006 conn: &Arc<Connection>,
5007 path: &str,
5008 alias: &str,
5009 reserved_space: Option<u8>,
5010 ) -> Result<()> {
5011 let mut state = AttachDatabaseState::default();
5012 loop {
5013 match conn.attach_database_with_config(path, alias, reserved_space, &mut state)? {
5014 IOResult::Done(()) => return Ok(()),
5015 IOResult::IO(io) => io.wait(conn.db.io.as_ref())?,
5016 }
5017 }
5018 }
5019
5020 fn query_single_i64(conn: &Arc<Connection>, sql: &str) -> i64 {
5021 let mut stmt = conn.prepare(sql).unwrap();
5022 match stmt.step().unwrap() {
5023 StepResult::Row => stmt.row().unwrap().get::<i64>(0).unwrap(),
5024 other => panic!("expected a row, got {other:?}"),
5025 }
5026 }
5027
5028 fn text_value(value: &Value) -> &str {
5029 match value {
5030 Value::Text(text) => text.as_str(),
5031 other => panic!("expected text value, got {other:?}"),
5032 }
5033 }
5034
5035 fn attached_entry(conn: &Connection, alias: &str) -> (Arc<Database>, Arc<Pager>) {
5037 let catalog = conn.attached_databases.read();
5038 let index = *catalog.name_to_index.get(alias).unwrap();
5039 catalog.index_to_data.get(&index).unwrap().clone()
5040 }
5041
5042 #[test]
5043 fn test_named_memory_databases_on_same_io_are_distinct() {
5044 let io: Arc<dyn IO> = Arc::new(MemoryIO::new());
5045 let draft_db = Database::open_file(io.clone(), ":memory:sync-draft").unwrap();
5046 let synced_db = Database::open_file(io, ":memory:sync-synced").unwrap();
5047 assert!(!Arc::ptr_eq(&draft_db, &synced_db));
5048
5049 let draft = draft_db.connect().unwrap();
5050 let synced = synced_db.connect().unwrap();
5051
5052 for conn in [&draft, &synced] {
5053 assert_eq!(conn.get_database_canonical_path(), "");
5054 assert_eq!(
5055 conn.list_all_databases(),
5056 vec![(MAIN_DB_ID, "main".to_string(), String::new())]
5057 );
5058 }
5059
5060 draft
5061 .execute("CREATE TABLE t(x INTEGER); INSERT INTO t VALUES(11)")
5062 .unwrap();
5063 synced
5064 .execute("CREATE TABLE t(x INTEGER); INSERT INTO t VALUES(22)")
5065 .unwrap();
5066
5067 assert_eq!(query_single_i64(&draft, "SELECT x FROM t"), 11);
5068 assert_eq!(query_single_i64(&synced, "SELECT x FROM t"), 22);
5069 }
5070
5071 #[test]
5072 fn test_named_memory_database_reopened_on_same_io_sees_same_rows() {
5073 let io: Arc<dyn IO> = Arc::new(MemoryIO::new());
5074
5075 let first_db = Database::open_file(io.clone(), ":memory:reopen").unwrap();
5076 let first = first_db.connect().unwrap();
5077 first
5078 .execute("CREATE TABLE t(x INTEGER); INSERT INTO t VALUES(99)")
5079 .unwrap();
5080
5081 let second_db = Database::open_file(io, ":memory:reopen").unwrap();
5082 let second = second_db.connect().unwrap();
5083 assert_eq!(query_single_i64(&second, "SELECT x FROM t"), 99);
5084 }
5085
5086 #[test]
5087 fn test_attach_named_memory_database_reports_empty_path() {
5088 let temp_dir = TempDir::new().unwrap();
5089 let main_path = temp_dir.path().join("main.db");
5090 let conn = open_connection_with_opts(&main_path, DatabaseOpts::new().with_attach(true));
5091
5092 conn.execute("ATTACH ':memory:aux' AS aux").unwrap();
5093 conn.execute("CREATE TABLE aux.t(x INTEGER); INSERT INTO aux.t VALUES(5)")
5094 .unwrap();
5095
5096 assert_eq!(query_single_i64(&conn, "SELECT x FROM aux.t"), 5);
5097 let database_list = conn.pragma_query("database_list").unwrap();
5098 let aux = database_list
5099 .iter()
5100 .find(|row| text_value(&row[1]) == "aux")
5101 .expect("attached aux database must be listed");
5102 assert_eq!(text_value(&aux[2]), "");
5103 }
5104
5105 #[test]
5106 fn test_named_memory_parent_can_attach_real_file_database() {
5107 let temp_dir = TempDir::new().unwrap();
5108 let aux_path = temp_dir.path().join("aux.db");
5109 let io: Arc<dyn IO> = Arc::new(MemoryIO::new());
5110 let db = Database::open_file_with_flags(
5111 io,
5112 ":memory:named-main",
5113 OpenFlags::default(),
5114 DatabaseOpts::new().with_attach(true),
5115 None,
5116 )
5117 .unwrap();
5118 let conn = db.connect().unwrap();
5119
5120 conn.execute(format!("ATTACH '{}' AS aux", aux_path.to_str().unwrap()))
5121 .unwrap();
5122 conn.execute("CREATE TABLE aux.t(x INTEGER); INSERT INTO aux.t VALUES(7)")
5123 .unwrap();
5124 conn.execute("DETACH aux").unwrap();
5125
5126 let reopened = open_connection(&aux_path);
5127 assert_eq!(query_single_i64(&reopened, "SELECT x FROM t"), 7);
5128 }
5129
5130 #[test]
5131 fn test_attach_database_with_config_overrides_reserved_space_before_initialization() {
5132 let temp_dir = TempDir::new().unwrap();
5133 let main_path = temp_dir.path().join("main.db");
5134 let aux_path = temp_dir.path().join("aux.db");
5135 let conn = open_connection(&main_path);
5136
5137 drive_attach_with_config(&conn, aux_path.to_str().unwrap(), "aux", Some(48)).unwrap();
5138
5139 let (attached_db, pager) = attached_entry(&conn, "aux");
5140 assert!(!attached_db.initialized());
5141 assert!(!pager.db_initialized());
5142 assert_eq!(pager.get_reserved_space(), Some(48));
5143 }
5144
5145 #[cfg(clt_turso_feature = "checksum")]
5146 #[test]
5147 fn test_attach_database_with_config_rejects_reserved_space_below_minimum() {
5148 let temp_dir = TempDir::new().unwrap();
5149 let main_path = temp_dir.path().join("main.db");
5150 let aux_path = temp_dir.path().join("aux.db");
5151 let conn = open_connection(&main_path);
5152
5153 let err = drive_attach_with_config(&conn, aux_path.to_str().unwrap(), "aux", Some(0))
5154 .unwrap_err()
5155 .to_string();
5156 assert_eq!(
5157 err,
5158 "Invalid argument supplied: cannot attach database 'aux': reserved space 0 is smaller than attached database minimum 8"
5159 );
5160 }
5161
5162 #[test]
5163 fn test_fresh_mvcc_attach_installs_wal_before_bootstrap() {
5164 let temp_dir = TempDir::new().unwrap();
5167 let main_path = temp_dir.path().join("main.db");
5168 let aux_path = temp_dir.path().join("aux.db");
5169 let conn = open_connection(&main_path);
5170
5171 conn.execute("PRAGMA journal_mode = 'mvcc'").unwrap();
5172 drive_attach(&conn, aux_path.to_str().unwrap(), "aux").unwrap();
5173
5174 let (attached_db, pager) = attached_entry(&conn, "aux");
5175 assert!(attached_db.get_mv_store().as_ref().is_some());
5176 assert!(pager.has_wal());
5177
5178 conn.execute("CREATE TABLE aux.t(x INTEGER)").unwrap();
5179 conn.execute("INSERT INTO aux.t VALUES(1)").unwrap();
5180 conn.execute("PRAGMA aux.wal_checkpoint(TRUNCATE)").unwrap();
5181 }
5182
5183 #[test]
5184 fn test_fresh_mvcc_attach_reuses_database_shared_wal() {
5185 let temp_dir = TempDir::new().unwrap();
5186 let main_path = temp_dir.path().join("main.db");
5187 let aux_path = temp_dir.path().join("aux.db");
5188 let conn = open_connection(&main_path);
5189
5190 conn.execute("PRAGMA journal_mode = 'mvcc'").unwrap();
5191 drive_attach(&conn, aux_path.to_str().unwrap(), "aux").unwrap();
5192 conn.execute("CREATE TABLE aux.t(x INTEGER)").unwrap();
5193 conn.execute("INSERT INTO aux.t VALUES(1)").unwrap();
5194
5195 let (attached_db, pager) = attached_entry(&conn, "aux");
5196 let pager_shared_ptr = pager
5197 .wal_shared_ptr()
5198 .expect("fresh MVCC attach must expose WAL shared state in tests");
5199 let db_shared_ptr = Arc::as_ptr(&attached_db.shared_wal) as usize;
5200
5201 assert_eq!(pager_shared_ptr, db_shared_ptr);
5202 }
5203
5204 #[test]
5205 fn test_temp_tables_are_connection_local_and_shadow_main() {
5206 let temp_dir = TempDir::new().unwrap();
5207 let db_path = temp_dir.path().join("main.db");
5208 let conn1 = open_connection(&db_path);
5209
5210 conn1.execute("CREATE TABLE t(x INTEGER)").unwrap();
5211 conn1.execute("INSERT INTO main.t VALUES(1)").unwrap();
5212 let conn2 = open_connection(&db_path);
5213 conn1.execute("CREATE TEMP TABLE t(x INTEGER)").unwrap();
5214 conn1.execute("INSERT INTO temp.t VALUES(2)").unwrap();
5215
5216 assert_eq!(query_single_i64(&conn1, "SELECT x FROM t"), 2);
5217 assert_eq!(query_single_i64(&conn1, "SELECT x FROM main.t"), 1);
5218 assert_eq!(query_single_i64(&conn2, "SELECT x FROM t"), 1);
5219
5220 let err = conn2
5221 .prepare("SELECT x FROM temp.t")
5222 .unwrap_err()
5223 .to_string();
5224 assert!(
5225 err.contains("no such table"),
5226 "expected no such table error, got: {err}"
5227 );
5228 }
5229
5230 #[test]
5231 fn test_reprepare_after_temp_store_reset_does_not_panic() {
5232 let temp_dir = TempDir::new().unwrap();
5233 let db_path = temp_dir.path().join("main.db");
5234 let conn = open_connection(&db_path);
5235
5236 conn.execute("CREATE TEMP TABLE t(x INTEGER)").unwrap();
5237 let mut stmt = conn.prepare("SELECT x FROM t").unwrap();
5238
5239 conn.execute("PRAGMA temp_store = MEMORY").unwrap();
5240
5241 let err = stmt.step().unwrap_err().to_string();
5242 assert!(
5243 err.contains("no such table"),
5244 "expected no such table after temp reset, got: {err}"
5245 );
5246 }
5247
5248 #[test]
5249 fn test_temp_trigger_abort_rolls_back_temp_writes_without_panicking() {
5250 let temp_dir = TempDir::new().unwrap();
5251 let db_path = temp_dir.path().join("main.db");
5252 let conn = open_connection(&db_path);
5253
5254 conn.execute("CREATE TEMP TABLE t(x INTEGER)").unwrap();
5255 conn.execute("CREATE TEMP TABLE u(y INTEGER)").unwrap();
5256 conn.execute(
5257 "CREATE TRIGGER tr BEFORE INSERT ON temp.t BEGIN \
5258 INSERT INTO u VALUES (NEW.x); \
5259 SELECT RAISE(ABORT, 'boom'); \
5260 END;",
5261 )
5262 .unwrap();
5263
5264 let err = conn.execute("INSERT INTO temp.t VALUES(1)").unwrap_err();
5265 assert!(
5266 err.to_string().contains("boom"),
5267 "expected trigger abort error, got: {err}"
5268 );
5269 assert_eq!(query_single_i64(&conn, "SELECT COUNT(*) FROM temp.u"), 0);
5270 assert_eq!(query_single_i64(&conn, "SELECT COUNT(*) FROM temp.t"), 0);
5271 }
5272
5273 #[test]
5274 fn test_temp_trigger_abort_rolls_back_main_and_temp_writes() {
5275 let temp_dir = TempDir::new().unwrap();
5276 let db_path = temp_dir.path().join("main.db");
5277 let conn = open_connection(&db_path);
5278
5279 conn.execute("CREATE TABLE m(x INTEGER)").unwrap();
5280 conn.execute("CREATE TEMP TABLE t(x INTEGER)").unwrap();
5281 conn.execute("CREATE TEMP TABLE u(y INTEGER)").unwrap();
5282 conn.execute(
5283 "CREATE TRIGGER tr BEFORE INSERT ON temp.t BEGIN \
5284 INSERT INTO m VALUES (NEW.x); \
5285 INSERT INTO u VALUES (NEW.x); \
5286 SELECT RAISE(ABORT, 'boom'); \
5287 END;",
5288 )
5289 .unwrap();
5290
5291 let err = conn.execute("INSERT INTO temp.t VALUES(1)").unwrap_err();
5292 assert!(
5293 err.to_string().contains("boom"),
5294 "expected trigger abort error, got: {err}"
5295 );
5296 assert_eq!(query_single_i64(&conn, "SELECT COUNT(*) FROM main.m"), 0);
5297 assert_eq!(query_single_i64(&conn, "SELECT COUNT(*) FROM temp.u"), 0);
5298 assert_eq!(query_single_i64(&conn, "SELECT COUNT(*) FROM temp.t"), 0);
5299 }
5300
5301 #[test]
5302 fn test_distinct_triggers_with_same_name_in_different_schemas_can_fire_nested() {
5303 let temp_dir = TempDir::new().unwrap();
5304 let db_path = temp_dir.path().join("main.db");
5305 let conn = open_connection(&db_path);
5306
5307 conn.execute("CREATE TABLE src(x INTEGER)").unwrap();
5308 conn.execute("CREATE TABLE dst(y INTEGER)").unwrap();
5309 conn.execute("CREATE TABLE audit(z INTEGER)").unwrap();
5310 conn.execute(
5311 "CREATE TRIGGER shared_name AFTER INSERT ON dst BEGIN \
5312 INSERT INTO audit VALUES (NEW.y); \
5313 END;",
5314 )
5315 .unwrap();
5316 conn.execute(
5317 "CREATE TEMP TRIGGER shared_name AFTER INSERT ON main.src BEGIN \
5318 INSERT INTO dst VALUES (NEW.x); \
5319 END;",
5320 )
5321 .unwrap();
5322
5323 conn.execute("INSERT INTO src VALUES(7)").unwrap();
5324
5325 assert_eq!(query_single_i64(&conn, "SELECT COUNT(*) FROM main.dst"), 1);
5326 assert_eq!(query_single_i64(&conn, "SELECT SUM(z) FROM main.audit"), 7);
5327 }
5328
5329 #[test]
5340 fn test_setval_uncalled_emits_stored_value_as_next() -> Result<()> {
5341 let temp_dir = TempDir::new().unwrap();
5342 let path = temp_dir.path().join("seq_init.db");
5343 let conn = open_connection_with_opts(&path, DatabaseOpts::new());
5344
5345 conn.execute("PRAGMA journal_mode = 'mvcc'").unwrap();
5346 conn.execute("CREATE SEQUENCE s START 1 INCREMENT 3")?;
5347 conn.execute("SELECT setval('s', 13, 0)")?;
5348
5349 let next_val = query_single_i64(&conn, "SELECT nextval('s')");
5350 assert_eq!(
5351 next_val, 13,
5352 "setval(13, false) committed: next nextval must return 13"
5353 );
5354 Ok(())
5355 }
5356}