1use crate::translate::plan::BitSet;
21use crate::types::{Extendable, Text};
22use crate::{turso_assert, turso_assert_ne, turso_debug_assert, NonNan};
23pub mod affinity;
24pub mod array;
25pub mod bloom_filter;
26pub mod builder;
27pub mod execute;
28pub mod explain;
29#[allow(dead_code)]
30pub mod hash_table;
31pub mod insn;
32pub mod metrics;
33pub mod rowset;
34pub mod sorter;
35#[cfg(clt_turso_tests)]
36mod statement_lifecycle_tests;
37pub mod vacuum;
38pub mod value;
39pub use crate::translate::collate::CollationSeq;
41use crate::{
42 alloc::DynAllocator,
43 error::LimboError,
44 function::FuncCtx,
45 mvcc::{database::CommitStateMachine, MvccClock},
46 numeric::Numeric,
47 return_if_io,
48 schema::Trigger,
49 state_machine::StateMachine,
50 translate::plan::TableReferences,
51 types::{IOCompletions, IOResult},
52 vdbe::{
53 execute::{
54 OpAttachState, OpClearBtreeState, OpColumnState, OpDeleteState, OpDeleteSubState,
55 OpDestroyState, OpIdxInsertState, OpInitCdcVersionState, OpInsertState,
56 OpInsertSubState, OpJournalModeState, OpNewRowidState, OpNoConflictState,
57 OpParseSchemaState, OpProgramState, OpRowIdState, OpSeekState, OpTransactionState,
58 VacuumIntoOpContext,
59 },
60 hash_table::HashTable,
61 metrics::StatementMetrics,
62 vacuum::VacuumInPlaceOpContext,
63 },
64 ValueRef, WalAutoActions,
65};
66use smallvec::SmallVec;
67
68#[cfg(clt_turso_feature = "json")]
69use crate::json::JsonCacheCell;
70use crate::sync::RwLock;
71use crate::{
72 storage::pager::Pager,
73 translate::plan::ResultSetColumn,
74 types::{AggContext, Cursor, ImmutableRecord, Value},
75 vdbe::{builder::CursorType, insn::Insn},
76};
77use crate::{
78 AtomicBool, CaptureDataChangesInfo, Connection, MvStore, Result, Statement, SyncMode,
79 TransactionState,
80};
81use branches::{mark_unlikely, unlikely};
82use builder::{CursorKey, QueryMode};
83use execute::{
84 InsnFunction, InsnFunctionStepResult, OpIdxDeleteState, OpIntegrityCheckState,
85 OpOpenEphemeralState,
86};
87use turso_parser::ast::ResolveType;
88
89use crate::io::TempFile;
90use crate::vdbe::bloom_filter::BloomFilter;
91use crate::vdbe::rowset::RowSet;
92use explain::{insn_to_row_with_comment, EXPLAIN_COLUMNS, EXPLAIN_QUERY_PLAN_COLUMNS};
93use std::{
94 collections::HashMap,
95 num::NonZero,
96 ops::Deref,
97 sync::{
98 atomic::{AtomicI64, AtomicIsize, Ordering},
99 Arc,
100 },
101 task::Waker,
102};
103use tracing::{instrument, Level};
104
105type MvccCommitStateMachine = CommitStateMachine<MvccClock, DynAllocator>;
106
107#[derive(Debug, Clone)]
109pub enum ViewDeltaCommitState {
110 NotStarted,
111 Processing {
112 views: Vec<String>, current_index: usize,
114 },
115 Done,
116}
117
118#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
119pub enum BranchOffset {
122 Label(u32),
127 Offset(InsnReference),
129 Placeholder,
132}
133
134impl BranchOffset {
135 pub fn is_offset(&self) -> bool {
137 matches!(self, BranchOffset::Offset(_))
138 }
139
140 pub fn as_offset_int(&self) -> InsnReference {
142 match self {
143 BranchOffset::Label(v) => unreachable!("Unresolved label: {}", v),
144 BranchOffset::Offset(v) => *v,
145 BranchOffset::Placeholder => unreachable!("Unresolved placeholder"),
146 }
147 }
148
149 pub fn as_debug_int(&self) -> i32 {
153 match self {
154 BranchOffset::Label(v) => *v as i32,
155 BranchOffset::Offset(v) => *v as i32,
156 BranchOffset::Placeholder => i32::MAX,
157 }
158 }
159}
160
161pub type CursorID = usize;
162
163pub type PageIdx = i64;
164
165type InsnReference = u32;
167
168#[derive(Debug)]
169pub enum StepResult {
170 Done,
171 IO,
172 Row,
173 Interrupt,
174 Busy,
175 Yield,
180}
181
182#[derive(Debug)]
183#[allow(clippy::large_enum_variant)]
184enum CommitState {
191 Ready,
192 Committing,
193 CommittingAttached,
195 CommittingMvcc {
196 state_machine: StateMachine<Box<MvccCommitStateMachine>>,
197 },
198 CommittingAttachedMvcc {
200 state_machine: StateMachine<Box<MvccCommitStateMachine>>,
201 db_id: usize,
202 mv_store: Arc<MvStore>,
203 },
204}
205
206impl CommitState {
207 fn cleanup_mvcc_checkpoint_state(&mut self) {
208 match self {
209 CommitState::CommittingMvcc { state_machine } => {
210 state_machine.inner_mut().cleanup_mvcc_checkpoint_state()
211 }
212 CommitState::CommittingAttachedMvcc { state_machine, .. } => {
213 state_machine.inner_mut().cleanup_mvcc_checkpoint_state()
214 }
215 CommitState::Ready | CommitState::Committing | CommitState::CommittingAttached => {}
216 }
217 }
218
219 fn cleanup_abandoned_mvcc_commit(&mut self, connection: &Connection) {
220 match self {
221 CommitState::CommittingAttachedMvcc {
222 state_machine,
223 db_id: attached_db_id,
224 ..
225 } if !state_machine.is_finalized() => {
226 if connection
227 .database_schemas()
228 .write()
229 .remove(attached_db_id)
230 .is_some()
231 {
232 connection.bump_prepare_context_generation();
233 }
234 }
235 CommitState::CommittingMvcc { state_machine } if !state_machine.is_finalized() => {}
236 _ => return, };
238
239 *self = CommitState::Ready;
248
249 connection.rollback_attached_mvcc_txs(true);
250 connection.rollback_attached_wal_txns();
251 connection.rollback_temp_schema();
252 }
253}
254
255#[derive(Debug, Clone, PartialEq)]
256pub enum Register {
257 Value(Value),
258 Aggregate(AggContext),
259 Record(ImmutableRecord),
260}
261
262impl Register {
263 #[inline]
264 pub const fn is_null(&self) -> bool {
265 matches!(self, Register::Value(Value::Null))
266 }
267
268 #[inline(always)]
269 pub fn set_int(&mut self, val: i64) {
273 match self {
274 Register::Value(Value::Numeric(Numeric::Integer(existing))) => {
275 *existing = val;
276 }
277 Register::Value(Value::Numeric(float)) => {
278 *float = Numeric::Integer(val);
279 }
280 Register::Value(other_value_kind) => {
281 *other_value_kind = Value::from_i64(val);
282 }
283 _ => {
284 *self = Register::Value(Value::from_i64(val));
285 }
286 }
287 }
288 #[inline(always)]
291 pub fn set_float(&mut self, val: NonNan) {
292 match self {
293 Register::Value(Value::Numeric(Numeric::Float(existing))) => {
294 *existing = val;
295 }
296 Register::Value(Value::Numeric(integer)) => {
297 *integer = Numeric::Float(val);
298 }
299 Register::Value(other_value_kind) => {
300 *other_value_kind = Value::Numeric(Numeric::Float(val));
301 }
302 _ => {
303 *self = Register::Value(Value::Numeric(Numeric::Float(val)));
304 }
305 }
306 }
307
308 #[inline]
311 pub fn set_text(&mut self, val: Text) -> Result<()> {
312 match self {
313 Register::Value(Value::Text(existing)) => {
314 existing.do_extend(&val)?;
315 }
316 Register::Value(other_value_kind) => {
317 *other_value_kind = Value::Text(val);
318 }
319 _ => {
320 *self = Register::Value(Value::Text(val));
321 }
322 }
323 Ok(())
324 }
325
326 #[inline]
329 pub fn set_blob(&mut self, val: Vec<u8>) -> Result<()> {
330 match self {
331 Register::Value(Value::Blob(existing)) => {
332 existing.do_extend(&val)?;
333 }
334 Register::Value(other_value_kind) => {
335 *other_value_kind = Value::Blob(val);
336 }
337 _ => {
338 *self = Register::Value(Value::Blob(val));
339 }
340 }
341 Ok(())
342 }
343
344 pub fn set_null(&mut self) {
347 match self {
348 Register::Value(Value::Null) => {}
349 Register::Value(other_value_kind) => {
350 *other_value_kind = Value::Null;
351 }
352 _ => {
353 *self = Register::Value(Value::Null);
354 }
355 }
356 }
357
358 pub fn set_value(&mut self, val: Value) {
360 match self {
361 Register::Value(v) => {
362 *v = val;
363 }
364 _ => {
365 *self = Register::Value(val);
366 }
367 }
368 }
369}
370
371#[derive(Debug)]
374pub struct Row {
375 values: *const Register,
376 count: usize,
377}
378
379#[derive(Debug, Clone, Copy, PartialEq)]
380pub enum TxnCleanup {
381 None,
382 RollbackTxn,
383 RollbackSavepoint,
387}
388
389#[derive(Debug, Clone, Copy, PartialEq)]
390pub enum ProgramExecutionState {
391 Init,
393 Running,
395 Interrupting,
397 Interrupted,
399 Done,
401 Failed,
403}
404
405impl ProgramExecutionState {
406 pub const fn is_running(&self) -> bool {
407 matches!(
408 self,
409 ProgramExecutionState::Interrupting | ProgramExecutionState::Running
410 )
411 }
412 pub const fn is_terminal(&self) -> bool {
413 matches!(
414 self,
415 ProgramExecutionState::Interrupted
416 | ProgramExecutionState::Failed
417 | ProgramExecutionState::Done
418 )
419 }
420}
421
422#[derive(Debug)]
425pub struct OpHashBuildState {
426 pub key_values: crate::alloc::Vec<Value>,
427 pub key_idx: usize,
428 pub payload_values: crate::alloc::Vec<Value>,
429 pub payload_idx: usize,
430 pub rowid: Option<i64>,
431 pub cursor_id: CursorID,
432 pub hash_table_id: usize,
433 pub key_start_reg: usize,
434 pub num_keys: usize,
435}
436
437#[derive(Debug)]
440pub struct OpHashProbeState {
441 pub probe_keys: crate::alloc::Vec<Value>,
443 pub hash_table_id: usize,
445 pub partition_idx: usize,
447 pub probe_buffered: bool,
449}
450
451enum ActiveOpState {
452 None,
453 ClearBtree(OpClearBtreeState),
454 Delete(OpDeleteState),
455 Destroy(OpDestroyState),
456 IdxDelete(OpIdxDeleteState),
457 IntegrityCheck(OpIntegrityCheckState),
458 OpenEphemeral(OpOpenEphemeralState),
459 Program(OpProgramState),
460 NewRowid(OpNewRowidState),
461 IdxInsert(OpIdxInsertState),
462 Insert(OpInsertState),
463 NoConflict(OpNoConflictState),
464 Column(OpColumnState),
465 RowId(OpRowIdState),
466 Transaction(OpTransactionState),
467 Attach(OpAttachState),
468 JournalMode(OpJournalModeState),
469 ParseSchema(OpParseSchemaState),
470 HashBuild(Option<OpHashBuildState>),
471 HashProbe(Option<OpHashProbeState>),
472 InitCdcVersion(OpInitCdcVersionState),
473}
474
475impl std::fmt::Debug for ActiveOpState {
476 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
477 let name = match self {
478 ActiveOpState::None => "None",
479 ActiveOpState::ClearBtree(_) => "ClearBtree",
480 ActiveOpState::Delete(_) => "Delete",
481 ActiveOpState::Destroy(_) => "Destroy",
482 ActiveOpState::IdxDelete(_) => "IdxDelete",
483 ActiveOpState::IntegrityCheck(_) => "IntegrityCheck",
484 ActiveOpState::OpenEphemeral(_) => "OpenEphemeral",
485 ActiveOpState::Program(_) => "Program",
486 ActiveOpState::NewRowid(_) => "NewRowid",
487 ActiveOpState::IdxInsert(_) => "IdxInsert",
488 ActiveOpState::Insert(_) => "Insert",
489 ActiveOpState::NoConflict(_) => "NoConflict",
490 ActiveOpState::Column(_) => "Column",
491 ActiveOpState::RowId(_) => "RowId",
492 ActiveOpState::Transaction(_) => "Transaction",
493 ActiveOpState::Attach(_) => "Attach",
494 ActiveOpState::JournalMode(_) => "JournalMode",
495 ActiveOpState::ParseSchema(_) => "ParseSchema",
496 ActiveOpState::HashBuild(_) => "HashBuild",
497 ActiveOpState::HashProbe(_) => "HashProbe",
498 ActiveOpState::InitCdcVersion(_) => "InitCdcVersion",
499 };
500 f.write_str(name)
501 }
502}
503
504#[derive(Debug, Default)]
505struct ActiveOpStateSlot {
506 state: ActiveOpState,
507}
508
509macro_rules! active_state_accessor {
510 ($name:ident, $variant:ident, $ty:ty, $init:expr) => {
511 fn $name(&mut self) -> &mut $ty {
512 if matches!(self.state, ActiveOpState::None) {
513 self.state = ActiveOpState::$variant($init);
514 }
515 match &mut self.state {
516 ActiveOpState::$variant(state) => state,
517 state => unreachable!(
518 "active opcode state mismatch: expected {}, got {:?}",
519 stringify!($variant),
520 state
521 ),
522 }
523 }
524 };
525}
526
527impl Default for ActiveOpState {
528 fn default() -> Self {
529 Self::None
530 }
531}
532
533impl ActiveOpStateSlot {
534 fn clear(&mut self) {
535 self.state = ActiveOpState::None;
536 }
537
538 active_state_accessor!(
539 delete,
540 Delete,
541 OpDeleteState,
542 OpDeleteState {
543 sub_state: OpDeleteSubState::MaybeCaptureRecord,
544 deleted_record: None,
545 }
546 );
547 active_state_accessor!(
548 clear_btree,
549 ClearBtree,
550 OpClearBtreeState,
551 OpClearBtreeState::CreateCursor
552 );
553 active_state_accessor!(
554 destroy,
555 Destroy,
556 OpDestroyState,
557 OpDestroyState::CreateCursor
558 );
559 active_state_accessor!(
560 idx_delete,
561 IdxDelete,
562 OpIdxDeleteState,
563 OpIdxDeleteState::Seeking
564 );
565 active_state_accessor!(
566 integrity_check,
567 IntegrityCheck,
568 OpIntegrityCheckState,
569 OpIntegrityCheckState::Start
570 );
571 active_state_accessor!(
572 open_ephemeral,
573 OpenEphemeral,
574 OpOpenEphemeralState,
575 OpOpenEphemeralState::Start
576 );
577 active_state_accessor!(program, Program, OpProgramState, OpProgramState::Start);
578 active_state_accessor!(new_rowid, NewRowid, OpNewRowidState, OpNewRowidState::Start);
579 active_state_accessor!(
580 idx_insert,
581 IdxInsert,
582 OpIdxInsertState,
583 OpIdxInsertState::MaybeSeek
584 );
585 active_state_accessor!(
586 insert,
587 Insert,
588 OpInsertState,
589 OpInsertState {
590 sub_state: OpInsertSubState::MaybeCaptureRecord,
591 old_record: None,
592 is_noop_update: false,
593 }
594 );
595 active_state_accessor!(
596 no_conflict,
597 NoConflict,
598 OpNoConflictState,
599 OpNoConflictState::Start
600 );
601 active_state_accessor!(column, Column, OpColumnState, OpColumnState::Start);
602 active_state_accessor!(row_id, RowId, OpRowIdState, OpRowIdState::Start);
603 active_state_accessor!(
604 transaction,
605 Transaction,
606 OpTransactionState,
607 OpTransactionState::Start
608 );
609 active_state_accessor!(attach, Attach, OpAttachState, OpAttachState::default());
610 active_state_accessor!(
611 journal_mode,
612 JournalMode,
613 OpJournalModeState,
614 OpJournalModeState::default()
615 );
616 active_state_accessor!(parse_schema, ParseSchema, OpParseSchemaState, None);
617 active_state_accessor!(hash_build, HashBuild, Option<OpHashBuildState>, None);
618 active_state_accessor!(hash_probe, HashProbe, Option<OpHashProbeState>, None);
619 active_state_accessor!(
620 init_cdc_version,
621 InitCdcVersion,
622 OpInitCdcVersionState,
623 None
624 );
625
626 fn take_parse_schema_if_active(&mut self) -> execute::OpParseSchemaState {
630 if let ActiveOpState::ParseSchema(inner) = &mut self.state {
631 let taken = inner.take();
632 self.state = ActiveOpState::None;
633 taken
634 } else {
635 None
636 }
637 }
638
639 fn program_ref(&self) -> Option<&OpProgramState> {
640 match &self.state {
641 ActiveOpState::Program(state) => Some(state),
642 _ => None,
643 }
644 }
645
646 fn program_mut(&mut self) -> Option<&mut OpProgramState> {
647 match &mut self.state {
648 ActiveOpState::Program(state) => Some(state),
649 _ => None,
650 }
651 }
652}
653
654#[derive(Debug, Clone)]
655pub(crate) struct DeferredSeekState {
656 pub index_cursor_id: CursorID,
657 pub table_cursor_id: CursorID,
658}
659
660pub(crate) enum VacuumOpState {
661 None,
662 IntoFile(Box<VacuumIntoOpContext>),
663 InPlace(Box<VacuumInPlaceOpContext>),
664}
665
666impl Default for VacuumOpState {
667 fn default() -> Self {
668 Self::None
669 }
670}
671
672#[derive(Clone)]
679pub struct SequenceInnerTxState {
680 pub db: usize,
681 pub inner_tx_id: crate::mvcc::database::TxID,
682 pub saved_outer: Option<(
683 crate::mvcc::database::TxID,
684 crate::translate::emitter::TransactionMode,
685 )>,
686}
687
688pub struct ProgramState {
689 pub io_completions: Option<IOCompletions>,
690 pub pc: InsnReference,
691 pub(crate) cursors: Vec<Option<Cursor>>,
692 cursor_seqs: Vec<i64>,
693 registers: Box<[Register]>,
694 pre_op_registers: Option<Box<[Register]>>,
696 pub(crate) result_row: Option<Row>,
697 last_compare: Option<std::cmp::Ordering>,
698 deferred_seeks: Vec<Option<DeferredSeekState>>,
699 ended_coroutine: Vec<u32>,
702 once: SmallVec<[u32; 4]>,
704 pub execution_state: ProgramExecutionState,
705 pub query_deadline: Option<crate::MonotonicInstant>,
708 pub parameters: Vec<Value>,
709 commit_state: CommitState,
710 pub sequence_inner_commit: Option<StateMachine<Box<MvccCommitStateMachine>>>,
716 pub sequence_inner_tx_pending: Option<SequenceInnerTxState>,
726 pub sequence_inner_retry_count: u32,
735 #[cfg(clt_turso_feature = "json")]
736 json_cache: JsonCacheCell,
737 active_op_state: ActiveOpStateSlot,
738 seek_state: OpSeekState,
739 pub metrics: StatementMetrics,
741 current_collation: Option<CollationSeq>,
743 op_vacuum_state: VacuumOpState,
744 view_delta_state: ViewDeltaCommitState,
746 pub(crate) auto_txn_cleanup: TxnCleanup,
749 pub explain_state: RwLock<ExplainState>,
750 distinct_key_values: Vec<Value>,
752 hash_tables: HashMap<usize, HashTable>,
753 ephemeral_temp_files: HashMap<usize, TempFile>,
756 attached_savepoint_pagers: Vec<Arc<Pager>>,
758 pub(crate) pending_fail_error: Option<LimboError>,
762 pub(crate) pending_cdc_info: Option<Option<CaptureDataChangesInfo>>,
769 pub(crate) subprogram_stmt_cache: HashMap<usize, Box<Statement>>,
772 rowsets: HashMap<usize, RowSet>,
774 pub(crate) bloom_filters: HashMap<usize, BloomFilter>,
777 fk_deferred_violations_when_stmt_started: AtomicIsize,
781 fk_immediate_violations_during_stmt: AtomicIsize,
784 uses_subjournal: bool,
785 pub(crate) is_active_write: bool,
787 has_stmt_transaction: bool,
789 pub n_change: AtomicI64,
790 pub n_total_change: AtomicI64,
791}
792
793impl std::fmt::Debug for Program {
794 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
795 f.debug_struct("Program").finish()
796 }
797}
798
799unsafe impl Send for ProgramState {}
805unsafe impl Sync for ProgramState {}
806crate::assert::assert_send_sync!(ProgramState);
807
808impl ProgramState {
809 pub fn new(max_registers: usize, max_cursors: usize) -> Self {
810 let cursors: Vec<Option<Cursor>> = (0..max_cursors).map(|_| None).collect();
811 let cursor_seqs = vec![0i64; max_cursors];
812 let registers = vec![Register::Value(Value::Null); max_registers].into_boxed_slice();
813 Self {
814 io_completions: None,
815 pc: 0,
816 cursors,
817 cursor_seqs,
818 registers,
819 pre_op_registers: None,
820 result_row: None,
821 last_compare: None,
822 deferred_seeks: vec![None; max_cursors],
823 ended_coroutine: vec![],
824 once: SmallVec::<[u32; 4]>::new(),
825 execution_state: ProgramExecutionState::Init,
826 query_deadline: None,
827 parameters: Vec::new(),
828 commit_state: CommitState::Ready,
829 sequence_inner_commit: None,
830 sequence_inner_tx_pending: None,
831 sequence_inner_retry_count: 0,
832 #[cfg(clt_turso_feature = "json")]
833 json_cache: JsonCacheCell::new(),
834 active_op_state: ActiveOpStateSlot::default(),
835 seek_state: OpSeekState::Start,
836 metrics: StatementMetrics::new(),
837 distinct_key_values: Vec::new(),
838 current_collation: None,
839 op_vacuum_state: VacuumOpState::None,
840 view_delta_state: ViewDeltaCommitState::NotStarted,
841 auto_txn_cleanup: TxnCleanup::None,
842 fk_deferred_violations_when_stmt_started: AtomicIsize::new(0),
843 fk_immediate_violations_during_stmt: AtomicIsize::new(0),
844 rowsets: HashMap::default(),
845 bloom_filters: HashMap::default(),
846 hash_tables: HashMap::default(),
847 ephemeral_temp_files: HashMap::default(),
848 uses_subjournal: false,
849 is_active_write: false,
850 has_stmt_transaction: false,
851 attached_savepoint_pagers: Vec::new(),
852 n_change: AtomicI64::new(0),
853 n_total_change: AtomicI64::new(0),
854 explain_state: RwLock::new(ExplainState::default()),
855 pending_fail_error: None,
856 pending_cdc_info: None,
857 subprogram_stmt_cache: HashMap::default(),
858 }
859 }
860
861 pub fn set_register(&mut self, idx: usize, value: Register) {
862 self.registers[idx] = value;
863 }
864
865 pub fn get_register(&self, idx: usize) -> &Register {
866 &self.registers[idx]
867 }
868
869 pub fn column_count(&self) -> usize {
870 self.registers.len()
871 }
872
873 pub fn column(&self, i: usize) -> Option<String> {
874 Some(format!("{:?}", self.registers[i]))
875 }
876
877 pub fn interrupt(&mut self) {
878 self.execution_state = ProgramExecutionState::Interrupting;
879 }
880
881 pub fn is_interrupted(&self) -> bool {
882 matches!(self.execution_state, ProgramExecutionState::Interrupting)
883 }
884
885 pub fn bind_at(&mut self, index: NonZero<usize>, value: Value) -> Result<()> {
886 let i = index.get() - 1;
887 if i >= self.parameters.len() {
888 self.parameters.resize(i + 1, Value::Null);
889 }
890 let slot = &mut self.parameters[i];
891 match (slot, value) {
892 (Value::Null, Value::Null) => {}
893 (Value::Numeric(Numeric::Integer(existing)), Value::Numeric(Numeric::Integer(new))) => {
894 *existing = new
895 }
896 (Value::Numeric(Numeric::Float(existing)), Value::Numeric(Numeric::Float(new))) => {
897 *existing = new
898 }
899 (Value::Text(existing), Value::Text(new)) => existing.do_extend(&new)?,
900 (Value::Blob(existing), Value::Blob(new)) => existing.do_extend(&new)?,
901 (slot, value) => *slot = value,
902 }
903 Ok(())
904 }
905
906 pub fn clear_bindings(&mut self) {
907 self.parameters.clear();
908 }
909
910 pub fn get_parameter(&self, index: NonZero<usize>) -> Value {
911 let i = index.get() - 1;
912 self.parameters.get(i).cloned().unwrap_or(Value::Null)
913 }
914
915 pub fn reset(&mut self, max_registers: Option<usize>, max_cursors: Option<usize>) {
916 self.io_completions = None;
917 self.pc = 0;
918
919 if let Some(max_cursors) = max_cursors {
920 self.cursors.resize_with(max_cursors, || None);
921 self.cursor_seqs.resize(max_cursors, 0);
922 self.deferred_seeks.resize(max_cursors, None);
923 }
924 self.result_row = None;
925 if let Some(max_registers) = max_registers {
926 let mut registers = std::mem::take(&mut self.registers).into_vec();
928 registers.resize_with(max_registers, || Register::Value(Value::Null));
930 self.registers = registers.into_boxed_slice();
931 }
932 self.cursors.iter_mut().for_each(|c| {
934 let _ = c.take();
935 });
936 for r in self.registers.iter_mut() {
937 match r {
938 Register::Value(v) => *v = Value::Null,
939 _ => r.set_null(),
940 }
941 }
942 self.last_compare = None;
943 self.deferred_seeks.iter_mut().for_each(|s| *s = None);
944 self.ended_coroutine.clear();
945 self.once.clear();
946 self.execution_state = ProgramExecutionState::Init;
947 self.query_deadline = None;
948 self.current_collation = None;
949 #[cfg(clt_turso_feature = "json")]
950 self.json_cache.clear();
951
952 self.commit_state.cleanup_mvcc_checkpoint_state();
957 self.active_op_state.clear();
958 self.seek_state = OpSeekState::Start;
959 self.current_collation = None;
960 self.commit_state = CommitState::Ready;
961 self.sequence_inner_commit = None;
967 self.op_vacuum_state = VacuumOpState::None;
968 self.view_delta_state = ViewDeltaCommitState::NotStarted;
969 self.auto_txn_cleanup = TxnCleanup::None;
970 self.fk_immediate_violations_during_stmt
971 .store(0, Ordering::SeqCst);
972 self.fk_deferred_violations_when_stmt_started
973 .store(0, Ordering::SeqCst);
974 self.rowsets.clear();
975 self.bloom_filters.clear();
976 self.hash_tables.clear();
977 self.ephemeral_temp_files.clear();
978 self.uses_subjournal = false;
979 self.is_active_write = false;
980 self.has_stmt_transaction = false;
981 self.distinct_key_values.clear();
982 self.attached_savepoint_pagers.clear();
983 self.n_change.store(0, Ordering::SeqCst);
984 self.n_total_change.store(0, Ordering::SeqCst);
985 *self.explain_state.write() = ExplainState::default();
986 self.pending_fail_error = None;
987 self.pending_cdc_info = None;
988 self.subprogram_stmt_cache.clear();
989 }
990
991 pub(crate) fn record_statement_change(&self) {
992 self.n_change.fetch_add(1, Ordering::SeqCst);
993 self.n_total_change.fetch_add(1, Ordering::SeqCst);
994 }
995
996 pub(crate) fn record_total_change(&self) {
997 self.n_total_change.fetch_add(1, Ordering::SeqCst);
998 }
999
1000 #[inline]
1003 pub(crate) fn can_autocommit_now(&self, connection: &Connection) -> bool {
1004 let is_already_committing = !matches!(self.commit_state, CommitState::Ready);
1005 if is_already_committing {
1006 return true;
1007 }
1008 if self.auto_txn_cleanup != TxnCleanup::RollbackTxn {
1009 return false;
1010 }
1011 let active_writers = connection.n_active_writes.load(Ordering::SeqCst);
1012 turso_assert!(
1013 active_writers <= 1,
1014 "n_active_writes must be 0 or 1, got {active_writers}"
1015 );
1016 if self.is_active_write {
1017 turso_assert!(
1018 active_writers == 1,
1019 "active writer state without an active writer count"
1020 );
1021 }
1022 if connection.mv_store().is_some() {
1023 return connection.n_active_root_statements.load(Ordering::SeqCst) == 1
1026 && (self.is_active_write || active_writers == 0);
1027 }
1028 if self.is_active_write {
1029 return true;
1032 }
1033 active_writers == 0
1035 }
1036
1037 #[inline]
1038 pub fn record_rows_read(&mut self, count: u64) {
1039 self.metrics.rows_read = self.metrics.rows_read.saturating_add(count);
1040 }
1041
1042 #[inline]
1043 pub fn record_rows_written(&mut self, count: u64) {
1044 self.metrics.rows_written = self.metrics.rows_written.saturating_add(count);
1045 }
1046
1047 pub(crate) fn metrics(&self) -> StatementMetrics {
1048 let mut metrics = self.metrics.clone();
1049 if let Some(OpProgramState::Step { statement, .. }) = self.active_op_state.program_ref() {
1050 metrics.merge(&statement.metrics());
1051 }
1052 for statement in self.subprogram_stmt_cache.values() {
1053 metrics.merge(&statement.metrics());
1054 }
1055 metrics
1056 }
1057
1058 pub(crate) fn reset_metrics(&mut self) {
1059 self.metrics.reset();
1060 if let Some(OpProgramState::Step { statement, .. }) = self.active_op_state.program_mut() {
1061 statement.reset_metrics();
1062 }
1063 for statement in self.subprogram_stmt_cache.values_mut() {
1064 statement.reset_metrics();
1065 }
1066 }
1067
1068 pub(crate) fn reset_stmt_status(&mut self, counter: crate::statement::StatementStatusCounter) {
1069 match counter {
1070 crate::statement::StatementStatusCounter::FullscanStep => {
1071 self.metrics.fullscan_steps = 0
1072 }
1073 crate::statement::StatementStatusCounter::Sort => self.metrics.sort_operations = 0,
1074 crate::statement::StatementStatusCounter::VmStep => self.metrics.insn_executed = 0,
1075 crate::statement::StatementStatusCounter::Reprepare => self.metrics.reprepares = 0,
1076 crate::statement::StatementStatusCounter::RowsRead => self.metrics.rows_read = 0,
1077 crate::statement::StatementStatusCounter::RowsWritten => self.metrics.rows_written = 0,
1078 }
1079 if let Some(OpProgramState::Step { statement, .. }) = self.active_op_state.program_mut() {
1080 statement.reset_stmt_status(counter);
1081 }
1082 for statement in self.subprogram_stmt_cache.values_mut() {
1083 statement.reset_stmt_status(counter);
1084 }
1085 }
1086
1087 pub fn get_cursor(&mut self, cursor_id: CursorID) -> &mut Cursor {
1088 self.cursors
1089 .get_mut(cursor_id)
1090 .unwrap_or_else(|| panic!("cursor id {cursor_id} out of bounds"))
1091 .as_mut()
1092 .unwrap_or_else(|| panic!("cursor id {cursor_id} is None"))
1093 }
1094
1095 pub fn begin_statement(
1106 &mut self,
1107 connection: &Connection,
1108 pager: &Arc<Pager>,
1109 write: bool,
1110 ) -> Result<IOResult<()>> {
1111 let in_explicit_txn = !connection.auto_commit.load(Ordering::SeqCst);
1112 if write && in_explicit_txn {
1113 if let Some(mv_store) = connection.mv_store().as_ref() {
1115 if let Some(tx_id) = connection.get_mv_tx_id() {
1116 mv_store.begin_savepoint(tx_id);
1117 }
1118 } else {
1119 let db_size = return_if_io!(pager.with_header(|header| header.database_size.get()));
1121 pager.open_subjournal()?;
1122 pager.try_use_subjournal()?;
1123 let result = pager.open_savepoint(db_size);
1124 if result.is_err() {
1125 pager.stop_use_subjournal();
1126 }
1127 result?;
1128 self.uses_subjournal = true;
1129 }
1130 }
1131
1132 self.has_stmt_transaction = true;
1133
1134 self.fk_deferred_violations_when_stmt_started.store(
1138 connection.fk_deferred_violations.load(Ordering::Acquire),
1139 Ordering::SeqCst,
1140 );
1141 self.fk_immediate_violations_during_stmt
1143 .store(0, Ordering::Release);
1144 Ok(IOResult::Done(()))
1145 }
1146
1147 pub fn end_statement(
1154 &mut self,
1155 connection: &Connection,
1156 pager: &Arc<Pager>,
1157 end_statement: EndStatement,
1158 ) -> Result<()> {
1159 if self.is_active_write {
1160 let previous = connection.n_active_writes.fetch_sub(1, Ordering::SeqCst);
1161 turso_assert!(
1162 previous == 1,
1163 "ending a writer with {previous} active writer(s)"
1164 );
1165 self.is_active_write = false;
1166 }
1167 if !self.has_stmt_transaction {
1169 return Ok(());
1170 }
1171 self.has_stmt_transaction = false;
1172
1173 let attached_pagers: Vec<Arc<Pager>> = self.attached_savepoint_pagers.drain(..).collect();
1175 let result = match end_statement {
1176 EndStatement::ReleaseSavepoint => {
1177 if let Some(mv_store) = connection.mv_store().as_ref() {
1178 if let Some(tx_id) = connection.get_mv_tx_id() {
1179 mv_store.release_savepoint(tx_id);
1180 }
1181 connection.for_each_attached_mv_tx(|db_id, tx_id| {
1182 if let Some(attached_mv) = connection.mv_store_for_db(db_id) {
1183 attached_mv.release_savepoint(tx_id);
1184 }
1185 });
1186 Ok(())
1187 } else if self.uses_subjournal || !attached_pagers.is_empty() {
1188 if self.uses_subjournal {
1189 pager.release_savepoint()?;
1190 }
1191 for p in &attached_pagers {
1192 p.release_savepoint()?;
1193 }
1194 Ok(())
1195 } else {
1196 Ok(())
1197 }
1198 }
1199 EndStatement::RollbackSavepoint => {
1200 let pager_err = if let Some(mv_store) = connection.mv_store().as_ref() {
1202 let mut err = None;
1203 if let Some(tx_id) = connection.get_mv_tx_id() {
1204 if let Err(e) = mv_store.rollback_first_savepoint(tx_id) {
1205 err = Some(e);
1206 }
1207 }
1208 connection.for_each_attached_mv_tx(|db_id, tx_id| {
1209 if let Some(attached_mv) = connection.mv_store_for_db(db_id) {
1210 if let Err(e) = attached_mv.rollback_first_savepoint(tx_id) {
1211 if err.is_none() {
1212 err = Some(e);
1213 }
1214 }
1215 }
1216 });
1217 err
1218 } else if self.uses_subjournal {
1219 match pager.rollback_to_newest_savepoint() {
1220 Ok(_) => {
1221 let mut err = None;
1222 for p in &attached_pagers {
1223 if let Err(e) = p.rollback_to_newest_savepoint() {
1224 err = Some(e);
1225 break;
1226 }
1227 }
1228 err
1229 }
1230 Err(e) => Some(e),
1231 }
1232 } else if !attached_pagers.is_empty() {
1233 let mut err = None;
1234 for p in &attached_pagers {
1235 if let Err(e) = p.rollback_to_newest_savepoint() {
1236 err = Some(e);
1237 }
1238 }
1239 err
1240 } else {
1241 None
1242 };
1243
1244 connection.fk_deferred_violations.store(
1248 self.fk_deferred_violations_when_stmt_started
1249 .load(Ordering::Acquire),
1250 Ordering::SeqCst,
1251 );
1252
1253 match pager_err {
1254 Some(e) => Err(e),
1255 None => Ok(()),
1256 }
1257 }
1258 };
1259 if self.uses_subjournal {
1260 pager.stop_use_subjournal();
1261 self.uses_subjournal = false;
1262 }
1263 for p in &attached_pagers {
1264 p.stop_use_subjournal();
1265 }
1266 result
1267 }
1268
1269 pub fn get_or_create_bloom_filter(&mut self, cursor_id: usize) -> &mut BloomFilter {
1271 self.bloom_filters.entry(cursor_id).or_default()
1272 }
1273
1274 pub fn get_or_create_bloom_filter_with_capacity(
1276 &mut self,
1277 cursor_id: usize,
1278 expected_items: u32,
1279 false_positive_rate: f32,
1280 ) -> &mut BloomFilter {
1281 self.bloom_filters
1282 .entry(cursor_id)
1283 .or_insert_with(|| BloomFilter::with_capacity(expected_items, false_positive_rate))
1284 }
1285
1286 pub fn get_bloom_filter(&self, cursor_id: usize) -> Option<&BloomFilter> {
1288 self.bloom_filters.get(&cursor_id)
1289 }
1290
1291 pub fn get_bloom_filter_mut(&mut self, cursor_id: usize) -> Option<&mut BloomFilter> {
1293 self.bloom_filters.get_mut(&cursor_id)
1294 }
1295
1296 pub fn remove_bloom_filter(&mut self, cursor_id: usize) {
1298 self.bloom_filters.remove(&cursor_id);
1299 }
1300
1301 pub fn has_bloom_filter(&self, cursor_id: usize) -> bool {
1303 self.bloom_filters.contains_key(&cursor_id)
1304 }
1305
1306 pub fn get_fk_immediate_violations_during_stmt(&self) -> isize {
1307 self.fk_immediate_violations_during_stmt
1308 .load(Ordering::Acquire)
1309 }
1310
1311 pub fn increment_fk_immediate_violations_during_stmt(&self, v: isize) {
1312 self.fk_immediate_violations_during_stmt
1313 .fetch_add(v, Ordering::AcqRel);
1314 }
1315}
1316
1317#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1318pub enum EndStatement {
1320 ReleaseSavepoint,
1322 RollbackSavepoint,
1325}
1326
1327impl Register {
1328 pub fn get_value(&self) -> &Value {
1329 match self {
1330 Register::Value(v) => v,
1331 Register::Record(r) => {
1332 turso_assert!(!r.is_invalidated());
1333 r.as_blob_value()
1334 }
1335 _ => panic!("register holds unexpected value: {self:?}"),
1336 }
1337 }
1338}
1339
1340#[macro_export]
1341macro_rules! must_be_btree_cursor {
1342 ($cursor_id:expr, $cursor_ref:expr, $state:expr, $insn_name:expr) => {{
1343 let (_, cursor_type) = $cursor_ref.get($cursor_id).unwrap();
1344 if matches!(
1345 cursor_type,
1346 CursorType::BTreeTable(_)
1347 | CursorType::BTreeIndex(_)
1348 | CursorType::MaterializedView(_, _)
1349 ) {
1350 $crate::get_cursor!($state, $cursor_id)
1351 } else {
1352 panic!("{} on unexpected cursor", $insn_name)
1353 }
1354 }};
1355}
1356
1357#[macro_export]
1360macro_rules! get_cursor {
1361 ($state:expr, $cursor_id:expr) => {
1362 $state
1363 .cursors
1364 .get_mut($cursor_id)
1365 .unwrap_or_else(|| panic!("cursor id {} out of bounds", $cursor_id))
1366 .as_mut()
1367 .unwrap_or_else(|| panic!("cursor id {} is None", $cursor_id))
1368 };
1369}
1370
1371#[derive(Default)]
1373pub struct ExplainState {
1374 pending: std::collections::VecDeque<Arc<PreparedProgram>>,
1376 queued_subprograms: std::collections::HashSet<usize>,
1382 current: Option<Arc<PreparedProgram>>,
1384}
1385
1386impl ExplainState {
1387 fn queue_subprogram_once(&mut self, subprogram: Arc<PreparedProgram>) {
1389 let subprogram_id = Arc::as_ptr(&subprogram) as usize;
1390 if self.queued_subprograms.insert(subprogram_id) {
1391 self.pending.push_back(subprogram);
1392 }
1393 }
1394}
1395
1396#[derive(Debug, Clone)]
1397pub struct PreparedProgram {
1398 pub max_registers: usize,
1399 pub insns: Vec<(Insn, usize)>,
1402 pub cursor_ref: Vec<(Option<CursorKey>, CursorType)>,
1403 pub comments: Vec<(InsnReference, &'static str)>,
1404 pub parameters: crate::parameters::Parameters,
1405 pub change_cnt_on: bool,
1406 pub readonly: bool,
1409 pub result_columns: Vec<ResultSetColumn>,
1410 pub table_references: TableReferences,
1411 pub sql: String,
1412 pub needs_stmt_subtransactions: Arc<AtomicBool>,
1416 pub trigger: Option<Arc<Trigger>>,
1418 pub is_subprogram: bool,
1420 pub resolve_type: ResolveType,
1421 pub prepare_context: PrepareContext,
1422 pub write_databases: BitSet,
1424 pub read_databases: BitSet,
1426}
1427
1428#[derive(Clone)]
1429pub struct Program {
1430 pub(crate) prepared: Arc<PreparedProgram>,
1431 pub connection: Arc<Connection>,
1432}
1433
1434#[derive(Debug, Clone, PartialEq, Eq)]
1447pub struct PrepareContext {
1448 database_ptr: usize,
1450 generation: u64,
1455}
1456
1457impl PrepareContext {
1458 pub fn from_connection(connection: &Connection) -> Self {
1459 Self {
1460 database_ptr: connection.database_ptr(),
1461 generation: connection.prepare_context_generation(),
1462 }
1463 }
1464
1465 #[inline]
1466 pub fn matches_connection(&self, connection: &Connection) -> bool {
1467 self.database_ptr == connection.database_ptr()
1468 && self.generation == connection.prepare_context_generation()
1469 }
1470}
1471
1472impl PreparedProgram {
1473 pub fn bind(self: Arc<Self>, connection: Arc<Connection>) -> Program {
1474 Program {
1475 prepared: self,
1476 connection,
1477 }
1478 }
1479
1480 pub fn is_compatible_with(&self, connection: &Connection) -> bool {
1481 self.prepare_context.matches_connection(connection)
1482 }
1483
1484 #[inline]
1485 pub const fn is_readonly(&self) -> bool {
1486 self.readonly
1487 }
1488}
1489
1490impl Program {
1491 #[inline]
1492 pub fn prepared(&self) -> &Arc<PreparedProgram> {
1493 &self.prepared
1494 }
1495
1496 pub fn from_prepared(prepared: Arc<PreparedProgram>, connection: Arc<Connection>) -> Self {
1497 Self {
1498 prepared,
1499 connection,
1500 }
1501 }
1502
1503 #[inline]
1504 pub fn is_readonly(&self) -> bool {
1505 self.prepared().is_readonly()
1506 }
1507}
1508
1509impl Program {
1510 fn get_pager_from_database_index(&self, idx: &usize) -> Result<Arc<Pager>> {
1511 self.connection.get_pager_from_database_index(idx)
1512 }
1513
1514 #[inline]
1515 fn maybe_request_interrupt<I>(&self, state: &mut ProgramState, io: &I) -> bool
1516 where
1517 I: crate::IO + ?Sized,
1518 {
1519 let connection_interrupt = self.connection.is_interrupted();
1520 let hit_query_deadline = state
1521 .query_deadline
1522 .is_some_and(|deadline| io.current_time_monotonic() >= deadline);
1523 let progress_interrupt = self
1524 .connection
1525 .should_interrupt_for_progress(state.metrics.vm_steps);
1526 if connection_interrupt || hit_query_deadline || progress_interrupt {
1527 state.interrupt();
1528 }
1529 state.is_interrupted()
1530 }
1531
1532 #[turso_macros::trace_stack]
1533 pub fn step(
1534 &self,
1535 state: &mut ProgramState,
1536 pager: &Arc<Pager>,
1537 query_mode: QueryMode,
1538 waker: Option<&Waker>,
1539 ) -> Result<StepResult> {
1540 state.execution_state = ProgramExecutionState::Running;
1541 let result = match query_mode {
1542 QueryMode::Normal => self.normal_step(state, pager, waker),
1543 QueryMode::Explain => self.explain_step(state, pager),
1544 QueryMode::ExplainQueryPlan => self.explain_query_plan_step(state, pager),
1545 };
1546 match &result {
1547 Ok(StepResult::Done) => {
1548 state.execution_state = ProgramExecutionState::Done;
1549 }
1550 Ok(StepResult::Interrupt) => {
1551 state.execution_state = ProgramExecutionState::Interrupted;
1552 }
1553 Err(_) => {
1554 state.execution_state = ProgramExecutionState::Failed;
1555 }
1556 _ => {}
1557 }
1558 result
1559 }
1560
1561 fn explain_step(&self, state: &mut ProgramState, pager: &Arc<Pager>) -> Result<StepResult> {
1562 turso_debug_assert!(state.column_count() == EXPLAIN_COLUMNS.len());
1563 if self.connection.is_closed() {
1564 let tx_state = self.connection.get_tx_state();
1565 if let TransactionState::Write { .. } = tx_state {
1566 pager.rollback_tx(&self.connection);
1567 }
1568 return Err(LimboError::InternalError("Connection closed".to_string()));
1569 }
1570
1571 if self.maybe_request_interrupt(state, pager.io.as_ref()) {
1572 return Ok(StepResult::Interrupt);
1573 }
1574
1575 state.metrics.vm_steps = state.metrics.vm_steps.saturating_add(1);
1576
1577 let mut explain_state = state.explain_state.write();
1578
1579 loop {
1581 if let Some(ref current) = explain_state.current {
1582 if (state.pc as usize) < current.insns.len() {
1583 break;
1584 }
1585 } else if (state.pc as usize) < self.insns.len() {
1586 break;
1587 }
1588 if let Some(next) = explain_state.pending.pop_front() {
1590 explain_state.current = Some(next);
1591 state.pc = 0;
1592 } else {
1593 explain_state.current = None;
1594 return Ok(StepResult::Done);
1595 }
1596 }
1597
1598 let pc = state.pc as usize;
1599
1600 let (row, subprogram) = if let Some(ref current) = explain_state.current {
1603 let (insn, _) = ¤t.insns[pc];
1604 let sub = if let Insn::Program {
1605 program: subprogram,
1606 ..
1607 } = insn
1608 {
1609 Some(subprogram.prepared_program()?)
1610 } else {
1611 None
1612 };
1613 let comment = current
1614 .comments
1615 .iter()
1616 .find(|(offset, _)| *offset == state.pc)
1617 .map(|(_, c)| *c);
1618 (insn_to_row_with_comment(current, insn, comment), sub)
1619 } else {
1620 let (insn, _) = &self.insns[pc];
1621 let sub = if let Insn::Program {
1622 program: subprogram,
1623 ..
1624 } = insn
1625 {
1626 Some(subprogram.prepared_program()?)
1627 } else {
1628 None
1629 };
1630 let comment = self
1631 .comments
1632 .iter()
1633 .find(|(offset, _)| *offset == state.pc)
1634 .map(|(_, c)| *c);
1635 (insn_to_row_with_comment(self, insn, comment), sub)
1636 };
1637 if let Some(sub) = subprogram {
1638 explain_state.queue_subprogram_once(sub);
1639 }
1640 let (opcode, p1, p2, p3, p4, p5, comment) = row;
1641
1642 state.registers[0].set_int(state.pc as i64);
1643 state.registers[1].set_value(Value::from_text(opcode));
1644 state.registers[2].set_int(p1);
1645 state.registers[3].set_int(p2);
1646 state.registers[4].set_int(p3);
1647 state.registers[5].set_value(p4);
1648 state.registers[6].set_int(p5);
1649 state.registers[7].set_value(Value::from_text(comment));
1650 state.result_row = Some(Row {
1651 values: &state.registers[0] as *const Register,
1652 count: EXPLAIN_COLUMNS.len(),
1653 });
1654 state.pc += 1;
1655 Ok(StepResult::Row)
1656 }
1657
1658 fn explain_query_plan_step(
1659 &self,
1660 state: &mut ProgramState,
1661 pager: &Arc<Pager>,
1662 ) -> Result<StepResult> {
1663 turso_debug_assert!(state.column_count() == EXPLAIN_QUERY_PLAN_COLUMNS.len());
1664 loop {
1665 if self.connection.is_closed() {
1666 let state = self.connection.get_tx_state();
1668 if let TransactionState::Write { .. } = state {
1669 pager.rollback_tx(&self.connection);
1670 }
1671 return Err(LimboError::InternalError("Connection closed".to_string()));
1672 }
1673
1674 if self.maybe_request_interrupt(state, pager.io.as_ref()) {
1675 return Ok(StepResult::Interrupt);
1676 }
1677
1678 state.metrics.vm_steps = state.metrics.vm_steps.saturating_add(1);
1680
1681 if state.pc as usize >= self.insns.len() {
1682 return Ok(StepResult::Done);
1683 }
1684
1685 let Insn::Explain { p1, p2, detail } = &self.insns[state.pc as usize].0 else {
1686 state.pc += 1;
1687 continue;
1688 };
1689
1690 state.registers[0].set_int(*p1 as i64);
1691 state.registers[1] =
1692 Register::Value(Value::from_i64(p2.as_ref().map(|p| *p).unwrap_or(0) as i64));
1693 state.registers[2].set_int(0);
1694 state.registers[3].set_value(Value::from_text(detail.clone()));
1695 state.result_row = Some(Row {
1696 values: &state.registers[0] as *const Register,
1697 count: EXPLAIN_QUERY_PLAN_COLUMNS.len(),
1698 });
1699 state.pc += 1;
1700 return Ok(StepResult::Row);
1701 }
1702 }
1703
1704 #[instrument(skip_all, level = Level::DEBUG)]
1705 fn normal_step(
1706 &self,
1707 state: &mut ProgramState,
1708 pager: &Arc<Pager>,
1709 waker: Option<&Waker>,
1710 ) -> Result<StepResult> {
1711 let enable_tracing = tracing::enabled!(tracing::Level::TRACE);
1712 loop {
1713 if self.connection.is_closed() {
1714 let state = self.connection.get_tx_state();
1716 if let TransactionState::Write { .. } = state {
1717 pager.rollback_tx(&self.connection);
1718 }
1719 return Err(LimboError::InternalError("Connection closed".to_string()));
1720 }
1721 if self.maybe_request_interrupt(state, pager.io.as_ref()) {
1722 self.abort(pager, None, state)?;
1723 return Ok(StepResult::Interrupt);
1724 }
1725
1726 if let Some(io) = &state.io_completions {
1727 if !io.finished() {
1728 io.set_waker(waker);
1729 return Ok(StepResult::IO);
1730 }
1731 if let Some(err) = io.get_error() {
1732 if pager.is_checkpointing() {
1733 let checkpoint_err = LimboError::CheckpointFailed(err.to_string());
1739 tracing::error!("Checkpoint failed: {checkpoint_err}");
1740 if let Err(abort_err) = self.abort(pager, Some(&checkpoint_err), state) {
1741 tracing::error!(
1742 "Abort also failed during checkpoint error handling: {abort_err}"
1743 );
1744 }
1745 return Err(checkpoint_err);
1746 }
1747 let err = err.into();
1748 if let Err(abort_err) = self.abort(pager, Some(&err), state) {
1749 tracing::error!("Abort failed during error handling: {abort_err}");
1750 }
1751 return Err(err);
1752 }
1753 state.io_completions = None;
1754 }
1755 let _ = state.result_row.take();
1757 let (insn, _) = &self.insns[state.pc as usize];
1758 let insn_function = insn.to_function();
1759 if enable_tracing {
1760 trace_insn(self, state.pc as InsnReference, insn);
1761 crate::stack::trace_remaining("program_step:opcode");
1762 }
1763 if self.connection.get_vdbe_trace() {
1764 if let Some(ref old) = state.pre_op_registers {
1768 for (i, (old_reg, new_reg)) in
1769 old.iter().zip(state.registers.iter()).enumerate()
1770 {
1771 if old_reg != new_reg {
1772 match new_reg {
1773 Register::Value(v) => eprintln!("R[{i}] = {v}"),
1774 Register::Aggregate(_) => eprintln!("R[{i}] = <aggregate>"),
1775 Register::Record(_) => eprintln!("R[{i}] = <record>"),
1776 }
1777 }
1778 }
1779 state.pre_op_registers = None;
1780 }
1781
1782 if matches!(insn, Insn::Init { .. }) {
1784 eprintln!("VDBE Trace:");
1785 }
1786 eprintln!(
1787 "{}",
1788 explain::insn_to_str(
1789 self,
1790 state.pc as InsnReference,
1791 insn,
1792 String::new(),
1793 self.comments
1794 .iter()
1795 .find(|(offset, _)| *offset == state.pc as InsnReference)
1796 .map(|(_, comment)| comment)
1797 .copied()
1798 )
1799 );
1800 state.pre_op_registers = Some(state.registers.clone());
1802 }
1803 state.metrics.vm_steps = state.metrics.vm_steps.saturating_add(1);
1805
1806 match insn_function(self, state, insn, pager) {
1807 Ok(InsnFunctionStepResult::Step) => {
1808 state.metrics.insn_executed = state.metrics.insn_executed.saturating_add(1);
1810 }
1811 Ok(InsnFunctionStepResult::Done) => {
1812 state.metrics.insn_executed = state.metrics.insn_executed.saturating_add(1);
1814 state.auto_txn_cleanup = TxnCleanup::None;
1815 return Ok(StepResult::Done);
1816 }
1817 Ok(InsnFunctionStepResult::IO(io)) => {
1818 io.set_waker(waker);
1820 let is_yield = io.is_explicit_yield();
1821 if is_yield {
1822 return Ok(StepResult::Yield);
1828 }
1829 let finished = io.finished();
1830 state.io_completions = Some(io);
1831 if !finished {
1832 return Ok(StepResult::IO);
1833 }
1834 }
1836 Ok(InsnFunctionStepResult::Row) => {
1837 state.metrics.insn_executed = state.metrics.insn_executed.saturating_add(1);
1839 return Ok(StepResult::Row);
1840 }
1841 Err(LimboError::Busy) => {
1842 return Ok(StepResult::Busy);
1844 }
1845 Err(LimboError::BusySnapshot)
1846 if self.connection.transaction_state.get() == TransactionState::None =>
1847 {
1848 return Ok(StepResult::Busy);
1853 }
1854 Err(err) => {
1855 if let Err(abort_err) = self.abort(pager, Some(&err), state) {
1856 tracing::error!("Abort failed during error handling: {abort_err}");
1857 }
1858 return Err(err);
1859 }
1860 }
1861 }
1862 }
1863
1864 #[instrument(skip_all, level = Level::DEBUG)]
1865 fn apply_view_deltas(
1866 &self,
1867 state: &mut ProgramState,
1868 rollback: bool,
1869 pager: &Arc<Pager>,
1870 ) -> Result<IOResult<()>> {
1871 use crate::types::IOResult;
1872
1873 loop {
1874 match &state.view_delta_state {
1875 ViewDeltaCommitState::NotStarted => {
1876 if self.connection.view_transaction_states.is_empty() {
1877 return Ok(IOResult::Done(()));
1878 }
1879
1880 if rollback {
1881 self.connection.view_transaction_states.clear();
1883 return Ok(IOResult::Done(()));
1884 }
1885
1886 let schema = self.connection.schema.read();
1888
1889 let mut views = Vec::new();
1891 for view_name in self.connection.view_transaction_states.get_view_names() {
1892 if let Some(view_mutex) = schema.get_materialized_view(&view_name) {
1893 let view = view_mutex.lock();
1894 let root_page = view.get_root_page();
1895
1896 turso_assert_ne!(
1898 root_page, 0,
1899 "Materialized view should have a root page",
1900 { "view_name": view_name }
1901 );
1902
1903 views.push(view_name);
1904 }
1905 }
1906
1907 state.view_delta_state = ViewDeltaCommitState::Processing {
1908 views,
1909 current_index: 0,
1910 };
1911 }
1912
1913 ViewDeltaCommitState::Processing {
1914 views,
1915 current_index,
1916 } => {
1917 if *current_index >= views.len() {
1919 self.connection.view_transaction_states.clear();
1921 state.view_delta_state = ViewDeltaCommitState::Done;
1922 return Ok(IOResult::Done(()));
1923 }
1924
1925 let view_name = &views[*current_index];
1926
1927 let table_deltas = self
1928 .connection
1929 .view_transaction_states
1930 .get(view_name)
1931 .expect("view should have transaction state")
1932 .get_table_deltas();
1933
1934 let schema = self.connection.schema.read();
1935 if let Some(view_mutex) = schema.get_materialized_view(view_name) {
1936 let mut view = view_mutex.lock();
1937
1938 let mut delta_set = crate::incremental::compiler::DeltaSet::new();
1940 for (table_name, delta) in table_deltas {
1941 delta_set.insert(table_name, delta);
1942 }
1943
1944 match view.merge_delta(delta_set, pager.clone())? {
1946 IOResult::Done(_) => {
1947 state.view_delta_state = ViewDeltaCommitState::Processing {
1949 views: views.clone(),
1950 current_index: current_index + 1,
1951 };
1952 }
1953 IOResult::IO(io) => {
1954 return Ok(IOResult::IO(io));
1956 }
1957 }
1958 }
1959 }
1960
1961 ViewDeltaCommitState::Done => {
1962 return Ok(IOResult::Done(()));
1963 }
1964 }
1965 }
1966 }
1967
1968 pub fn commit_txn(
1969 &self,
1970 pager: Arc<Pager>,
1971 program_state: &mut ProgramState,
1972 mv_store: Option<&Arc<MvStore>>,
1973 rollback: bool,
1974 ) -> Result<IOResult<()>> {
1975 match self.apply_view_deltas(program_state, rollback, &pager)? {
1977 IOResult::IO(io) => return Ok(IOResult::IO(io)),
1978 IOResult::Done(_) => {}
1979 }
1980
1981 program_state.view_delta_state = ViewDeltaCommitState::NotStarted;
1983 let tx_state = self.connection.get_tx_state();
1984 if tx_state == TransactionState::None
1985 && matches!(program_state.commit_state, CommitState::Ready)
1986 {
1987 let has_attached_mv_tx = self.connection.next_attached_mv_tx().is_some();
1992 let has_attached_wal_tx =
1993 self.connection
1994 .with_all_attached_pagers_with_index(|pagers| {
1995 pagers.iter().any(|(_, pager)| pager.holds_read_lock())
1996 });
1997 if !has_attached_mv_tx && !has_attached_wal_tx {
1998 return Ok(IOResult::Done(()));
1999 }
2000 }
2001 if self.connection.is_nested_stmt() {
2002 return Ok(IOResult::Done(()));
2004 }
2005 let res = if let Some(mv_store) = mv_store {
2006 self.commit_txn_mvcc(pager, program_state, mv_store, rollback)
2007 } else {
2008 self.commit_txn_wal(pager, program_state, rollback)
2009 }?;
2010 if !res.is_io() {
2011 if self.change_cnt_on {
2012 self.connection
2013 .set_changes(program_state.n_change.load(Ordering::SeqCst));
2014 self.connection
2015 .add_total_changes(program_state.n_total_change.load(Ordering::SeqCst));
2016 }
2017 let transaction_finished = self.connection.auto_commit.load(Ordering::SeqCst)
2018 && self.connection.get_tx_state() == TransactionState::None;
2019 if transaction_finished {
2020 if rollback {
2026 self.connection.rollback_temp_schema();
2027 } else {
2028 self.connection.commit_temp_schema();
2029 }
2030 }
2031 }
2032 Ok(res)
2033 }
2034
2035 fn commit_txn_wal(
2036 &self,
2037 pager: Arc<Pager>,
2038 program_state: &mut ProgramState,
2039 rollback: bool,
2040 ) -> Result<IOResult<()>> {
2041 let connection = self.connection.clone();
2042 let auto_commit = connection.auto_commit.load(Ordering::SeqCst);
2043 let tx_state = connection.get_tx_state();
2044 tracing::debug!(
2045 "Halt auto_commit {}, commit_state={:?}, tx_state={:?}",
2046 auto_commit,
2047 program_state.commit_state,
2048 tx_state,
2049 );
2050 if matches!(program_state.commit_state, CommitState::Committing) {
2051 let TransactionState::Write { .. } = tx_state else {
2052 unreachable!("invalid state for write commit step")
2053 };
2054 self.step_end_write_txn(&pager, &connection, program_state, rollback)
2055 } else if matches!(program_state.commit_state, CommitState::CommittingAttached) {
2056 match self.end_attached_write_txns(&connection, rollback)? {
2058 IOResult::Done(_) => {
2059 program_state.commit_state = CommitState::Ready;
2060 if pager.holds_read_lock() {
2061 pager.end_read_tx();
2062 }
2063 self.end_attached_read_txns(&connection);
2064 Ok(IOResult::Done(()))
2065 }
2066 IOResult::IO(io) => Ok(IOResult::IO(io)),
2067 }
2068 } else if auto_commit {
2069 match tx_state {
2070 TransactionState::Write { .. } => {
2071 self.step_end_write_txn(&pager, &connection, program_state, rollback)
2072 }
2073 TransactionState::Read => {
2074 connection.set_tx_state(TransactionState::None);
2075 match self.end_attached_write_txns(&connection, rollback)? {
2080 IOResult::Done(_) => {}
2081 IOResult::IO(io) => {
2082 program_state.commit_state = CommitState::CommittingAttached;
2083 return Ok(IOResult::IO(io));
2084 }
2085 }
2086 pager.end_read_tx();
2087 self.end_attached_read_txns(&connection);
2088 Ok(IOResult::Done(()))
2089 }
2090 TransactionState::None => {
2091 match self.end_attached_write_txns(&connection, rollback)? {
2092 IOResult::Done(_) => {}
2093 IOResult::IO(io) => {
2094 program_state.commit_state = CommitState::CommittingAttached;
2095 return Ok(IOResult::IO(io));
2096 }
2097 }
2098 self.end_attached_read_txns(&connection);
2099 Ok(IOResult::Done(()))
2100 }
2101 TransactionState::PendingUpgrade { .. } => {
2102 panic!("Unexpected transaction state: {tx_state:?} during auto-commit",)
2103 }
2104 }
2105 } else {
2106 Ok(IOResult::Done(()))
2107 }
2108 }
2109
2110 fn commit_txn_mvcc(
2123 &self,
2124 pager: Arc<Pager>,
2125 program_state: &mut ProgramState,
2126 mv_store: &Arc<MvStore>,
2127 rollback: bool,
2128 ) -> Result<IOResult<()>> {
2129 let conn = self.connection.clone();
2130 let auto_commit = conn.auto_commit.load(Ordering::SeqCst);
2131 if !auto_commit {
2132 return Ok(IOResult::Done(()));
2133 }
2134
2135 if matches!(program_state.commit_state, CommitState::Ready) {
2137 if let Some(tx_id) = conn.get_mv_tx_id() {
2138 let state_machine = mv_store.commit_tx(tx_id, &conn, crate::MAIN_DB_ID)?;
2139 program_state.commit_state = CommitState::CommittingMvcc { state_machine };
2140 }
2141 }
2145
2146 if matches!(
2147 program_state.commit_state,
2148 CommitState::CommittingMvcc { .. }
2149 ) {
2150 let CommitState::CommittingMvcc { state_machine } = &mut program_state.commit_state
2151 else {
2152 unreachable!()
2153 };
2154 match self.step_end_mvcc_txn(state_machine, mv_store)? {
2155 IOResult::Done(_) => {
2156 assert!(state_machine.is_finalized());
2157 conn.set_mv_tx(None);
2158 conn.set_tx_state(TransactionState::None);
2159 pager.end_read_tx();
2160 program_state.commit_state = CommitState::Ready;
2161 }
2163 IOResult::IO(io) => return Ok(IOResult::IO(io)),
2164 }
2165 }
2166
2167 if matches!(
2170 program_state.commit_state,
2171 CommitState::CommittingAttachedMvcc { .. }
2172 ) {
2173 let (step_result, db_id) = {
2174 let CommitState::CommittingAttachedMvcc {
2175 state_machine,
2176 db_id,
2177 mv_store: ref attached_mv,
2178 } = &mut program_state.commit_state
2179 else {
2180 unreachable!()
2181 };
2182 (state_machine.step(attached_mv)?, *db_id)
2183 };
2184 match step_result {
2185 IOResult::Done(_) => {
2186 let attached_pager = conn
2187 .get_pager_from_database_index(&db_id)
2188 .expect("attached MVCC transaction should always have a pager");
2189 conn.publish_database_schema(db_id);
2190 conn.set_mv_tx_for_db(db_id, None);
2191 attached_pager.end_read_tx();
2192 }
2194 IOResult::IO(io) => return Ok(IOResult::IO(io)),
2195 }
2196 }
2197
2198 loop {
2200 let Some((db_id, tx_id, _mode)) = conn.next_attached_mv_tx() else {
2201 break;
2202 };
2203 let Some(attached_mv_store) = conn.mv_store_for_db(db_id) else {
2204 conn.set_mv_tx_for_db(db_id, None);
2205 continue;
2206 };
2207 let mut state_machine = match attached_mv_store.commit_tx(tx_id, &conn, db_id) {
2208 Ok(sm) => sm,
2209 Err(e) => {
2210 tracing::error!(
2211 db_id,
2212 "attached DB commit failed after main DB already committed; \
2213 cross-database state is inconsistent: {e}"
2214 );
2215 conn.rollback_attached_mvcc_txs(true);
2218 return Err(e);
2219 }
2220 };
2221 match state_machine.step(&attached_mv_store)? {
2222 IOResult::Done(_) => {
2223 let attached_pager = conn
2224 .get_pager_from_database_index(&db_id)
2225 .expect("attached MVCC transaction should always have a pager");
2226 conn.publish_database_schema(db_id);
2227 conn.set_mv_tx_for_db(db_id, None);
2228 attached_pager.end_read_tx();
2229 continue;
2230 }
2231 IOResult::IO(io) => {
2232 program_state.commit_state = CommitState::CommittingAttachedMvcc {
2233 state_machine,
2234 db_id,
2235 mv_store: attached_mv_store,
2236 };
2237 return Ok(IOResult::IO(io));
2238 }
2239 }
2240 }
2241
2242 if matches!(program_state.commit_state, CommitState::CommittingAttached) {
2246 match self.end_attached_write_txns(&conn, rollback)? {
2248 IOResult::Done(_) => {
2249 program_state.commit_state = CommitState::Ready;
2250 self.end_attached_read_txns(&conn);
2251 return Ok(IOResult::Done(()));
2252 }
2253 IOResult::IO(io) => return Ok(IOResult::IO(io)),
2254 }
2255 }
2256
2257 match self.end_attached_write_txns(&conn, rollback)? {
2258 IOResult::Done(_) => {}
2259 IOResult::IO(io) => {
2260 program_state.commit_state = CommitState::CommittingAttached;
2261 return Ok(IOResult::IO(io));
2262 }
2263 }
2264 self.end_attached_read_txns(&conn);
2265
2266 program_state.commit_state = CommitState::Ready;
2267 Ok(IOResult::Done(()))
2268 }
2269
2270 #[instrument(skip(self, pager, connection, program_state), level = Level::DEBUG)]
2271 fn step_end_write_txn(
2272 &self,
2273 pager: &Arc<Pager>,
2274 connection: &Connection,
2275 program_state: &mut ProgramState,
2276 rollback: bool,
2277 ) -> Result<IOResult<()>> {
2278 let commit_state = &mut program_state.commit_state;
2279 if matches!(commit_state, CommitState::CommittingAttached) {
2280 match self.end_attached_write_txns(connection, rollback)? {
2282 IOResult::Done(_) => {
2283 *commit_state = CommitState::Ready;
2284 }
2285 IOResult::IO(io) => {
2286 return Ok(IOResult::IO(io));
2287 }
2288 }
2289 self.end_attached_read_txns(connection);
2292 return Ok(IOResult::Done(()));
2293 }
2294 let txn_finish_result = if !rollback {
2295 pager.commit_tx(connection, true)
2296 } else {
2297 pager.rollback_tx(connection);
2298 Ok(IOResult::Done(()))
2299 };
2300 tracing::debug!("txn_finish_result: {:?}", txn_finish_result);
2301 match txn_finish_result? {
2302 IOResult::Done(_) => {
2303 match self.end_attached_write_txns(connection, rollback)? {
2305 IOResult::Done(_) => {
2306 *commit_state = CommitState::Ready;
2307 }
2308 IOResult::IO(io) => {
2309 *commit_state = CommitState::CommittingAttached;
2310 return Ok(IOResult::IO(io));
2311 }
2312 }
2313 }
2314 IOResult::IO(io) => {
2315 tracing::trace!("Cacheflush IO");
2316 *commit_state = CommitState::Committing;
2317 return Ok(IOResult::IO(io));
2318 }
2319 }
2320 self.end_attached_read_txns(connection);
2323 Ok(IOResult::Done(()))
2324 }
2325
2326 fn end_attached_write_txns(
2332 &self,
2333 connection: &Connection,
2334 rollback: bool,
2335 ) -> Result<IOResult<()>> {
2336 connection.with_all_attached_pagers_with_index(|pagers| {
2337 for (db_id, attached_pager) in pagers {
2338 let db_id = *db_id;
2339 if connection.mv_store_for_db(db_id).is_some() {
2341 continue;
2342 }
2343 if !attached_pager.holds_write_lock() {
2344 continue;
2345 }
2346 if !rollback {
2347 match attached_pager.commit_wal(
2351 WalAutoActions::empty(),
2352 SyncMode::Normal,
2353 false,
2354 ) {
2355 Ok(IOResult::Done(_)) => {}
2356 Ok(IOResult::IO(io)) => {
2357 return Ok(IOResult::IO(io));
2361 }
2362 Err(e) => return Err(e),
2363 }
2364 connection.publish_database_schema(db_id);
2367 attached_pager.end_write_tx();
2368 attached_pager.end_read_tx();
2369 attached_pager.commit_wal_end();
2370 } else {
2371 connection.database_schemas().write().remove(&db_id);
2373 attached_pager.rollback_attached();
2374 }
2375 }
2376 Ok(IOResult::Done(()))
2377 })
2378 }
2379
2380 fn end_attached_read_txns(&self, connection: &Connection) {
2382 connection.with_all_attached_pagers_with_index(|pagers| {
2383 pagers.iter().for_each(|(db_id, attached_pager)| {
2384 if connection.mv_store_for_db(*db_id).is_some() {
2385 return;
2387 }
2388 if attached_pager.holds_write_lock() {
2389 return;
2391 }
2392 if attached_pager.holds_read_lock() {
2393 attached_pager.end_read_tx();
2394 }
2395 });
2396 })
2397 }
2398
2399 #[instrument(skip(self, commit_state, mv_store), level = Level::DEBUG)]
2400 fn step_end_mvcc_txn(
2401 &self,
2402 commit_state: &mut StateMachine<Box<MvccCommitStateMachine>>,
2403 mv_store: &Arc<MvStore>,
2404 ) -> Result<IOResult<()>> {
2405 commit_state.step(mv_store)
2406 }
2407
2408 pub fn abort(
2412 &self,
2413 pager: &Arc<Pager>,
2414 err: Option<&LimboError>,
2415 state: &mut ProgramState,
2416 ) -> Result<()> {
2417 fn capture_abort_error(
2418 abort_error: &mut Option<LimboError>,
2419 err: LimboError,
2420 context: &str,
2421 ) {
2422 tracing::error!("{context}: {err}");
2423 if abort_error.is_none() {
2424 *abort_error = Some(err);
2425 }
2426 }
2427
2428 let mut abort_error: Option<LimboError> = None;
2429 state.commit_state.cleanup_mvcc_checkpoint_state();
2435 state
2445 .commit_state
2446 .cleanup_abandoned_mvcc_commit(&self.connection);
2447
2448 if let Some(inner) = state.active_op_state.take_parse_schema_if_active() {
2458 self.connection
2459 .auto_commit
2460 .store(inner.previous_auto_commit(), Ordering::SeqCst);
2461 drop(inner);
2462 }
2463
2464 if let Err(err) = execute::cleanup_vacuum_state(&self.connection, state) {
2468 capture_abort_error(
2469 &mut abort_error,
2470 err,
2471 "Failed to clean up VACUUM state during abort",
2472 );
2473 }
2474
2475 if self.is_trigger_subprogram() && state.execution_state.is_running() {
2480 self.connection.end_trigger_execution();
2481 }
2482 if err.is_some() && !pager.is_checkpointing() {
2502 if let Some(pending) = state.sequence_inner_tx_pending.as_ref() {
2503 if let Some((outer_tx_id, _)) = pending.saved_outer {
2504 if let Some(mv_store) = self.connection.mv_store_for_db(pending.db) {
2505 if let Err(e) = mv_store.rollback_first_savepoint(outer_tx_id) {
2506 tracing::error!(
2507 "Failed to rollback outer-tx savepoint after sequence \
2508 inner-tx aborted: {e}"
2509 );
2510 }
2511 }
2512 }
2513 }
2514 }
2515 if !self.connection.is_nested_stmt() && !self.is_trigger_subprogram() {
2517 let unfinished_statement_reset_or_drop =
2518 err.is_none() && state.execution_state.is_running();
2519 let inside_explicit_transaction = !self.connection.get_auto_commit();
2520 let unfinished_writer = state.is_active_write;
2521 let can_rollback_just_this_statement =
2522 state.auto_txn_cleanup == TxnCleanup::RollbackSavepoint;
2523
2524 let poison_tx = unfinished_statement_reset_or_drop
2525 && inside_explicit_transaction
2526 && unfinished_writer
2527 && !can_rollback_just_this_statement;
2528 if poison_tx {
2529 self.connection.mark_tx_poisoned();
2534 }
2535
2536 let can_autocommit_now = state.can_autocommit_now(&self.connection);
2537 let is_mvcc = self.connection.mv_store().is_some();
2538 let changed_shared_mvcc_auto_txn = !can_autocommit_now
2539 && state.auto_txn_cleanup == TxnCleanup::RollbackTxn
2540 && state.n_change.load(Ordering::SeqCst) > 0;
2541 if changed_shared_mvcc_auto_txn {
2542 turso_assert!(
2543 is_mvcc,
2544 "shared autocommit transaction needed full rollback outside MVCC"
2545 );
2546 }
2551 let must_rollback_tx_if_needed = can_autocommit_now || changed_shared_mvcc_auto_txn;
2552 if err.is_some() && !pager.is_checkpointing() {
2553 let is_fail_constraint = (matches!(err, Some(LimboError::Constraint(_)))
2557 && self.resolve_type == ResolveType::Fail)
2558 || matches!(err, Some(LimboError::Raise(ResolveType::Fail, _)));
2559 if !is_fail_constraint {
2560 if let Err(end_stmt_err) = state.end_statement(
2561 &self.connection,
2562 pager,
2563 EndStatement::RollbackSavepoint,
2564 ) {
2565 capture_abort_error(
2566 &mut abort_error,
2567 end_stmt_err,
2568 "Failed to rollback statement savepoint during abort",
2569 );
2570 }
2571 }
2572 }
2573 match err {
2574 Some(LimboError::TxError(_)) => {}
2576 Some(LimboError::TableLocked) => {}
2578 Some(LimboError::Busy) => {}
2580 Some(LimboError::StatementsInProgress(_)) => {}
2586 Some(LimboError::BusySnapshot) => {}
2591 Some(LimboError::SchemaUpdated) => {}
2594 Some(LimboError::WriteWriteConflict | LimboError::SchemaConflict) => {
2595 self.rollback_current_txn(pager);
2599 self.connection.set_changes(0);
2600 }
2601 Some(LimboError::ForeignKeyConstraint(_)) => {
2605 if must_rollback_tx_if_needed {
2606 self.rollback_current_txn(pager);
2607 }
2608 self.connection.set_changes(0);
2609 }
2610 Some(LimboError::Constraint(_)) | Some(LimboError::Raise(_, _)) => {
2617 let effective_resolve = match err {
2618 Some(LimboError::Raise(rt, _)) => *rt,
2619 _ => self.resolve_type,
2620 };
2621 match effective_resolve {
2622 ResolveType::Rollback => {
2623 self.rollback_current_txn(pager);
2624 self.connection.clear_deferred_foreign_key_violations();
2626 }
2627 ResolveType::Fail => {
2628 if let Err(end_stmt_err) = state.end_statement(
2631 &self.connection,
2632 pager,
2633 EndStatement::ReleaseSavepoint,
2634 ) {
2635 capture_abort_error(
2636 &mut abort_error,
2637 end_stmt_err,
2638 "Failed to release statement savepoint during abort",
2639 );
2640 }
2641 if can_autocommit_now {
2642 let mv_store = self.connection.mv_store();
2645 if let Err(e) = execute::vtab_commit_all(&self.connection) {
2646 capture_abort_error(
2647 &mut abort_error,
2648 e,
2649 "vtab_commit_all failed during FAIL abort",
2650 );
2651 }
2652 if let Err(e) = execute::index_method_pre_commit_all(state, pager) {
2653 capture_abort_error(
2654 &mut abort_error,
2655 e,
2656 "index_method_pre_commit_all failed during FAIL abort",
2657 );
2658 }
2659 loop {
2660 match self.commit_txn(
2661 pager.clone(),
2662 state,
2663 mv_store.as_ref(),
2664 false,
2665 ) {
2666 Ok(IOResult::Done(_)) => break,
2667 Ok(IOResult::IO(io)) => {
2668 if let Err(e) = io.wait(pager.io.as_ref()) {
2669 capture_abort_error(
2670 &mut abort_error,
2671 e,
2672 "IO error during FAIL commit in abort",
2673 );
2674 break;
2675 }
2676 }
2677 Err(e) => {
2678 capture_abort_error(
2679 &mut abort_error,
2680 e,
2681 "commit_txn failed during FAIL abort",
2682 );
2683 break;
2684 }
2685 }
2686 }
2687 }
2688 }
2689 _ => {
2690 if must_rollback_tx_if_needed {
2691 self.rollback_current_txn(pager);
2692 }
2693 }
2694 }
2695 let last_change = match effective_resolve {
2696 ResolveType::Fail => state.n_change.load(Ordering::SeqCst),
2697 _ => 0,
2698 };
2699 self.connection.set_changes(last_change);
2700 }
2701 Some(LimboError::RaiseIgnore) => {
2702 tracing::error!(
2703 "BUG: RaiseIgnore reached abort() - should be caught by op_program"
2704 );
2705 debug_assert!(
2706 false,
2707 "RaiseIgnore should be caught by op_program, not reach abort"
2708 );
2709 }
2710 _ => match state.auto_txn_cleanup {
2711 TxnCleanup::RollbackTxn => {
2712 if must_rollback_tx_if_needed {
2713 self.rollback_current_txn(pager);
2714 }
2715 }
2716 TxnCleanup::RollbackSavepoint => {
2717 if can_autocommit_now {
2718 self.rollback_current_txn(pager);
2719 } else if err.is_none() && !pager.is_checkpointing() {
2720 if let Err(end_stmt_err) = state.end_statement(
2721 &self.connection,
2722 pager,
2723 EndStatement::RollbackSavepoint,
2724 ) {
2725 capture_abort_error(
2726 &mut abort_error,
2727 end_stmt_err,
2728 "Failed to rollback statement savepoint during abort",
2729 );
2730 }
2731 }
2732 }
2733 TxnCleanup::None => {
2734 if can_autocommit_now
2735 || (!self.connection.get_auto_commit() && err.is_some())
2736 {
2737 self.rollback_current_txn(pager);
2738 }
2739 }
2740 },
2741 }
2742 }
2743 if state.uses_subjournal {
2744 pager.stop_use_subjournal();
2745 state.uses_subjournal = false;
2746 }
2747 state.auto_txn_cleanup = TxnCleanup::None;
2748 if let Some(err) = abort_error {
2749 return Err(err);
2750 }
2751 Ok(())
2752 }
2753
2754 fn rollback_current_txn(&self, pager: &Arc<Pager>) {
2755 self.connection.rollback_current_txn_state(pager, true);
2756 }
2757
2758 pub fn is_trigger_subprogram(&self) -> bool {
2759 self.trigger.is_some() || self.is_subprogram
2760 }
2761}
2762
2763impl Deref for Program {
2764 type Target = PreparedProgram;
2765
2766 fn deref(&self) -> &PreparedProgram {
2767 &self.prepared
2768 }
2769}
2770
2771pub(crate) fn make_record(
2772 registers: &[Register],
2773 start_reg: &usize,
2774 count: &usize,
2775) -> Result<ImmutableRecord> {
2776 let regs = ®isters[*start_reg..*start_reg + *count];
2777 ImmutableRecord::from_registers(regs, regs.len())
2778}
2779
2780pub(crate) fn split_registers(
2782 registers: &mut [Register],
2783 src: usize,
2784 dst: usize,
2785) -> (&Register, &mut Register) {
2786 debug_assert_ne!(src, dst, "split_registers: src and dst must differ");
2787 if src < dst {
2788 let (left, right) = registers.split_at_mut(dst);
2789 (&left[src], &mut right[0])
2790 } else {
2791 let (left, right) = registers.split_at_mut(src);
2792 (&right[0], &mut left[dst])
2793 }
2794}
2795
2796pub fn registers_to_ref_values<'a>(
2797 registers: &'a [Register],
2798) -> impl ExactSizeIterator<Item = ValueRef<'a>> {
2799 registers.iter().map(|reg| reg.get_value().as_ref())
2800}
2801
2802#[instrument(skip(program), level = Level::DEBUG)]
2803fn trace_insn(program: &Program, addr: InsnReference, insn: &Insn) {
2804 tracing::trace!(
2805 "\n{}",
2806 explain::insn_to_str(
2807 program,
2808 addr,
2809 insn,
2810 String::new(),
2811 program
2812 .comments
2813 .iter()
2814 .find(|(offset, _)| *offset == addr)
2815 .map(|(_, comment)| comment)
2816 .copied()
2817 )
2818 );
2819}
2820
2821pub trait FromValueRow<'a> {
2822 fn from_value(value: &'a Value) -> Result<Self>
2823 where
2824 Self: Sized + 'a;
2825}
2826
2827impl<'a> FromValueRow<'a> for i64 {
2828 fn from_value(value: &'a Value) -> Result<Self> {
2829 match value {
2830 Value::Numeric(Numeric::Integer(i)) => Ok(*i),
2831 _ => Err(LimboError::ConversionError("Expected integer value".into())),
2832 }
2833 }
2834}
2835
2836impl<'a> FromValueRow<'a> for f64 {
2837 fn from_value(value: &'a Value) -> Result<Self> {
2838 match value {
2839 Value::Numeric(Numeric::Float(f)) => Ok(f64::from(*f)),
2840 _ => Err(LimboError::ConversionError("Expected integer value".into())),
2841 }
2842 }
2843}
2844
2845impl<'a> FromValueRow<'a> for String {
2846 fn from_value(value: &'a Value) -> Result<Self> {
2847 match value {
2848 Value::Text(s) => Ok(s.as_str().to_string()),
2849 _ => Err(LimboError::ConversionError("Expected text value".into())),
2850 }
2851 }
2852}
2853
2854impl<'a> FromValueRow<'a> for &'a str {
2855 fn from_value(value: &'a Value) -> Result<Self> {
2856 match value {
2857 Value::Text(s) => Ok(s.as_str()),
2858 _ => Err(LimboError::ConversionError("Expected text value".into())),
2859 }
2860 }
2861}
2862
2863impl<'a> FromValueRow<'a> for &'a Value {
2864 fn from_value(value: &'a Value) -> Result<Self> {
2865 Ok(value)
2866 }
2867}
2868
2869impl Row {
2870 pub fn get<'a, T: FromValueRow<'a> + 'a>(&'a self, idx: usize) -> Result<T> {
2871 let value = unsafe {
2872 self.values
2873 .add(idx)
2874 .as_ref()
2875 .expect("row value pointer should be valid")
2876 };
2877 let value = match value {
2878 Register::Value(value) => value,
2879 _ => unreachable!("a row should be formed of values only"),
2880 };
2881 T::from_value(value)
2882 }
2883
2884 pub fn get_value(&self, idx: usize) -> &Value {
2885 let value = unsafe {
2886 self.values
2887 .add(idx)
2888 .as_ref()
2889 .expect("row value pointer should be valid")
2890 };
2891 match value {
2892 Register::Value(value) => value,
2893 _ => unreachable!("a row should be formed of values only"),
2894 }
2895 }
2896
2897 pub fn get_values(&self) -> impl Iterator<Item = &Value> {
2898 let values = unsafe { std::slice::from_raw_parts(self.values, self.count) };
2899 values.iter().map(|v| v.get_value())
2902 }
2903
2904 pub fn len(&self) -> usize {
2905 self.count
2906 }
2907
2908 pub fn is_empty(&self) -> bool {
2909 self.count == 0
2910 }
2911}
2912
2913pub trait ValueIteratorExt {
2916 fn nth_into_register(&mut self, n: usize, dest: &mut Register) -> Option<Result<()>>;
2920}
2921
2922impl<'a> ValueIteratorExt for crate::types::ValueIterator<'a> {
2923 #[inline(always)]
2924 fn nth_into_register(&mut self, n: usize, dest: &mut Register) -> Option<Result<()>> {
2925 use crate::storage::sqlite3_ondisk::read_varint;
2926 use crate::types::{get_serial_type_size, Extendable, Text};
2927
2928 let mut header = self.header_section_ref();
2929 let mut data = self.data_section_ref();
2930
2931 let mut data_sum = 0;
2933 for _ in 0..n {
2934 if header.is_empty() {
2935 return None;
2936 }
2937
2938 let (serial_type, bytes_read) = match read_varint(header) {
2939 Ok(v) => v,
2940 Err(e) => return Some(Err(e)),
2941 };
2942 header = &header[bytes_read..];
2943
2944 data_sum += match get_serial_type_size(serial_type) {
2945 Ok(size) => size,
2946 Err(e) => return Some(Err(e)),
2947 };
2948 }
2949
2950 if data_sum > data.len() {
2951 return Some(Err(LimboError::Corrupt(
2952 "Data section too small for indicated serial type size".into(),
2953 )));
2954 }
2955 data = &data[data_sum..];
2956
2957 if header.is_empty() {
2959 return None;
2960 }
2961
2962 let (serial_type, bytes_read) = match read_varint(header) {
2963 Ok(v) => v,
2964 Err(e) => return Some(Err(e)),
2965 };
2966
2967 self.set_header_section(&header[bytes_read..]);
2969
2970 match serial_type {
2972 0 => {
2974 self.set_data_section(data);
2975 dest.set_null();
2976 }
2977 1 => {
2979 if unlikely(data.is_empty()) {
2980 return Some(Err(LimboError::Corrupt("Invalid 1-byte int".into())));
2981 }
2982 self.set_data_section(&data[1..]);
2983 dest.set_int(data[0] as i8 as i64);
2984 }
2985 2 => {
2987 if unlikely(data.len() < 2) {
2988 return Some(Err(LimboError::Corrupt("Invalid 2-byte int".into())));
2989 }
2990 self.set_data_section(&data[2..]);
2991 dest.set_int(i16::from_be_bytes([data[0], data[1]]) as i64);
2992 }
2993 3 => {
2995 if unlikely(data.len() < 3) {
2996 return Some(Err(LimboError::Corrupt("Invalid 3-byte int".into())));
2997 }
2998 self.set_data_section(&data[3..]);
2999 let sign_extension = if data[0] <= 0x7F { 0 } else { 0xFF };
3000 dest.set_int(
3001 i32::from_be_bytes([sign_extension, data[0], data[1], data[2]]) as i64,
3002 );
3003 }
3004 4 => {
3006 if unlikely(data.len() < 4) {
3007 return Some(Err(LimboError::Corrupt("Invalid 4-byte int".into())));
3008 }
3009 self.set_data_section(&data[4..]);
3010 dest.set_int(i32::from_be_bytes([data[0], data[1], data[2], data[3]]) as i64);
3011 }
3012 5 => {
3014 if unlikely(data.len() < 6) {
3015 return Some(Err(LimboError::Corrupt("Invalid 6-byte int".into())));
3016 }
3017 self.set_data_section(&data[6..]);
3018 let sign_extension = if data[0] <= 0x7F { 0 } else { 0xFF };
3019 dest.set_int(i64::from_be_bytes([
3020 sign_extension,
3021 sign_extension,
3022 data[0],
3023 data[1],
3024 data[2],
3025 data[3],
3026 data[4],
3027 data[5],
3028 ]));
3029 }
3030 6 => {
3032 if unlikely(data.len() < 8) {
3033 return Some(Err(LimboError::Corrupt("Invalid 8-byte int".into())));
3034 }
3035 self.set_data_section(&data[8..]);
3036 dest.set_int(i64::from_be_bytes([
3037 data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
3038 ]));
3039 }
3040 7 => {
3042 if unlikely(data.len() < 8) {
3043 return Some(Err(LimboError::Corrupt("Invalid 8-byte float".into())));
3044 }
3045 self.set_data_section(&data[8..]);
3046 let val = f64::from_be_bytes([
3047 data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
3048 ]);
3049 if let Some(nn) = NonNan::new(val) {
3050 dest.set_float(nn);
3051 } else {
3052 dest.set_null();
3053 }
3054 }
3055 8 => {
3057 self.set_data_section(data);
3058 dest.set_int(0);
3059 }
3060 9 => {
3062 self.set_data_section(data);
3063 dest.set_int(1);
3064 }
3065 10 | 11 => {
3067 mark_unlikely();
3068 return Some(Err(LimboError::Corrupt(format!(
3069 "Reserved serial type: {serial_type}"
3070 ))));
3071 }
3072 n if n >= 12 && n & 1 == 0 => {
3074 let content_size = ((n - 12) / 2) as usize;
3075 if unlikely(data.len() < content_size) {
3076 return Some(Err(LimboError::Corrupt("Invalid Blob value".into())));
3077 }
3078 self.set_data_section(&data[content_size..]);
3079 let blob_data = &data[..content_size];
3080 match dest {
3081 Register::Value(Value::Blob(existing_blob)) => {
3082 if let Err(err) = existing_blob.do_extend(&blob_data) {
3083 return Some(Err(err));
3084 }
3085 }
3086 _ => {
3087 if let Err(err) = dest.set_blob(blob_data.to_vec()) {
3088 return Some(Err(err));
3089 }
3090 }
3091 }
3092 }
3093 n if n >= 13 && n & 1 == 1 => {
3095 let content_size = ((n - 13) / 2) as usize;
3096 if unlikely(data.len() < content_size) {
3097 return Some(Err(LimboError::Corrupt("Invalid Text value".into())));
3098 }
3099 self.set_data_section(&data[content_size..]);
3100 let text_data = &data[..content_size];
3101 let text_str = if cfg!(debug_assertions) {
3103 match std::str::from_utf8(text_data) {
3104 Ok(s) => s,
3105 Err(e) => {
3106 return Some(Err(LimboError::InternalError(format!(
3107 "Invalid UTF-8 in TEXT serial type: {e}"
3108 ))));
3109 }
3110 }
3111 } else {
3112 unsafe { std::str::from_utf8_unchecked(text_data) }
3113 };
3114 match dest {
3115 Register::Value(Value::Text(existing_text)) => {
3116 if let Err(err) = existing_text.do_extend(&text_str) {
3117 return Some(Err(err));
3118 }
3119 }
3120 _ => {
3121 if let Err(err) = dest.set_text(Text::new(text_str.to_string())) {
3122 return Some(Err(err));
3123 }
3124 }
3125 }
3126 }
3127 _ => {
3128 mark_unlikely();
3129 return Some(Err(LimboError::Corrupt(format!(
3130 "Invalid serial type: {serial_type}"
3131 ))));
3132 }
3133 }
3134
3135 Some(Ok(()))
3136 }
3137}
3138
3139#[cfg(clt_turso_tests)]
3140mod tests {
3141 use super::*;
3142 use std::panic::{catch_unwind, AssertUnwindSafe};
3143
3144 #[test]
3145 fn active_opcode_helpers_initialize_defaults() {
3146 let mut state = ProgramState::new(1, 0);
3147
3148 assert!(matches!(state.active_op_state.state, ActiveOpState::None));
3149 assert!(matches!(
3150 state.active_op_state.column(),
3151 OpColumnState::Start
3152 ));
3153 state.active_op_state.clear();
3154 assert!(state.active_op_state.parse_schema().is_none());
3155 }
3156
3157 #[test]
3158 fn active_opcode_helpers_reject_mismatched_resumes() {
3159 let mut state = ProgramState::new(1, 0);
3160 *state.active_op_state.column() = OpColumnState::GetColumn;
3161
3162 let panic = catch_unwind(AssertUnwindSafe(|| {
3163 let _ = state.active_op_state.parse_schema();
3164 }));
3165 assert!(panic.is_err(), "mismatched opcode resume should panic");
3166 }
3167
3168 #[test]
3169 fn seek_state_is_independent_from_active_opcode_slot() {
3170 let mut state = ProgramState::new(1, 0);
3171
3172 *state.active_op_state.insert() = OpInsertState {
3173 sub_state: OpInsertSubState::Seek,
3174 old_record: None,
3175 is_noop_update: false,
3176 };
3177 state.seek_state = OpSeekState::MoveLast;
3178
3179 assert!(matches!(
3180 state.active_op_state.insert().sub_state,
3181 OpInsertSubState::Seek
3182 ));
3183 assert!(matches!(state.seek_state, OpSeekState::MoveLast));
3184 }
3185}
3186
3187#[cfg(all(shuttle, clt_turso_tests))]
3198mod shuttle_tests {
3199 use super::*;
3200 use crate::sync::Arc;
3201 use crate::thread;
3202 use crate::types::Value;
3203
3204 fn create_test_state(num_registers: usize, num_cursors: usize) -> ProgramState {
3206 ProgramState::new(num_registers, num_cursors)
3207 }
3208
3209 #[test]
3212 fn shuttle_program_state_send() {
3213 shuttle::check_random(
3214 || {
3215 let mut state = create_test_state(10, 2);
3216
3217 state.registers[0].set_int(42);
3219 state.registers[1].set_text(Text::new("test".to_string()));
3220
3221 let handle = thread::spawn(move || {
3223 assert!(matches!(
3225 &state.registers[0],
3226 Register::Value(Value::Numeric(Numeric::Integer(42)))
3227 ));
3228 if let Register::Value(Value::Text(t)) = &state.registers[1] {
3229 assert_eq!(t.as_str(), "test");
3230 } else {
3231 panic!("Expected text value");
3232 }
3233
3234 state.registers[2].set_int(100);
3236 state
3237 });
3238
3239 let state = handle.join().unwrap();
3240 assert!(matches!(
3241 &state.registers[2],
3242 Register::Value(Value::Numeric(Numeric::Integer(100)))
3243 ));
3244 },
3245 1000,
3246 );
3247 }
3248
3249 #[test]
3252 fn shuttle_program_state_send_with_row() {
3253 shuttle::check_random(
3254 || {
3255 let mut state = create_test_state(10, 2);
3256
3257 state.registers[0].set_int(1);
3259 state.registers[1].set_int(2);
3260 state.registers[2].set_int(3);
3261
3262 state.result_row = Some(Row {
3264 values: &state.registers[0] as *const Register,
3265 count: 3,
3266 });
3267
3268 let handle = thread::spawn(move || {
3271 if let Some(row) = &state.result_row {
3273 assert_eq!(row.len(), 3);
3274 let val = row.get::<i64>(0).unwrap();
3276 assert_eq!(val, 1);
3277 let val = row.get::<i64>(1).unwrap();
3278 assert_eq!(val, 2);
3279 let val = row.get::<i64>(2).unwrap();
3280 assert_eq!(val, 3);
3281 } else {
3282 panic!("Expected result_row to be set");
3283 }
3284 state
3285 });
3286
3287 let _ = handle.join().unwrap();
3288 },
3289 1000,
3290 );
3291 }
3292
3293 #[test]
3296 fn shuttle_program_state_sync_concurrent_reads() {
3297 shuttle::check_random(
3298 || {
3299 let mut state = create_test_state(10, 2);
3300
3301 state.registers[0].set_int(42);
3303 state.registers[1].set_int(43);
3304
3305 state.result_row = Some(Row {
3307 values: &state.registers[0] as *const Register,
3308 count: 2,
3309 });
3310
3311 let state = Arc::new(state);
3312 let state2 = Arc::clone(&state);
3313 let state3 = Arc::clone(&state);
3314
3315 let h1 = thread::spawn(move || {
3317 if let Some(row) = &state.result_row {
3318 let val = row.get::<i64>(0).unwrap();
3319 assert_eq!(val, 42);
3320 }
3321 });
3322
3323 let h2 = thread::spawn(move || {
3324 if let Some(row) = &state2.result_row {
3325 let val = row.get::<i64>(1).unwrap();
3326 assert_eq!(val, 43);
3327 }
3328 });
3329
3330 let h3 = thread::spawn(move || {
3331 if let Some(row) = &state3.result_row {
3332 assert_eq!(row.len(), 2);
3333 }
3334 });
3335
3336 h1.join().unwrap();
3337 h2.join().unwrap();
3338 h3.join().unwrap();
3339 },
3340 1000,
3341 );
3342 }
3343
3344 #[test]
3347 fn shuttle_row_pointer_consistency() {
3348 shuttle::check_random(
3349 || {
3350 let mut state = create_test_state(10, 2);
3351
3352 for i in 0..5 {
3354 state.registers[i].set_int(i as i64 * 10);
3355 }
3356
3357 state.result_row = Some(Row {
3358 values: &state.registers[0] as *const Register,
3359 count: 5,
3360 });
3361
3362 let state = Arc::new(state);
3363 let mut handles = vec![];
3364
3365 for _ in 0..4 {
3366 let state_clone = Arc::clone(&state);
3367 let h = thread::spawn(move || {
3368 if let Some(row) = &state_clone.result_row {
3369 for i in 0..5 {
3371 let val = row.get::<i64>(i).unwrap();
3372 assert_eq!(val, i as i64 * 10);
3373 }
3374 }
3375 });
3376 handles.push(h);
3377 }
3378
3379 for h in handles {
3380 h.join().unwrap();
3381 }
3382 },
3383 1000,
3384 );
3385 }
3386
3387 #[test]
3391 fn shuttle_result_row_invalidation() {
3392 shuttle::check_random(
3393 || {
3394 let mut state = create_test_state(10, 2);
3395
3396 state.registers[0].set_int(100);
3397 state.result_row = Some(Row {
3398 values: &state.registers[0] as *const Register,
3399 count: 1,
3400 });
3401
3402 let taken_row = state.result_row.take();
3405
3406 assert!(state.result_row.is_none());
3408
3409 if let Some(row) = taken_row {
3411 let val = row.get::<i64>(0).unwrap();
3412 assert_eq!(val, 100);
3413 }
3414 },
3415 1000,
3416 );
3417 }
3418
3419 #[test]
3422 fn shuttle_register_modification_after_invalidation() {
3423 shuttle::check_random(
3424 || {
3425 let mut state = create_test_state(10, 2);
3426
3427 state.registers[0].set_int(1);
3428 state.result_row = Some(Row {
3429 values: &state.registers[0] as *const Register,
3430 count: 1,
3431 });
3432
3433 let _ = state.result_row.take();
3435
3436 state.registers[0].set_int(999);
3438
3439 state.result_row = Some(Row {
3441 values: &state.registers[0] as *const Register,
3442 count: 1,
3443 });
3444
3445 if let Some(row) = &state.result_row {
3447 let val = row.get::<i64>(0).unwrap();
3448 assert_eq!(val, 999);
3449 }
3450 },
3451 1000,
3452 );
3453 }
3454
3455 #[test]
3458 fn shuttle_sequential_thread_transfer() {
3459 shuttle::check_random(
3460 || {
3461 let mut state = create_test_state(10, 2);
3462 state.registers[0].set_int(0);
3463
3464 let h1 = thread::spawn(move || {
3466 if let Register::Value(Value::Numeric(Numeric::Integer(v))) =
3467 &state.registers[0]
3468 {
3469 state.registers[0].set_int(v + 1);
3470 }
3471 state
3472 });
3473
3474 let mut state = h1.join().unwrap();
3475
3476 let h2 = thread::spawn(move || {
3478 if let Register::Value(Value::Numeric(Numeric::Integer(v))) =
3479 &state.registers[0]
3480 {
3481 state.registers[0].set_int(v + 1);
3482 }
3483 state
3484 });
3485
3486 let mut state = h2.join().unwrap();
3487
3488 let h3 = thread::spawn(move || {
3490 if let Register::Value(Value::Numeric(Numeric::Integer(v))) =
3491 &state.registers[0]
3492 {
3493 state.registers[0].set_int(v + 1);
3494 }
3495 state
3496 });
3497
3498 let state = h3.join().unwrap();
3499
3500 assert!(matches!(
3502 &state.registers[0],
3503 Register::Value(Value::Numeric(Numeric::Integer(3)))
3504 ));
3505 },
3506 1000,
3507 );
3508 }
3509
3510 #[test]
3513 fn shuttle_arc_wrapped_state() {
3514 shuttle::check_random(
3515 || {
3516 let mut state = create_test_state(10, 2);
3517
3518 for i in 0..5 {
3520 state.registers[i].set_int(i as i64);
3521 }
3522
3523 let state = Arc::new(state);
3524 let mut handles = vec![];
3525
3526 for thread_id in 0u8..4 {
3528 let state_clone = Arc::clone(&state);
3529 let h = thread::spawn(move || {
3530 for i in 0..5 {
3532 if let Register::Value(Value::Numeric(Numeric::Integer(v))) =
3533 &state_clone.registers[i]
3534 {
3535 assert_eq!(*v, i as i64);
3536 }
3537 }
3538 thread_id
3539 });
3540 handles.push(h);
3541 }
3542
3543 for h in handles {
3544 h.join().unwrap();
3545 }
3546 },
3547 1000,
3548 );
3549 }
3550
3551 #[test]
3553 fn shuttle_row_get_values_concurrent() {
3554 shuttle::check_random(
3555 || {
3556 let mut state = create_test_state(10, 2);
3557
3558 state.registers[0].set_int(10);
3559 state.registers[1].set_int(20);
3560 state.registers[2].set_int(30);
3561
3562 state.result_row = Some(Row {
3563 values: &state.registers[0] as *const Register,
3564 count: 3,
3565 });
3566
3567 let state = Arc::new(state);
3568 let state2 = Arc::clone(&state);
3569
3570 let h1 = thread::spawn(move || {
3571 if let Some(row) = &state.result_row {
3572 let values: Vec<_> = row.get_values().collect();
3573 assert_eq!(values.len(), 3);
3574 }
3575 });
3576
3577 let h2 = thread::spawn(move || {
3578 if let Some(row) = &state2.result_row {
3579 let mut sum = 0i64;
3580 for val in row.get_values() {
3581 if let Value::Numeric(Numeric::Integer(i)) = val {
3582 sum += i;
3583 }
3584 }
3585 assert_eq!(sum, 60); }
3587 });
3588
3589 h1.join().unwrap();
3590 h2.join().unwrap();
3591 },
3592 1000,
3593 );
3594 }
3595
3596 #[test]
3598 fn shuttle_stress_concurrent_reads() {
3599 shuttle::check_random(
3600 || {
3601 let mut state = create_test_state(20, 2);
3602
3603 for i in 0..20 {
3605 state.registers[i].set_int(i as i64 * 100);
3606 }
3607
3608 state.result_row = Some(Row {
3609 values: &state.registers[0] as *const Register,
3610 count: 20,
3611 });
3612
3613 let state = Arc::new(state);
3614 let mut handles = vec![];
3615
3616 for thread_id in 0..6u8 {
3617 let state_clone = Arc::clone(&state);
3618 let h = thread::spawn(move || {
3619 let start = (thread_id as usize * 3) % 20;
3621 if let Some(row) = &state_clone.result_row {
3622 for i in 0..3 {
3623 let idx = (start + i) % row.len();
3624 let val = row.get::<i64>(idx).unwrap();
3625 assert_eq!(val, idx as i64 * 100);
3626 }
3627 }
3628 thread_id
3629 });
3630 handles.push(h);
3631 }
3632
3633 for h in handles {
3634 h.join().unwrap();
3635 }
3636 },
3637 1000,
3638 );
3639 }
3640}