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 =
60                conn.query_row("SELECT sqlite_version()", [], |row| row.get(0))?;
61
62            // Get all tables and views (excluding internal sqlite_ tables and refinery schema)
63            let mut stmt = conn.prepare(
64                "SELECT name, type, sql FROM sqlite_master 
65                 WHERE type IN ('table', 'view') 
66                 AND name NOT LIKE 'sqlite_%'
67                 AND name NOT LIKE 'refinery_%'
68                 ORDER BY type DESC, name",
69            )?;
70
71            let table_names: Vec<(String, String, Option<String>)> = stmt
72                .query_map([], |row| {
73                    Ok((
74                        row.get::<_, String>(0)?,
75                        row.get::<_, String>(1)?,
76                        row.get::<_, Option<String>>(2)?,
77                    ))
78                })?
79                .collect::<Result<Vec<_>, _>>()?;
80
81            let mut tables = Vec::new();
82
83            for (table_name, table_type, sql) in table_names {
84                // Get column info using PRAGMA table_info
85                let columns = self.get_table_columns(conn, &table_name)?;
86
87                // Get indexes for this table
88                let indexes = self.get_table_indexes(conn, &table_name)?;
89
90                // Get foreign keys for this table
91                let foreign_keys = self.get_table_foreign_keys(conn, &table_name)?;
92
93                tables.push(TableInfo {
94                    name: table_name,
95                    table_type,
96                    columns,
97                    indexes,
98                    foreign_keys,
99                    sql: if include_sql { sql } else { None },
100                });
101            }
102
103            Ok(DatabaseSchema {
104                tables,
105                sqlite_version,
106            })
107        })
108    }
109
110    /// Get column information for a table.
111    fn get_table_columns(
112        &self,
113        conn: &rusqlite::Connection,
114        table_name: &str,
115    ) -> Result<Vec<ColumnInfo>> {
116        let mut stmt = conn.prepare(&format!("PRAGMA table_info('{}')", table_name))?;
117
118        let columns: Vec<ColumnInfo> = stmt
119            .query_map([], |row| {
120                Ok(ColumnInfo {
121                    name: row.get(1)?,
122                    data_type: row.get::<_, String>(2)?.to_uppercase(),
123                    nullable: row.get::<_, i32>(3)? == 0,
124                    default_value: row.get(4)?,
125                    primary_key: row.get::<_, i32>(5)? > 0,
126                })
127            })?
128            .collect::<Result<Vec<_>, _>>()?;
129
130        Ok(columns)
131    }
132
133    /// Get index information for a table.
134    fn get_table_indexes(
135        &self,
136        conn: &rusqlite::Connection,
137        table_name: &str,
138    ) -> Result<Vec<IndexInfo>> {
139        // Get list of indexes
140        let mut stmt = conn.prepare(&format!("PRAGMA index_list('{}')", table_name))?;
141
142        let index_list: Vec<(String, bool)> = stmt
143            .query_map([], |row| {
144                Ok((
145                    row.get::<_, String>(1)?,
146                    row.get::<_, i32>(2)? == 1, // unique
147                ))
148            })?
149            .collect::<Result<Vec<_>, _>>()?;
150
151        let mut indexes = Vec::new();
152
153        for (index_name, unique) in index_list {
154            // Skip auto-generated indexes for primary keys
155            if index_name.starts_with("sqlite_autoindex_") {
156                continue;
157            }
158
159            // Get columns in this index
160            let mut stmt = conn.prepare(&format!("PRAGMA index_info('{}')", index_name))?;
161
162            let columns: Vec<String> = stmt
163                .query_map([], |row| row.get(2))?
164                .collect::<Result<Vec<_>, _>>()?;
165
166            indexes.push(IndexInfo {
167                name: index_name,
168                table_name: table_name.to_string(),
169                unique,
170                columns,
171            });
172        }
173
174        Ok(indexes)
175    }
176
177    /// Get foreign key information for a table.
178    fn get_table_foreign_keys(
179        &self,
180        conn: &rusqlite::Connection,
181        table_name: &str,
182    ) -> Result<Vec<ForeignKeyInfo>> {
183        let mut stmt = conn.prepare(&format!("PRAGMA foreign_key_list('{}')", table_name))?;
184
185        let foreign_keys: Vec<ForeignKeyInfo> = stmt
186            .query_map([], |row| {
187                Ok(ForeignKeyInfo {
188                    from_column: row.get(3)?,
189                    to_table: row.get(2)?,
190                    to_column: row.get(4)?,
191                    on_update: row.get(5)?,
192                    on_delete: row.get(6)?,
193                })
194            })?
195            .collect::<Result<Vec<_>, _>>()?;
196
197        Ok(foreign_keys)
198    }
199
200    /// Get a list of table names only (lightweight).
201    pub fn get_table_names(&self) -> Result<Vec<String>> {
202        self.with_conn(|conn| {
203            let mut stmt = conn.prepare(
204                "SELECT name FROM sqlite_master 
205                 WHERE type = 'table' 
206                 AND name NOT LIKE 'sqlite_%'
207                 AND name NOT LIKE 'refinery_%'
208                 ORDER BY name",
209            )?;
210
211            let names: Vec<String> = stmt
212                .query_map([], |row| row.get(0))?
213                .collect::<Result<Vec<_>, _>>()?;
214
215            Ok(names)
216        })
217    }
218}