Skip to main content

sbol_db_storage/
stats.rs

1//! Optional database-introspection capability.
2//!
3//! [`DbStats`] is the maintenance/observability surface the CLI's `inspect`
4//! command and the lab's Postgres page read. It is a capability a backend may
5//! or may not provide: a backend that offers it returns `Some(Arc<dyn
6//! DbStats>)` from the factory, and one that does not (or cannot expose these
7//! engine internals) returns `None`. The shapes here are deliberately
8//! engine-flavored — they mirror what a relational engine reports about
9//! tables, indexes, sessions, and locks.
10
11use async_trait::async_trait;
12use chrono::{DateTime, Utc};
13use sbol_db_core::DomainError;
14use serde::Serialize;
15
16#[derive(Clone, Debug, Serialize)]
17pub struct DatabaseSize {
18    pub database: String,
19    pub total_bytes: i64,
20}
21
22#[derive(Clone, Debug, Serialize)]
23pub struct TableStats {
24    pub name: String,
25    pub rows_estimate: i64,
26    pub total_bytes: i64,
27    pub index_bytes: i64,
28    pub n_live_tup: i64,
29    pub n_dead_tup: i64,
30    pub last_vacuum: Option<DateTime<Utc>>,
31    pub last_autovacuum: Option<DateTime<Utc>>,
32    pub last_analyze: Option<DateTime<Utc>>,
33}
34
35#[derive(Clone, Debug, Serialize)]
36pub struct IndexStats {
37    pub table: String,
38    pub index: String,
39    pub idx_scan: i64,
40    pub bytes: i64,
41}
42
43#[derive(Clone, Debug, Serialize)]
44pub struct Activity {
45    pub pid: i32,
46    pub application_name: Option<String>,
47    pub state: Option<String>,
48    pub wait_event_type: Option<String>,
49    pub wait_event: Option<String>,
50    pub query: Option<String>,
51    pub query_start: Option<DateTime<Utc>>,
52    pub duration_secs: Option<f64>,
53    pub client_addr: Option<String>,
54}
55
56#[derive(Clone, Debug, Serialize)]
57pub struct BlockingLock {
58    pub blocker_pid: i32,
59    pub blocker_query: Option<String>,
60    pub blocked_pid: i32,
61    pub blocked_query: Option<String>,
62    pub mode: Option<String>,
63    pub locktype: Option<String>,
64}
65
66#[derive(Clone, Debug, Serialize)]
67pub struct SlowQuery {
68    pub queryid: String,
69    pub query: Option<String>,
70    pub calls: i64,
71    pub total_exec_ms: f64,
72    pub mean_exec_ms: f64,
73    pub rows: i64,
74}
75
76#[derive(Clone, Debug, Serialize)]
77pub struct TableSchema {
78    pub name: String,
79    pub comment: Option<String>,
80    pub columns: Vec<TableColumn>,
81    pub foreign_keys_out: Vec<OutgoingForeignKey>,
82    pub foreign_keys_in: Vec<IncomingForeignKey>,
83}
84
85#[derive(Clone, Debug, Serialize)]
86pub struct TableColumn {
87    pub name: String,
88    /// Column type in the engine's own vocabulary (`text`, `int4`, `INTEGER`,
89    /// …). Backend-neutral name; the value is whatever the engine reports.
90    pub column_type: String,
91    pub nullable: bool,
92    pub default_expr: Option<String>,
93    pub ordinal: i32,
94    pub comment: Option<String>,
95    pub is_primary_key: bool,
96}
97
98#[derive(Clone, Debug, Serialize)]
99pub struct OutgoingForeignKey {
100    pub name: String,
101    pub columns: Vec<String>,
102    pub target_table: String,
103    pub target_columns: Vec<String>,
104}
105
106#[derive(Clone, Debug, Serialize)]
107pub struct IncomingForeignKey {
108    pub name: String,
109    pub source_table: String,
110    pub source_columns: Vec<String>,
111    pub target_columns: Vec<String>,
112}
113
114/// A whole-schema listing of tables and their columns, for the lab's schema
115/// browser and SQL-editor click-to-insert sidebar.
116#[derive(Clone, Debug, Serialize)]
117pub struct RelationalSchema {
118    pub tables: Vec<RelationalTable>,
119}
120
121#[derive(Clone, Debug, Serialize)]
122pub struct RelationalTable {
123    pub name: String,
124    pub columns: Vec<RelationalColumn>,
125}
126
127#[derive(Clone, Debug, Serialize)]
128pub struct RelationalColumn {
129    pub name: String,
130    /// Column type in the engine's own vocabulary.
131    pub column_type: String,
132    pub nullable: bool,
133}
134
135/// Read-only introspection of a storage engine's tables, indexes, sessions,
136/// and locks. Provided only by backends with a relational engine.
137///
138/// Postgres fills every method. SQLite, a single-writer embedded engine,
139/// fills the table/index/schema surfaces and reports an empty session/lock
140/// list with [`has_slow_query_stats`](DbStats::has_slow_query_stats)
141/// returning `false`; the lab UI gates those panels on the backend's
142/// [`crate::Capabilities`] so an empty surface is never rendered as a
143/// feature.
144#[async_trait]
145pub trait DbStats: Send + Sync {
146    /// Every browsable table with its columns, for the schema sidebar.
147    async fn schema_overview(&self) -> Result<RelationalSchema, DomainError>;
148    async fn database_size(&self) -> Result<DatabaseSize, DomainError>;
149    async fn table_stats(&self, limit: i64, offset: i64) -> Result<Vec<TableStats>, DomainError>;
150    async fn index_stats(&self, limit: i64) -> Result<Vec<IndexStats>, DomainError>;
151    async fn activity(&self, limit: i64) -> Result<Vec<Activity>, DomainError>;
152    async fn blocking_locks(&self) -> Result<Vec<BlockingLock>, DomainError>;
153    async fn table_schema(&self, name: &str) -> Result<Option<TableSchema>, DomainError>;
154    /// Whether per-statement timing (and therefore [`Self::slow_queries`]) is
155    /// available on this engine right now.
156    async fn has_slow_query_stats(&self) -> Result<bool, DomainError>;
157    async fn slow_queries(&self, limit: i64) -> Result<Vec<SlowQuery>, DomainError>;
158
159    /// Run the engine's routine optimize/refresh-statistics maintenance
160    /// (SQLite `VACUUM`/`ANALYZE`, Postgres `ANALYZE`). The default is a
161    /// no-op so a backend without a meaningful action need not implement it;
162    /// the lab UI gates the button on the backend reporting one.
163    async fn optimize(&self) -> Result<(), DomainError> {
164        Ok(())
165    }
166}