1use std::collections::BTreeMap;
2use std::sync::atomic::{AtomicBool, Ordering};
3use std::sync::{Arc, Mutex, MutexGuard};
4use std::time::{Duration, Instant};
5
6use anyhow::Result;
7use thiserror::Error;
8
9use lora_analyzer::Analyzer;
10use lora_compiler::{CompiledQuery, Compiler};
11use lora_executor::{
12 classify_stream, compiled_result_columns, project_rows, ExecuteOptions, ExecutionContext,
13 Executor, LoraValue, MutableExecutionContext, MutableExecutor, MutablePullExecutor,
14 PullExecutor, QueryResult, Row, RowSource,
15};
16use lora_parser::parse_query;
17use lora_store::{InMemoryGraph, MutationEvent, MutationRecorder};
18use lora_wal::WalRecorder;
19
20use crate::error::LoraError;
21use crate::explain::{OperatorMetrics, ProfileMetrics, QueryPlan, QueryProfile};
22use crate::live_store::LiveStore;
23use crate::snapshot::ManagedSnapshotStore;
24use crate::stream::QueryStream;
25use crate::wal::write_scope::ensure_wal_not_poisoned;
26use lora_compiler::plan_tree_from_compiled;
27use lora_executor::{plan_result_columns, CollectorGuard, MetricsCollector};
28
29#[derive(Debug, Clone, PartialEq, Eq, Error)]
35pub enum TransactionError {
36 #[error("transaction is already closed")]
37 AlreadyClosed,
38
39 #[error("transaction has no live graph guard")]
40 NoGraphGuard,
41
42 #[error("transaction has no staged graph")]
43 NoStagedGraph,
44
45 #[error("cannot commit transaction while a streaming cursor is still active")]
46 CursorActiveCommit,
47
48 #[error("cannot start a new statement while a streaming cursor is still active")]
49 CursorActiveStatement,
50
51 #[error("cannot execute mutating query in read-only transaction")]
52 ReadOnlyMutation,
53
54 #[error("streaming write cursor requires a ReadWrite transaction")]
55 StreamingRequiresReadWrite,
56
57 #[error("read-only transaction cannot publish staged graph")]
58 ReadOnlyCommit,
59
60 #[error("transaction state lock is poisoned")]
61 Poisoned,
62}
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
66pub enum TransactionMode {
67 ReadOnly,
69 ReadWrite,
71}
72
73pub(crate) enum LiveStoreGuard<'db> {
83 Read(Arc<InMemoryGraph>),
84 Write(WriteLease<'db>),
85}
86
87pub(crate) struct WriteLease<'db> {
92 pub(crate) _writer_lock: MutexGuard<'db, ()>,
94 pub(crate) store: Arc<LiveStore<InMemoryGraph>>,
96 pub(crate) snapshot: Arc<InMemoryGraph>,
99}
100
101impl LiveStoreGuard<'_> {
102 fn as_graph(&self) -> &InMemoryGraph {
103 match self {
104 Self::Read(arc) => arc,
105 Self::Write(lease) => &lease.snapshot,
106 }
107 }
108}
109
110pub(crate) struct Savepoint {
115 staged: Option<InMemoryGraph>,
116 buffer_len: usize,
117}
118
119#[derive(Debug, Clone, Copy, PartialEq, Eq)]
123pub(crate) enum TxStreamOutcome {
124 Exhausted,
125 Interrupted,
126}
127
128impl TxStreamOutcome {
129 fn should_restore_savepoint(self, rollback_on_drop: bool) -> bool {
130 matches!(self, Self::Interrupted) && rollback_on_drop
131 }
132}
133
134pub(crate) struct TxCursorLease {
141 handle: Arc<Mutex<TxInner>>,
142 rollback_on_drop: bool,
143 finalized: bool,
144}
145
146impl TxCursorLease {
147 pub(crate) fn new(handle: Arc<Mutex<TxInner>>, rollback_on_drop: bool) -> Self {
148 Self {
149 handle,
150 rollback_on_drop,
151 finalized: false,
152 }
153 }
154
155 pub(crate) fn finalize(&mut self, outcome: TxStreamOutcome) {
156 if self.finalized {
157 return;
158 }
159 finalize_tx_stream(&self.handle, outcome, self.rollback_on_drop);
160 self.finalized = true;
161 }
162}
163
164impl Drop for TxCursorLease {
165 fn drop(&mut self) {
166 self.finalize(TxStreamOutcome::Interrupted);
167 }
168}
169
170pub(crate) struct BufferingRecorder {
177 buffer: Arc<Mutex<Vec<MutationEvent>>>,
178 failed: Arc<AtomicBool>,
179}
180
181impl BufferingRecorder {
182 pub(crate) fn new(buffer: Arc<Mutex<Vec<MutationEvent>>>, failed: Arc<AtomicBool>) -> Self {
183 Self { buffer, failed }
184 }
185}
186
187impl MutationRecorder for BufferingRecorder {
188 fn record(&self, event: MutationEvent) {
189 match self.buffer.lock() {
190 Ok(mut buf) => buf.push(event),
191 Err(_) => self.failed.store(true, Ordering::Release),
192 }
193 }
194}
195
196pub(crate) struct TxInner {
201 pub(crate) staged: Option<InMemoryGraph>,
205 pub(crate) buffer: Arc<Mutex<Vec<MutationEvent>>>,
209 pub(crate) buffer_failed: Arc<AtomicBool>,
213 pub(crate) pending_savepoint: Option<Savepoint>,
217 pub(crate) cursor_active: bool,
221 pub(crate) closed: bool,
225 pub(crate) mode: TransactionMode,
227 pub(crate) buffer_mutations: bool,
231}
232
233pub struct Transaction<'db> {
249 pub(crate) live: Option<LiveStoreGuard<'db>>,
250 pub(crate) inner: Arc<Mutex<TxInner>>,
251 pub(crate) wal: Option<Arc<WalRecorder>>,
252 pub(crate) snapshots: Option<Arc<ManagedSnapshotStore>>,
253 mode: TransactionMode,
254}
255
256impl<'db> Transaction<'db> {
257 pub(crate) fn new(
259 live: LiveStoreGuard<'db>,
260 wal: Option<Arc<WalRecorder>>,
261 snapshots: Option<Arc<ManagedSnapshotStore>>,
262 mode: TransactionMode,
263 ) -> Self {
264 let buffer_mutations = wal.is_some();
265 let inner = TxInner {
266 staged: None,
267 buffer: Arc::new(Mutex::new(Vec::new())),
268 buffer_failed: Arc::new(AtomicBool::new(false)),
269 pending_savepoint: None,
270 cursor_active: false,
271 closed: false,
272 mode,
273 buffer_mutations,
274 };
275 Self {
276 live: Some(live),
277 inner: Arc::new(Mutex::new(inner)),
278 wal,
279 snapshots,
280 mode,
281 }
282 }
283
284 pub fn mode(&self) -> TransactionMode {
286 self.mode
287 }
288
289 pub fn execute(
292 &mut self,
293 query: &str,
294 options: Option<ExecuteOptions>,
295 ) -> Result<QueryResult, LoraError> {
296 self.execute_with_params(query, options, BTreeMap::new())
297 }
298
299 pub fn execute_with_timeout(
301 &mut self,
302 query: &str,
303 options: Option<ExecuteOptions>,
304 timeout: Duration,
305 ) -> Result<QueryResult, LoraError> {
306 let rows = self.execute_rows_with_params_deadline(
307 query,
308 BTreeMap::new(),
309 Some(deadline_after(timeout)),
310 )?;
311 Ok(project_rows(rows, options.unwrap_or_default()))
312 }
313
314 pub fn execute_with_params(
316 &mut self,
317 query: &str,
318 options: Option<ExecuteOptions>,
319 params: BTreeMap<String, LoraValue>,
320 ) -> Result<QueryResult, LoraError> {
321 let rows = self.execute_rows_with_params_deadline(query, params, None)?;
322 Ok(project_rows(rows, options.unwrap_or_default()))
323 }
324
325 pub fn execute_with_params_timeout(
328 &mut self,
329 query: &str,
330 options: Option<ExecuteOptions>,
331 params: BTreeMap<String, LoraValue>,
332 timeout: Duration,
333 ) -> Result<QueryResult, LoraError> {
334 let rows =
335 self.execute_rows_with_params_deadline(query, params, Some(deadline_after(timeout)))?;
336 Ok(project_rows(rows, options.unwrap_or_default()))
337 }
338
339 pub fn execute_rows(&mut self, query: &str) -> Result<Vec<Row>, LoraError> {
342 self.execute_rows_with_params(query, BTreeMap::new())
343 }
344
345 pub fn execute_rows_with_params(
348 &mut self,
349 query: &str,
350 params: BTreeMap<String, LoraValue>,
351 ) -> Result<Vec<Row>, LoraError> {
352 Ok(self.execute_rows_with_params_deadline(query, params, None)?)
353 }
354
355 fn execute_rows_with_params_deadline(
356 &mut self,
357 query: &str,
358 params: BTreeMap<String, LoraValue>,
359 deadline: Option<Instant>,
360 ) -> Result<Vec<Row>> {
361 let compiled = self.compile_in_tx(query)?;
362 self.execute_rows_compiled_deadline(&compiled, params, deadline)
363 }
364
365 fn execute_rows_compiled_deadline(
366 &mut self,
367 compiled: &CompiledQuery,
368 params: BTreeMap<String, LoraValue>,
369 deadline: Option<Instant>,
370 ) -> Result<Vec<Row>> {
371 if self.is_read_only_unchecked() {
373 self.precheck_open_no_savepoint()?;
374 return self.execute_live_compiled(compiled, params, deadline);
375 }
376
377 let mut inner = self.begin_statement()?;
379 let is_mutating = classify_stream(compiled).is_mutating();
380
381 if !is_mutating {
382 return self.execute_read_statement(inner, compiled, params, deadline);
388 }
389
390 let savepoint = self.prepare_mutating_statement(&mut inner)?;
394
395 let exec_result: ExecResultRows = {
396 let staged = inner.staged_mut()?;
397 execute_mutable_compiled(staged, compiled, params, deadline)
398 };
399
400 match exec_result {
401 Ok(rows) => Ok(rows),
402 Err(err) => {
403 restore_savepoint(&mut inner, savepoint);
404 Err(err)
405 }
406 }
407 }
408
409 pub(crate) fn open_streaming_compiled_autocommit(
438 &mut self,
439 compiled: Arc<CompiledQuery>,
440 params: BTreeMap<String, LoraValue>,
441 ) -> Result<Box<dyn RowSource + 'static>> {
442 if self.is_read_only_unchecked() {
443 return Err(TransactionError::StreamingRequiresReadWrite.into());
444 }
445
446 let mut inner = self.begin_statement()?;
447 self.ensure_staged_locked(&mut inner)?;
448 inner.activate_cursor();
449
450 let staged_ptr: *mut InMemoryGraph = inner
454 .staged
455 .as_mut()
456 .ok_or(TransactionError::NoStagedGraph)?
457 as *mut _;
458 drop(inner);
459
460 let storage_static: &'static mut InMemoryGraph = unsafe { &mut *staged_ptr };
466 let compiled_static: &'static CompiledQuery =
467 unsafe { std::mem::transmute::<&CompiledQuery, _>(compiled.as_ref()) };
468
469 let cursor = MutablePullExecutor::new(storage_static, params)
473 .open_compiled(compiled_static)
474 .map_err(|e| {
475 if let Ok(mut inner) = self.inner.lock() {
479 discard_transaction_state(&mut inner);
480 }
481 self.live.take();
482 anyhow::Error::from(e)
483 })?;
484
485 Ok(Box::new(StreamingCursorWithArc {
491 cursor,
492 _compiled: compiled,
493 }))
494 }
495
496 fn compile_in_tx(&self, query: &str) -> Result<CompiledQuery> {
502 let document = parse_query(query)?;
503 let (resolved, stats) = {
504 let inner = self.lock_inner()?;
505 if let Some(staged) = &inner.staged {
506 let mut analyzer = Analyzer::new(staged);
507 let resolved = analyzer.analyze(&document)?;
508 let stats = staged.graph_stats();
509 (resolved, stats)
510 } else {
511 drop(inner);
512 let live = self.live.as_ref().ok_or(TransactionError::NoGraphGuard)?;
513 let graph = live.as_graph();
514 let mut analyzer = Analyzer::new(graph);
515 let resolved = analyzer.analyze(&document)?;
516 let stats = graph.graph_stats();
517 (resolved, stats)
518 }
519 };
520 Ok(Compiler::compile(&resolved, &stats))
521 }
522
523 fn ensure_staged_locked(&self, inner: &mut MutexGuard<'_, TxInner>) -> Result<()> {
527 if inner.staged.is_some() {
528 return Ok(());
529 }
530 let live = self.live.as_ref().ok_or(TransactionError::NoGraphGuard)?;
531 let mut staged: InMemoryGraph = live.as_graph().clone();
532 if matches!(inner.mode, TransactionMode::ReadWrite) && inner.buffer_mutations {
533 staged.set_mutation_recorder(Some(Arc::new(BufferingRecorder::new(
534 inner.buffer.clone(),
535 inner.buffer_failed.clone(),
536 )) as Arc<dyn MutationRecorder>));
537 }
538 inner.staged = Some(staged);
539 Ok(())
540 }
541
542 fn execute_live_compiled(
543 &self,
544 compiled: &CompiledQuery,
545 params: BTreeMap<String, LoraValue>,
546 deadline: Option<Instant>,
547 ) -> Result<Vec<Row>> {
548 let live = self.live.as_ref().ok_or(TransactionError::NoGraphGuard)?;
549 execute_read_compiled(live.as_graph(), compiled, params, deadline)
550 }
551
552 fn execute_read_statement(
553 &self,
554 inner: MutexGuard<'_, TxInner>,
555 compiled: &CompiledQuery,
556 params: BTreeMap<String, LoraValue>,
557 deadline: Option<Instant>,
558 ) -> Result<Vec<Row>> {
559 match inner.staged.as_ref() {
560 Some(staged) => execute_read_compiled(staged, compiled, params, deadline),
561 None => {
562 drop(inner);
563 self.execute_live_compiled(compiled, params, deadline)
564 }
565 }
566 }
567
568 fn prepare_mutating_statement(
569 &self,
570 inner: &mut MutexGuard<'_, TxInner>,
571 ) -> Result<Option<Savepoint>> {
572 let clone_savepoint_graph = inner.staged.is_some();
573 self.ensure_staged_locked(inner)?;
574 Ok(Some(take_savepoint(inner, clone_savepoint_graph)))
575 }
576
577 pub fn explain(
582 &self,
583 query: &str,
584 _params: Option<BTreeMap<String, LoraValue>>,
585 ) -> Result<QueryPlan, LoraError> {
586 let compiled = self.compile_in_tx(query).map_err(LoraError::from_anyhow)?;
587 Ok(query_plan_for(query, &compiled))
588 }
589
590 pub fn profile(
598 &mut self,
599 query: &str,
600 params: Option<BTreeMap<String, LoraValue>>,
601 ) -> Result<QueryProfile, LoraError> {
602 let params = params.unwrap_or_default();
603 let compiled = self.compile_in_tx(query).map_err(LoraError::from_anyhow)?;
604 let plan = query_plan_for(query, &compiled);
605 let shape = plan.shape;
606
607 let collector = Arc::new(MetricsCollector::new());
608 let _guard = CollectorGuard::install(collector.clone());
609
610 let started = Instant::now();
611 let rows = self
612 .execute_rows_compiled_deadline(&compiled, params, None)
613 .map_err(LoraError::from_anyhow)?;
614 let total_elapsed_ns = started.elapsed().as_nanos() as u64;
615
616 drop(_guard);
617 let per_operator = collector
618 .snapshot()
619 .into_iter()
620 .map(|(id, op)| {
621 (
622 id,
623 OperatorMetrics {
624 rows: op.rows,
625 elapsed_ns: op.elapsed_ns,
626 next_calls: op.next_calls,
627 db_hits: 0,
628 },
629 )
630 })
631 .collect();
632
633 let metrics = ProfileMetrics {
634 total_elapsed_ns,
635 total_rows: rows.len() as u64,
636 mutated: shape.is_mutating(),
637 per_operator,
638 };
639
640 Ok(QueryProfile { plan, metrics })
641 }
642
643 pub fn stream(&mut self, query: &str) -> Result<QueryStream<'static>, LoraError> {
645 self.stream_with_params(query, BTreeMap::new())
646 }
647
648 pub fn stream_with_params(
651 &mut self,
652 query: &str,
653 params: BTreeMap<String, LoraValue>,
654 ) -> Result<QueryStream<'static>, LoraError> {
655 let compiled = Arc::new(self.compile_in_tx(query)?);
656 let columns = compiled_result_columns(&compiled);
657 Ok(self.stream_compiled(compiled, columns, params)?)
658 }
659
660 pub(crate) fn stream_compiled(
664 &mut self,
665 compiled: Arc<CompiledQuery>,
666 columns: Vec<String>,
667 params: BTreeMap<String, LoraValue>,
668 ) -> Result<QueryStream<'static>> {
669 let mut inner = self.begin_statement()?;
670 let is_mutating = classify_stream(&compiled).is_mutating();
671 ensure_stream_allowed(&inner, is_mutating)?;
672
673 let rollback_on_drop = stream_rolls_back_on_drop(is_mutating);
674 let staged_ptr = self.prepare_stream_staging(&mut inner, rollback_on_drop)?;
675 drop(inner);
676
677 let cursor = open_tx_stream_cursor(staged_ptr, compiled, params, is_mutating);
678
679 match cursor {
680 Ok(cursor) => Ok(QueryStream::for_tx_cursor(
681 cursor,
682 columns,
683 TxCursorLease::new(self.inner.clone(), rollback_on_drop),
684 )),
685 Err(err) => {
686 finalize_tx_stream(&self.inner, TxStreamOutcome::Interrupted, rollback_on_drop);
687 Err(err)
688 }
689 }
690 }
691
692 fn prepare_stream_staging(
693 &self,
694 inner: &mut MutexGuard<'_, TxInner>,
695 rollback_on_drop: bool,
696 ) -> Result<*mut InMemoryGraph> {
697 let clone_savepoint_graph = inner.staged.is_some();
702 self.ensure_staged_locked(inner)?;
703 inner.activate_cursor();
704
705 if rollback_on_drop {
706 inner.pending_savepoint = Some(take_savepoint(inner, clone_savepoint_graph));
707 } else {
708 inner.pending_savepoint = None;
709 }
710
711 Ok(inner
712 .staged
713 .as_mut()
714 .ok_or(TransactionError::NoStagedGraph)? as *mut _)
715 }
716
717 pub fn commit(mut self) -> Result<(), LoraError> {
724 let CommitState {
725 staged,
726 buffer_events,
727 mode,
728 } = self.take_commit_state()?;
729
730 let wrote_wal_commit = self.replay_commit_wal(mode, buffer_events)?;
731 self.publish_staged_graph(mode, staged, wrote_wal_commit)?;
732
733 self.live.take();
734 Ok(())
735 }
736
737 fn take_commit_state(&self) -> Result<CommitState> {
738 let mut inner = self.lock_inner()?;
739 if inner.cursor_active {
740 return Err(TransactionError::CursorActiveCommit.into());
741 }
742 if inner.closed {
743 return Err(TransactionError::AlreadyClosed.into());
744 }
745
746 let mode = inner.mode;
747 if inner.buffer_failed.load(Ordering::Acquire) {
748 inner.closed = true;
749 return Err(TransactionError::Poisoned.into());
750 }
751 let buffer_events = {
752 let mut buffer = inner
753 .buffer
754 .lock()
755 .map_err(|_| TransactionError::Poisoned)?;
756 std::mem::take(&mut *buffer)
757 };
758 let staged = inner.staged.take();
762 inner.closed = true;
763
764 Ok(CommitState {
765 staged,
766 buffer_events,
767 mode,
768 })
769 }
770
771 fn replay_commit_wal(
772 &self,
773 mode: TransactionMode,
774 buffer_events: Vec<MutationEvent>,
775 ) -> Result<bool> {
776 let Some(rec) = &self.wal else {
777 return Ok(false);
778 };
779
780 if !matches!(mode, TransactionMode::ReadWrite) {
781 ensure_wal_not_poisoned(rec)?;
782 return Ok(false);
783 }
784
785 Ok(rec.commit_events(buffer_events)?.wrote())
786 }
787
788 fn publish_staged_graph(
789 &mut self,
790 mode: TransactionMode,
791 staged: Option<InMemoryGraph>,
792 wrote_wal_commit: bool,
793 ) -> Result<()> {
794 if !matches!(mode, TransactionMode::ReadWrite) {
795 return Ok(());
796 }
797
798 let Some(mut staged) = staged else {
799 return Ok(());
800 };
801
802 staged.set_mutation_recorder(None);
807 let wal = self.wal.clone();
808 if let Some(rec) = &wal {
809 staged.set_mutation_recorder(Some(rec.clone() as Arc<dyn MutationRecorder>));
810 }
811
812 let live = self.live.as_mut().ok_or(TransactionError::NoGraphGuard)?;
813 let lease = match live {
814 LiveStoreGuard::Write(lease) => lease,
815 LiveStoreGuard::Read(_) => {
816 return Err(TransactionError::ReadOnlyCommit.into());
817 }
818 };
819
820 if wrote_wal_commit {
821 if let (Some(snapshots), Some(rec)) = (&self.snapshots, wal.as_ref()) {
822 snapshots.observe_commit(&staged, rec)?;
823 }
824 }
825
826 lease.store.store(Arc::new(staged));
830
831 Ok(())
832 }
833
834 pub fn rollback(mut self) -> Result<(), LoraError> {
837 let mut inner = self.lock_inner()?;
838 if inner.closed {
839 return Err(TransactionError::AlreadyClosed.into());
840 }
841 discard_transaction_state(&mut inner);
842 drop(inner);
843 self.live.take();
844 Ok(())
845 }
846
847 fn begin_statement(&self) -> Result<MutexGuard<'_, TxInner>> {
853 let inner = self.lock_inner()?;
854 if inner.closed {
855 return Err(TransactionError::AlreadyClosed.into());
856 }
857 if inner.cursor_active {
858 return Err(TransactionError::CursorActiveStatement.into());
859 }
860 Ok(inner)
861 }
862
863 fn precheck_open_no_savepoint(&self) -> Result<()> {
867 let inner = self.lock_inner()?;
868 if inner.closed {
869 return Err(TransactionError::AlreadyClosed.into());
870 }
871 if inner.cursor_active {
872 return Err(TransactionError::CursorActiveStatement.into());
873 }
874 Ok(())
875 }
876
877 fn is_read_only_unchecked(&self) -> bool {
881 matches!(self.mode, TransactionMode::ReadOnly)
882 }
883
884 fn lock_inner(&self) -> Result<MutexGuard<'_, TxInner>> {
885 self.inner
886 .lock()
887 .map_err(|_| TransactionError::Poisoned.into())
888 }
889
890 pub(crate) fn release_streaming_cursor(&self) {
891 if let Ok(mut inner) = self.inner.lock() {
892 inner.release_cursor();
893 }
894 }
895}
896
897type ExecResultRows = Result<Vec<Row>>;
898
899fn deadline_after(timeout: Duration) -> Instant {
900 Instant::now()
901 .checked_add(timeout)
902 .unwrap_or_else(Instant::now)
903}
904
905fn execute_read_compiled(
906 storage: &InMemoryGraph,
907 compiled: &CompiledQuery,
908 params: BTreeMap<String, LoraValue>,
909 deadline: Option<Instant>,
910) -> Result<Vec<Row>> {
911 let executor = Executor::with_deadline(ExecutionContext { storage, params }, deadline);
912 executor
913 .execute_compiled_rows(compiled)
914 .map_err(anyhow::Error::from)
915}
916
917fn execute_mutable_compiled(
918 storage: &mut InMemoryGraph,
919 compiled: &CompiledQuery,
920 params: BTreeMap<String, LoraValue>,
921 deadline: Option<Instant>,
922) -> Result<Vec<Row>> {
923 let mut executor =
924 MutableExecutor::with_deadline(MutableExecutionContext { storage, params }, deadline);
925 executor
926 .execute_compiled_rows(compiled)
927 .map_err(anyhow::Error::from)
928}
929
930fn query_plan_for(query: &str, compiled: &CompiledQuery) -> QueryPlan {
931 QueryPlan {
932 query: query.to_string(),
933 tree: plan_tree_from_compiled(compiled),
934 shape: classify_stream(compiled).into(),
935 result_columns: plan_result_columns(&compiled.physical),
936 }
937}
938
939fn ensure_stream_allowed(inner: &TxInner, is_mutating: bool) -> Result<()> {
940 if matches!(inner.mode, TransactionMode::ReadOnly) && is_mutating {
941 Err(TransactionError::ReadOnlyMutation.into())
942 } else {
943 Ok(())
944 }
945}
946
947fn stream_rolls_back_on_drop(is_mutating: bool) -> bool {
948 is_mutating
949}
950
951fn open_tx_stream_cursor(
952 staged_ptr: *mut InMemoryGraph,
953 compiled: Arc<CompiledQuery>,
954 params: BTreeMap<String, LoraValue>,
955 is_mutating: bool,
956) -> Result<Box<dyn RowSource + 'static>> {
957 let compiled_static: &'static CompiledQuery =
961 unsafe { std::mem::transmute::<&CompiledQuery, _>(compiled.as_ref()) };
962
963 if is_mutating {
964 let storage_static: &'static mut InMemoryGraph = unsafe { &mut *staged_ptr };
967 MutablePullExecutor::new(storage_static, params)
968 .open_compiled(compiled_static)
969 .map(|cursor| boxed_streaming_cursor(cursor, compiled))
970 .map_err(anyhow::Error::from)
971 } else {
972 let storage_static: &'static InMemoryGraph = unsafe { &*staged_ptr };
975 PullExecutor::new(storage_static, params)
976 .open_compiled(compiled_static)
977 .map(|cursor| boxed_streaming_cursor(cursor, compiled))
978 .map_err(anyhow::Error::from)
979 }
980}
981
982fn boxed_streaming_cursor(
983 cursor: Box<dyn RowSource + 'static>,
984 compiled: Arc<CompiledQuery>,
985) -> Box<dyn RowSource + 'static> {
986 Box::new(StreamingCursorWithArc {
987 cursor,
988 _compiled: compiled,
989 })
990}
991
992struct CommitState {
993 staged: Option<InMemoryGraph>,
994 buffer_events: Vec<MutationEvent>,
995 mode: TransactionMode,
996}
997
998impl TxInner {
999 fn staged_mut(&mut self) -> Result<&mut InMemoryGraph> {
1000 self.staged
1001 .as_mut()
1002 .ok_or(TransactionError::NoStagedGraph.into())
1003 }
1004
1005 fn activate_cursor(&mut self) {
1006 self.cursor_active = true;
1007 }
1008
1009 fn release_cursor(&mut self) {
1010 self.cursor_active = false;
1011 }
1012
1013 fn clear_pending_savepoint(&mut self) {
1014 self.pending_savepoint = None;
1015 }
1016
1017 fn restore_pending_savepoint(&mut self) {
1018 if let Some(sp) = self.pending_savepoint.take() {
1019 apply_savepoint(self, sp);
1020 }
1021 }
1022
1023 fn finalize_stream(&mut self, outcome: TxStreamOutcome, rollback_on_drop: bool) {
1024 self.release_cursor();
1025
1026 if self.closed {
1027 discard_transaction_state(self);
1028 return;
1029 }
1030
1031 if outcome.should_restore_savepoint(rollback_on_drop) {
1032 self.restore_pending_savepoint();
1033 } else {
1034 self.clear_pending_savepoint();
1035 }
1036 }
1037}
1038
1039struct StreamingCursorWithArc {
1044 cursor: Box<dyn RowSource + 'static>,
1045 _compiled: Arc<CompiledQuery>,
1046}
1047
1048impl RowSource for StreamingCursorWithArc {
1049 fn next_row(&mut self) -> lora_executor::ExecResult<Option<Row>> {
1050 self.cursor.next_row()
1051 }
1052}
1053
1054fn finalize_tx_stream(
1055 handle: &Arc<Mutex<TxInner>>,
1056 outcome: TxStreamOutcome,
1057 rollback_on_drop: bool,
1058) {
1059 if let Ok(mut inner) = handle.lock() {
1060 inner.finalize_stream(outcome, rollback_on_drop);
1061 }
1062}
1063
1064fn discard_transaction_state(inner: &mut TxInner) {
1065 inner.clear_pending_savepoint();
1067 inner.release_cursor();
1068 inner.staged = None;
1069 if let Ok(mut buf) = inner.buffer.lock() {
1070 buf.clear();
1071 } else {
1072 inner.buffer_failed.store(true, Ordering::Release);
1073 }
1074 inner.closed = true;
1075}
1076
1077fn take_savepoint(inner: &TxInner, clone_staged: bool) -> Savepoint {
1078 let buffer_len = match inner.buffer.lock() {
1079 Ok(buffer) => buffer.len(),
1080 Err(_) => {
1081 inner.buffer_failed.store(true, Ordering::Release);
1082 0
1083 }
1084 };
1085 Savepoint {
1086 staged: if clone_staged {
1087 inner.staged.as_ref().cloned()
1088 } else {
1089 None
1090 },
1091 buffer_len,
1092 }
1093}
1094
1095fn restore_savepoint(inner: &mut TxInner, savepoint: Option<Savepoint>) {
1096 if let Some(sp) = savepoint {
1097 apply_savepoint(inner, sp);
1098 }
1099}
1100
1101fn apply_savepoint(inner: &mut TxInner, sp: Savepoint) {
1102 if let Ok(mut buf) = inner.buffer.lock() {
1103 buf.truncate(sp.buffer_len);
1104 } else {
1105 inner.buffer_failed.store(true, Ordering::Release);
1106 }
1107
1108 let Some(mut graph) = sp.staged else {
1109 inner.staged = None;
1110 return;
1111 };
1112
1113 if matches!(inner.mode, TransactionMode::ReadWrite) && inner.buffer_mutations {
1117 graph.set_mutation_recorder(Some(Arc::new(BufferingRecorder::new(
1118 inner.buffer.clone(),
1119 inner.buffer_failed.clone(),
1120 )) as Arc<dyn MutationRecorder>));
1121 }
1122 inner.staged = Some(graph);
1123}
1124
1125impl Drop for Transaction<'_> {
1126 fn drop(&mut self) {
1127 if let Ok(mut inner) = self.inner.lock() {
1131 if !inner.closed {
1132 if inner.cursor_active {
1133 inner.closed = true;
1139 } else {
1140 discard_transaction_state(&mut inner);
1141 }
1142 }
1143 }
1144 }
1145}
1146
1147#[cfg(test)]
1148mod tests {
1149 use std::thread;
1150
1151 use super::*;
1152
1153 #[test]
1154 fn buffering_recorder_latches_poisoned_buffer() {
1155 let buffer = Arc::new(Mutex::new(Vec::new()));
1156 let failed = Arc::new(AtomicBool::new(false));
1157
1158 let poisoned_buffer = buffer.clone();
1159 let _ = thread::spawn(move || {
1160 let _guard = poisoned_buffer.lock().unwrap();
1161 panic!("poison mutation buffer");
1162 })
1163 .join();
1164
1165 let recorder = BufferingRecorder::new(buffer, failed.clone());
1166 recorder.record(MutationEvent::Clear);
1167
1168 assert!(failed.load(Ordering::Acquire));
1169 }
1170}