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