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}