Skip to main content

linkrs_storage/client/
traits_admin.rs

1//! Administration, maintenance and statistics operations.
2
3use crate::engine::background_freeze::FreezeStats;
4use linkrs_core::types::{CompactConfig, PasswordInfo, UserAlterInfo, UserInfo};
5use linkrs_core::{Edge, RoleType, StorageError, StorageResult};
6use std::sync::Arc;
7
8use super::StorageOperationContext;
9
10/// Authentication and authorization operations.
11pub trait StorageAuthOps: Send + Sync + std::fmt::Debug {
12    fn change_password(&mut self, info: &PasswordInfo) -> Result<bool, StorageError>;
13    fn create_user(&mut self, info: &UserInfo) -> Result<bool, StorageError>;
14    fn alter_user(&mut self, info: &UserAlterInfo) -> Result<bool, StorageError>;
15    fn drop_user(&mut self, username: &str) -> Result<bool, StorageError>;
16    fn user_exists(&self, username: &str) -> bool;
17    fn list_users(&self) -> Vec<String>;
18    fn get_user(&self, username: &str) -> Option<UserInfo>;
19    fn update_last_login(&self, username: &str) -> Result<bool, StorageError>;
20    fn list_user_roles(&self, username: &str) -> Vec<(i64, RoleType)>;
21    fn list_all_user_roles(&self) -> Vec<(String, Vec<(i64, RoleType)>)>;
22    fn grant_role(
23        &mut self,
24        username: &str,
25        space_id: i64,
26        role: RoleType,
27    ) -> Result<bool, StorageError>;
28    fn revoke_role(&mut self, username: &str, space_id: i64) -> Result<bool, StorageError>;
29}
30
31/// Administrative operations: stats, maintenance, optional components.
32pub trait StorageAdmin: Send + Sync + std::fmt::Debug {
33    fn load_from_disk(&mut self) -> Result<(), StorageError>;
34    fn save_to_disk(&self) -> Result<(), StorageError>;
35    fn get_storage_stats(&self) -> StorageStats;
36
37    fn find_dangling_edges(&self, space: &str) -> Result<Vec<Edge>, StorageError>;
38    fn repair_dangling_edges(&mut self, space: &str) -> Result<usize, StorageError>;
39
40    /// Physically delete TTL-expired rows.
41    ///
42    /// `target = None` scans every TTL-configured tag and edge type in the
43    /// space and returns one [`TtlPurgeEntry`] per such schema (even when it
44    /// deletes nothing). `target = Some(..)` scans only that schema and
45    /// returns its single entry, whose `removed` is 0 when the schema exists
46    /// but has no TTL or nothing expired; a schema that does not exist is an
47    /// explicit error. Deletions run through the normal write path, so they
48    /// are WAL-logged and crash-recoverable.
49    ///
50    /// Callers are the `PURGE EXPIRED` statement and the optional background
51    /// sweep ([`StorageTtlPurgeOps`]), which invokes this per space without a
52    /// target schema. Both share this one entry point.
53    fn purge_expired(
54        &self,
55        space: &str,
56        target: Option<PurgeTarget<'_>>,
57    ) -> Result<Vec<TtlPurgeEntry>, StorageError>;
58
59    fn get_db_path(&self) -> &str;
60}
61
62/// Target schema of a [`StorageAdmin::purge_expired`] call: a single tag or
63/// edge type by name. `None` passed instead means "every TTL-configured
64/// schema in the space".
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
66pub enum PurgeTarget<'a> {
67    Tag(&'a str),
68    Edge(&'a str),
69}
70
71/// Physical-deletion result for one TTL schema scanned by a purge.
72///
73/// `is_edge` distinguishes an edge type (`true`) from a tag (`false`);
74/// `removed` reports how many expired rows were deleted.
75#[derive(Debug, Clone)]
76pub struct TtlPurgeEntry {
77    pub schema_name: String,
78    pub is_edge: bool,
79    pub removed: usize,
80}
81
82/// Persistence operations for flushing, checkpointing, and compaction.
83pub trait StoragePersistenceOps: Send + Sync + std::fmt::Debug {
84    fn flush(&self) -> StorageResult<()>;
85
86    fn create_checkpoint(&self) -> StorageResult<Option<crate::CheckpointStats>>;
87
88    fn verify_snapshot(&self, snapshot_id: u64) -> StorageResult<bool>;
89
90    fn cleanup_snapshots(&self) -> StorageResult<usize>;
91
92    fn snapshot_stats(&self) -> crate::SnapshotStats;
93
94    fn persistence_diagnostics(&self) -> Option<crate::PersistenceDiagnostics>;
95
96    fn compact(&self, config: &CompactConfig) -> StorageResult<()>;
97
98    fn save_data(&self) -> StorageResult<()> {
99        self.flush()
100    }
101
102    fn save_data_to_dir(&self, dir: &std::path::Path) -> StorageResult<()>;
103
104    fn auto_flush_if_needed(&self) -> StorageResult<bool> {
105        if self.should_flush() {
106            self.flush()?;
107            Ok(true)
108        } else {
109            Ok(false)
110        }
111    }
112
113    fn auto_checkpoint_if_needed(&self) -> StorageResult<Option<crate::CheckpointStats>> {
114        if self.should_checkpoint() {
115            self.create_checkpoint()
116        } else {
117            Ok(None)
118        }
119    }
120
121    fn should_flush(&self) -> bool;
122
123    fn should_checkpoint(&self) -> bool;
124
125    fn set_outbox_materialized_lsn_provider(
126        &self,
127        _provider: Arc<
128            dyn Fn() -> StorageResult<Option<linkrs_core::types::CommitLsn>> + Send + Sync,
129        >,
130    ) {
131    }
132}
133
134/// Creates an immutable storage handle bound to a single operation context.
135pub trait StorageOperationContextOps: Send + Sync + std::fmt::Debug {
136    fn bind_auto_commit_context(&self) -> StorageResult<Self>
137    where
138        Self: Sized;
139
140    /// Bind a read-only statement context with a fixed snapshot timestamp.
141    ///
142    /// Read statements get a consistent statement-level snapshot: every
143    /// storage access observes the same `read_timestamp`, and per-table MVCC
144    /// snapshots are lazily registered on first table access so GC keeps the
145    /// versions the statement may still read. The snapshot is unregistered by
146    /// [`finalize_operation`](Self::finalize_operation) (or on Drop as a
147    /// backstop). The bound `(space, snapshot_ts)` pair is also the
148    /// serialization boundary for distributed reads.
149    ///
150    /// The default implementation returns `not_supported`; engines without a
151    /// native read context fall back to the unbound handle.
152    fn bind_read_operation_context(&self) -> StorageResult<Self>
153    where
154        Self: Sized,
155    {
156        Err(StorageError::not_supported(
157            "Read operation context binding is not supported by this storage implementation",
158        ))
159    }
160
161    fn bind_operation_context(&self, context: StorageOperationContext) -> Self
162    where
163        Self: Sized;
164
165    fn operation_context(&self) -> Option<Arc<StorageOperationContext>>;
166
167    /// Finalize an operation-owned auto-commit timestamp.
168    ///
169    /// Explicit transaction contexts are finalized by `TransactionManager`
170    /// and therefore treat this as a no-op.
171    fn finalize_operation(&self, _committed: bool) -> StorageResult<()> {
172        Ok(())
173    }
174}
175
176/// Access to sync runtime context shared with higher-level components.
177pub trait StorageSyncContextOps: Send + Sync + std::fmt::Debug {
178    fn get_sync_manager(&self) -> Option<Arc<linkrs_sync::SyncManager>>;
179}
180
181/// Index GC operations.
182pub trait StorageGcOps: Send + Sync + std::fmt::Debug {
183    fn is_index_gc_running(&self) -> bool;
184
185    fn start_index_gc(&self) -> Option<crate::thread_pool::BackgroundTaskHandle>;
186
187    fn stop_index_gc(&self);
188}
189
190/// Background TTL purge operations.
191///
192/// The sweep is opt-in and off by default: without a `start_ttl_purge` call,
193/// expired rows are only reclaimed by an explicit `PURGE EXPIRED` statement.
194/// The task handle is retained by the storage so `stop_ttl_purge` can always
195/// join the loop, which is why `start_ttl_purge` does not return it.
196pub trait StorageTtlPurgeOps: Send + Sync + std::fmt::Debug {
197    /// Start the periodic purge sweep at `interval`; a zero interval leaves
198    /// the sweep stopped. Repeated calls keep the running sweep.
199    fn start_ttl_purge(&self, interval: std::time::Duration);
200
201    fn stop_ttl_purge(&self);
202
203    fn is_ttl_purge_running(&self) -> bool;
204}
205
206/// Background freeze operations.
207pub trait StorageSnapshotOps: Send + Sync + std::fmt::Debug {
208    fn get_freeze_stats(&self) -> Option<FreezeStats>;
209    fn trigger_background_freeze(&self) -> StorageResult<()>;
210}
211
212/// Storing statistical information
213///
214/// Size semantics: `total_size_bytes` is allocated bytes (vertex tables plus
215/// edge tables, including holes and encoding overhead); `data_size_bytes` is
216/// the live-data estimate; `index_size_bytes` is the derived residual
217/// `total - data` (fragmentation plus overhead), not an independent index
218/// measurement. Row counts mix censuses: vertices count allocated slots while
219/// edges count live rows; use the optimizer snapshots for consistent live
220/// counts.
221#[derive(Debug, Clone)]
222pub struct StorageStats {
223    pub total_vertices: usize,
224    pub total_edges: usize,
225    pub total_spaces: usize,
226    pub total_tags: usize,
227    pub total_edge_types: usize,
228    /// Total allocated storage size in bytes (vertex tables + edge tables + indexes)
229    pub total_size_bytes: u64,
230    /// Data size in bytes (vertex + edge data, excluding index structures)
231    pub data_size_bytes: u64,
232    /// Property index structure size in bytes
233    pub index_size_bytes: u64,
234}