import logging import json from sqlalchemy import text logger = logging.getLogger(__name__) class DatabaseVisualizer: def __init__(self, db_connector, schema_inspector): self.db = db_connector self.inspector = schema_inspector def get_database_schema(self): """Get complete database schema with tables, columns, and relationships""" try: # Get all tables tables_query = """ SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type = 'BASE TABLE' ORDER BY table_name """ tables_result = self.db.execute_query(tables_query) tables = [row[0] for row in tables_result] schema_data = { "tables": {}, "relationships": [] } # Get detailed info for each table for table in tables: # Get columns columns = self.inspector.get_column_info(table) # Get foreign keys foreign_keys = self.inspector.get_foreign_keys(table) # Get primary key pk_query = f""" SELECT column_name FROM information_schema.key_column_usage WHERE table_name = '{table}' AND constraint_name IN ( SELECT constraint_name FROM information_schema.table_constraints WHERE table_name = '{table}' AND constraint_type = 'PRIMARY KEY' ) """ pk_result = self.db.execute_query(pk_query) primary_keys = [row[0] for row in pk_result] # Store table info schema_data["tables"][table] = { "columns": columns, "primary_keys": primary_keys, "foreign_keys": foreign_keys } # Add relationships for fk in foreign_keys: relationship = { "from_table": table, "from_column": fk["column_name"], "to_table": fk["foreign_table_name"], "to_column": fk["foreign_column_name"] } schema_data["relationships"].append(relationship) return schema_data except Exception as e: logger.error(f"Error getting database schema: {str(e)}") return {"tables": {}, "relationships": []} def generate_mermaid_diagram(self): """Generate a Mermaid ER diagram of the database""" schema = self.get_database_schema() if not schema["tables"]: return "No tables found in database" mermaid_lines = ["erDiagram"] # Add tables and columns for table_name, table_info in schema["tables"].items(): mermaid_lines.append(f" {table_name} {{") for col in table_info["columns"]: col_name = col["column_name"] data_type = col["data_type"] is_pk = col_name in table_info["primary_keys"] is_fk = any(fk["column_name"] == col_name for fk in table_info["foreign_keys"]) # Add type indicators type_indicator = "" if is_pk: type_indicator = " PK" elif is_fk: type_indicator = " FK" mermaid_lines.append(f" {data_type} {col_name}{type_indicator}") mermaid_lines.append(" }") # Add relationships for rel in schema["relationships"]: # Mermaid relationship syntax: TableA ||--o{ TableB : relationship mermaid_lines.append( f" {rel['to_table']} ||--o{{ {rel['from_table']} : \"{rel['from_column']} -> {rel['to_column']}\"" ) return "\n".join(mermaid_lines) def generate_table_summary(self): """Generate a text summary of database structure""" schema = self.get_database_schema() if not schema["tables"]: return "No tables found in database" summary_lines = ["# Database Schema Summary\n"] # Table overview summary_lines.append(f"**Total Tables:** {len(schema['tables'])}") summary_lines.append(f"**Total Relationships:** {len(schema['relationships'])}\n") # Detailed table info summary_lines.append("## Tables\n") for table_name, table_info in schema["tables"].items(): summary_lines.append(f"### {table_name}") summary_lines.append(f"- **Columns:** {len(table_info['columns'])}") summary_lines.append(f"- **Primary Keys:** {', '.join(table_info['primary_keys']) if table_info['primary_keys'] else 'None'}") summary_lines.append(f"- **Foreign Keys:** {len(table_info['foreign_keys'])}") # Column details summary_lines.append("\n**Columns:**") for col in table_info["columns"]: nullable = "NULL" if col["is_nullable"] == "YES" else "NOT NULL" summary_lines.append(f"- `{col['column_name']}` ({col['data_type']}) {nullable}") summary_lines.append("") # Relationships summary if schema["relationships"]: summary_lines.append("## Relationships\n") for rel in schema["relationships"]: summary_lines.append( f"- `{rel['from_table']}.{rel['from_column']}` → `{rel['to_table']}.{rel['to_column']}`" ) return "\n".join(summary_lines)