Skip to main content

mixtape_tools/sqlite/database/
info.rs

1//! Database info tool
2
3use crate::prelude::*;
4use crate::sqlite::manager::with_connection;
5use crate::sqlite::types::DatabaseInfo;
6use std::path::Path;
7
8/// Input for getting database info
9#[derive(Debug, Deserialize, JsonSchema)]
10pub struct DatabaseInfoInput {
11    /// Database file path. If not specified, uses the default database.
12    #[serde(default)]
13    pub db_path: Option<String>,
14}
15
16/// Tool for retrieving database metadata and statistics
17///
18/// Returns comprehensive information about a database including:
19/// - File size
20/// - Table, index, view, and trigger counts
21/// - SQLite version and configuration
22pub struct DatabaseInfoTool;
23
24impl Tool for DatabaseInfoTool {
25    type Input = DatabaseInfoInput;
26
27    fn name(&self) -> &str {
28        "sqlite_database_info"
29    }
30
31    fn description(&self) -> &str {
32        "Get comprehensive metadata and statistics about a SQLite database including file size, table counts, indexes, and configuration."
33    }
34
35    async fn execute(&self, input: Self::Input) -> Result<ToolResult, ToolError> {
36        let result = with_connection(input.db_path, |conn| {
37            // Get database file path
38            let path: String = conn
39                .query_row("PRAGMA database_list", [], |row| row.get(2))
40                .unwrap_or_else(|_| "unknown".to_string());
41
42            // Get file size
43            let size_bytes = Path::new(&path)
44                .metadata()
45                .map(|m| m.len())
46                .unwrap_or(0);
47
48            // Count tables
49            let table_count: usize = conn
50                .query_row(
51                    "SELECT COUNT(*) FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%'",
52                    [],
53                    |row| row.get::<_, i64>(0).map(|v| v as usize),
54                )
55                .unwrap_or(0);
56
57            // Count indexes
58            let index_count: usize = conn
59                .query_row(
60                    "SELECT COUNT(*) FROM sqlite_master WHERE type = 'index'",
61                    [],
62                    |row| row.get::<_, i64>(0).map(|v| v as usize),
63                )
64                .unwrap_or(0);
65
66            // Count views
67            let view_count: usize = conn
68                .query_row(
69                    "SELECT COUNT(*) FROM sqlite_master WHERE type = 'view'",
70                    [],
71                    |row| row.get::<_, i64>(0).map(|v| v as usize),
72                )
73                .unwrap_or(0);
74
75            // Count triggers
76            let trigger_count: usize = conn
77                .query_row(
78                    "SELECT COUNT(*) FROM sqlite_master WHERE type = 'trigger'",
79                    [],
80                    |row| row.get::<_, i64>(0).map(|v| v as usize),
81                )
82                .unwrap_or(0);
83
84            // Get SQLite version
85            let sqlite_version: String = conn
86                .query_row("SELECT sqlite_version()", [], |row| row.get(0))
87                .unwrap_or_else(|_| "unknown".to_string());
88
89            // Get page size
90            let page_size: i64 = conn
91                .query_row("PRAGMA page_size", [], |row| row.get(0))
92                .unwrap_or(0);
93
94            // Get page count
95            let page_count: i64 = conn
96                .query_row("PRAGMA page_count", [], |row| row.get(0))
97                .unwrap_or(0);
98
99            // Check if WAL mode
100            let journal_mode: String = conn
101                .query_row("PRAGMA journal_mode", [], |row| row.get(0))
102                .unwrap_or_else(|_| "delete".to_string());
103            let wal_mode = journal_mode.to_lowercase() == "wal";
104
105            Ok(DatabaseInfo {
106                path,
107                size_bytes,
108                table_count,
109                index_count,
110                view_count,
111                trigger_count,
112                sqlite_version,
113                page_size,
114                page_count,
115                wal_mode,
116            })
117        })
118        .await?;
119
120        Ok(ToolResult::Json(serde_json::to_value(result)?))
121    }
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127    use crate::sqlite::test_utils::{unwrap_json, TestDatabase};
128
129    #[tokio::test]
130    async fn test_database_info() {
131        let db =
132            TestDatabase::with_schema("CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT);")
133                .await;
134
135        // Get database info with explicit reference
136        let info_tool = DatabaseInfoTool;
137        let info_input = DatabaseInfoInput {
138            db_path: Some(db.key()),
139        };
140
141        let result = info_tool.execute(info_input).await.unwrap();
142        let json = unwrap_json(result);
143
144        assert_eq!(json["table_count"].as_i64().unwrap(), 1);
145        assert!(json["sqlite_version"].as_str().is_some());
146    }
147
148    #[test]
149    fn test_tool_metadata() {
150        let tool = DatabaseInfoTool;
151        assert_eq!(tool.name(), "sqlite_database_info");
152        assert!(!tool.description().is_empty());
153    }
154}