Skip to main content

task_graph_mcp/db/
schema.rs

1//! Schema introspection queries for the task-graph database.
2
3use super::Database;
4use anyhow::Result;
5use serde::{Deserialize, Serialize};
6
7/// Information about a table column.
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct ColumnInfo {
10    pub name: String,
11    pub data_type: String,
12    pub nullable: bool,
13    pub default_value: Option<String>,
14    pub primary_key: bool,
15}
16
17/// Information about an index.
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct IndexInfo {
20    pub name: String,
21    pub table_name: String,
22    pub unique: bool,
23    pub columns: Vec<String>,
24}
25
26/// Information about a foreign key relationship.
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct ForeignKeyInfo {
29    pub from_column: String,
30    pub to_table: String,
31    pub to_column: String,
32    pub on_update: String,
33    pub on_delete: String,
34}
35
36/// Information about a table.
37#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct TableInfo {
39    pub name: String,
40    pub table_type: String, // "table" or "view"
41    pub columns: Vec<ColumnInfo>,
42    pub indexes: Vec<IndexInfo>,
43    pub foreign_keys: Vec<ForeignKeyInfo>,
44    pub sql: Option<String>,
45}
46
47/// Complete database schema.
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct DatabaseSchema {
50    pub tables: Vec<TableInfo>,
51    pub sqlite_version: String,
52}
53
54impl Database {
55    /// Get complete schema information for the database.
56    pub fn get_schema(&self, include_sql: bool) -> Result<DatabaseSchema> {
57        self.with_conn(|conn| {
58            // Get SQLite version
59            let sqlite_version: String = conn.query_row(
60                "SELECT sqlite_version()",
61                [],
62                |row| row.get(0),
63            )?;
64
65            // Get all tables and views (excluding internal sqlite_ tables and refinery schema)
66            let mut stmt = conn.prepare(
67                "SELECT name, type, sql FROM sqlite_master 
68                 WHERE type IN ('table', 'view') 
69                 AND name NOT LIKE 'sqlite_%'
70                 AND name NOT LIKE 'refinery_%'
71                 ORDER BY type DESC, name"
72            )?;
73
74            let table_names: Vec<(String, String, Option<String>)> = stmt
75                .query_map([], |row| {
76                    Ok((
77                        row.get::<_, String>(0)?,
78                        row.get::<_, String>(1)?,
79                        row.get::<_, Option<String>>(2)?,
80                    ))
81                })?
82                .collect::<Result<Vec<_>, _>>()?;
83
84            let mut tables = Vec::new();
85
86            for (table_name, table_type, sql) in table_names {
87                // Get column info using PRAGMA table_info
88                let columns = self.get_table_columns(conn, &table_name)?;
89
90                // Get indexes for this table
91                let indexes = self.get_table_indexes(conn, &table_name)?;
92
93                // Get foreign keys for this table
94                let foreign_keys = self.get_table_foreign_keys(conn, &table_name)?;
95
96                tables.push(TableInfo {
97                    name: table_name,
98                    table_type,
99                    columns,
100                    indexes,
101                    foreign_keys,
102                    sql: if include_sql { sql } else { None },
103                });
104            }
105
106            Ok(DatabaseSchema {
107                tables,
108                sqlite_version,
109            })
110        })
111    }
112
113    /// Get column information for a table.
114    fn get_table_columns(&self, conn: &rusqlite::Connection, table_name: &str) -> Result<Vec<ColumnInfo>> {
115        let mut stmt = conn.prepare(&format!("PRAGMA table_info('{}')", table_name))?;
116
117        let columns: Vec<ColumnInfo> = stmt
118            .query_map([], |row| {
119                Ok(ColumnInfo {
120                    name: row.get(1)?,
121                    data_type: row.get::<_, String>(2)?.to_uppercase(),
122                    nullable: row.get::<_, i32>(3)? == 0,
123                    default_value: row.get(4)?,
124                    primary_key: row.get::<_, i32>(5)? > 0,
125                })
126            })?
127            .collect::<Result<Vec<_>, _>>()?;
128
129        Ok(columns)
130    }
131
132    /// Get index information for a table.
133    fn get_table_indexes(&self, conn: &rusqlite::Connection, table_name: &str) -> Result<Vec<IndexInfo>> {
134        // Get list of indexes
135        let mut stmt = conn.prepare(&format!("PRAGMA index_list('{}')", table_name))?;
136
137        let index_list: Vec<(String, bool)> = stmt
138            .query_map([], |row| {
139                Ok((
140                    row.get::<_, String>(1)?,
141                    row.get::<_, i32>(2)? == 1, // unique
142                ))
143            })?
144            .collect::<Result<Vec<_>, _>>()?;
145
146        let mut indexes = Vec::new();
147
148        for (index_name, unique) in index_list {
149            // Skip auto-generated indexes for primary keys
150            if index_name.starts_with("sqlite_autoindex_") {
151                continue;
152            }
153
154            // Get columns in this index
155            let mut stmt = conn.prepare(&format!("PRAGMA index_info('{}')", index_name))?;
156
157            let columns: Vec<String> = stmt
158                .query_map([], |row| row.get(2))?
159                .collect::<Result<Vec<_>, _>>()?;
160
161            indexes.push(IndexInfo {
162                name: index_name,
163                table_name: table_name.to_string(),
164                unique,
165                columns,
166            });
167        }
168
169        Ok(indexes)
170    }
171
172    /// Get foreign key information for a table.
173    fn get_table_foreign_keys(&self, conn: &rusqlite::Connection, table_name: &str) -> Result<Vec<ForeignKeyInfo>> {
174        let mut stmt = conn.prepare(&format!("PRAGMA foreign_key_list('{}')", table_name))?;
175
176        let foreign_keys: Vec<ForeignKeyInfo> = stmt
177            .query_map([], |row| {
178                Ok(ForeignKeyInfo {
179                    from_column: row.get(3)?,
180                    to_table: row.get(2)?,
181                    to_column: row.get(4)?,
182                    on_update: row.get(5)?,
183                    on_delete: row.get(6)?,
184                })
185            })?
186            .collect::<Result<Vec<_>, _>>()?;
187
188        Ok(foreign_keys)
189    }
190
191    /// Get a list of table names only (lightweight).
192    pub fn get_table_names(&self) -> Result<Vec<String>> {
193        self.with_conn(|conn| {
194            let mut stmt = conn.prepare(
195                "SELECT name FROM sqlite_master 
196                 WHERE type = 'table' 
197                 AND name NOT LIKE 'sqlite_%'
198                 AND name NOT LIKE 'refinery_%'
199                 ORDER BY name"
200            )?;
201
202            let names: Vec<String> = stmt
203                .query_map([], |row| row.get(0))?
204                .collect::<Result<Vec<_>, _>>()?;
205
206            Ok(names)
207        })
208    }
209}