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(clt_turso_feature = "simulator", target_pointer_width = "64", host_shared_wal))]
2480 pub fn install_unpublished_backfill_proof_for_testing(
2481 &self,
2482 upper_bound_inclusive: u64,
2483 ) -> Result<()> {
2484 let pager = self.pager.load();
2485 let proof_nbackfills =
2486 pager.run_checkpoint_until_post_sync_gap_for_testing(CheckpointMode::Passive {
2487 upper_bound_inclusive: Some(upper_bound_inclusive),
2488 })?;
2489 let authority = self.db.shared_wal_coordination()?.ok_or_else(|| {
2490 LimboError::InternalError("shared WAL authority is unavailable".into())
2491 })?;
2492 let snapshot_before_publish = authority.snapshot();
2493 if snapshot_before_publish.nbackfills != 0 {
2494 return Err(LimboError::InternalError(
2495 "unpublished-proof setup requires nbackfills to remain unpublished".into(),
2496 ));
2497 }
2498
2499 let (db_size_pages, db_header_crc32c) = db_identity_for_testing(Path::new(&self.db.path))?;
2500 authority.install_backfill_proof(
2501 crate::storage::shared_wal_coordination::SharedWalCoordinationHeader {
2502 nbackfills: proof_nbackfills,
2503 ..snapshot_before_publish
2504 },
2505 db_size_pages,
2506 db_header_crc32c,
2507 );
2508 Ok(())
2509 }
2510
2511 pub fn last_insert_rowid(&self) -> i64 {
2512 self.last_insert_rowid.load(Ordering::SeqCst)
2513 }
2514
2515 pub(crate) fn update_last_rowid(&self, rowid: i64) {
2516 self.last_insert_rowid.store(rowid, Ordering::SeqCst);
2517 }
2518
2519 pub(crate) fn add_total_changes(&self, num_changes: i64) {
2520 self.total_changes.fetch_add(num_changes, Ordering::SeqCst);
2521 }
2522
2523 pub fn set_changes(&self, num_changes: i64) {
2524 self.changes.store(num_changes, Ordering::SeqCst);
2525 }
2526
2527 pub fn changes(&self) -> i64 {
2528 self.changes.load(Ordering::SeqCst)
2529 }
2530
2531 pub fn total_changes(&self) -> i64 {
2532 self.total_changes.load(Ordering::SeqCst)
2533 }
2534
2535 pub fn get_cache_size(&self) -> i32 {
2536 self.cache_size.load(Ordering::SeqCst)
2537 }
2538 pub fn set_cache_size(&self, size: i32) {
2539 self.cache_size.store(size, Ordering::SeqCst);
2540 self.bump_prepare_context_generation();
2541 }
2542
2543 pub fn get_capture_data_changes_info(
2544 &self,
2545 ) -> crate::sync::RwLockReadGuard<'_, Option<CaptureDataChangesInfo>> {
2546 self.capture_data_changes.read()
2547 }
2548 pub fn set_capture_data_changes_info(&self, opts: Option<CaptureDataChangesInfo>) {
2549 *self.capture_data_changes.write() = opts;
2550 self.bump_prepare_context_generation();
2551 }
2552 pub fn get_cdc_transaction_id(&self) -> i64 {
2553 self.cdc_transaction_id.load(Ordering::SeqCst)
2554 }
2555 pub fn set_cdc_transaction_id(&self, id: i64) {
2556 self.cdc_transaction_id.store(id, Ordering::SeqCst);
2557 }
2558 pub fn get_page_size(&self) -> PageSize {
2559 let value = self.page_size.load(Ordering::SeqCst);
2560 PageSize::new_from_header_u16(value).unwrap_or_default()
2561 }
2562
2563 pub fn is_closed(&self) -> bool {
2564 self.closed.load(Ordering::SeqCst)
2565 }
2566
2567 pub fn is_query_only(&self) -> bool {
2568 self.query_only.load(Ordering::SeqCst)
2569 }
2570
2571 pub fn get_database_canonical_path(&self) -> String {
2572 self.db.get_database_canonical_path()
2573 }
2574
2575 pub fn is_readonly(&self, index: usize) -> bool {
2577 match index {
2578 crate::MAIN_DB_ID => self.db.is_readonly(),
2579 crate::TEMP_DB_ID => self
2580 .temp
2581 .database
2582 .read()
2583 .as_ref()
2584 .is_some_and(|temp_db| temp_db.db.is_readonly()),
2585 _ => {
2586 let db = self.attached_databases.read().get_database_by_index(index);
2587 db.expect("Should never have called this without being sure the database exists")
2588 .is_readonly()
2589 }
2590 }
2591 }
2592
2593 pub fn reset_page_size(&self, size: u32) -> Result<()> {
2600 if self.db.initialized() {
2601 return Ok(());
2602 }
2603 let Some(size) = PageSize::new(size) else {
2604 return Ok(());
2605 };
2606
2607 self.page_size.store(size.get_raw(), Ordering::SeqCst);
2608 self.pager.load().set_initial_page_size(size)?;
2609 if let Some(mv_store) = self.db.get_mv_store().as_ref() {
2616 mv_store.set_global_page_size(size);
2617 }
2618 self.bump_prepare_context_generation();
2619
2620 Ok(())
2621 }
2622
2623 #[cfg(clt_turso_feature = "fs")]
2624 pub fn open_new(&self, path: &str, vfs: &str) -> Result<(Arc<dyn IO>, Arc<Database>)> {
2625 Database::open_with_vfs(&self.db, path, vfs)
2626 }
2627
2628 pub fn list_vfs(&self) -> Vec<String> {
2629 #[allow(unused_mut)]
2630 let mut all_vfs = vec![String::from("memory")];
2631 #[cfg(clt_turso_feature = "fs")]
2632 {
2633 #[cfg(target_family = "unix")]
2634 {
2635 all_vfs.push("syscall".to_string());
2636 }
2637 #[cfg(all(target_os = "linux", clt_turso_feature = "io_uring"))]
2638 {
2639 all_vfs.push("io_uring".to_string());
2640 }
2641 #[cfg(all(target_os = "windows", clt_turso_feature = "experimental_win_iocp"))]
2642 {
2643 all_vfs.push("experimental_win_iocp".to_string());
2644 }
2645 all_vfs.extend(crate::ext::list_vfs_modules());
2646 }
2647 all_vfs
2648 }
2649
2650 pub fn get_auto_commit(&self) -> bool {
2651 self.auto_commit.load(Ordering::SeqCst)
2652 }
2653
2654 pub(crate) fn mark_tx_poisoned(&self) {
2659 self.poisoned_tx.store(true, Ordering::SeqCst);
2660 }
2661
2662 pub(crate) fn tx_is_poisoned(&self) -> bool {
2664 self.poisoned_tx.load(Ordering::SeqCst)
2665 }
2666
2667 pub(crate) fn clear_tx_poison(&self) {
2669 self.poisoned_tx.store(false, Ordering::SeqCst);
2670 }
2671
2672 pub fn set_load_extension_enabled(&self, enabled: bool) {
2673 self.enable_load_extension.store(enabled, Ordering::Release);
2674 }
2675
2676 pub(crate) fn can_load_extensions(&self) -> bool {
2677 self.enable_load_extension.load(Ordering::Acquire)
2678 }
2679
2680 pub fn reparse_schema_after_extension_load(self: &Arc<Connection>) -> Result<()> {
2681 if self.is_closed() {
2682 return Err(LimboError::InternalError("Connection closed".to_string()));
2683 }
2684 let mut rows_data: Vec<(String, String, String, i64, Option<String>)> = Vec::new();
2689 {
2690 let mut rows = self
2691 .query("SELECT * FROM sqlite_schema")?
2692 .expect("query must be parsed to statement");
2693 rows.run_with_row_callback(|row| {
2694 let ty = row.get::<&str>(0)?.to_string();
2695 let name = row.get::<&str>(1)?.to_string();
2696 let table_name = row.get::<&str>(2)?.to_string();
2697 let root_page = row.get::<i64>(3)?;
2698 let sql = row.get::<&str>(4).ok().map(|s| s.to_string());
2699 rows_data.push((ty, name, table_name, root_page, sql));
2700 Ok(())
2701 })?;
2702 } let syms = self.syms.read();
2705 self.with_schema_mut(|schema| -> Result<()> {
2706 let mut from_sql_indexes = crate::alloc::vec![];
2711 let mut automatic_indices = HashMap::default();
2712 let mut dbsp_state_roots = HashMap::default();
2713 let mut dbsp_state_index_roots = HashMap::default();
2714 let mut materialized_view_info = HashMap::default();
2715
2716 let attached_resolver = |name: &str| -> Option<usize> {
2717 self.attached_databases
2718 .read()
2719 .get_database_by_name(&crate::util::normalize_ident(name))
2720 .map(|(idx, _)| idx)
2721 };
2722 for (ty, name, table_name, root_page, sql) in &rows_data {
2723 match schema.handle_schema_row(
2724 ty,
2725 name,
2726 table_name,
2727 *root_page,
2728 sql.as_deref(),
2729 &syms,
2730 &mut from_sql_indexes,
2731 &mut automatic_indices,
2732 &mut dbsp_state_roots,
2733 &mut dbsp_state_index_roots,
2734 &mut materialized_view_info,
2735 &attached_resolver,
2736 ) {
2737 Ok(()) => {}
2738 Err(LimboError::ParseError(msg)) if msg.contains("already exists") => {}
2739 Err(LimboError::ExtensionError(msg)) => {
2740 eprintln!("Warning: {msg}");
2741 }
2742 Err(e) => return Err(e),
2743 }
2744 }
2745
2746 match schema.populate_indices(&syms, from_sql_indexes, automatic_indices, false) {
2747 Ok(()) => {}
2748 Err(LimboError::ParseError(msg)) if msg.contains("already exists") => {}
2749 Err(LimboError::ExtensionError(msg)) => eprintln!("Warning: {msg}"),
2750 Err(e) => return Err(e),
2751 }
2752 match schema.populate_materialized_views(
2753 materialized_view_info,
2754 dbsp_state_roots,
2755 dbsp_state_index_roots,
2756 ) {
2757 Ok(()) => {}
2758 Err(LimboError::ExtensionError(msg)) => eprintln!("Warning: {msg}"),
2759 Err(e) => return Err(e),
2760 }
2761 Ok(())
2762 })?
2763 }
2764
2765 pub fn pragma_query(self: &Arc<Connection>, pragma_name: &str) -> Result<Vec<Vec<Value>>> {
2768 if self.is_closed() {
2769 return Err(LimboError::InternalError("Connection closed".to_string()));
2770 }
2771 let pragma = format!("PRAGMA {pragma_name}");
2772 let mut stmt = self.prepare(pragma)?;
2773 stmt.run_collect_rows()
2774 }
2775
2776 pub fn pragma_update<V: Display>(
2781 self: &Arc<Connection>,
2782 pragma_name: &str,
2783 pragma_value: V,
2784 ) -> Result<Vec<Vec<Value>>> {
2785 if self.is_closed() {
2786 return Err(LimboError::InternalError("Connection closed".to_string()));
2787 }
2788 let pragma = format!("PRAGMA {pragma_name} = {pragma_value}");
2789 let mut stmt = self.prepare(pragma)?;
2790 stmt.run_collect_rows()
2791 }
2792
2793 pub fn experimental_views_enabled(&self) -> bool {
2794 self.db.experimental_views_enabled()
2795 }
2796
2797 pub fn experimental_index_method_enabled(&self) -> bool {
2798 self.db.experimental_index_method_enabled()
2799 }
2800
2801 pub fn experimental_custom_types_enabled(&self) -> bool {
2802 self.db.experimental_custom_types_enabled()
2803 }
2804
2805 pub fn experimental_attach_enabled(&self) -> bool {
2806 self.db.experimental_attach_enabled()
2807 }
2808
2809 pub fn experimental_vacuum_enabled(&self) -> bool {
2810 self.db.experimental_vacuum_enabled()
2811 }
2812
2813 pub fn experimental_mvcc_passive_checkpoint_enabled(&self) -> bool {
2814 self.db.experimental_mvcc_passive_checkpoint_enabled()
2815 }
2816
2817 pub fn experimental_multiprocess_wal_enabled(&self) -> bool {
2818 self.db.experimental_multiprocess_wal_enabled()
2819 }
2820
2821 pub fn experimental_generated_columns_enabled(&self) -> bool {
2822 self.db.experimental_generated_columns_enabled()
2823 }
2824
2825 pub fn experimental_without_rowid_enabled(&self) -> bool {
2826 self.db.experimental_without_rowid_enabled()
2827 }
2828
2829 pub fn mvcc_enabled(&self) -> bool {
2830 self.db.mvcc_enabled()
2831 }
2832
2833 pub fn mv_store(&self) -> impl Deref<Target = Option<Arc<MvStore>>> {
2834 struct TransparentWrapper<T>(T);
2835
2836 impl<T> Deref for TransparentWrapper<T> {
2837 type Target = T;
2838
2839 fn deref(&self) -> &Self::Target {
2840 &self.0
2841 }
2842 }
2843
2844 if !self.is_mvcc_bootstrap_connection() {
2846 either::Left(self.db.get_mv_store())
2847 } else {
2848 either::Right(TransparentWrapper(None))
2849 }
2850 }
2851
2852 #[cfg(any(clt_turso_tests, injected_yields))]
2853 pub fn set_yield_injector(&self, injector: Option<Arc<dyn YieldInjector>>) {
2854 let mut slot = self.yield_injector.write();
2855 match injector {
2856 Some(injector) => {
2857 turso_assert!(
2858 slot.is_none(),
2859 "yield injector should be empty before installing a new one"
2860 );
2861 *slot = Some(injector);
2862 }
2863 None => {
2864 turso_assert!(
2865 slot.is_some(),
2866 "yield injector should be installed before it is cleared"
2867 );
2868 *slot = None;
2869 }
2870 }
2871 }
2872
2873 #[cfg(any(clt_turso_tests, injected_yields))]
2874 pub(crate) fn yield_injector(&self) -> Option<Arc<dyn YieldInjector>> {
2875 self.yield_injector.read().clone()
2876 }
2877
2878 #[cfg(any(clt_turso_tests, injected_yields))]
2879 pub fn set_failure_injector(&self, injector: Option<Arc<dyn FailureInjector>>) {
2880 let mut slot = self.failure_injector.write();
2881 match injector {
2882 Some(injector) => {
2883 turso_assert!(
2884 slot.is_none(),
2885 "failure injector should be empty before installing a new one"
2886 );
2887 *slot = Some(injector);
2888 }
2889 None => {
2890 turso_assert!(
2891 slot.is_some(),
2892 "failure injector should be installed before it is cleared"
2893 );
2894 *slot = None;
2895 }
2896 }
2897 }
2898
2899 #[cfg(any(clt_turso_tests, injected_yields))]
2900 pub(crate) fn failure_injector(&self) -> Option<Arc<dyn FailureInjector>> {
2901 self.failure_injector.read().clone()
2902 }
2903
2904 #[cfg(any(clt_turso_tests, injected_yields))]
2905 #[inline(always)]
2906 pub(crate) fn next_yield_instance_id(&self) -> u64 {
2907 self.yield_instance_id_counter
2908 .fetch_add(1, Ordering::Relaxed)
2909 }
2910
2911 pub fn pragma<V: Display>(
2918 self: &Arc<Connection>,
2919 pragma_name: &str,
2920 pragma_value: V,
2921 ) -> Result<Vec<Vec<Value>>> {
2922 if self.is_closed() {
2923 return Err(LimboError::InternalError("Connection closed".to_string()));
2924 }
2925 let pragma = format!("PRAGMA {pragma_name}({pragma_value})");
2926 let mut stmt = self.prepare(pragma)?;
2927 let mut results = Vec::new();
2928 loop {
2929 match stmt.step()? {
2930 vdbe::StepResult::Row => {
2931 let row: Vec<Value> = stmt.row().unwrap().get_values().cloned().collect();
2932 results.push(row);
2933 }
2934 vdbe::StepResult::Interrupt | vdbe::StepResult::Busy => {
2935 return Err(LimboError::Busy);
2936 }
2937 _ => break,
2938 }
2939 }
2940
2941 Ok(results)
2942 }
2943
2944 #[inline]
2945 pub fn with_schema_mut<T>(&self, f: impl FnOnce(&mut Schema) -> T) -> Result<T> {
2946 let mut schema_ref = self.schema.write();
2947 let schema = Schema::try_make_mut(&mut schema_ref)?;
2948 Ok(f(schema))
2949 }
2950
2951 pub(crate) fn with_database_schema_mut<T>(
2953 &self,
2954 database_id: usize,
2955 f: impl FnOnce(&mut Schema) -> T,
2956 ) -> Result<T> {
2957 match database_id {
2958 crate::MAIN_DB_ID => self.with_schema_mut(f),
2959 crate::TEMP_DB_ID => {
2960 let temp_db_guard = self.temp.database.read();
2964 let temp_db = temp_db_guard
2965 .as_ref()
2966 .expect("temp database should be initialized before schema mutation");
2967 let mut schema_guard = temp_db.db.schema.lock();
2968 let schema = Schema::try_make_mut(&mut schema_guard)?;
2969 let result = f(schema);
2970 self.bump_prepare_context_generation();
2971 Ok(result)
2972 }
2973 _ => {
2974 let mut schemas = self.database_schemas.write();
2979 let schema_arc = schemas.entry(database_id).or_insert_with(|| {
2980 let attached_dbs = self.attached_databases.read();
2981 let (db, _pager) = attached_dbs
2982 .index_to_data
2983 .get(&database_id)
2984 .expect("Database ID should be valid");
2985 let schema = db.schema.lock().clone();
2986 schema
2987 });
2988 let schema = Schema::try_make_mut(schema_arc)?;
2989 let result = f(schema);
2990 self.bump_prepare_context_generation();
2991 Ok(result)
2992 }
2993 }
2994 }
2995
2996 pub fn is_db_initialized(&self) -> bool {
2997 self.db.initialized()
2998 }
2999
3000 pub(crate) fn get_pager_from_database_index(&self, index: &usize) -> Result<Arc<Pager>> {
3001 match *index {
3002 crate::MAIN_DB_ID => Ok(self.pager.load().clone()),
3003 crate::TEMP_DB_ID => {
3004 if self.temp.database.read().is_none() {
3006 self.ensure_temp_database()?;
3007 }
3008 Ok(self
3009 .temp
3010 .database
3011 .read()
3012 .as_ref()
3013 .map(|temp_db| temp_db.pager.clone())
3014 .expect("temp database should be initialized after ensure_temp_database"))
3015 }
3016 _ => Ok(self.attached_databases.read().get_pager_by_index(index)),
3017 }
3018 }
3019
3020 pub(crate) fn get_database_name_by_index(&self, index: usize) -> Option<String> {
3023 match index {
3024 MAIN_DB_ID => Some("main".to_string()),
3025 TEMP_DB_ID => Some("temp".to_string()),
3026 _ => self.attached_databases.read().get_name_by_index(index),
3027 }
3028 }
3029
3030 pub(crate) fn get_database_id_by_name(&self, name: &str) -> Result<usize> {
3032 let normalized: String = crate::util::normalize_ident(name);
3033 match normalized.as_str() {
3034 "main" => Ok(MAIN_DB_ID),
3035 "temp" => Ok(TEMP_DB_ID),
3036 _ => self
3037 .attached_databases
3038 .read()
3039 .get_database_by_name(&normalized)
3040 .map(|(idx, _)| idx)
3041 .ok_or_else(|| LimboError::InvalidArgument(format!("no such database: {name}"))),
3042 }
3043 }
3044
3045 pub(crate) fn get_source_database(&self, database_id: usize) -> Arc<Database> {
3047 match database_id {
3048 MAIN_DB_ID => self.db.clone(),
3049 TEMP_DB_ID => self
3050 .temp
3051 .database
3052 .read()
3053 .as_ref()
3054 .map(|temp_db| temp_db.db.clone())
3055 .unwrap_or_else(|| self.db.clone()),
3056 _ => self
3057 .attached_databases
3058 .read()
3059 .get_database_by_index(database_id)
3060 .expect("database index should be valid"),
3061 }
3062 }
3063
3064 fn is_attached(&self, alias: &str) -> bool {
3065 self.attached_databases
3066 .read()
3067 .name_to_index
3068 .contains_key(alias)
3069 }
3070
3071 fn inherited_reserved_space_for_fresh_attach(&self) -> u8 {
3074 let pager = self.pager.load();
3075 pager
3076 .get_reserved_space()
3077 .unwrap_or_else(|| pager.io_ctx.read().get_reserved_space_bytes())
3078 }
3079
3080 fn minimum_reserved_space_for_fresh_attach(pager: &Pager) -> u8 {
3083 pager
3084 .get_reserved_space()
3085 .unwrap_or(0)
3086 .max(pager.io_ctx.read().get_reserved_space_bytes())
3087 }
3088
3089 fn database_has_existing_wal_state(db: &Database) -> bool {
3090 let shared_wal = db.shared_wal.read();
3091 shared_wal.page_size() != 0 || shared_wal.last_checksum_and_max_frame().1 != 0
3092 }
3093
3094 fn install_database_wal_on_pager(db: &Arc<Database>, pager: &mut Arc<Pager>) {
3095 let shared_wal = db.shared_wal.clone();
3096 let last_checksum_and_max_frame = shared_wal.read().last_checksum_and_max_frame();
3097 let wal = Arc::new(crate::storage::wal::WalFile::new(
3098 db.io.clone(),
3099 shared_wal,
3100 last_checksum_and_max_frame,
3101 db.buffer_pool.clone(),
3102 ));
3103
3104 let pager = Arc::get_mut(pager)
3105 .expect("fresh attached pager must not be shared before bootstrap or publication");
3106 pager.set_wal(wal);
3107 }
3108
3109 fn set_mvcc_journal_mode_fresh_db(pager: &Pager) -> Result<()> {
3110 turso_assert!(!pager.db_initialized());
3111 pager.set_initial_journal_version(crate::storage::sqlite3_ondisk::Version::Mvcc)
3112 }
3113
3114 fn validate_attach_target(db: &Database, is_fresh: bool, alias: &str) -> Result<()> {
3115 if is_fresh && Self::database_has_existing_wal_state(db) {
3116 return Err(LimboError::InvalidArgument(format!(
3117 "cannot attach database '{alias}': main database file is uninitialized but WAL state exists"
3118 )));
3119 }
3120
3121 if is_fresh && db.is_readonly() {
3122 return Err(LimboError::InvalidArgument(format!(
3123 "cannot attach database '{alias}': fresh read-only databases cannot be initialized during attach"
3124 )));
3125 }
3126 Ok(())
3127 }
3128
3129 fn apply_page_layout_to_fresh_attach_db(
3130 &self,
3131 alias: &str,
3132 attached_db_pager: &Pager,
3133 reserved_space: Option<u8>,
3134 ) -> Result<()> {
3135 let target_page_size = self.get_page_size();
3136 let attached_min_reserved_space =
3137 Self::minimum_reserved_space_for_fresh_attach(attached_db_pager);
3138 let target_reserved_space = match reserved_space {
3139 Some(space) => {
3140 if space < attached_min_reserved_space {
3142 return Err(LimboError::InvalidArgument(format!(
3143 "cannot attach database '{alias}': reserved space {space} is smaller than attached database minimum {attached_min_reserved_space}"
3144 )));
3145 }
3146 Some(space)
3147 }
3148 None => Some(
3149 self.inherited_reserved_space_for_fresh_attach()
3150 .max(attached_min_reserved_space),
3151 ),
3152 };
3153
3154 attached_db_pager.set_initial_page_size(target_page_size)?;
3155 if let Some(reserved_space) = target_reserved_space {
3156 attached_db_pager.set_reserved_space_bytes(reserved_space);
3157 }
3158 Ok(())
3159 }
3160
3161 fn reject_initialized_attach_mismatches(
3162 &self,
3163 alias: &str,
3164 db: &Database,
3165 pager: &Pager,
3166 ) -> Result<()> {
3167 if self.mvcc_enabled() != db.mvcc_enabled() {
3170 let main_mode = if self.mvcc_enabled() { "MVCC" } else { "WAL" };
3171 let attached_mode = if db.mvcc_enabled() { "MVCC" } else { "WAL" };
3172 return Err(LimboError::InvalidArgument(format!(
3173 "cannot attach database '{alias}': main database uses {main_mode} journal mode \
3174 but attached database uses {attached_mode}. Both must use the same journal mode."
3175 )));
3176 }
3177
3178 let main_pager = self.pager.load();
3181 if let (Some(main_ps), Some(attached_ps)) =
3182 (main_pager.get_page_size(), pager.get_page_size())
3183 {
3184 if main_ps != attached_ps {
3185 return Err(LimboError::InvalidArgument(format!(
3186 "cannot attach database '{alias}': page size mismatch \
3187 (main={main_ps:?}, attached={attached_ps:?})"
3188 )));
3189 }
3190 }
3191
3192 Ok(())
3193 }
3194
3195 fn reject_unsupported_fresh_mvcc_attach_durable_storage(
3196 &self,
3197 alias: &str,
3198 db: &Database,
3199 attached_is_fresh: bool,
3200 ) -> Result<()> {
3201 if attached_is_fresh
3202 && self.mvcc_enabled()
3203 && self.db.durable_storage.is_some()
3204 && db.durable_storage.is_none()
3205 {
3206 return Err(LimboError::InvalidArgument(format!(
3207 "cannot attach database '{alias}': fresh MVCC attach does not support inheriting custom durable storage"
3208 )));
3209 }
3210
3211 Ok(())
3212 }
3213
3214 #[cfg(not(clt_turso_feature = "fs"))]
3216 pub(crate) fn attach_database(
3217 &self,
3218 _path: &str,
3219 _alias: &str,
3220 _state: &mut AttachDatabaseState,
3221 ) -> Result<IOResult<()>> {
3222 Err(LimboError::InvalidArgument(
3223 "attach not available in this build (no-fs)".to_string(),
3224 ))
3225 }
3226
3227 #[cfg(not(clt_turso_feature = "fs"))]
3228 pub(crate) fn attach_database_with_config(
3229 &self,
3230 _path: &str,
3231 _alias: &str,
3232 _reserved_space: Option<u8>,
3233 _state: &mut AttachDatabaseState,
3234 ) -> Result<IOResult<()>> {
3235 self.attach_database(_path, _alias, _state)
3238 }
3239
3240 #[cfg(clt_turso_feature = "fs")]
3242 pub(crate) fn attach_database(
3243 &self,
3244 path: &str,
3245 alias: &str,
3246 state: &mut AttachDatabaseState,
3247 ) -> Result<IOResult<()>> {
3248 self.attach_database_with_config(path, alias, None, state)
3249 }
3250
3251 #[cfg(clt_turso_feature = "fs")]
3253 #[cfg_attr(not(clt_turso_tests), allow(dead_code))]
3254 pub(crate) fn attach_database_with_config(
3255 &self,
3256 path: &str,
3257 alias: &str,
3258 reserved_space: Option<u8>,
3259 state: &mut AttachDatabaseState,
3260 ) -> Result<IOResult<()>> {
3261 loop {
3262 match state {
3263 AttachDatabaseState::Start => {
3264 if self.is_closed() {
3265 return Err(LimboError::InternalError("Connection closed".to_string()));
3266 }
3267
3268 if self.is_attached(alias) {
3269 return Err(LimboError::InvalidArgument(format!(
3270 "database {alias} is already in use"
3271 )));
3272 }
3273
3274 if alias.eq_ignore_ascii_case("main") || alias.eq_ignore_ascii_case("temp") {
3275 return Err(LimboError::InvalidArgument(format!(
3276 "reserved name {alias} is already in use"
3277 )));
3278 }
3279
3280 let db_opts = DatabaseOpts::new()
3281 .with_views(self.db.experimental_views_enabled())
3282 .with_custom_types(self.db.experimental_custom_types_enabled())
3283 .with_index_method(self.db.experimental_index_method_enabled())
3284 .with_vacuum(self.db.experimental_vacuum_enabled())
3285 .with_generated_columns(self.db.experimental_generated_columns_enabled())
3286 .with_without_rowid(self.db.experimental_without_rowid_enabled());
3287 let is_memory_db = is_memory_like(path);
3288 let io: Arc<dyn IO> = if is_memory_db {
3289 Arc::new(MemoryIO::new())
3290 } else if self.db.is_in_memory_db() {
3291 Database::io_for_path(path)?
3292 } else {
3293 self.db.io.clone()
3294 };
3295 let main_db_flags = self.db.open_flags;
3296 let (db, encryption_opts) =
3297 Self::from_uri_attached(path, db_opts, main_db_flags, io)?;
3298 let attached_is_fresh = !db.initialized();
3299 if !is_memory_db {
3300 Self::validate_attach_target(&db, attached_is_fresh, alias)?;
3301 }
3302 self.reject_unsupported_fresh_mvcc_attach_durable_storage(
3303 alias,
3304 &db,
3305 attached_is_fresh,
3306 )?;
3307
3308 let encryption_key = if let Some(ref enc) = encryption_opts {
3309 Some(EncryptionKey::from_hex_string(&enc.hexkey)?)
3310 } else {
3311 None
3312 };
3313
3314 *state = AttachDatabaseState::Init(Box::new(AttachDatabaseInitState {
3315 alias: alias.to_string(),
3316 reserved_space,
3317 db,
3318 attached_is_fresh,
3319 encryption_key,
3320 init_st: crate::InitState::default(),
3321 }));
3322 }
3323 AttachDatabaseState::Init(init) => {
3324 let mut pager = Arc::new(crate::return_if_io!(init
3325 .db
3326 ._init_nonblock(&mut init.init_st, init.encryption_key.as_ref(),)));
3327
3328 if !init.attached_is_fresh {
3329 self.reject_initialized_attach_mismatches(&init.alias, &init.db, &pager)?;
3330 *state = AttachDatabaseState::Publish {
3331 alias: init.alias.clone(),
3332 db: init.db.clone(),
3333 pager,
3334 };
3335 continue;
3336 }
3337
3338 self.apply_page_layout_to_fresh_attach_db(
3339 &init.alias,
3340 &pager,
3341 init.reserved_space,
3342 )?;
3343
3344 if self.mvcc_enabled() && !init.db.mvcc_enabled() {
3345 Self::set_mvcc_journal_mode_fresh_db(&pager)?;
3346 Self::install_database_wal_on_pager(&init.db, &mut pager);
3347 let enc_ctx = pager.io_ctx.read().encryption_context().cloned();
3348 let mv_store = journal_mode::open_mv_store(
3349 init.db.io.clone(),
3350 &init.db.path,
3351 init.db.open_flags,
3352 init.db.durable_storage.clone(),
3353 enc_ctx,
3354 init.db.mv_store_allocator.clone(),
3355 init.db.experimental_mvcc_passive_checkpoint_enabled(),
3356 )?;
3357 init.db.mv_store.store(Some(mv_store));
3358 *state = AttachDatabaseState::Bootstrap(Box::new(
3359 AttachDatabaseBootstrapState {
3360 alias: init.alias.clone(),
3361 db: init.db.clone(),
3362 pager,
3363 encryption_key: init.encryption_key.take(),
3364 bootstrap_conn: None,
3365 bootstrap_st: crate::mvcc::database::BootstrapState::default(),
3366 },
3367 ));
3368 } else {
3369 *state = AttachDatabaseState::Publish {
3370 alias: init.alias.clone(),
3371 db: init.db.clone(),
3372 pager,
3373 };
3374 }
3375 }
3376 AttachDatabaseState::Bootstrap(bootstrap) => {
3377 if bootstrap.bootstrap_conn.is_none() {
3378 let default_cache_size = match bootstrap
3379 .pager
3380 .with_header(|header| header.default_page_cache_size)
3381 {
3382 Ok(IOResult::Done(default_cache_size)) => default_cache_size.get(),
3383 Ok(IOResult::IO(io)) => return Ok(IOResult::IO(io)),
3384 Err(_) => 0,
3385 };
3386 bootstrap.bootstrap_conn =
3387 Some(bootstrap.db._connect_with_pager_and_default_cache_size(
3388 true,
3389 bootstrap.pager.clone(),
3390 bootstrap.encryption_key.take(),
3391 default_cache_size,
3392 )?);
3393 }
3394
3395 let mv_store_guard = bootstrap.db.get_mv_store();
3396 let Some(mv_store) = mv_store_guard.as_ref() else {
3397 return Err(LimboError::InternalError(
3398 "fresh MVCC attach missing MV store".to_string(),
3399 ));
3400 };
3401 crate::return_if_io!(mv_store.bootstrap_nonblock(
3402 bootstrap
3403 .bootstrap_conn
3404 .as_ref()
3405 .expect("bootstrap connection initialized above"),
3406 &mut bootstrap.bootstrap_st,
3407 ));
3408
3409 *state = AttachDatabaseState::Publish {
3410 alias: bootstrap.alias.clone(),
3411 db: bootstrap.db.clone(),
3412 pager: bootstrap.pager.clone(),
3413 };
3414 }
3415 AttachDatabaseState::Publish { alias, db, pager } => {
3416 self.attached_databases
3417 .write()
3418 .insert(alias.as_str(), (db.clone(), pager.clone()));
3419 self.bump_prepare_context_generation();
3420 *state = AttachDatabaseState::Done;
3421 return Ok(IOResult::Done(()));
3422 }
3423 AttachDatabaseState::Done => {
3424 return Err(LimboError::InternalError(
3425 "attach_database called after completion".to_string(),
3426 ));
3427 }
3428 }
3429 }
3430 }
3431
3432 pub(crate) fn detach_database(&self, alias: &str) -> Result<()> {
3434 if self.is_closed() {
3435 return Err(LimboError::InternalError("Connection closed".to_string()));
3436 }
3437
3438 if alias == "main" || alias == "temp" {
3439 return Err(LimboError::InvalidArgument(format!(
3440 "cannot detach database: {alias}"
3441 )));
3442 }
3443
3444 let database_id = {
3449 let attached_dbs = self.attached_databases.read();
3450 match attached_dbs.name_to_index.get(alias).copied() {
3451 Some(id) => id,
3452 None => {
3453 return Err(LimboError::InvalidArgument(format!(
3454 "no such database: {alias}"
3455 )));
3456 }
3457 }
3458 };
3459
3460 let pager = self
3464 .get_pager_from_database_index(&database_id)
3465 .expect("attached database should always have a pager");
3466
3467 if pager.holds_read_lock() || pager.holds_write_lock() {
3468 return Err(LimboError::InvalidArgument(format!(
3469 "database {alias} is locked"
3470 )));
3471 }
3472
3473 if let Some((tx_id, _mode)) = self.get_mv_tx_for_db(database_id) {
3474 if let Some(mv_store) = self.mv_store_for_db(database_id) {
3475 mv_store.rollback_tx(tx_id, pager.clone(), self, database_id);
3476 pager.end_read_tx();
3477 }
3478 self.set_mv_tx_for_db(database_id, None);
3479 } else {
3480 pager.rollback_attached();
3482 }
3483
3484 {
3488 let mut attached_dbs = self.attached_databases.write();
3489 attached_dbs.remove(alias);
3490 }
3491
3492 self.database_schemas.write().remove(&database_id);
3495 self.bump_prepare_context_generation();
3496
3497 Ok(())
3498 }
3499
3500 pub fn list_attached_databases(&self) -> Vec<String> {
3502 self.attached_databases
3503 .read()
3504 .name_to_index
3505 .keys()
3506 .cloned()
3507 .collect()
3508 }
3509
3510 pub(crate) fn with_all_attached_pagers_with_index<F, R>(&self, f: F) -> R
3515 where
3516 F: FnOnce(&[(usize, Arc<Pager>)]) -> R,
3517 {
3518 let mut pagers: SmallVec<[(usize, Arc<Pager>); 8]> = SmallVec::new();
3519 if let Some(temp_db) = self.temp.database.read().as_ref() {
3520 pagers.push((crate::TEMP_DB_ID, temp_db.pager.clone()));
3521 }
3522 {
3523 let catalog = self.attached_databases.read();
3524 for (&idx, (_db, pager)) in catalog.index_to_data.iter() {
3525 pagers.push((idx, pager.clone()));
3526 }
3527 }
3528 f(&pagers)
3529 }
3530
3531 pub(crate) fn database_schemas(&self) -> &RwLock<HashMap<usize, Arc<Schema>>> {
3532 &self.database_schemas
3533 }
3534
3535 fn cached_non_main_schema(&self, database_id: usize) -> Arc<Schema> {
3536 turso_assert_ne!(database_id, crate::MAIN_DB_ID);
3537 if database_id == crate::TEMP_DB_ID {
3541 return self
3542 .temp
3543 .database
3544 .read()
3545 .as_ref()
3546 .map(|temp_db| temp_db.db.schema.lock().clone())
3547 .unwrap_or_else(|| self.empty_temp_schema());
3548 }
3549 if let Some(schema) = self.database_schemas.read().get(&database_id).cloned() {
3550 return schema;
3551 }
3552
3553 let attached_dbs = self.attached_databases.read();
3554 let (db, _pager) = attached_dbs
3555 .index_to_data
3556 .get(&database_id)
3557 .expect("Database ID should be valid after resolve_database_id");
3558 let schema = db.schema.lock().clone();
3559 schema
3560 }
3561
3562 pub(crate) fn publish_database_schema(&self, database_id: usize) {
3570 if database_id == crate::TEMP_DB_ID {
3571 return;
3572 }
3573 let mut schemas = self.database_schemas.write();
3574 if let Some(local_schema) = schemas.remove(&database_id) {
3575 let attached_dbs = self.attached_databases.read();
3576 if let Some((db, _pager)) = attached_dbs.index_to_data.get(&database_id) {
3577 *db.schema.lock() = local_schema;
3578 }
3579 self.bump_prepare_context_generation();
3580 }
3581 }
3582
3583 pub(crate) fn attached_databases(&self) -> &RwLock<DatabaseCatalog> {
3584 &self.attached_databases
3585 }
3586
3587 pub(crate) fn with_schema<T>(&self, database_id: usize, f: impl FnOnce(&Schema) -> T) -> T {
3589 match database_id {
3590 crate::MAIN_DB_ID => {
3591 let schema = self.schema.read();
3592 f(&schema)
3593 }
3594 _ => {
3595 let schema = self.cached_non_main_schema(database_id);
3596 f(&schema)
3597 }
3598 }
3599 }
3600
3601 pub(crate) fn clone_shared_schema(&self, database_id: usize) -> Arc<Schema> {
3611 if database_id == crate::MAIN_DB_ID {
3612 self.db.clone_schema()
3613 } else {
3614 self.attached_databases
3615 .read()
3616 .index_to_data
3617 .get(&database_id)
3618 .map(|(db, _)| db.schema.lock().clone())
3619 .unwrap_or_else(|| self.db.clone_schema())
3620 }
3621 }
3622
3623 fn get_canonical_path_for_database(db: &Database) -> String {
3625 if db.is_in_memory_db() {
3626 String::new()
3628 } else {
3629 match std::fs::canonicalize(&db.path) {
3631 Ok(abs_path) => abs_path.to_string_lossy().to_string(),
3632 Err(_) => db.path.to_string(),
3633 }
3634 }
3635 }
3636
3637 pub fn list_all_databases(&self) -> Vec<(usize, String, String)> {
3640 let mut databases = Vec::new();
3641
3642 let main_path = Self::get_canonical_path_for_database(&self.db);
3644 databases.push((MAIN_DB_ID, "main".to_string(), main_path));
3645
3646 if self.temp.database.read().is_some() {
3650 databases.push((crate::TEMP_DB_ID, "temp".to_string(), String::new()));
3651 }
3652
3653 let attached_dbs = self.attached_databases.read();
3655 for (alias, &seq_number) in attached_dbs.name_to_index.iter() {
3656 let file_path = if let Some((db, _pager)) = attached_dbs.index_to_data.get(&seq_number)
3657 {
3658 Self::get_canonical_path_for_database(db)
3659 } else {
3660 String::new()
3661 };
3662 databases.push((seq_number, alias.clone(), file_path));
3663 }
3664
3665 databases.sort_by_key(|&(seq, _, _)| seq);
3667 databases
3668 }
3669
3670 pub fn get_pager(&self) -> Arc<Pager> {
3671 self.pager.load().clone()
3672 }
3673
3674 pub fn get_query_only(&self) -> bool {
3675 self.is_query_only()
3676 }
3677
3678 pub fn set_query_only(&self, value: bool) {
3679 self.query_only.store(value, Ordering::SeqCst);
3680 self.bump_prepare_context_generation();
3681 }
3682
3683 pub fn set_vdbe_trace(&self, value: bool) {
3684 self.vdbe_trace.store(value, Ordering::SeqCst);
3685 }
3686
3687 pub fn get_vdbe_trace(&self) -> bool {
3688 self.vdbe_trace.load(Ordering::SeqCst)
3689 }
3690
3691 pub fn get_dml_require_where(&self) -> bool {
3692 self.dml_require_where.load(Ordering::SeqCst)
3693 }
3694
3695 pub fn set_dml_require_where(&self, value: bool) {
3696 self.dml_require_where.store(value, Ordering::SeqCst);
3697 }
3698
3699 pub fn get_dqs_dml(&self) -> bool {
3700 self.dqs_dml.load(Ordering::SeqCst)
3701 }
3702
3703 pub fn set_dqs_dml(&self, value: bool) {
3704 self.dqs_dml.store(value, Ordering::SeqCst);
3705 self.bump_prepare_context_generation();
3706 }
3707
3708 pub fn get_full_column_names(&self) -> bool {
3709 self.full_column_names.load(Ordering::SeqCst)
3710 }
3711
3712 pub fn set_full_column_names(&self, value: bool) {
3713 self.full_column_names.store(value, Ordering::SeqCst);
3714 self.bump_prepare_context_generation();
3715 }
3716
3717 pub fn get_short_column_names(&self) -> bool {
3718 self.short_column_names.load(Ordering::SeqCst)
3719 }
3720
3721 pub fn set_short_column_names(&self, value: bool) {
3722 self.short_column_names.store(value, Ordering::SeqCst);
3723 self.bump_prepare_context_generation();
3724 }
3725
3726 pub fn get_sync_mode(&self) -> SyncMode {
3727 self.sync_mode.get()
3728 }
3729
3730 pub fn set_sync_mode(&self, mode: SyncMode) {
3731 self.sync_mode.set(mode);
3732 self.bump_prepare_context_generation();
3733 }
3734
3735 pub fn get_temp_store(&self) -> crate::TempStore {
3736 self.temp_store.get()
3737 }
3738
3739 pub fn set_temp_store(&self, value: crate::TempStore) {
3740 if self.temp_store.get() == value {
3741 return;
3742 }
3743 self.reset_temp_database();
3744 self.temp_store.set(value);
3745 self.bump_prepare_context_generation();
3746 }
3747
3748 pub fn find_sequence(&self, name: &str) -> Result<Arc<crate::schema::Sequence>> {
3753 let (db_id, seq_name) = if let Some((schema, seq)) = name.split_once('.') {
3754 let db_id = self.get_database_id_by_name(schema)?;
3755 (db_id, crate::util::normalize_ident(seq))
3756 } else {
3757 (MAIN_DB_ID, crate::util::normalize_ident(name))
3758 };
3759
3760 self.with_schema(db_id, |schema| {
3761 schema.get_sequence(&seq_name).map(Arc::clone)
3762 })
3763 .ok_or_else(|| LimboError::ParseError(format!("sequence \"{name}\" does not exist")))
3764 }
3765
3766 pub fn set_sequence_currval(&self, name: &str, value: i64) {
3768 let normalized = crate::util::normalize_ident(name);
3769 self.sequence_currvals.write().insert(normalized, value);
3770 }
3771
3772 pub fn get_sequence_currval(&self, name: &str) -> Option<i64> {
3774 let normalized = crate::util::normalize_ident(name);
3775 self.sequence_currvals.read().get(&normalized).copied()
3776 }
3777
3778 pub fn clear_sequence_currval(&self, name: &str) {
3785 let normalized = crate::util::normalize_ident(name);
3786 self.sequence_currvals.write().remove(&normalized);
3787 }
3788
3789 pub fn sequence_inner_retries(&self) -> u64 {
3796 self.sequence_inner_retries
3797 .load(std::sync::atomic::Ordering::Relaxed)
3798 }
3799
3800 #[doc(hidden)]
3804 pub fn reset_sequence_inner_retries(&self) {
3805 self.sequence_inner_retries
3806 .store(0, std::sync::atomic::Ordering::Relaxed);
3807 }
3808
3809 pub(crate) fn load_sequence_descriptors_via_sql_nonblock(
3820 self: &Arc<Connection>,
3821 state: &mut LoadSequenceDescriptorsState,
3822 ) -> Result<crate::types::IOResult<()>> {
3823 use crate::types::IOResult;
3824 loop {
3825 match state {
3826 LoadSequenceDescriptorsState::Start => {
3827 let pending =
3833 self.with_schema(MAIN_DB_ID, |s| s.sequence_backing_table_names());
3834 *state = LoadSequenceDescriptorsState::Reading {
3835 pending,
3836 idx: 0,
3837 stmt: None,
3838 meta: None,
3839 seq: None,
3840 watermark_stmt: None,
3841 watermark_row: None,
3842 };
3843 }
3844 LoadSequenceDescriptorsState::Reading {
3845 pending,
3846 idx,
3847 stmt,
3848 meta,
3849 seq,
3850 watermark_stmt,
3851 watermark_row,
3852 } => loop {
3853 let entry = {
3854 if *idx >= pending.len() {
3855 return Ok(IOResult::Done(()));
3856 }
3857 pending[*idx].clone()
3858 };
3859 let (backing_table_name, seq_name) = entry;
3860 let normalized = crate::util::normalize_ident(&seq_name);
3861 let already_present =
3862 self.with_schema(MAIN_DB_ID, |s| s.get_sequence(&normalized).is_some());
3863 if already_present {
3864 *idx += 1;
3865 *stmt = None;
3866 *meta = None;
3867 *seq = None;
3868 *watermark_stmt = None;
3869 *watermark_row = None;
3870 continue;
3871 }
3872 if seq.is_none() {
3873 crate::return_if_io!(self.read_seq_descriptor_row_nonblock(
3874 &backing_table_name,
3875 &seq_name,
3876 stmt,
3877 meta,
3878 ));
3879 *seq = Some(Self::sequence_from_descriptor_meta(
3880 &seq_name,
3881 &backing_table_name,
3882 *meta,
3883 )?);
3884 *stmt = None;
3885 *meta = None;
3886 }
3887 let sequence = seq.as_ref().expect("sequence set above");
3888 crate::return_if_io!(self.read_sequence_watermark_row_nonblock(
3889 &backing_table_name,
3890 sequence,
3891 watermark_stmt,
3892 watermark_row,
3893 ));
3894 let watermark = Self::sequence_watermark_from_row(
3895 &backing_table_name,
3896 sequence,
3897 *watermark_row,
3898 )?;
3899 if let Some(mv_store) = self.db.get_mv_store().as_ref() {
3900 mv_store.set_sequence_watermark(&normalized, watermark);
3901 }
3902 let sequence = seq.take().expect("sequence set above");
3903 self.with_database_schema_mut(MAIN_DB_ID, |schema| {
3904 schema
3905 .sequences
3906 .insert(normalized.clone(), Arc::new(sequence));
3907 })?;
3908 *idx += 1;
3909 *stmt = None;
3910 *meta = None;
3911 *watermark_stmt = None;
3912 *watermark_row = None;
3913 },
3914 }
3915 }
3916 }
3917
3918 fn read_seq_descriptor_row_nonblock(
3927 self: &Arc<Connection>,
3928 backing_table_name: &str,
3929 seq_name: &str,
3930 stmt: &mut Option<Box<Statement>>,
3931 meta: &mut Option<(i64, i64, i64, i64, bool)>,
3932 ) -> Result<crate::types::IOResult<()>> {
3933 use crate::types::IOResult;
3934 if stmt.is_none() {
3935 let escaped = backing_table_name.replace('"', "\"\"");
3936 let sql = format!("SELECT start, inc, min, max, cycle FROM \"{escaped}\" LIMIT 1");
3937 let prepared = self.prepare_internal(sql).map_err(|err| {
3938 LimboError::Corrupt(format!(
3939 "internal sequence backing table \"{backing_table_name}\" for sequence \
3940 \"{seq_name}\": cannot prepare descriptor SELECT: {err}"
3941 ))
3942 })?;
3943 *stmt = Some(Box::new(prepared));
3944 *meta = None;
3948 }
3949 let s = stmt.as_mut().expect("stmt set above");
3950 match s.run_with_row_callback_nonblock(|row| {
3951 *meta = Some((
3952 row.get::<i64>(0)?,
3953 row.get::<i64>(1)?,
3954 row.get::<i64>(2)?,
3955 row.get::<i64>(3)?,
3956 row.get::<i64>(4)? != 0,
3957 ));
3958 Ok(())
3959 }) {
3960 Ok(IOResult::IO(io)) => Ok(IOResult::IO(io)),
3961 Ok(IOResult::Done(())) => Ok(IOResult::Done(())),
3962 Err(err) => Err(LimboError::Corrupt(format!(
3963 "internal sequence backing table \"{backing_table_name}\" for sequence \
3964 \"{seq_name}\": descriptor row read failed: {err}"
3965 ))),
3966 }
3967 }
3968
3969 fn sequence_from_descriptor_meta(
3973 seq_name: &str,
3974 backing_table_name: &str,
3975 meta: Option<(i64, i64, i64, i64, bool)>,
3976 ) -> Result<crate::schema::Sequence> {
3977 let (start, inc, min, max, cycle) = meta.ok_or_else(|| {
3978 LimboError::Corrupt(format!(
3979 "internal sequence backing table \"{backing_table_name}\" for sequence \
3980 \"{seq_name}\" is empty; the descriptor metadata row must always be present"
3981 ))
3982 })?;
3983 crate::schema::Sequence::new(
3984 seq_name.to_string(),
3985 Some(start),
3986 Some(inc),
3987 Some(min),
3988 Some(max),
3989 cycle,
3990 )
3991 .map_err(|err| {
3992 LimboError::Corrupt(format!(
3993 "internal sequence backing table \"{backing_table_name}\" for sequence \
3994 \"{seq_name}\" descriptor is invalid: {err}"
3995 ))
3996 })
3997 }
3998
3999 fn read_sequence_watermark_row_nonblock(
4004 self: &Arc<Connection>,
4005 backing_table_name: &str,
4006 seq: &crate::schema::Sequence,
4007 stmt: &mut Option<Box<Statement>>,
4008 row: &mut Option<(i64, bool)>,
4009 ) -> Result<crate::types::IOResult<()>> {
4010 use crate::types::IOResult;
4011 if stmt.is_none() {
4012 let escaped = backing_table_name.replace('"', "\"\"");
4013 let direction = if seq.increment_by >= 0 { "DESC" } else { "ASC" };
4014 let sql = format!(
4015 "SELECT value, is_called FROM \"{escaped}\" ORDER BY value {direction} LIMIT 1"
4016 );
4017 let prepared = self.prepare_internal(sql).map_err(|err| {
4018 LimboError::Corrupt(format!(
4019 "internal sequence backing table \"{backing_table_name}\" for sequence \
4020 \"{}\": cannot prepare watermark SELECT: {err}",
4021 seq.name
4022 ))
4023 })?;
4024 *stmt = Some(Box::new(prepared));
4025 *row = None;
4026 }
4027 let s = stmt.as_mut().expect("stmt set above");
4028 match s.run_with_row_callback_nonblock(|r| {
4029 let value = r.get::<i64>(0)?;
4030 let is_called = r.get::<i64>(1)? != 0;
4031 *row = Some((value, is_called));
4032 Ok(())
4033 }) {
4034 Ok(IOResult::IO(io)) => Ok(IOResult::IO(io)),
4035 Ok(IOResult::Done(())) => Ok(IOResult::Done(())),
4036 Err(err) => Err(LimboError::Corrupt(format!(
4037 "internal sequence backing table \"{backing_table_name}\" for sequence \
4038 \"{}\": watermark row read failed: {err}",
4039 seq.name
4040 ))),
4041 }
4042 }
4043
4044 fn sequence_watermark_from_row(
4045 backing_table_name: &str,
4046 seq: &crate::schema::Sequence,
4047 row: Option<(i64, bool)>,
4048 ) -> Result<i64> {
4049 let (value, is_called) = row.ok_or_else(|| {
4050 LimboError::Corrupt(format!(
4051 "internal sequence backing table \"{backing_table_name}\" for sequence \
4052 \"{}\" is empty; cannot derive sequence watermark",
4053 seq.name
4054 ))
4055 })?;
4056 Ok(crate::mvcc::database::first_unsafe_sequence_watermark(
4057 seq, value, is_called,
4058 ))
4059 }
4060
4061 pub(crate) fn sync_autoincrement_backing_tables_from_sqlite_sequence_nonblock(
4091 self: &Arc<Connection>,
4092 state: &mut SyncAutoincrementState,
4093 ) -> Result<crate::types::IOResult<()>> {
4094 use crate::schema::{autoincrement_sequence_name, SQLITE_SEQUENCE_TABLE_NAME};
4095 use crate::translate::sequence::sequence_backing_table_name;
4096 use crate::types::IOResult;
4097
4098 loop {
4099 match state {
4100 SyncAutoincrementState::Start => {
4101 let has_seq_table = self.with_schema(MAIN_DB_ID, |s| {
4102 s.get_btree_table(SQLITE_SEQUENCE_TABLE_NAME).is_some()
4103 });
4104 if !has_seq_table {
4105 return Ok(IOResult::Done(()));
4106 }
4107 let stmt = self.prepare_internal(format!(
4108 "SELECT name, seq FROM {SQLITE_SEQUENCE_TABLE_NAME}"
4109 ))?;
4110 *state = SyncAutoincrementState::ReadSeqRows {
4111 stmt: Box::new(stmt),
4112 rows: Vec::new(),
4113 };
4114 }
4115 SyncAutoincrementState::ReadSeqRows { stmt, rows } => {
4116 crate::return_if_io!(stmt.run_with_row_callback_nonblock(|row| {
4117 let name = row.get::<&str>(0)?.to_string();
4118 let seq = row.get::<i64>(1)?;
4119 rows.push((name, seq));
4120 Ok(())
4121 }));
4122 let rows = std::mem::take(rows);
4123 *state = SyncAutoincrementState::Process {
4124 rows,
4125 idx: 0,
4126 sub: SyncRowStep::Start,
4127 };
4128 }
4129 SyncAutoincrementState::Process { rows, idx, sub } => {
4130 if *idx >= rows.len() {
4131 return Ok(IOResult::Done(()));
4132 }
4133 match sub {
4134 SyncRowStep::Start => {
4135 let backing_table_name = sequence_backing_table_name(
4136 &autoincrement_sequence_name(&rows[*idx].0),
4137 );
4138 let has_backing = self.with_schema(MAIN_DB_ID, |s| {
4139 s.get_btree_table(&backing_table_name).is_some()
4140 });
4141 if !has_backing {
4142 *idx += 1;
4143 continue;
4144 }
4145 let escaped = backing_table_name.replace('"', "\"\"");
4148 let stmt = self.prepare_internal(format!(
4149 "SELECT MAX(value) FROM \"{escaped}\""
4150 ))?;
4151 *sub = SyncRowStep::ReadMax {
4152 backing_table_name,
4153 stmt: Box::new(stmt),
4154 current_max: None,
4155 };
4156 }
4157 SyncRowStep::ReadMax {
4158 backing_table_name,
4159 stmt,
4160 current_max,
4161 } => {
4162 crate::return_if_io!(stmt.run_with_row_callback_nonblock(|row| {
4163 if let crate::Value::Numeric(crate::Numeric::Integer(v)) =
4164 row.get_value(0)
4165 {
4166 *current_max = Some(*v);
4167 }
4168 Ok(())
4169 }));
4170 let watermark = rows[*idx].1;
4171 if matches!(*current_max, Some(c) if c > watermark) {
4180 *idx += 1;
4181 *sub = SyncRowStep::Start;
4182 continue;
4183 }
4184 let escaped = backing_table_name.replace('"', "\"\"");
4189 let insert_sql = format!(
4190 "INSERT OR REPLACE INTO \"{escaped}\"\
4191 (value, is_called, start, inc, min, max, cycle) \
4192 VALUES ({watermark}, 1, 1, 1, 1, {}, 0)",
4193 i64::MAX
4194 );
4195 let stmt = self.prepare_internal(insert_sql)?;
4196 *sub = SyncRowStep::Upsert {
4197 stmt: Box::new(stmt),
4198 };
4199 }
4200 SyncRowStep::Upsert { stmt } => {
4201 crate::return_if_io!(stmt.run_with_row_callback_nonblock(|_| Ok(())));
4202 if let Some(mv_store) = self.db.get_mv_store().as_ref() {
4203 let watermark = rows[*idx].1;
4204 let first_unsafe = watermark.checked_add(1).unwrap_or(watermark);
4205 mv_store.set_sequence_watermark(
4206 &autoincrement_sequence_name(&rows[*idx].0),
4207 first_unsafe,
4208 );
4209 }
4210 *idx += 1;
4211 *sub = SyncRowStep::Start;
4212 }
4213 }
4214 }
4215 }
4216 }
4217 }
4218
4219 #[cfg(not(target_family = "wasm"))]
4226 pub(crate) fn create_tempdir(&self) -> Result<TempDir> {
4227 let res = if let Some(d) = std::env::var_os("TURSO_TMPDIR") {
4228 tempfile::tempdir_in(d)
4229 } else if let Some(d) = std::env::var_os("SQLITE_TMPDIR") {
4230 tempfile::tempdir_in(d)
4231 } else {
4232 tempfile::tempdir()
4233 };
4234 res.map_err(|e| io_error(e, "tempdir"))
4235 }
4236
4237 pub fn get_data_sync_retry(&self) -> bool {
4238 self.data_sync_retry
4239 .load(crate::sync::atomic::Ordering::SeqCst)
4240 }
4241
4242 pub fn set_data_sync_retry(&self, value: bool) {
4243 self.data_sync_retry
4244 .store(value, crate::sync::atomic::Ordering::SeqCst);
4245 self.bump_prepare_context_generation();
4246 }
4247
4248 pub fn get_sync_type(&self) -> crate::io::FileSyncType {
4250 self.pager.load().get_sync_type()
4251 }
4252
4253 pub fn set_sync_type(&self, value: crate::io::FileSyncType) {
4255 self.pager.load().set_sync_type(value);
4256 }
4257
4258 pub fn get_syms_vtab_mods(&self) -> HashSet<String> {
4260 self.syms.read().vtab_modules.keys().cloned().collect()
4261 }
4262
4263 pub fn get_syms_functions(&self) -> Vec<(String, bool, i32, bool)> {
4265 self.syms
4266 .read()
4267 .functions
4268 .values()
4269 .map(|f| {
4270 let is_agg = f.func.is_aggregate();
4271 let argc = match &f.func {
4272 function::ExtFunc::Aggregate { argc, .. } => *argc,
4273 function::ExtFunc::Scalar { argc, .. } => *argc,
4274 };
4275 (
4276 f.name.clone(),
4277 is_agg,
4278 argc,
4279 function::Deterministic::is_deterministic(f.as_ref()),
4280 )
4281 })
4282 .collect()
4283 }
4284
4285 pub fn register_external_collation(
4286 &self,
4287 name: String,
4288 context: usize,
4289 callback: crate::ContextCollationFunction,
4290 context_destructor: Option<crate::ContextDestructor>,
4291 ) {
4292 let collation = CollationSeq::custom(&name);
4293 let normalized_name = crate::util::normalize_ident(&name);
4294 self.syms.write().collations.insert(
4295 collation.id(),
4296 Arc::new(function::ExternalCollation::new(
4297 normalized_name,
4298 context,
4299 callback,
4300 context_destructor,
4301 )),
4302 );
4303 self.bump_prepare_context_generation();
4304 }
4305
4306 pub fn unregister_external_collation(&self, name: &str) {
4307 if let Some(collation) = CollationSeq::known_custom(name) {
4308 if self
4309 .syms
4310 .write()
4311 .collations
4312 .remove(&collation.id())
4313 .is_some()
4314 {
4315 self.bump_prepare_context_generation();
4316 }
4317 }
4318 }
4319
4320 pub(crate) fn get_external_collation(
4321 &self,
4322 collation: CollationSeq,
4323 ) -> Result<Arc<function::ExternalCollation>> {
4324 self.syms
4325 .read()
4326 .collations
4327 .get(&collation.id())
4328 .cloned()
4329 .ok_or_else(|| {
4330 LimboError::ParseError(format!("no such collation sequence: {}", collation.name()))
4331 })
4332 }
4333
4334 pub(crate) fn custom_collation_compare(
4335 external: &function::ExternalCollation,
4336 left: &str,
4337 right: &str,
4338 ) -> CmpOrdering {
4339 let result = unsafe {
4340 (external.callback)(
4341 external.context,
4342 left.as_ptr(),
4343 left.len(),
4344 right.as_ptr(),
4345 right.len(),
4346 )
4347 };
4348 result.cmp(&0)
4349 }
4350
4351 pub(crate) fn external_collation_comparator(
4352 external: Arc<function::ExternalCollation>,
4353 ) -> crate::vdbe::sorter::SortComparator {
4354 Arc::new(move |left, right| {
4355 Ok(match (left, right) {
4356 (crate::ValueRef::Text(left), crate::ValueRef::Text(right)) => {
4357 Self::custom_collation_compare(&external, left.as_str(), right.as_str())
4358 }
4359 _ => left.partial_cmp(right).unwrap_or(CmpOrdering::Equal),
4360 })
4361 })
4362 }
4363
4364 pub(crate) fn make_collation_comparator(
4365 &self,
4366 collation: CollationSeq,
4367 ) -> Result<crate::vdbe::sorter::SortComparator> {
4368 let external = self.get_external_collation(collation)?;
4369 Ok(Self::external_collation_comparator(external))
4370 }
4371
4372 pub(crate) fn compare_external_collation(
4373 &self,
4374 collation: CollationSeq,
4375 left: &str,
4376 right: &str,
4377 ) -> Result<CmpOrdering> {
4378 let external = self.get_external_collation(collation)?;
4379 Ok(Self::custom_collation_compare(&external, left, right))
4380 }
4381
4382 pub(crate) fn database_ptr(&self) -> usize {
4383 Arc::as_ptr(&self.db) as usize
4384 }
4385
4386 pub fn set_encryption_key(&self, key: EncryptionKey) -> Result<()> {
4387 tracing::trace!("setting encryption key for connection");
4388 self.ensure_can_change_encryption_settings()?;
4389 *self.encryption_key.write() = Some(key);
4390 self.bump_prepare_context_generation();
4391 self.set_encryption_context()
4392 }
4393
4394 pub fn set_encryption_cipher(&self, cipher_mode: CipherMode) -> Result<()> {
4395 tracing::trace!("setting encryption cipher for connection");
4396 self.ensure_can_change_encryption_settings()?;
4397 self.encryption_cipher_mode.set(cipher_mode);
4398 self.bump_prepare_context_generation();
4399 self.set_encryption_context()
4400 }
4401
4402 pub fn set_reserved_bytes(&self, reserved_bytes: u8) -> Result<()> {
4403 let pager = self.pager.load();
4404 pager.set_reserved_space_bytes(reserved_bytes);
4405 Ok(())
4406 }
4407
4408 pub fn get_reserved_bytes(&self) -> Option<u8> {
4411 let pager = self.pager.load();
4412 pager.get_reserved_space()
4413 }
4414
4415 pub fn get_encryption_cipher_mode(&self) -> Option<CipherMode> {
4416 match self.encryption_cipher_mode.get() {
4417 CipherMode::None => None,
4418 mode => Some(mode),
4419 }
4420 }
4421
4422 fn ensure_can_change_encryption_settings(&self) -> Result<()> {
4423 let pager = self.pager.load();
4424 if pager.is_encryption_ctx_set() {
4425 return Err(LimboError::InvalidArgument(
4426 "cannot reset encryption attributes if already set in the session".to_string(),
4427 ));
4428 }
4429 if self.db.get_mv_store().is_some() {
4430 return Err(LimboError::InvalidArgument(
4431 "cannot enable encryption after MVCC is active; configure encryption before PRAGMA journal_mode='mvcc'"
4432 .to_string(),
4433 ));
4434 }
4435 Ok(())
4436 }
4437
4438 fn set_encryption_context(&self) -> Result<()> {
4440 let key_guard = self.encryption_key.read();
4441 let Some(key) = key_guard.as_ref() else {
4442 return Ok(());
4443 };
4444 let cipher_mode = self.get_encryption_cipher_mode();
4445 let Some(cipher_mode) = cipher_mode else {
4446 return Ok(());
4447 };
4448 tracing::trace!("setting encryption ctx for connection");
4449 let pager = self.pager.load();
4450 pager.set_encryption_context(cipher_mode, key)
4451 }
4452
4453 pub fn set_busy_handler(&self, handler: Option<BusyHandlerCallback>) {
4455 *self.busy_handler.write() = match handler {
4456 Some(callback) => BusyHandler::Custom { callback },
4457 None => BusyHandler::None,
4458 };
4459 self.bump_prepare_context_generation();
4460 }
4461
4462 pub fn set_busy_timeout(&self, duration: Duration) {
4464 *self.busy_handler.write() = if duration.is_zero() {
4465 BusyHandler::None
4466 } else {
4467 BusyHandler::Timeout(duration)
4468 };
4469 self.bump_prepare_context_generation();
4470 }
4471
4472 pub fn get_busy_timeout(&self) -> Duration {
4474 match &*self.busy_handler.read() {
4475 BusyHandler::Timeout(d) => *d,
4476 _ => Duration::ZERO,
4477 }
4478 }
4479
4480 pub fn set_query_timeout(&self, duration: Duration) {
4483 let millis = duration.as_millis().min(u128::from(u64::MAX)) as u64;
4484 self.query_timeout_ms.store(millis, Ordering::SeqCst);
4485 }
4486
4487 pub fn get_query_timeout(&self) -> Duration {
4489 Duration::from_millis(self.query_timeout_ms.load(Ordering::SeqCst))
4490 }
4491
4492 pub fn get_busy_handler(&self) -> crate::sync::RwLockReadGuard<'_, BusyHandler> {
4494 self.busy_handler.read()
4495 }
4496
4497 pub fn set_progress_handler(&self, ops: u64, handler: Option<ProgressHandlerCallback>) {
4500 self.progress_handler.set(ops, handler);
4501 }
4502
4503 pub fn should_interrupt_for_progress(&self, vm_steps: u64) -> bool {
4505 self.progress_handler.should_interrupt(vm_steps)
4506 }
4507
4508 pub fn interrupt(&self) {
4511 if self.n_active_root_statements.load(Ordering::SeqCst) > 0 {
4512 self.interrupt_requested.store(true, Ordering::SeqCst);
4513 }
4514 }
4515
4516 pub fn is_interrupted(&self) -> bool {
4518 self.interrupt_requested.load(Ordering::SeqCst)
4519 }
4520
4521 pub(crate) fn clear_interrupt_if_idle(&self) {
4523 if self.n_active_root_statements.load(Ordering::SeqCst) == 0 {
4524 self.interrupt_requested.store(false, Ordering::SeqCst);
4525 }
4526 }
4527
4528 pub(crate) fn set_tx_state(&self, state: TransactionState) {
4529 self.transaction_state.set(state);
4530 }
4531
4532 pub(crate) fn get_tx_state(&self) -> TransactionState {
4533 self.transaction_state.get()
4534 }
4535
4536 pub fn is_in_write_tx(&self) -> bool {
4539 matches!(self.get_tx_state(), TransactionState::Write { .. })
4540 }
4541
4542 pub(crate) fn get_mv_tx_id(&self) -> Option<u64> {
4543 self.mv_tx.read().map(|(tx_id, _)| tx_id)
4544 }
4545
4546 pub(crate) fn get_mv_tx(&self) -> Option<(u64, TransactionMode)> {
4547 *self.mv_tx.read()
4548 }
4549
4550 #[inline(always)]
4551 pub(crate) fn set_mv_tx(&self, tx_id_and_mode: Option<(u64, TransactionMode)>) {
4552 tracing::debug!("set_mv_tx: {:?}", tx_id_and_mode);
4553 *self.mv_tx.write() = tx_id_and_mode;
4554 }
4555
4556 pub(crate) fn get_mv_tx_id_for_db(&self, db: usize) -> Option<u64> {
4559 if db == crate::MAIN_DB_ID {
4560 self.get_mv_tx_id()
4561 } else {
4562 self.attached_mv_txs
4563 .read()
4564 .get(&db)
4565 .map(|(tx_id, _)| *tx_id)
4566 }
4567 }
4568
4569 pub(crate) fn get_mv_tx_for_db(&self, db: usize) -> Option<(u64, TransactionMode)> {
4571 if db == crate::MAIN_DB_ID {
4572 self.get_mv_tx()
4573 } else {
4574 self.attached_mv_txs.read().get(&db).copied()
4575 }
4576 }
4577
4578 pub(crate) fn set_mv_tx_for_db(&self, db: usize, val: Option<(u64, TransactionMode)>) {
4580 if db == crate::MAIN_DB_ID {
4581 self.set_mv_tx(val);
4582 } else {
4583 let mut txs = self.attached_mv_txs.write();
4584 match val {
4585 Some(v) => {
4586 txs.insert(db, v);
4587 }
4588 None => {
4589 txs.remove(&db);
4590 }
4591 }
4592 }
4593 }
4594
4595 pub(crate) fn rollback_attached_mvcc_txs(&self, clear_schemas: bool) {
4604 let txs: HashMap<usize, _> = self.attached_mv_txs.read().clone();
4605 let mut cleared_any_schema = false;
4606 for (&db_id, &(tx_id, _mode)) in &txs {
4607 if let Some(attached_mv_store) = self.mv_store_for_db(db_id) {
4608 let attached_pager = self
4609 .get_pager_from_database_index(&db_id)
4610 .expect("attached MVCC transaction should always have a pager");
4611 if attached_mv_store.is_tx_rollbackable(tx_id) {
4612 attached_mv_store.rollback_tx(tx_id, attached_pager.clone(), self, db_id);
4613 } else {
4614 self.set_mv_tx_for_db(db_id, None);
4615 }
4616 if clear_schemas {
4617 self.database_schemas().write().remove(&db_id);
4618 cleared_any_schema = true;
4619 }
4620 attached_pager.end_read_tx();
4621 }
4622 }
4623 self.attached_mv_txs.write().clear();
4624 if cleared_any_schema {
4625 self.bump_prepare_context_generation();
4626 }
4627 }
4628
4629 pub(crate) fn rollback_attached_wal_txns(&self) {
4633 self.with_all_attached_pagers_with_index(|pagers| {
4634 let mut wal_indices: SmallVec<[usize; 4]> = SmallVec::new();
4638 for (i, (db_id, _)) in pagers.iter().enumerate() {
4639 if self.mv_store_for_db(*db_id).is_none() {
4640 wal_indices.push(i);
4641 }
4642 }
4643 if wal_indices.is_empty() {
4644 return;
4645 }
4646 {
4647 let mut schemas = self.database_schemas().write();
4648 for &i in &wal_indices {
4649 schemas.remove(&pagers[i].0);
4650 }
4651 }
4652 self.bump_prepare_context_generation();
4653 for &i in &wal_indices {
4654 pagers[i].1.rollback_attached();
4655 }
4656 });
4657 }
4658
4659 pub(crate) fn with_named_savepoints<F, T>(&self, f: F) -> T
4660 where
4661 F: FnOnce(&[NamedSavepointFrame]) -> T,
4662 {
4663 let savepoints = self.named_savepoints.read();
4664 f(&savepoints)
4665 }
4666
4667 pub(crate) fn push_named_savepoint(&self, frame: NamedSavepointFrame) {
4668 self.named_savepoints.write().push(frame);
4669 }
4670
4671 pub(crate) fn with_savepoint_schema_snapshot<F, T>(&self, f: F) -> T
4677 where
4678 F: FnOnce(Arc<Schema>, Option<Arc<Schema>>, HashMap<usize, Arc<Schema>>) -> T,
4679 {
4680 let main_schema_snapshot = self.schema.read().clone();
4681 let temp_schema_snapshot = self
4682 .temp
4683 .database
4684 .read()
4685 .as_ref()
4686 .map(|temp_db| temp_db.db.schema.lock().clone());
4687 let staged_schema_snapshot = self.database_schemas.read().clone();
4688 f(
4689 main_schema_snapshot,
4690 temp_schema_snapshot,
4691 staged_schema_snapshot,
4692 )
4693 }
4694
4695 pub(crate) fn release_named_savepoint_frame(&self, name: &str) -> SavepointResult {
4696 let mut savepoints = self.named_savepoints.write();
4697 let Some(target_idx) = savepoints
4698 .iter()
4699 .rposition(|savepoint| savepoint.name == name)
4700 else {
4701 return SavepointResult::NotFound;
4702 };
4703 if savepoints[target_idx].starts_transaction && target_idx == 0 {
4704 return SavepointResult::Commit;
4705 }
4706 savepoints.truncate(target_idx);
4707 SavepointResult::Release
4708 }
4709
4710 pub(crate) fn rollback_named_savepoint_frame(&self, name: &str) -> Option<RollbackFrameInfo> {
4711 let mut savepoints = self.named_savepoints.write();
4712 let target_idx = savepoints
4713 .iter()
4714 .rposition(|savepoint| savepoint.name == name)?;
4715 let frame = &savepoints[target_idx];
4716 let info = RollbackFrameInfo {
4717 main_schema_snapshot: frame.main_schema_snapshot.clone(),
4718 temp_schema_snapshot: frame.temp_schema_snapshot.clone(),
4719 staged_schema_snapshot: frame.staged_schema_snapshot.clone(),
4720 };
4721 savepoints.truncate(target_idx + 1);
4724 Some(info)
4725 }
4726
4727 pub(crate) fn clear_named_savepoints(&self) {
4728 self.named_savepoints.write().clear();
4729 }
4730
4731 pub(crate) fn rollback_current_txn_state(
4734 &self,
4735 pager: &Arc<Pager>,
4736 clear_attached_schemas: bool,
4737 ) {
4738 if let Some(mv_store) = self.mv_store().as_ref() {
4739 if let Some(tx_id) = self.get_mv_tx_id() {
4740 self.auto_commit.store(true, Ordering::SeqCst);
4741 if mv_store.is_tx_rollbackable(tx_id) {
4742 mv_store.rollback_tx(tx_id, pager.clone(), self, crate::MAIN_DB_ID);
4743 } else {
4744 self.set_mv_tx(None);
4745 }
4746 }
4747 pager.end_read_tx();
4748 self.rollback_attached_mvcc_txs(clear_attached_schemas);
4749 } else {
4750 pager.rollback_tx(self);
4751 self.auto_commit.store(true, Ordering::SeqCst);
4752 }
4753 self.rollback_attached_wal_txns();
4754 self.set_tx_state(TransactionState::None);
4755 self.clear_tx_poison();
4756 }
4757
4758 pub(crate) fn rollback_manual_txn_cleanup(
4765 &self,
4766 pager: &Arc<Pager>,
4767 clear_attached_schemas: bool,
4768 ) {
4769 let main_has_implicit_state = self.get_tx_state() != TransactionState::None
4770 || self.get_mv_tx().is_some()
4771 || pager.holds_read_lock()
4772 || pager.holds_write_lock();
4773
4774 if main_has_implicit_state {
4775 self.rollback_current_txn_state(pager, clear_attached_schemas);
4776 } else {
4777 if self.next_attached_mv_tx().is_some() {
4778 self.rollback_attached_mvcc_txs(clear_attached_schemas);
4779 }
4780 self.rollback_attached_wal_txns();
4781 self.set_tx_state(TransactionState::None);
4782 self.auto_commit.store(true, Ordering::SeqCst);
4783 }
4784
4785 self.rollback_temp_schema();
4786 self.clear_tx_poison();
4787 self.set_cdc_transaction_id(-1);
4788 self.clear_named_savepoints();
4789 self.clear_deferred_foreign_key_violations();
4790 }
4791
4792 pub(crate) fn for_each_attached_mv_tx(&self, mut f: impl FnMut(usize, u64)) {
4794 let txs = self.attached_mv_txs.read();
4795 for (&db_id, &(tx_id, _)) in txs.iter() {
4796 f(db_id, tx_id);
4797 }
4798 }
4799
4800 pub(crate) fn next_attached_mv_tx(&self) -> Option<(usize, u64, TransactionMode)> {
4803 self.attached_mv_txs
4804 .read()
4805 .iter()
4806 .next()
4807 .map(|(&db_id, &(tx_id, mode))| (db_id, tx_id, mode))
4808 }
4809
4810 pub(crate) fn mv_store_for_db(&self, db: usize) -> Option<Arc<MvStore>> {
4813 if self.is_mvcc_bootstrap_connection() {
4814 return None;
4815 }
4816 match db {
4817 crate::MAIN_DB_ID => self.db.get_mv_store().as_ref().cloned(),
4818 crate::TEMP_DB_ID => None,
4819 _ => {
4820 let catalog = self.attached_databases.read();
4821 catalog
4822 .index_to_data
4823 .get(&db)
4824 .and_then(|(db, _)| db.get_mv_store().as_ref().cloned())
4825 }
4826 }
4827 }
4828
4829 pub(crate) fn set_mvcc_checkpoint_threshold(&self, threshold: i64) -> Result<()> {
4830 match self.db.get_mv_store().as_ref() {
4831 Some(mv_store) => {
4832 mv_store.set_checkpoint_threshold(threshold);
4833 self.bump_prepare_context_generation();
4834 Ok(())
4835 }
4836 None => Err(LimboError::InternalError("MVCC not enabled".into())),
4837 }
4838 }
4839
4840 pub(crate) fn mvcc_checkpoint_threshold(&self) -> Result<i64> {
4841 match self.db.get_mv_store().as_ref() {
4842 Some(mv_store) => Ok(mv_store.checkpoint_threshold()),
4843 None => Err(LimboError::InternalError("MVCC not enabled".into())),
4844 }
4845 }
4846
4847 pub(crate) fn set_mvcc_gc_threshold(&self, threshold: i64) -> Result<()> {
4848 match self.db.get_mv_store().as_ref() {
4849 Some(mv_store) => {
4850 mv_store.set_gc_threshold(threshold);
4851 self.bump_prepare_context_generation();
4852 Ok(())
4853 }
4854 None => Err(LimboError::InternalError("MVCC not enabled".into())),
4855 }
4856 }
4857
4858 pub(crate) fn mvcc_gc_threshold(&self) -> Result<i64> {
4859 match self.db.get_mv_store().as_ref() {
4860 Some(mv_store) => Ok(mv_store.gc_threshold()),
4861 None => Err(LimboError::InternalError("MVCC not enabled".into())),
4862 }
4863 }
4864
4865 pub(crate) fn mvcc_tx_should_abort(&self) -> bool {
4866 match (self.db.get_mv_store().clone(), self.get_mv_tx_id()) {
4867 (Some(mv_store), Some(tx_id)) => mv_store.tx_should_abort(tx_id),
4868 _ => false,
4869 }
4870 }
4871}
4872
4873pub type Row = vdbe::Row;
4874
4875pub type StepResult = vdbe::StepResult;
4876
4877#[derive(Default)]
4878pub struct SymbolTable {
4879 pub functions: HashMap<String, Arc<function::ExternalFunc>>,
4880 pub collations: HashMap<u32, Arc<function::ExternalCollation>>,
4881 pub vtabs: HashMap<String, Arc<VirtualTable>>,
4882 pub vtab_modules: HashMap<String, Arc<crate::ext::VTabImpl>>,
4883 pub index_methods: HashMap<String, Arc<dyn IndexMethod>>,
4884}
4885
4886impl std::fmt::Debug for SymbolTable {
4887 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4888 f.debug_struct("SymbolTable")
4889 .field("functions", &self.functions)
4890 .field("collations", &self.collations)
4891 .finish()
4892 }
4893}
4894
4895fn is_shared_library(path: &std::path::Path) -> bool {
4896 path.extension()
4897 .is_some_and(|ext| ext == "so" || ext == "dylib" || ext == "dll")
4898}
4899
4900pub fn resolve_ext_path(extpath: &str) -> Result<std::path::PathBuf> {
4901 let path = std::path::Path::new(extpath);
4902 if !path.exists() {
4903 if is_shared_library(path) {
4904 return Err(LimboError::ExtensionError(format!(
4905 "Extension file not found: {extpath}"
4906 )));
4907 };
4908 let maybe = path.with_extension(std::env::consts::DLL_EXTENSION);
4909 maybe.exists().then_some(maybe).ok_or_else(|| {
4910 LimboError::ExtensionError(format!("Extension file not found: {extpath}"))
4911 })
4912 } else {
4913 Ok(path.to_path_buf())
4914 }
4915}
4916
4917impl SymbolTable {
4918 pub fn new() -> Self {
4919 Self {
4920 functions: HashMap::default(),
4921 collations: HashMap::default(),
4922 vtabs: HashMap::default(),
4923 vtab_modules: HashMap::default(),
4924 index_methods: HashMap::default(),
4925 }
4926 }
4927 pub fn resolve_function(
4928 &self,
4929 name: &str,
4930 arg_count: usize,
4931 ) -> Option<Arc<function::ExternalFunc>> {
4932 self.functions
4933 .get(name)
4934 .cloned()
4935 .or_else(|| {
4936 self.functions
4937 .get(&crate::util::normalize_ident(name))
4938 .cloned()
4939 })
4940 .filter(|func| func.func.matches_arg_count(arg_count))
4941 }
4942
4943 pub fn resolve_collation(&self, name: &str) -> Option<CollationSeq> {
4944 let collation = CollationSeq::known_custom(name)?;
4945 self.collations
4946 .contains_key(&collation.id())
4947 .then_some(collation)
4948 }
4949
4950 pub fn extend(&mut self, other: &SymbolTable) {
4951 for (name, func) in &other.functions {
4952 self.functions.insert(name.clone(), func.clone());
4953 }
4954 for (id, collation) in &other.collations {
4955 self.collations.insert(*id, collation.clone());
4956 }
4957 for (name, vtab) in &other.vtabs {
4958 self.vtabs.insert(name.clone(), vtab.clone());
4959 }
4960 for (name, module) in &other.vtab_modules {
4961 self.vtab_modules.insert(name.clone(), module.clone());
4962 }
4963 for (name, module) in &other.index_methods {
4964 self.index_methods.insert(name.clone(), module.clone());
4965 }
4966 }
4967}
4968
4969#[cfg(all(clt_turso_tests, clt_turso_feature = "fs"))]
4970mod tests {
4971 use super::*;
4972 use tempfile::TempDir;
4973
4974 fn open_connection_with_opts(path: &std::path::Path, opts: DatabaseOpts) -> Arc<Connection> {
4975 let io: Arc<dyn IO> = Arc::new(crate::PlatformIO::new().unwrap());
4976 let db = Database::open_file_with_flags(
4977 io,
4978 path.to_str().unwrap(),
4979 OpenFlags::default(),
4980 opts,
4981 None,
4982 )
4983 .unwrap();
4984 db.connect().unwrap()
4985 }
4986
4987 fn open_connection(path: &std::path::Path) -> Arc<Connection> {
4988 open_connection_with_opts(path, DatabaseOpts::new())
4989 }
4990
4991 fn drive_attach(conn: &Arc<Connection>, path: &str, alias: &str) -> Result<()> {
4992 let mut state = AttachDatabaseState::default();
4993 loop {
4994 match conn.attach_database(path, alias, &mut state)? {
4995 IOResult::Done(()) => return Ok(()),
4996 IOResult::IO(io) => io.wait(conn.db.io.as_ref())?,
4997 }
4998 }
4999 }
5000
5001 fn drive_attach_with_config(
5002 conn: &Arc<Connection>,
5003 path: &str,
5004 alias: &str,
5005 reserved_space: Option<u8>,
5006 ) -> Result<()> {
5007 let mut state = AttachDatabaseState::default();
5008 loop {
5009 match conn.attach_database_with_config(path, alias, reserved_space, &mut state)? {
5010 IOResult::Done(()) => return Ok(()),
5011 IOResult::IO(io) => io.wait(conn.db.io.as_ref())?,
5012 }
5013 }
5014 }
5015
5016 fn query_single_i64(conn: &Arc<Connection>, sql: &str) -> i64 {
5017 let mut stmt = conn.prepare(sql).unwrap();
5018 match stmt.step().unwrap() {
5019 StepResult::Row => stmt.row().unwrap().get::<i64>(0).unwrap(),
5020 other => panic!("expected a row, got {other:?}"),
5021 }
5022 }
5023
5024 fn text_value(value: &Value) -> &str {
5025 match value {
5026 Value::Text(text) => text.as_str(),
5027 other => panic!("expected text value, got {other:?}"),
5028 }
5029 }
5030
5031 fn attached_entry(conn: &Connection, alias: &str) -> (Arc<Database>, Arc<Pager>) {
5033 let catalog = conn.attached_databases.read();
5034 let index = *catalog.name_to_index.get(alias).unwrap();
5035 catalog.index_to_data.get(&index).unwrap().clone()
5036 }
5037
5038 #[test]
5039 fn test_named_memory_databases_on_same_io_are_distinct() {
5040 let io: Arc<dyn IO> = Arc::new(MemoryIO::new());
5041 let draft_db = Database::open_file(io.clone(), ":memory:sync-draft").unwrap();
5042 let synced_db = Database::open_file(io, ":memory:sync-synced").unwrap();
5043 assert!(!Arc::ptr_eq(&draft_db, &synced_db));
5044
5045 let draft = draft_db.connect().unwrap();
5046 let synced = synced_db.connect().unwrap();
5047
5048 for conn in [&draft, &synced] {
5049 assert_eq!(conn.get_database_canonical_path(), "");
5050 assert_eq!(
5051 conn.list_all_databases(),
5052 vec![(MAIN_DB_ID, "main".to_string(), String::new())]
5053 );
5054 }
5055
5056 draft
5057 .execute("CREATE TABLE t(x INTEGER); INSERT INTO t VALUES(11)")
5058 .unwrap();
5059 synced
5060 .execute("CREATE TABLE t(x INTEGER); INSERT INTO t VALUES(22)")
5061 .unwrap();
5062
5063 assert_eq!(query_single_i64(&draft, "SELECT x FROM t"), 11);
5064 assert_eq!(query_single_i64(&synced, "SELECT x FROM t"), 22);
5065 }
5066
5067 #[test]
5068 fn test_named_memory_database_reopened_on_same_io_sees_same_rows() {
5069 let io: Arc<dyn IO> = Arc::new(MemoryIO::new());
5070
5071 let first_db = Database::open_file(io.clone(), ":memory:reopen").unwrap();
5072 let first = first_db.connect().unwrap();
5073 first
5074 .execute("CREATE TABLE t(x INTEGER); INSERT INTO t VALUES(99)")
5075 .unwrap();
5076
5077 let second_db = Database::open_file(io, ":memory:reopen").unwrap();
5078 let second = second_db.connect().unwrap();
5079 assert_eq!(query_single_i64(&second, "SELECT x FROM t"), 99);
5080 }
5081
5082 #[test]
5083 fn test_attach_named_memory_database_reports_empty_path() {
5084 let temp_dir = TempDir::new().unwrap();
5085 let main_path = temp_dir.path().join("main.db");
5086 let conn = open_connection_with_opts(&main_path, DatabaseOpts::new().with_attach(true));
5087
5088 conn.execute("ATTACH ':memory:aux' AS aux").unwrap();
5089 conn.execute("CREATE TABLE aux.t(x INTEGER); INSERT INTO aux.t VALUES(5)")
5090 .unwrap();
5091
5092 assert_eq!(query_single_i64(&conn, "SELECT x FROM aux.t"), 5);
5093 let database_list = conn.pragma_query("database_list").unwrap();
5094 let aux = database_list
5095 .iter()
5096 .find(|row| text_value(&row[1]) == "aux")
5097 .expect("attached aux database must be listed");
5098 assert_eq!(text_value(&aux[2]), "");
5099 }
5100
5101 #[test]
5102 fn test_named_memory_parent_can_attach_real_file_database() {
5103 let temp_dir = TempDir::new().unwrap();
5104 let aux_path = temp_dir.path().join("aux.db");
5105 let io: Arc<dyn IO> = Arc::new(MemoryIO::new());
5106 let db = Database::open_file_with_flags(
5107 io,
5108 ":memory:named-main",
5109 OpenFlags::default(),
5110 DatabaseOpts::new().with_attach(true),
5111 None,
5112 )
5113 .unwrap();
5114 let conn = db.connect().unwrap();
5115
5116 conn.execute(format!("ATTACH '{}' AS aux", aux_path.to_str().unwrap()))
5117 .unwrap();
5118 conn.execute("CREATE TABLE aux.t(x INTEGER); INSERT INTO aux.t VALUES(7)")
5119 .unwrap();
5120 conn.execute("DETACH aux").unwrap();
5121
5122 let reopened = open_connection(&aux_path);
5123 assert_eq!(query_single_i64(&reopened, "SELECT x FROM t"), 7);
5124 }
5125
5126 #[test]
5127 fn test_attach_database_with_config_overrides_reserved_space_before_initialization() {
5128 let temp_dir = TempDir::new().unwrap();
5129 let main_path = temp_dir.path().join("main.db");
5130 let aux_path = temp_dir.path().join("aux.db");
5131 let conn = open_connection(&main_path);
5132
5133 drive_attach_with_config(&conn, aux_path.to_str().unwrap(), "aux", Some(48)).unwrap();
5134
5135 let (attached_db, pager) = attached_entry(&conn, "aux");
5136 assert!(!attached_db.initialized());
5137 assert!(!pager.db_initialized());
5138 assert_eq!(pager.get_reserved_space(), Some(48));
5139 }
5140
5141 #[cfg(clt_turso_feature = "checksum")]
5142 #[test]
5143 fn test_attach_database_with_config_rejects_reserved_space_below_minimum() {
5144 let temp_dir = TempDir::new().unwrap();
5145 let main_path = temp_dir.path().join("main.db");
5146 let aux_path = temp_dir.path().join("aux.db");
5147 let conn = open_connection(&main_path);
5148
5149 let err = drive_attach_with_config(&conn, aux_path.to_str().unwrap(), "aux", Some(0))
5150 .unwrap_err()
5151 .to_string();
5152 assert_eq!(
5153 err,
5154 "Invalid argument supplied: cannot attach database 'aux': reserved space 0 is smaller than attached database minimum 8"
5155 );
5156 }
5157
5158 #[test]
5159 fn test_fresh_mvcc_attach_installs_wal_before_bootstrap() {
5160 let temp_dir = TempDir::new().unwrap();
5163 let main_path = temp_dir.path().join("main.db");
5164 let aux_path = temp_dir.path().join("aux.db");
5165 let conn = open_connection(&main_path);
5166
5167 conn.execute("PRAGMA journal_mode = 'mvcc'").unwrap();
5168 drive_attach(&conn, aux_path.to_str().unwrap(), "aux").unwrap();
5169
5170 let (attached_db, pager) = attached_entry(&conn, "aux");
5171 assert!(attached_db.get_mv_store().as_ref().is_some());
5172 assert!(pager.has_wal());
5173
5174 conn.execute("CREATE TABLE aux.t(x INTEGER)").unwrap();
5175 conn.execute("INSERT INTO aux.t VALUES(1)").unwrap();
5176 conn.execute("PRAGMA aux.wal_checkpoint(TRUNCATE)").unwrap();
5177 }
5178
5179 #[test]
5180 fn test_fresh_mvcc_attach_reuses_database_shared_wal() {
5181 let temp_dir = TempDir::new().unwrap();
5182 let main_path = temp_dir.path().join("main.db");
5183 let aux_path = temp_dir.path().join("aux.db");
5184 let conn = open_connection(&main_path);
5185
5186 conn.execute("PRAGMA journal_mode = 'mvcc'").unwrap();
5187 drive_attach(&conn, aux_path.to_str().unwrap(), "aux").unwrap();
5188 conn.execute("CREATE TABLE aux.t(x INTEGER)").unwrap();
5189 conn.execute("INSERT INTO aux.t VALUES(1)").unwrap();
5190
5191 let (attached_db, pager) = attached_entry(&conn, "aux");
5192 let pager_shared_ptr = pager
5193 .wal_shared_ptr()
5194 .expect("fresh MVCC attach must expose WAL shared state in tests");
5195 let db_shared_ptr = Arc::as_ptr(&attached_db.shared_wal) as usize;
5196
5197 assert_eq!(pager_shared_ptr, db_shared_ptr);
5198 }
5199
5200 #[test]
5201 fn test_temp_tables_are_connection_local_and_shadow_main() {
5202 let temp_dir = TempDir::new().unwrap();
5203 let db_path = temp_dir.path().join("main.db");
5204 let conn1 = open_connection(&db_path);
5205
5206 conn1.execute("CREATE TABLE t(x INTEGER)").unwrap();
5207 conn1.execute("INSERT INTO main.t VALUES(1)").unwrap();
5208 let conn2 = open_connection(&db_path);
5209 conn1.execute("CREATE TEMP TABLE t(x INTEGER)").unwrap();
5210 conn1.execute("INSERT INTO temp.t VALUES(2)").unwrap();
5211
5212 assert_eq!(query_single_i64(&conn1, "SELECT x FROM t"), 2);
5213 assert_eq!(query_single_i64(&conn1, "SELECT x FROM main.t"), 1);
5214 assert_eq!(query_single_i64(&conn2, "SELECT x FROM t"), 1);
5215
5216 let err = conn2
5217 .prepare("SELECT x FROM temp.t")
5218 .unwrap_err()
5219 .to_string();
5220 assert!(
5221 err.contains("no such table"),
5222 "expected no such table error, got: {err}"
5223 );
5224 }
5225
5226 #[test]
5227 fn test_reprepare_after_temp_store_reset_does_not_panic() {
5228 let temp_dir = TempDir::new().unwrap();
5229 let db_path = temp_dir.path().join("main.db");
5230 let conn = open_connection(&db_path);
5231
5232 conn.execute("CREATE TEMP TABLE t(x INTEGER)").unwrap();
5233 let mut stmt = conn.prepare("SELECT x FROM t").unwrap();
5234
5235 conn.execute("PRAGMA temp_store = MEMORY").unwrap();
5236
5237 let err = stmt.step().unwrap_err().to_string();
5238 assert!(
5239 err.contains("no such table"),
5240 "expected no such table after temp reset, got: {err}"
5241 );
5242 }
5243
5244 #[test]
5245 fn test_temp_trigger_abort_rolls_back_temp_writes_without_panicking() {
5246 let temp_dir = TempDir::new().unwrap();
5247 let db_path = temp_dir.path().join("main.db");
5248 let conn = open_connection(&db_path);
5249
5250 conn.execute("CREATE TEMP TABLE t(x INTEGER)").unwrap();
5251 conn.execute("CREATE TEMP TABLE u(y INTEGER)").unwrap();
5252 conn.execute(
5253 "CREATE TRIGGER tr BEFORE INSERT ON temp.t BEGIN \
5254 INSERT INTO u VALUES (NEW.x); \
5255 SELECT RAISE(ABORT, 'boom'); \
5256 END;",
5257 )
5258 .unwrap();
5259
5260 let err = conn.execute("INSERT INTO temp.t VALUES(1)").unwrap_err();
5261 assert!(
5262 err.to_string().contains("boom"),
5263 "expected trigger abort error, got: {err}"
5264 );
5265 assert_eq!(query_single_i64(&conn, "SELECT COUNT(*) FROM temp.u"), 0);
5266 assert_eq!(query_single_i64(&conn, "SELECT COUNT(*) FROM temp.t"), 0);
5267 }
5268
5269 #[test]
5270 fn test_temp_trigger_abort_rolls_back_main_and_temp_writes() {
5271 let temp_dir = TempDir::new().unwrap();
5272 let db_path = temp_dir.path().join("main.db");
5273 let conn = open_connection(&db_path);
5274
5275 conn.execute("CREATE TABLE m(x INTEGER)").unwrap();
5276 conn.execute("CREATE TEMP TABLE t(x INTEGER)").unwrap();
5277 conn.execute("CREATE TEMP TABLE u(y INTEGER)").unwrap();
5278 conn.execute(
5279 "CREATE TRIGGER tr BEFORE INSERT ON temp.t BEGIN \
5280 INSERT INTO m VALUES (NEW.x); \
5281 INSERT INTO u VALUES (NEW.x); \
5282 SELECT RAISE(ABORT, 'boom'); \
5283 END;",
5284 )
5285 .unwrap();
5286
5287 let err = conn.execute("INSERT INTO temp.t VALUES(1)").unwrap_err();
5288 assert!(
5289 err.to_string().contains("boom"),
5290 "expected trigger abort error, got: {err}"
5291 );
5292 assert_eq!(query_single_i64(&conn, "SELECT COUNT(*) FROM main.m"), 0);
5293 assert_eq!(query_single_i64(&conn, "SELECT COUNT(*) FROM temp.u"), 0);
5294 assert_eq!(query_single_i64(&conn, "SELECT COUNT(*) FROM temp.t"), 0);
5295 }
5296
5297 #[test]
5298 fn test_distinct_triggers_with_same_name_in_different_schemas_can_fire_nested() {
5299 let temp_dir = TempDir::new().unwrap();
5300 let db_path = temp_dir.path().join("main.db");
5301 let conn = open_connection(&db_path);
5302
5303 conn.execute("CREATE TABLE src(x INTEGER)").unwrap();
5304 conn.execute("CREATE TABLE dst(y INTEGER)").unwrap();
5305 conn.execute("CREATE TABLE audit(z INTEGER)").unwrap();
5306 conn.execute(
5307 "CREATE TRIGGER shared_name AFTER INSERT ON dst BEGIN \
5308 INSERT INTO audit VALUES (NEW.y); \
5309 END;",
5310 )
5311 .unwrap();
5312 conn.execute(
5313 "CREATE TEMP TRIGGER shared_name AFTER INSERT ON main.src BEGIN \
5314 INSERT INTO dst VALUES (NEW.x); \
5315 END;",
5316 )
5317 .unwrap();
5318
5319 conn.execute("INSERT INTO src VALUES(7)").unwrap();
5320
5321 assert_eq!(query_single_i64(&conn, "SELECT COUNT(*) FROM main.dst"), 1);
5322 assert_eq!(query_single_i64(&conn, "SELECT SUM(z) FROM main.audit"), 7);
5323 }
5324
5325 #[test]
5336 fn test_setval_uncalled_emits_stored_value_as_next() -> Result<()> {
5337 let temp_dir = TempDir::new().unwrap();
5338 let path = temp_dir.path().join("seq_init.db");
5339 let conn = open_connection_with_opts(&path, DatabaseOpts::new());
5340
5341 conn.execute("PRAGMA journal_mode = 'mvcc'").unwrap();
5342 conn.execute("CREATE SEQUENCE s START 1 INCREMENT 3")?;
5343 conn.execute("SELECT setval('s', 13, 0)")?;
5344
5345 let next_val = query_single_i64(&conn, "SELECT nextval('s')");
5346 assert_eq!(
5347 next_val, 13,
5348 "setval(13, false) committed: next nextval must return 13"
5349 );
5350 Ok(())
5351 }
5352}