Skip to main content

mongreldb_core/
handle.rs

1//! `DatabaseHandle`: a lightweight caller-specific object referencing one
2//! `DatabaseCore` (spec §10.1, S1A-001).
3//!
4//! Handles are issued by [`crate::manager::DatabaseManager::open_shared`].
5//! Every handle carries its own [`HandleIdentity`] and [`HandleAccess`], while
6//! recovery, WAL opening, open-generation advancement, and table mounting all
7//! happened exactly once on the shared core. Dropping one handle never closes
8//! storage; the core closes when the last reference drops or when
9//! [`DatabaseHandle::shutdown`] drains it (S1A-004).
10//!
11//! The full storage API is available through `Deref<Target = Database>`: a
12//! handle behaves exactly like a `Database` facade over the shared core, with
13//! per-handle identity layered on top. The core itself never stores one
14//! mutable "current principal" (spec §4.6) — identity lives here, on the
15//! handle.
16
17use std::sync::Arc;
18use std::time::Duration;
19
20use crate::core::{LifecycleState, OperationGuard};
21use crate::database::{Database, DatabaseCore};
22use crate::error::Result;
23
24/// The per-caller identity bound to one handle (spec §10.1, S1A-001).
25///
26/// This is a label the core never consults for shared-table authorization in
27/// Stage 1A (the embedded enforcement path reads the facade's auth state, and
28/// shared cores reject auth-mode transitions); per-request enforcement over
29/// handles arrives with Stage 1D sessions.
30#[derive(Debug, Clone, PartialEq, Eq)]
31pub enum HandleIdentity {
32    /// No credentials were supplied for this handle.
33    Credentialless,
34    /// A user resolved from the database catalog. `user_id` +
35    /// `created_version` pin the exact catalog generation so username reuse
36    /// cannot revive a stale identity.
37    CatalogUser {
38        username: String,
39        user_id: u64,
40        created_version: u64,
41    },
42    /// A non-catalog principal (service account, daemon worker, test actor).
43    ServicePrincipal { principal_id: [u8; 16] },
44}
45
46/// The identity requested when attaching a shared handle (spec §2.2).
47///
48/// Stage 1A supports credentialless and service-principal attaches. Catalog
49/// users authenticate through an attached handle (Stage 1D session work adds
50/// credentialed attaches).
51#[derive(Debug, Clone, PartialEq, Eq)]
52pub enum OpenIdentity {
53    /// Attach without credentials. Rejected (fail closed) when the database
54    /// catalog has `require_auth` enabled.
55    Credentialless,
56    /// Attach as a non-catalog service principal.
57    ServicePrincipal { principal_id: [u8; 16] },
58}
59
60impl OpenIdentity {
61    pub(crate) fn handle_identity(&self) -> HandleIdentity {
62        match self {
63            OpenIdentity::Credentialless => HandleIdentity::Credentialless,
64            OpenIdentity::ServicePrincipal { principal_id } => HandleIdentity::ServicePrincipal {
65                principal_id: *principal_id,
66            },
67        }
68    }
69}
70
71/// Per-handle access restriction (spec §2.2: "each handle may have its own
72/// principal and read-only restriction").
73///
74/// Stage 1A issues read-write handles; the read-only restriction is enforced
75/// once per-handle admission lands with Stage 1D sessions.
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
77pub struct HandleAccess {
78    read_only: bool,
79}
80
81impl HandleAccess {
82    /// A read-write handle (the Stage 1A default).
83    pub fn read_write() -> Self {
84        Self { read_only: false }
85    }
86
87    /// A read-only handle.
88    pub fn read_only() -> Self {
89        Self { read_only: true }
90    }
91
92    /// Whether this handle is restricted to reads.
93    pub fn is_read_only(&self) -> bool {
94        self.read_only
95    }
96}
97
98/// A lightweight caller-specific handle onto one shared [`DatabaseCore`]
99/// (spec §10.1, S1A-001).
100///
101/// Obtained from [`crate::manager::DatabaseManager::open_shared`]. Cloning or
102/// dropping a handle has no storage side effects; storage closes when the
103/// last core reference drops or [`Self::shutdown`] runs.
104pub struct DatabaseHandle {
105    /// The facade carrying this handle's auth context (principal-less for
106    /// Stage 1A attaches) over the shared core. `Deref` exposes its full API.
107    database: Database,
108    identity: HandleIdentity,
109    access: HandleAccess,
110}
111
112impl DatabaseHandle {
113    pub(crate) fn new(database: Database, identity: HandleIdentity, access: HandleAccess) -> Self {
114        Self {
115            database,
116            identity,
117            access,
118        }
119    }
120
121    /// The identity bound to this handle.
122    pub fn identity(&self) -> &HandleIdentity {
123        &self.identity
124    }
125
126    /// The access restriction bound to this handle.
127    pub fn access(&self) -> HandleAccess {
128        self.access
129    }
130
131    /// The shared storage core behind this handle. Two handles over the same
132    /// root return `Arc`s to the *same* core.
133    pub fn core(&self) -> Arc<DatabaseCore> {
134        self.database.core()
135    }
136
137    /// The current lifecycle state of the shared core (S1A-004).
138    pub fn lifecycle_state(&self) -> LifecycleState {
139        self.database.lifecycle_state()
140    }
141
142    /// Admit one operation against the shared core (S1A-004). The RAII guard
143    /// releases the operation slot on drop; new operations are rejected once
144    /// the core leaves [`LifecycleState::Open`].
145    pub fn operation_guard(&self) -> Result<OperationGuard> {
146        self.database.operation_guard()
147    }
148
149    /// Shut the shared core down (spec §10.1, S1A-004): drain in-flight
150    /// operations within `drain_deadline`, sync durable state, release the
151    /// file lock, and mark the core `Closed`. Every handle — including this
152    /// one — then rejects further operations. Dropping a handle never closes
153    /// storage; only this method or the last core drop does.
154    pub fn shutdown(&self, drain_deadline: Duration) -> Result<()> {
155        self.database.core().shutdown(drain_deadline)
156    }
157}
158
159impl std::ops::Deref for DatabaseHandle {
160    type Target = Database;
161
162    fn deref(&self) -> &Database {
163        &self.database
164    }
165}
166
167impl std::fmt::Debug for DatabaseHandle {
168    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
169        f.debug_struct("DatabaseHandle")
170            .field("identity", &self.identity)
171            .field("access", &self.access)
172            .field("database", &self.database)
173            .finish()
174    }
175}