axion_db/
metadata.rs

1// in axion-db/src/metadata.rs
2
3use owo_colors::{OwoColorize, Style};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::fmt; // The essential import for custom formatting
7
8// =================================================================================
9//  1. The Formatting Macro: A helper to create clean, aligned key-value output.
10// =================================================================================
11
12macro_rules! write_field {
13    // For simple fields: `write_field!(f, "Name", &self.name)`
14    ($f:expr, $name:literal, $value:expr) => {
15        writeln!($f, "  {:<20} : {:?}", $name, $value)
16    };
17    // For fields that are collections: `write_field!(f, "Tables", &self.tables, collection)`
18    ($f:expr, $name:literal, $collection:expr, collection) => {
19        writeln!($f, "  {:<20} : {} items", $name, $collection.len())
20    };
21}
22
23// =================================================================================
24//  2. The Metadata Structs (Now with manual Display and Debug implementations)
25// =================================================================================
26
27// --- Root Metadata Structs ---
28
29#[derive(Clone, Serialize, Deserialize, Default)]
30pub struct DatabaseMetadata {
31    pub schemas: HashMap<String, SchemaMetadata>,
32}
33
34impl fmt::Display for DatabaseMetadata {
35    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36        write!(f, "Database with {} schemas", self.schemas.len())
37    }
38}
39
40impl fmt::Debug for DatabaseMetadata {
41    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42        writeln!(f, "DatabaseMetadata ({} schemas):", self.schemas.len())?;
43        for (name, schema) in &self.schemas {
44            writeln!(f, "{:#?}", schema)?;
45        }
46        Ok(())
47    }
48}
49
50#[derive(Clone, Serialize, Deserialize, Default)]
51pub struct SchemaMetadata {
52    pub name: String,
53    pub tables: HashMap<String, TableMetadata>,
54    pub views: HashMap<String, ViewMetadata>,
55    pub enums: HashMap<String, EnumMetadata>,
56    pub functions: HashMap<String, FunctionMetadata>,
57}
58
59impl fmt::Display for SchemaMetadata {
60    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61        write!(
62            f,
63            "Schema '{}' [{} tables, {} views, {} enums]",
64            self.name,
65            self.tables.len(),
66            self.views.len(),
67            self.enums.len()
68        )
69    }
70}
71
72impl fmt::Debug for SchemaMetadata {
73    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74        writeln!(f, "Schema '{}':", self.name)?;
75        write_field!(f, "Tables", self.tables, collection)?;
76        write_field!(f, "Views", self.views, collection)?;
77        write_field!(f, "Enums", self.enums, collection)?;
78        write_field!(f, "Functions", self.functions, collection)?;
79        Ok(())
80    }
81}
82
83// --- Type and Reference Structs ---
84
85#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
86pub enum AxionDataType {
87    Text,
88    Integer(i32),
89    Float(i32),
90    Numeric,
91    Boolean,
92    Timestamp,
93    TimestampTz,
94    Date,
95    Time,
96    Bytes,
97    Uuid,
98    Json,
99    JsonB,
100    Inet,
101    Enum(String),
102    Array(Box<AxionDataType>),
103    Unsupported(String),
104}
105
106// Display for AxionDataType will be its compact representation (e.g., "TEXT", "INT4", "UUID[]")
107impl fmt::Display for AxionDataType {
108    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109        match self {
110            Self::Text => write!(f, "TEXT"),
111            Self::Integer(bits) => write!(f, "INT{}", bits),
112            Self::Float(bits) => write!(f, "FLOAT{}", bits),
113            Self::Numeric => write!(f, "NUMERIC"),
114            Self::Boolean => write!(f, "BOOL"),
115            Self::Timestamp => write!(f, "TIMESTAMP"),
116            Self::TimestampTz => write!(f, "TIMESTAMPTZ"),
117            Self::Date => write!(f, "DATE"),
118            Self::Time => write!(f, "TIME"),
119            Self::Bytes => write!(f, "BYTES"),
120            Self::Uuid => write!(f, "UUID"),
121            Self::Json => write!(f, "JSON"),
122            Self::JsonB => write!(f, "JSONB"),
123            Self::Inet => write!(f, "INET"),
124            Self::Enum(name) => write!(f, "{}", name),
125            Self::Array(inner) => write!(f, "{}[]", inner),
126            Self::Unsupported(name) => write!(f, "UNSUPPORTED({})", name),
127        }
128    }
129}
130// Debug for AxionDataType will be the verbose, struct-like representation
131impl fmt::Debug for AxionDataType {
132    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133        match self {
134            Self::Enum(name) => f.debug_tuple("Enum").field(name).finish(),
135            Self::Array(inner) => f.debug_tuple("Array").field(inner).finish(),
136            Self::Unsupported(name) => f.debug_tuple("Unsupported").field(name).finish(),
137            _ => write!(f, "{}", self), // For simple variants, Display and Debug are the same
138        }
139    }
140}
141
142#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
143pub struct ForeignKeyReference {
144    pub schema: String,
145    pub table: String,
146    pub column: String,
147}
148// Let's create a compact display for FKs
149impl fmt::Display for ForeignKeyReference {
150    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151        write!(f, "{}.{}.{}", self.schema, self.table, self.column)
152    }
153}
154impl fmt::Debug for ForeignKeyReference {
155    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
156        // For Debug, a more structured view is nice
157        f.debug_struct("ForeignKey")
158            .field("schema", &self.schema)
159            .field("table", &self.table)
160            .field("column", &self.column)
161            .finish()
162    }
163}
164
165// --- Core Entity Structs ---
166
167#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)]
168pub struct ColumnMetadata {
169    pub name: String,
170    pub sql_type_name: String,
171    pub axion_type: AxionDataType,
172    pub is_nullable: bool,
173    pub is_primary_key: bool,
174    pub default_value: Option<String>,
175    pub comment: Option<String>,
176    pub foreign_key: Option<ForeignKeyReference>,
177}
178// This provides the `column_name    VARCHAR(255)    TEXT` format
179
180// This provides the `column_name    VARCHAR(255)    TEXT` format
181impl fmt::Display for ColumnMetadata {
182    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183        // Define styles for different parts
184        let pk_style = Style::new().green().bold();
185        let fk_style = Style::new().cyan();
186        let enum_style = Style::new().yellow();
187        let type_style = Style::new().magenta();
188        let nullable_style = Style::new().red().bold();
189
190        // 1. Column Name and Nullable Marker
191        // ======================= THE CORRECTED FIX =======================
192        // A plain &str already implements Display. We just need to Box it.
193        let nullable_marker: Box<dyn fmt::Display> = if self.is_nullable {
194            Box::new(" ") // <-- Simply box the plain string slice.
195        } else {
196            Box::new("*".style(nullable_style))
197        };
198        // ===============================================================
199        write!(f, "  {} {:<25}", nullable_marker, self.name.bold())?;
200
201        // 2. SQL Type
202        write!(f, "{:<20}", self.sql_type_name.dimmed())?;
203
204        // 3. Axion Type (with special coloring for enums)
205        let binding = self.axion_type.to_string();
206        let axion_type_display: Box<dyn fmt::Display> = match &self.axion_type {
207            AxionDataType::Enum(name) => Box::new(name.style(enum_style)),
208            _ => Box::new(binding.style(type_style)),
209        };
210
211        write!(f, "{:<20}", axion_type_display)?;
212
213        // 4. Constraints (PK, FK)
214        let mut constraints = Vec::new();
215        if self.is_primary_key {
216            constraints.push(format!("{}", "PK".style(pk_style)));
217        }
218        if let Some(fk) = &self.foreign_key {
219            constraints.push(format!("{} -> {}", "FK".style(fk_style), fk));
220        }
221        write!(f, "{}", constraints.join(" "))?;
222
223        Ok(())
224    }
225}
226impl fmt::Debug for ColumnMetadata {
227    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
228        writeln!(f, "Column '{}':", self.name)?;
229        write_field!(f, "SQL Type", &self.sql_type_name)?;
230        write_field!(f, "Axion Type", &self.axion_type)?;
231        write_field!(f, "Nullable", &self.is_nullable)?;
232        write_field!(f, "Primary Key", &self.is_primary_key)?;
233        write_field!(f, "Default", &self.default_value)?;
234        write_field!(f, "Foreign Key", &self.foreign_key)?;
235        write_field!(f, "Comment", &self.comment)
236    }
237}
238
239#[derive(Clone, Serialize, Deserialize, Default)]
240pub struct TableMetadata {
241    pub name: String,
242    pub schema: String,
243    pub columns: Vec<ColumnMetadata>,
244    pub primary_key_columns: Vec<String>,
245    pub comment: Option<String>,
246}
247impl fmt::Display for TableMetadata {
248    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
249        // Use bright_blue for the table name header
250        writeln!(
251            f,
252            "{}",
253            format!("{}.{}", self.schema, self.name)
254                .bright_blue()
255                .bold()
256        )?;
257
258        // Print columns
259        for col in &self.columns {
260            writeln!(f, "{}", col)?;
261        }
262        Ok(())
263    }
264}
265impl fmt::Debug for TableMetadata {
266    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
267        writeln!(f, "Table '{}.{}':", self.schema, self.name)?;
268        write_field!(f, "Primary Keys", &self.primary_key_columns)?;
269        write_field!(f, "Comment", &self.comment)?;
270        writeln!(f, "  Columns ({}):", self.columns.len())?;
271        for col in &self.columns {
272            writeln!(f, "{:#?}", col)?;
273        }
274        Ok(())
275    }
276}
277
278#[derive(Clone, Serialize, Deserialize, Default)]
279pub struct ViewMetadata {
280    pub name: String,
281    pub schema: String,
282    pub columns: Vec<ColumnMetadata>,
283    pub definition: Option<String>,
284    pub comment: Option<String>,
285}
286// Views can use the same Display format as Tables
287impl fmt::Display for ViewMetadata {
288    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
289        // Use bright_green for the view name header
290        writeln!(
291            f,
292            "{}",
293            format!("{}.{}", self.schema, self.name)
294                .bright_green()
295                .bold()
296        )?;
297
298        for col in &self.columns {
299            writeln!(f, "{}", col)?;
300        }
301        Ok(())
302    }
303}
304impl fmt::Debug for ViewMetadata {
305    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
306        writeln!(f, "View '{}.{}':", self.schema, self.name)?;
307        write_field!(f, "Comment", &self.comment)?;
308        if let Some(def) = &self.definition {
309            writeln!(
310                f,
311                "  Definition           : {}...",
312                &def.chars().take(50).collect::<String>()
313            )?;
314        }
315        writeln!(f, "  Columns ({}):", self.columns.len())?;
316        for col in &self.columns {
317            writeln!(f, "{:#?}", col)?;
318        }
319        Ok(())
320    }
321}
322
323#[derive(Clone, Serialize, Deserialize, Default)]
324pub struct EnumMetadata {
325    pub name: String,
326    pub schema: String,
327    pub values: Vec<String>,
328    pub comment: Option<String>,
329}
330impl fmt::Display for EnumMetadata {
331    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
332        write!(
333            f,
334            "{}.{} ({})",
335            self.schema,
336            self.name,
337            self.values.join(", ")
338        )
339    }
340}
341impl fmt::Debug for EnumMetadata {
342    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
343        writeln!(f, "Enum '{}.{}':", self.schema, self.name)?;
344        write_field!(f, "Values", &self.values)?;
345        write_field!(f, "Comment", &self.comment)
346    }
347}
348
349// NOTE: Function-related structs are left with derived Debug for now,
350// as they are not yet implemented in the introspector.
351
352#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
353pub enum RoutineKind {
354    Function,
355    Procedure,
356    Aggregate,
357    Window,
358    Trigger,
359}
360
361#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
362pub enum ParameterMode {
363    In,
364    Out,
365    InOut,
366    Variadic,
367}
368
369#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
370pub struct ParameterMetadata {
371    pub name: String,
372    pub sql_type_name: String,
373    pub axion_type: AxionDataType,
374    pub mode: ParameterMode,
375    pub has_default: bool,
376}
377
378#[derive(Debug, Clone, Serialize, Deserialize, Default)]
379pub struct FunctionMetadata {
380    pub name: String,
381    pub schema: String,
382    pub kind: Option<RoutineKind>,
383    pub parameters: Vec<ParameterMetadata>,
384    pub return_type: Option<AxionDataType>,
385    pub return_table: Option<Vec<ColumnMetadata>>,
386    pub comment: Option<String>,
387}