1use crate::{
3 client::DbClient,
4 config::DbConfig,
5 error::DbResult,
6 introspection::{self, Introspector},
7 metadata::{DatabaseMetadata, RoutineKind},
9};
10use comfy_table::{presets::UTF8_FULL, Cell, CellAlignment, Table}; use owo_colors::OwoColorize; use std::sync::Arc;
13use tracing::info;
14
15#[derive(Clone)]
18pub struct ModelManager {
19 pub db_client: Arc<DbClient>,
20 pub metadata: Arc<DatabaseMetadata>,
21 introspector: Arc<dyn Introspector>,
22}
23
24impl ModelManager {
25 pub async fn new(config: DbConfig) -> DbResult<Self> {
27 info!("Initializing ModelManager...");
28 let db_client = Arc::new(DbClient::new(config).await?);
29 let introspector = introspection::new_introspector(db_client.clone())?;
30
31 info!("Discovering user schemas...");
32 let schemas = introspector.list_user_schemas().await?;
33
34 info!("Performing full database introspection...");
35 let metadata = introspector.introspect(&schemas).await?;
36 info!(
37 "Introspection complete. Found {} schemas.",
38 metadata.schemas.len()
39 );
40
41 Ok(Self {
42 db_client,
43 metadata: Arc::new(metadata),
44 introspector: Arc::from(introspector),
45 })
46 }
47
48 pub fn display_summary(&self) {
54 println!(); let mut table = Table::new();
57
58 table
62 .load_preset(comfy_table::presets::UTF8_BORDERS_ONLY)
63 .set_header(vec![
64 Cell::new("Schema").add_attribute(comfy_table::Attribute::Bold),
65 Cell::new("Tables").add_attribute(comfy_table::Attribute::Bold),
66 Cell::new("Views").add_attribute(comfy_table::Attribute::Bold),
67 Cell::new("Enums").add_attribute(comfy_table::Attribute::Bold),
68 Cell::new("Functions").add_attribute(comfy_table::Attribute::Bold),
69 Cell::new("Procedures").add_attribute(comfy_table::Attribute::Bold),
70 Cell::new("Triggers").add_attribute(comfy_table::Attribute::Bold),
71 Cell::new("Total").add_attribute(comfy_table::Attribute::Bold),
72 ]);
73
74 let mut total_tables = 0;
76 let mut total_views = 0;
77 let mut total_enums = 0;
78 let mut total_functions = 0;
79 let mut total_procedures = 0;
80 let mut total_triggers = 0;
81
82 let mut schemas: Vec<_> = self.metadata.schemas.keys().collect();
84 schemas.sort();
85
86 for schema_name in schemas {
87 if let Some(schema_data) = self.metadata.schemas.get(schema_name) {
88 let tables_count = schema_data.tables.len();
90 let views_count = schema_data.views.len();
91 let enums_count = schema_data.enums.len();
92
93 let mut functions_count = 0;
94 let mut procedures_count = 0;
95 let mut triggers_count = 0;
96 for func_meta in schema_data.functions.values() {
97 match func_meta.kind {
98 Some(RoutineKind::Function) => functions_count += 1,
99 Some(RoutineKind::Procedure) => procedures_count += 1,
100 Some(RoutineKind::Trigger) => triggers_count += 1,
101 _ => {}
102 }
103 }
104
105 let schema_total = tables_count + views_count + enums_count + functions_count + procedures_count + triggers_count;
106
107 total_tables += tables_count;
109 total_views += views_count;
110 total_enums += enums_count;
111 total_functions += functions_count;
112 total_procedures += procedures_count;
113 total_triggers += triggers_count;
114
115 table.add_row(vec![
117 Cell::new(schema_name).fg(comfy_table::Color::Cyan),
118 Cell::new(tables_count).set_alignment(CellAlignment::Right).fg(comfy_table::Color::Blue),
119 Cell::new(views_count).set_alignment(CellAlignment::Right).fg(comfy_table::Color::Green),
120 Cell::new(enums_count).set_alignment(CellAlignment::Right).fg(comfy_table::Color::Magenta),
121 Cell::new(functions_count).set_alignment(CellAlignment::Right).fg(comfy_table::Color::Red),
122 Cell::new(procedures_count).set_alignment(CellAlignment::Right).fg(comfy_table::Color::Yellow),
123 Cell::new(triggers_count).set_alignment(CellAlignment::Right).fg(comfy_table::Color::DarkYellow),
124 Cell::new(schema_total).set_alignment(CellAlignment::Right).add_attribute(comfy_table::Attribute::Bold),
125 ]);
126 }
127 }
128
129 let grand_total = total_tables + total_views + total_enums + total_functions + total_procedures + total_triggers;
131
132 table.add_row(vec![
135 Cell::new("TOTAL").add_attribute(comfy_table::Attribute::Bold),
136 Cell::new(total_tables).set_alignment(CellAlignment::Right).fg(comfy_table::Color::Blue).add_attribute(comfy_table::Attribute::Bold),
137 Cell::new(total_views).set_alignment(CellAlignment::Right).fg(comfy_table::Color::Green).add_attribute(comfy_table::Attribute::Bold),
138 Cell::new(total_enums).set_alignment(CellAlignment::Right).fg(comfy_table::Color::Magenta).add_attribute(comfy_table::Attribute::Bold),
139 Cell::new(total_functions).set_alignment(CellAlignment::Right).fg(comfy_table::Color::Red).add_attribute(comfy_table::Attribute::Bold),
140 Cell::new(total_procedures).set_alignment(CellAlignment::Right).fg(comfy_table::Color::Yellow).add_attribute(comfy_table::Attribute::Bold),
141 Cell::new(total_triggers).set_alignment(CellAlignment::Right).fg(comfy_table::Color::DarkYellow).add_attribute(comfy_table::Attribute::Bold),
142 Cell::new(grand_total).set_alignment(CellAlignment::Right).add_attribute(comfy_table::Attribute::Bold),
143 ]);
144
145 println!("{}", " ModelManager Statistics".green().bold().underline());
147 println!("{table}");
148 }
149
150 pub fn display_tables(&self, schemas: &[&str]) {
153 println!("\n{:=<80}", "");
154 println!(" TABLES OVERVIEW");
155 println!("{:=<80}\n", "");
156
157 let schemas_to_display: Box<dyn Iterator<Item = &str>> = if schemas.is_empty() {
158 Box::new(self.metadata.schemas.keys().map(|s| s.as_str()))
159 } else {
160 Box::new(schemas.iter().copied())
161 };
162
163 for schema_name in schemas_to_display {
164 if let Some(schema_data) = self.metadata.schemas.get(schema_name) {
165 for table_data in schema_data.tables.values() {
166 println!("{}\n", table_data);
168 }
169 }
170 }
171 }
172
173 pub fn display_views(&self, schemas: &[&str]) {
176 println!("\n{:=<80}", "");
177 println!(" VIEWS OVERVIEW");
178 println!("{:=<80}\n", "");
179
180 let schemas_to_display: Box<dyn Iterator<Item = &str>> = if schemas.is_empty() {
181 Box::new(self.metadata.schemas.keys().map(|s| s.as_str()))
182 } else {
183 Box::new(schemas.iter().copied())
184 };
185
186 for schema_name in schemas_to_display {
187 if let Some(schema_data) = self.metadata.schemas.get(schema_name) {
188 for view_data in schema_data.views.values() {
189 println!("{}\n", view_data);
191 }
192 }
193 }
194 }
195
196 pub fn display_enums(&self, schemas: &[&str]) {
199 println!("\n{:=<80}", "");
200 println!(" ENUMS OVERVIEW");
201 println!("{:=<80}\n", "");
202
203 let schemas_to_display: Box<dyn Iterator<Item = &str>> = if schemas.is_empty() {
204 Box::new(self.metadata.schemas.keys().map(|s| s.as_str()))
205 } else {
206 Box::new(schemas.iter().copied())
207 };
208
209 for schema_name in schemas_to_display {
210 if let Some(schema_data) = self.metadata.schemas.get(schema_name) {
211 if !schema_data.enums.is_empty() {
212 println!("Schema '{}':", schema_name.cyan().bold());
213 for enum_data in schema_data.enums.values() {
214 println!(" {}", enum_data.name.yellow());
216
217 let values_str = format!("({})", enum_data.values.join(", "));
219 println!(" {}", values_str.dimmed().italic());
220
221 println!();
223 }
224 }
225 }
226 }
227 }
228}