Skip to main content

uqa_storage_sqlite/
connection_lease.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Owned physical connection lease for an independently scoped transaction.
8
9use std::ops::{Deref, DerefMut};
10
11use rusqlite::Connection;
12
13use crate::connection::PooledConnection;
14
15/// An exclusive physical connection checked out from a managed pool. It is
16/// independent of the originating logical session. Dropping the lease rolls
17/// back an unfinished transaction before returning the connection to the pool;
18/// a connection whose rollback fails is discarded.
19pub struct SQLiteConnectionLease(pub(crate) PooledConnection);
20
21impl Deref for SQLiteConnectionLease {
22    type Target = Connection;
23
24    fn deref(&self) -> &Self::Target {
25        self.0
26            .connection()
27            .expect("a live connection lease owns its checked-out connection")
28    }
29}
30
31impl DerefMut for SQLiteConnectionLease {
32    fn deref_mut(&mut self) -> &mut Self::Target {
33        self.0
34            .connection_mut()
35            .expect("a live connection lease owns its checked-out connection")
36    }
37}
38
39#[cfg(test)]
40mod tests {
41    use crate::{ManagedConnection, SQLiteError};
42
43    #[test]
44    fn auxiliary_pool_preserves_rollback_mode_and_rejects_wal_without_conversion() {
45        let directory = tempfile::tempdir().unwrap();
46        let path = directory.path().join("auxiliary.db");
47        let auxiliary = ManagedConnection::open_auxiliary(&path, None).unwrap();
48        assert!(!auxiliary.supports_concurrent_pinned_read_and_write());
49        assert!(auxiliary.data_version_monitor_is_nonblocking().unwrap());
50        auxiliary.begin_deferred_transaction().unwrap();
51        assert!(!auxiliary.data_version_monitor_is_nonblocking().unwrap());
52        auxiliary.rollback_transaction().unwrap();
53        drop(auxiliary);
54        let wal = ManagedConnection::open(&path).unwrap();
55        wal.with(|connection| {
56            connection.execute_batch(
57                "CREATE TABLE retained(value TEXT); INSERT INTO retained VALUES ('unchanged')",
58            )?;
59            Ok(())
60        })
61        .unwrap();
62        drop(wal);
63        let before = std::fs::read(&path).unwrap();
64        let error = ManagedConnection::open_auxiliary(&path, None)
65            .err()
66            .expect("reject WAL");
67        assert!(matches!(error, SQLiteError::AuxiliaryJournalMode(mode) if mode == "wal"));
68        assert_eq!(std::fs::read(&path).unwrap(), before);
69    }
70
71    #[test]
72    fn encrypted_lease_reuses_its_connection_and_rolls_back_independently() {
73        let directory = tempfile::tempdir().unwrap();
74        let managed = ManagedConnection::open_encrypted(
75            &directory.path().join("leases.db"),
76            "lease-encryption-test-key",
77        )
78        .unwrap();
79        {
80            let leased = managed.lease_connection().unwrap();
81            leased
82                .execute_batch(
83                    "CREATE TABLE retained(value TEXT); \
84                 CREATE TEMP TABLE connection_identity(value INTEGER); \
85                 INSERT INTO connection_identity VALUES (73); \
86                 BEGIN IMMEDIATE; INSERT INTO retained VALUES ('must roll back')",
87                )
88                .unwrap();
89        }
90        let leased = managed.lease_connection().unwrap();
91        assert!(leased.is_autocommit());
92        let identity: i64 = leased
93            .query_row("SELECT value FROM connection_identity", [], |row| {
94                row.get(0)
95            })
96            .unwrap();
97        assert_eq!(
98            identity, 73,
99            "the physical connection and key derivation are reused"
100        );
101        let retained: i64 = leased
102            .query_row("SELECT count(*) FROM retained", [], |row| row.get(0))
103            .unwrap();
104        assert_eq!(retained, 0);
105
106        // The held lease forces an independently keyed connection for the
107        // logical session. Dropping the lease must not end that session.
108        managed.begin_deferred_transaction().unwrap();
109        leased
110            .execute_batch("BEGIN IMMEDIATE; INSERT INTO retained VALUES ('separate')")
111            .unwrap();
112        drop(leased);
113        managed
114            .with(|connection| {
115                assert!(!connection.is_autocommit());
116                let retained: i64 =
117                    connection.query_row("SELECT count(*) FROM retained", [], |row| row.get(0))?;
118                assert_eq!(retained, 0);
119                Ok(())
120            })
121            .unwrap();
122        managed.rollback_transaction().unwrap();
123    }
124}