Skip to main content

alopex_core/kv/
owned.rs

1//! Owned key-value session contracts for long-lived local consumers.
2//!
3//! The legacy [`crate::kv::KVStore`] API intentionally returns transactions borrowed from the
4//! store.  Those transactions remain the compatibility API.  Python and asynchronous stream
5//! paths need a different boundary: every transaction, cursor, and store reference must be
6//! owned without extending a Rust borrow through `unsafe` or a foreign-runtime lifetime.
7
8use std::fmt;
9use std::sync::{Arc, Mutex, MutexGuard};
10
11use crate::error::{Error, Result};
12use crate::kv::{search, KVTransaction, KeySearchCancellation, KeySearchPage, KeySearchRequest};
13use crate::txn::{OwnedLeaseOutcome, OwnedReadSessionStatus, OwnedTransactionSessionStatus};
14use crate::types::{Key, TxnId, TxnMode, Value};
15
16/// An incremental key/value cursor whose state outlives the method that opened it.
17///
18/// Implementations must own every snapshot guard and backend reference needed to advance.  A
19/// cursor must never borrow an [`OwnedKVTransaction`] through a raw pointer or an extended
20/// lifetime.
21pub trait OwnedKVScan: Send {
22    /// Return one key/value pair, or `None` after normal exhaustion.
23    fn next_entry(&mut self) -> Result<Option<(Key, Value)>>;
24
25    /// Return one physical search candidate, including a deleted key.
26    fn next_search_entry(&mut self) -> Result<Option<(Key, Option<Value>)>> {
27        self.next_entry()
28            .map(|entry| entry.map(|(key, value)| (key, Some(value))))
29    }
30
31    /// Release backend cursor resources.  Repeated calls must be harmless.
32    fn close(&mut self) -> Result<()> {
33        Ok(())
34    }
35}
36
37/// A transaction that owns its backend state instead of borrowing a [`crate::kv::KVStore`].
38///
39/// `commit` and `rollback` consume the boxed transaction.  This makes a terminal action
40/// unrepeatable even if a caller retains an `OwnedTransactionSession` handle.
41pub trait OwnedKVTransaction: Send {
42    /// Return this transaction's stable identifier.
43    fn id(&self) -> TxnId;
44
45    /// Return whether this transaction permits writes.
46    fn mode(&self) -> TxnMode;
47
48    /// Read one key at the transaction's snapshot.
49    fn get(&mut self, key: &Key) -> Result<Option<Value>>;
50
51    /// Stage one value for commit.
52    fn put(&mut self, key: Key, value: Value) -> Result<()>;
53
54    /// Stage one deletion for commit.
55    fn delete(&mut self, key: Key) -> Result<()>;
56
57    /// Open an owned prefix cursor at the transaction's snapshot.
58    fn scan_prefix(&mut self, prefix: &[u8]) -> Result<Box<dyn OwnedKVScan>>;
59
60    /// Open an owned half-open range cursor at the transaction's snapshot.
61    fn scan_range(&mut self, start: &[u8], end: &[u8]) -> Result<Box<dyn OwnedKVScan>>;
62
63    /// Open an owned cursor at or after `start`.
64    fn scan_from(&mut self, start: &[u8]) -> Result<Box<dyn OwnedKVScan>> {
65        let _ = start;
66        Err(Error::InvalidParameter {
67            param: "scan_from".into(),
68            reason: "backend does not implement cursor-based scanning".into(),
69        })
70    }
71
72    /// Searches opaque key bytes with the common bounded search contract.
73    fn search_keys(&mut self, request: &KeySearchRequest) -> Result<KeySearchPage> {
74        self.search_keys_with_cancellation(request, &KeySearchCancellation::default())
75    }
76
77    /// Searches key bytes while observing a cooperative cancellation token.
78    fn search_keys_with_cancellation(
79        &mut self,
80        request: &KeySearchRequest,
81        cancellation: &KeySearchCancellation,
82    ) -> Result<KeySearchPage> {
83        let prepared = search::PreparedKeySearch::new(request)?;
84        let mut cursor_start = request.cursor.clone().unwrap_or_default();
85        let mut scan = if request.cursor.is_some() {
86            cursor_start.push(0);
87            self.scan_from(&cursor_start)?
88        } else {
89            self.scan_prefix(prepared.prefix())?
90        };
91        let result = prepared.collect(|| scan.next_search_entry(), request, cancellation);
92        match (result, scan.close()) {
93            (Err(error), _) | (Ok(_), Err(error)) => Err(error),
94            (Ok(page), Ok(())) => Ok(page),
95        }
96    }
97
98    /// Commit once.  The caller cannot use this transaction afterwards.
99    fn commit(self: Box<Self>) -> Result<()>;
100
101    /// Roll back once.  The caller cannot use this transaction afterwards.
102    fn rollback(self: Box<Self>) -> Result<()>;
103}
104
105/// A short-lived [`KVTransaction`] view over an owned transaction.
106///
107/// This adapter is only for synchronous compatibility operations such as the embedded SQL
108/// executor.  It cannot commit or roll back: the [`OwnedTransactionSession`] remains the only
109/// owner of the terminal transition.  Public streams must use [`OwnedKVScan`] directly.
110///
111/// The legacy `KVTransaction` iterator has no error channel for `next`, so a cursor is fully
112/// collected before it is exposed through that trait.  It is consequently not a streaming
113/// primitive and is never used by public stream paths.
114pub struct OwnedKVTransactionAdapter<'a> {
115    transaction: &'a mut dyn OwnedKVTransaction,
116}
117
118impl<'a> OwnedKVTransactionAdapter<'a> {
119    /// Borrow an owned transaction for one synchronous compatibility operation.
120    pub fn new(transaction: &'a mut dyn OwnedKVTransaction) -> Self {
121        Self { transaction }
122    }
123
124    fn collect_cursor(cursor: Box<dyn OwnedKVScan>) -> Result<Vec<(Key, Value)>> {
125        let mut cursor = cursor;
126        let mut entries = Vec::new();
127        while let Some(entry) = cursor.next_entry()? {
128            entries.push(entry);
129        }
130        cursor.close()?;
131        Ok(entries)
132    }
133}
134
135impl<'a> KVTransaction<'a> for OwnedKVTransactionAdapter<'a> {
136    fn id(&self) -> TxnId {
137        self.transaction.id()
138    }
139
140    fn mode(&self) -> TxnMode {
141        self.transaction.mode()
142    }
143
144    fn get(&mut self, key: &Key) -> Result<Option<Value>> {
145        self.transaction.get(key)
146    }
147
148    fn put(&mut self, key: Key, value: Value) -> Result<()> {
149        self.transaction.put(key, value)
150    }
151
152    fn delete(&mut self, key: Key) -> Result<()> {
153        self.transaction.delete(key)
154    }
155
156    fn scan_prefix(
157        &mut self,
158        prefix: &[u8],
159    ) -> Result<Box<dyn Iterator<Item = (Key, Value)> + '_>> {
160        let entries = Self::collect_cursor(self.transaction.scan_prefix(prefix)?)?;
161        Ok(Box::new(entries.into_iter()))
162    }
163
164    fn scan_range(
165        &mut self,
166        start: &[u8],
167        end: &[u8],
168    ) -> Result<Box<dyn Iterator<Item = (Key, Value)> + '_>> {
169        let entries = Self::collect_cursor(self.transaction.scan_range(start, end)?)?;
170        Ok(Box::new(entries.into_iter()))
171    }
172
173    fn search_keys_with_cancellation(
174        &mut self,
175        request: &KeySearchRequest,
176        cancellation: &KeySearchCancellation,
177    ) -> Result<KeySearchPage> {
178        self.transaction
179            .search_keys_with_cancellation(request, cancellation)
180    }
181
182    fn commit_self(self) -> Result<()> {
183        Err(Error::InvalidParameter {
184            param: "owned_transaction_adapter".to_owned(),
185            reason: "terminal ownership belongs to OwnedTransactionSession".to_owned(),
186        })
187    }
188
189    fn rollback_self(self) -> Result<()> {
190        Err(Error::InvalidParameter {
191            param: "owned_transaction_adapter".to_owned(),
192            reason: "terminal ownership belongs to OwnedTransactionSession".to_owned(),
193        })
194    }
195}
196
197/// A store that can create an owned transaction without returning a borrowed facade.
198///
199/// Backend-specific implementations are introduced separately.  The contract is deliberately
200/// distinct from [`crate::kv::KVStore`] so existing borrowed APIs retain their v0.7 behavior.
201pub trait OwnedKVStore: Send + Sync + 'static {
202    /// Begin a transaction whose lifetime is independent of the `Arc<Self>` call site.
203    fn begin_owned_kv_transaction(
204        self: Arc<Self>,
205        mode: TxnMode,
206    ) -> Result<Box<dyn OwnedKVTransaction>>;
207}
208
209/// Options shared by owned read session factories.
210///
211/// Resource limits and deadlines belong to higher query/DataFrame layers.  Keeping this type
212/// explicit makes that later wiring additive without changing the ownership boundary.
213#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
214#[non_exhaustive]
215pub struct OwnedReadOptions {}
216
217/// Factory for owned read and transaction sessions.
218pub trait OwnedSessionFactory: OwnedKVStore {
219    /// Begin an owned read-only session.
220    fn begin_owned_read(self: Arc<Self>, _options: OwnedReadOptions) -> Result<OwnedReadSession> {
221        let transaction = self.begin_owned_kv_transaction(TxnMode::ReadOnly)?;
222        OwnedReadSession::new(transaction)
223    }
224
225    /// Begin an owned transaction session.
226    fn begin_owned_transaction(self: Arc<Self>, mode: TxnMode) -> Result<OwnedTransactionSession> {
227        let transaction = self.begin_owned_kv_transaction(mode)?;
228        Ok(OwnedTransactionSession::new(transaction))
229    }
230}
231
232impl<T> OwnedSessionFactory for T where T: OwnedKVStore {}
233
234/// Common operations exposed by an owned read session.
235pub trait OwnedReadSessionApi: Send + Sync {
236    /// Return the current one-way lifecycle state.
237    fn status(&self) -> OwnedReadSessionStatus;
238
239    /// Acquire the only lease that may advance this session's transaction/cursors.
240    fn acquire_lease(&self) -> Result<OwnedReadLease>;
241
242    /// Close the session and release its read-only transaction.
243    fn close(&self) -> Result<OwnedReadSessionStatus>;
244}
245
246/// Common operations exposed by an owned transaction session.
247pub trait OwnedTransactionSessionApi: Send + Sync {
248    /// Return the current one-way lifecycle state.
249    fn status(&self) -> OwnedTransactionSessionStatus;
250
251    /// Acquire the only active stream lease for this transaction.
252    fn acquire_lease(&self) -> Result<OwnedTransactionLease>;
253
254    /// Commit once after every active lease has released as committable.
255    fn commit(&self) -> Result<OwnedTransactionSessionStatus>;
256
257    /// Roll back once, including the conservative `must abort` path.
258    fn rollback(&self) -> Result<OwnedTransactionSessionStatus>;
259}
260
261/// A read-only session that owns a transaction until a stream terminal action releases it.
262#[derive(Clone)]
263pub struct OwnedReadSession {
264    inner: Arc<Mutex<OwnedReadInner>>,
265}
266
267struct OwnedReadInner {
268    transaction: Option<Box<dyn OwnedKVTransaction>>,
269    status: OwnedReadSessionStatus,
270}
271
272impl Drop for OwnedReadInner {
273    fn drop(&mut self) {
274        if let Some(transaction) = self.transaction.take() {
275            let _ = transaction.rollback();
276        }
277    }
278}
279
280impl OwnedReadSession {
281    /// Wrap a backend-owned read-only transaction.
282    pub fn new(transaction: Box<dyn OwnedKVTransaction>) -> Result<Self> {
283        if transaction.mode() != TxnMode::ReadOnly {
284            return Err(Error::InvalidParameter {
285                param: "owned_read_session".to_owned(),
286                reason: "requires a read-only transaction".to_owned(),
287            });
288        }
289        Ok(Self {
290            inner: Arc::new(Mutex::new(OwnedReadInner {
291                transaction: Some(transaction),
292                status: OwnedReadSessionStatus::Open,
293            })),
294        })
295    }
296
297    /// Return the current lifecycle state.
298    pub fn status(&self) -> OwnedReadSessionStatus {
299        self.lock().status
300    }
301
302    /// Acquire the only lease allowed to advance this read session.
303    pub fn acquire_lease(&self) -> Result<OwnedReadLease> {
304        let mut inner = self.lock();
305        if inner.status != OwnedReadSessionStatus::Open || inner.transaction.is_none() {
306            return Err(Error::TxnClosed);
307        }
308        inner.status = OwnedReadSessionStatus::LeaseActive;
309        Ok(OwnedReadLease {
310            inner: self.inner.clone(),
311            released: false,
312        })
313    }
314
315    /// Close this session.  Closing is idempotent after any terminal state.
316    pub fn close(&self) -> Result<OwnedReadSessionStatus> {
317        finish_read_session(&self.inner, OwnedLeaseOutcome::Closed)
318    }
319
320    fn lock(&self) -> MutexGuard<'_, OwnedReadInner> {
321        self.inner
322            .lock()
323            .expect("owned read session mutex poisoned")
324    }
325}
326
327impl OwnedReadSessionApi for OwnedReadSession {
328    fn status(&self) -> OwnedReadSessionStatus {
329        Self::status(self)
330    }
331
332    fn acquire_lease(&self) -> Result<OwnedReadLease> {
333        Self::acquire_lease(self)
334    }
335
336    fn close(&self) -> Result<OwnedReadSessionStatus> {
337        Self::close(self)
338    }
339}
340
341impl fmt::Debug for OwnedReadSession {
342    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
343        formatter
344            .debug_struct("OwnedReadSession")
345            .field("status", &self.status())
346            .finish_non_exhaustive()
347    }
348}
349
350/// The single active owner allowed to advance an [`OwnedReadSession`].
351pub struct OwnedReadLease {
352    inner: Arc<Mutex<OwnedReadInner>>,
353    released: bool,
354}
355
356impl OwnedReadLease {
357    /// Execute one operation while this lease is active.
358    ///
359    /// The closure cannot retain a borrowed transaction.  It may instead return an owned cursor
360    /// from [`OwnedKVTransaction::scan_prefix`] or [`OwnedKVTransaction::scan_range`].
361    pub fn with_transaction<T>(
362        &self,
363        operation: impl FnOnce(&mut dyn OwnedKVTransaction) -> Result<T>,
364    ) -> Result<T> {
365        let mut inner = self
366            .inner
367            .lock()
368            .expect("owned read session mutex poisoned");
369        if inner.status != OwnedReadSessionStatus::LeaseActive {
370            return Err(Error::TxnClosed);
371        }
372        operation(inner.transaction.as_deref_mut().ok_or(Error::TxnClosed)?)
373    }
374
375    /// End the lease and release the read transaction exactly once.
376    pub fn finish(mut self, outcome: OwnedLeaseOutcome) -> Result<OwnedReadSessionStatus> {
377        let result = finish_read_session(&self.inner, outcome);
378        self.released = true;
379        result
380    }
381}
382
383impl Drop for OwnedReadLease {
384    fn drop(&mut self) {
385        if !self.released {
386            let _ = finish_read_session(&self.inner, OwnedLeaseOutcome::Closed);
387            self.released = true;
388        }
389    }
390}
391
392fn finish_read_session(
393    inner: &Arc<Mutex<OwnedReadInner>>,
394    outcome: OwnedLeaseOutcome,
395) -> Result<OwnedReadSessionStatus> {
396    let (transaction, target) = {
397        let mut inner = inner.lock().expect("owned read session mutex poisoned");
398        if inner.status.is_terminal() {
399            return Ok(inner.status);
400        }
401        let target = OwnedReadSessionStatus::from(outcome);
402        let transaction = inner.transaction.take();
403        inner.status = target;
404        (transaction, target)
405    };
406
407    if let Some(transaction) = transaction {
408        if transaction.rollback().is_err() {
409            let mut inner = inner.lock().expect("owned read session mutex poisoned");
410            inner.status = OwnedReadSessionStatus::Failed;
411            return Err(Error::TxnClosed);
412        }
413    }
414    Ok(target)
415}
416
417/// A transaction session that serializes stream leases and owns exactly one terminal action.
418#[derive(Clone)]
419pub struct OwnedTransactionSession {
420    inner: Arc<Mutex<OwnedTransactionInner>>,
421}
422
423struct OwnedTransactionInner {
424    transaction: Option<Box<dyn OwnedKVTransaction>>,
425    status: OwnedTransactionSessionStatus,
426}
427
428impl Drop for OwnedTransactionInner {
429    fn drop(&mut self) {
430        if let Some(transaction) = self.transaction.take() {
431            let _ = transaction.rollback();
432        }
433    }
434}
435
436impl OwnedTransactionSession {
437    /// Wrap one backend-owned transaction.
438    pub fn new(transaction: Box<dyn OwnedKVTransaction>) -> Self {
439        Self {
440            inner: Arc::new(Mutex::new(OwnedTransactionInner {
441                transaction: Some(transaction),
442                status: OwnedTransactionSessionStatus::Open,
443            })),
444        }
445    }
446
447    /// Return the current transaction/lease lifecycle state.
448    pub fn status(&self) -> OwnedTransactionSessionStatus {
449        self.lock().status
450    }
451
452    /// Acquire a single active lease.  A later lease may start only after normal exhaustion.
453    pub fn acquire_lease(&self) -> Result<OwnedTransactionLease> {
454        let mut inner = self.lock();
455        if !inner.status.can_acquire_lease() || inner.transaction.is_none() {
456            return Err(Error::TxnClosed);
457        }
458        inner.status = OwnedTransactionSessionStatus::LeaseActive;
459        Ok(OwnedTransactionLease {
460            inner: self.inner.clone(),
461            released: false,
462        })
463    }
464
465    /// Run one finite compatibility operation without exposing the active lease.
466    ///
467    /// This is intentionally distinct from a stream lease: the operation cannot retain a
468    /// cursor, and a successful or classified operation error releases the transaction as
469    /// committable.  A panic or an abandoned lease remains conservative and marks the session
470    /// `MustAbort` through [`OwnedTransactionLease::drop`].
471    pub fn with_transaction<T>(
472        &self,
473        operation: impl FnOnce(&mut dyn OwnedKVTransaction) -> Result<T>,
474    ) -> Result<T> {
475        let lease = self.acquire_lease()?;
476        let result = lease.with_transaction(operation);
477        let release = lease.finish(OwnedLeaseOutcome::Exhausted);
478        match (result, release) {
479            (Ok(value), Ok(_)) => Ok(value),
480            (Err(error), Ok(_)) => Err(error),
481            (_, Err(error)) => Err(error),
482        }
483    }
484
485    /// Commit this owned transaction once.
486    pub fn commit(&self) -> Result<OwnedTransactionSessionStatus> {
487        self.finish_transaction(true)
488    }
489
490    /// Roll back this owned transaction once.
491    pub fn rollback(&self) -> Result<OwnedTransactionSessionStatus> {
492        self.finish_transaction(false)
493    }
494
495    fn finish_transaction(&self, commit: bool) -> Result<OwnedTransactionSessionStatus> {
496        let transaction = {
497            let mut inner = self.lock();
498            let allowed = if commit {
499                inner.status.can_commit()
500            } else {
501                inner.status.can_rollback()
502            };
503            if !allowed {
504                return Err(Error::TxnClosed);
505            }
506            inner.status = OwnedTransactionSessionStatus::Closed;
507            inner.transaction.take().ok_or(Error::TxnClosed)?
508        };
509
510        let result = if commit {
511            transaction.commit()
512        } else {
513            transaction.rollback()
514        };
515        let mut inner = self.lock();
516        inner.status = if result.is_ok() {
517            if commit {
518                OwnedTransactionSessionStatus::Committed
519            } else {
520                OwnedTransactionSessionStatus::RolledBack
521            }
522        } else {
523            OwnedTransactionSessionStatus::Closed
524        };
525        match result {
526            Ok(()) => Ok(inner.status),
527            Err(error) => Err(error),
528        }
529    }
530
531    fn lock(&self) -> MutexGuard<'_, OwnedTransactionInner> {
532        self.inner
533            .lock()
534            .expect("owned transaction session mutex poisoned")
535    }
536}
537
538impl OwnedTransactionSessionApi for OwnedTransactionSession {
539    fn status(&self) -> OwnedTransactionSessionStatus {
540        Self::status(self)
541    }
542
543    fn acquire_lease(&self) -> Result<OwnedTransactionLease> {
544        Self::acquire_lease(self)
545    }
546
547    fn commit(&self) -> Result<OwnedTransactionSessionStatus> {
548        Self::commit(self)
549    }
550
551    fn rollback(&self) -> Result<OwnedTransactionSessionStatus> {
552        Self::rollback(self)
553    }
554}
555
556impl fmt::Debug for OwnedTransactionSession {
557    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
558        formatter
559            .debug_struct("OwnedTransactionSession")
560            .field("status", &self.status())
561            .finish_non_exhaustive()
562    }
563}
564
565/// The only active owner allowed to advance an [`OwnedTransactionSession`].
566pub struct OwnedTransactionLease {
567    inner: Arc<Mutex<OwnedTransactionInner>>,
568    released: bool,
569}
570
571impl OwnedTransactionLease {
572    /// Execute one operation while the transaction's stream lease is active.
573    pub fn with_transaction<T>(
574        &self,
575        operation: impl FnOnce(&mut dyn OwnedKVTransaction) -> Result<T>,
576    ) -> Result<T> {
577        let mut inner = self
578            .inner
579            .lock()
580            .expect("owned transaction session mutex poisoned");
581        if inner.status != OwnedTransactionSessionStatus::LeaseActive {
582            return Err(Error::TxnClosed);
583        }
584        operation(inner.transaction.as_deref_mut().ok_or(Error::TxnClosed)?)
585    }
586
587    /// Release the active lease and record its effect on later commit/rollback.
588    pub fn finish(mut self, outcome: OwnedLeaseOutcome) -> Result<OwnedTransactionSessionStatus> {
589        let mut inner = self
590            .inner
591            .lock()
592            .expect("owned transaction session mutex poisoned");
593        if inner.status != OwnedTransactionSessionStatus::LeaseActive {
594            self.released = true;
595            return Err(Error::TxnClosed);
596        }
597        inner.status = OwnedTransactionSessionStatus::from(outcome);
598        self.released = true;
599        Ok(inner.status)
600    }
601}
602
603impl Drop for OwnedTransactionLease {
604    fn drop(&mut self) {
605        if !self.released {
606            let mut inner = self
607                .inner
608                .lock()
609                .expect("owned transaction session mutex poisoned");
610            if inner.status == OwnedTransactionSessionStatus::LeaseActive {
611                inner.status = OwnedTransactionSessionStatus::MustAbort;
612            }
613            self.released = true;
614        }
615    }
616}
617
618#[cfg(test)]
619mod tests {
620    use std::sync::atomic::{AtomicUsize, Ordering};
621    use std::sync::Arc;
622
623    use super::{
624        OwnedKVScan, OwnedKVTransaction, OwnedKVTransactionAdapter, OwnedReadSession,
625        OwnedSessionFactory, OwnedTransactionSession,
626    };
627    use crate::error::Result;
628    use crate::kv::{memory::MemoryKV, KVTransaction};
629    use crate::txn::{OwnedLeaseOutcome, OwnedReadSessionStatus, OwnedTransactionSessionStatus};
630    use crate::types::{Key, TxnId, TxnMode, Value};
631
632    #[derive(Default)]
633    struct Calls {
634        commits: AtomicUsize,
635        rollbacks: AtomicUsize,
636    }
637
638    struct TestCursor;
639
640    impl OwnedKVScan for TestCursor {
641        fn next_entry(&mut self) -> Result<Option<(Key, Value)>> {
642            Ok(None)
643        }
644    }
645
646    struct TestTransaction {
647        calls: Arc<Calls>,
648        mode: TxnMode,
649    }
650
651    impl TestTransaction {
652        fn new(calls: Arc<Calls>, mode: TxnMode) -> Self {
653            Self { calls, mode }
654        }
655    }
656
657    impl OwnedKVTransaction for TestTransaction {
658        fn id(&self) -> TxnId {
659            TxnId(7)
660        }
661
662        fn mode(&self) -> TxnMode {
663            self.mode
664        }
665
666        fn get(&mut self, _key: &Key) -> Result<Option<Value>> {
667            Ok(None)
668        }
669
670        fn put(&mut self, _key: Key, _value: Value) -> Result<()> {
671            Ok(())
672        }
673
674        fn delete(&mut self, _key: Key) -> Result<()> {
675            Ok(())
676        }
677
678        fn scan_prefix(&mut self, _prefix: &[u8]) -> Result<Box<dyn OwnedKVScan>> {
679            Ok(Box::new(TestCursor))
680        }
681
682        fn scan_range(&mut self, _start: &[u8], _end: &[u8]) -> Result<Box<dyn OwnedKVScan>> {
683            Ok(Box::new(TestCursor))
684        }
685
686        fn scan_from(&mut self, _start: &[u8]) -> Result<Box<dyn OwnedKVScan>> {
687            Ok(Box::new(TestCursor))
688        }
689
690        fn commit(self: Box<Self>) -> Result<()> {
691            self.calls.commits.fetch_add(1, Ordering::SeqCst);
692            Ok(())
693        }
694
695        fn rollback(self: Box<Self>) -> Result<()> {
696            self.calls.rollbacks.fetch_add(1, Ordering::SeqCst);
697            Ok(())
698        }
699    }
700
701    #[test]
702    fn read_session_releases_its_transaction_once_at_terminal_outcome() {
703        let calls = Arc::new(Calls::default());
704        let session = OwnedReadSession::new(Box::new(TestTransaction::new(
705            calls.clone(),
706            TxnMode::ReadOnly,
707        )))
708        .unwrap();
709
710        let lease = session.acquire_lease().unwrap();
711        assert_eq!(session.status(), OwnedReadSessionStatus::LeaseActive);
712        assert!(session.acquire_lease().is_err());
713        assert_eq!(
714            lease.finish(OwnedLeaseOutcome::Exhausted).unwrap(),
715            OwnedReadSessionStatus::Exhausted
716        );
717        assert_eq!(calls.rollbacks.load(Ordering::SeqCst), 1);
718        assert_eq!(session.close().unwrap(), OwnedReadSessionStatus::Exhausted);
719        assert_eq!(calls.rollbacks.load(Ordering::SeqCst), 1);
720    }
721
722    #[test]
723    fn transaction_session_blocks_commit_while_leased_then_commits_once() {
724        let calls = Arc::new(Calls::default());
725        let session = OwnedTransactionSession::new(Box::new(TestTransaction::new(
726            calls.clone(),
727            TxnMode::ReadWrite,
728        )));
729
730        let lease = session.acquire_lease().unwrap();
731        assert!(session.commit().is_err());
732        assert_eq!(
733            lease.finish(OwnedLeaseOutcome::Exhausted).unwrap(),
734            OwnedTransactionSessionStatus::Committable
735        );
736        assert_eq!(
737            session.commit().unwrap(),
738            OwnedTransactionSessionStatus::Committed
739        );
740        assert_eq!(calls.commits.load(Ordering::SeqCst), 1);
741        assert!(session.rollback().is_err());
742        assert_eq!(calls.rollbacks.load(Ordering::SeqCst), 0);
743    }
744
745    #[test]
746    fn cancelled_or_dropped_lease_requires_a_single_rollback() {
747        let calls = Arc::new(Calls::default());
748        let session = OwnedTransactionSession::new(Box::new(TestTransaction::new(
749            calls.clone(),
750            TxnMode::ReadWrite,
751        )));
752
753        let lease = session.acquire_lease().unwrap();
754        assert_eq!(
755            lease.finish(OwnedLeaseOutcome::Cancelled).unwrap(),
756            OwnedTransactionSessionStatus::MustAbort
757        );
758        assert!(session.commit().is_err());
759        assert_eq!(
760            session.rollback().unwrap(),
761            OwnedTransactionSessionStatus::RolledBack
762        );
763        assert_eq!(calls.rollbacks.load(Ordering::SeqCst), 1);
764
765        let drop_calls = Arc::new(Calls::default());
766        let dropped = OwnedTransactionSession::new(Box::new(TestTransaction::new(
767            drop_calls.clone(),
768            TxnMode::ReadWrite,
769        )));
770        drop(dropped.acquire_lease().unwrap());
771        assert_eq!(dropped.status(), OwnedTransactionSessionStatus::MustAbort);
772        drop(dropped);
773        assert_eq!(drop_calls.rollbacks.load(Ordering::SeqCst), 1);
774    }
775
776    #[test]
777    fn compatibility_adapter_never_consumes_the_owned_terminal_transition() {
778        let store = Arc::new(MemoryKV::new());
779        let session = store
780            .clone()
781            .begin_owned_transaction(TxnMode::ReadWrite)
782            .unwrap();
783
784        session
785            .with_transaction(|owned| {
786                let mut compatibility = OwnedKVTransactionAdapter::new(owned);
787                compatibility.put(b"adapter".to_vec(), b"value".to_vec())?;
788                let rows = compatibility.scan_prefix(b"adapter")?.collect::<Vec<_>>();
789                assert_eq!(rows, vec![(b"adapter".to_vec(), b"value".to_vec())]);
790                assert!(compatibility.commit_self().is_err());
791                Ok(())
792            })
793            .unwrap();
794
795        assert_eq!(session.status(), OwnedTransactionSessionStatus::Committable);
796        session.commit().unwrap();
797
798        let read = store.begin_owned_read(Default::default()).unwrap();
799        let lease = read.acquire_lease().unwrap();
800        let value = lease
801            .with_transaction(|transaction| transaction.get(&b"adapter".to_vec()))
802            .unwrap();
803        assert_eq!(value, Some(b"value".to_vec()));
804        lease.finish(OwnedLeaseOutcome::Exhausted).unwrap();
805    }
806}