Spaces:
Paused
Paused
Commit
·
3545eca
1
Parent(s):
38f1436
Adds database visualization feature
Browse filesImplements database visualization using Mermaid diagrams and text summaries.
Provides a visual representation of the database schema, including tables, columns, and relationships. This allows users to better understand the structure of their database.
Automatically generates visualizations after successful merge operations or SQL execution. Includes a button to manually generate the visualization at any time.
Updates the Gradio UI to display the visualization.
- schema_sync/app.py +97 -18
- schema_sync/db_visualizer.py +153 -0
schema_sync/app.py
CHANGED
|
@@ -5,6 +5,7 @@ import os
|
|
| 5 |
from .db_connector import DBConnector
|
| 6 |
from .schema_inspector import SchemaInspector
|
| 7 |
from .merge_operations import MergeOperations
|
|
|
|
| 8 |
from .config import get_config
|
| 9 |
|
| 10 |
# Set up logging
|
|
@@ -17,10 +18,11 @@ config = get_config()
|
|
| 17 |
db = None
|
| 18 |
inspector = None
|
| 19 |
merge_ops = None
|
|
|
|
| 20 |
|
| 21 |
def connect_to_database(db_url):
|
| 22 |
"""Connect to the database with provided URL"""
|
| 23 |
-
global db, inspector, merge_ops
|
| 24 |
|
| 25 |
try:
|
| 26 |
# Initialize new connection
|
|
@@ -32,6 +34,7 @@ def connect_to_database(db_url):
|
|
| 32 |
|
| 33 |
inspector = SchemaInspector(db)
|
| 34 |
merge_ops = MergeOperations(db, inspector)
|
|
|
|
| 35 |
|
| 36 |
# Test connection by fetching tables
|
| 37 |
tables = get_db_tables()
|
|
@@ -50,10 +53,10 @@ def connect_to_database(db_url):
|
|
| 50 |
def handle_merge(action, table, column, from_values, target_value, preview_only=True):
|
| 51 |
"""Handler for Gradio interface"""
|
| 52 |
if not db:
|
| 53 |
-
return "Error: Not connected to database. Please connect first."
|
| 54 |
|
| 55 |
if not table or not column:
|
| 56 |
-
return "Error: Table and column must be specified"
|
| 57 |
|
| 58 |
# Parse from_values as comma-separated list
|
| 59 |
from_values_list = [v.strip() for v in from_values.split(',')]
|
|
@@ -61,12 +64,21 @@ def handle_merge(action, table, column, from_values, target_value, preview_only=
|
|
| 61 |
if action == "Merge Values":
|
| 62 |
if preview_only:
|
| 63 |
result = merge_ops.preview_merge(table, column, from_values_list, target_value)
|
| 64 |
-
return result["preview"]
|
| 65 |
else:
|
| 66 |
result = merge_ops.run_merge(table, column, from_values_list, target_value)
|
| 67 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
else:
|
| 69 |
-
return "Action not implemented yet"
|
| 70 |
|
| 71 |
def get_db_tables():
|
| 72 |
"""Get list of tables from the database for dropdown"""
|
|
@@ -116,10 +128,10 @@ def execute_sql_file(sql_file):
|
|
| 116 |
global db
|
| 117 |
|
| 118 |
if not db:
|
| 119 |
-
return "Error: Not connected to database. Please connect first."
|
| 120 |
|
| 121 |
if sql_file is None:
|
| 122 |
-
return "Error: No SQL file provided."
|
| 123 |
|
| 124 |
try:
|
| 125 |
# Read the uploaded file
|
|
@@ -127,7 +139,7 @@ def execute_sql_file(sql_file):
|
|
| 127 |
sql_content = f.read()
|
| 128 |
|
| 129 |
if not sql_content.strip():
|
| 130 |
-
return "Error: SQL file is empty."
|
| 131 |
|
| 132 |
# Split SQL content by semicolons and execute each statement
|
| 133 |
sql_statements = [stmt.strip() for stmt in sql_content.split(';') if stmt.strip()]
|
|
@@ -152,19 +164,52 @@ def execute_sql_file(sql_file):
|
|
| 152 |
|
| 153 |
session.commit()
|
| 154 |
results.insert(0, f"Successfully executed {len(sql_statements)} SQL statements.")
|
| 155 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 156 |
|
| 157 |
except Exception as e:
|
| 158 |
session.rollback()
|
| 159 |
error_msg = f"Error executing SQL: {str(e)}"
|
| 160 |
logger.error(error_msg)
|
| 161 |
-
return error_msg
|
| 162 |
finally:
|
| 163 |
session.close()
|
| 164 |
|
| 165 |
except Exception as e:
|
| 166 |
logger.error(f"Error reading SQL file: {str(e)}")
|
| 167 |
-
return f"Error reading SQL file: {str(e)}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 168 |
|
| 169 |
def create_ui():
|
| 170 |
"""Create and configure the Gradio UI"""
|
|
@@ -172,7 +217,7 @@ def create_ui():
|
|
| 172 |
gr.Markdown("# SchemaSync - Database Schema Manipulation Tool")
|
| 173 |
|
| 174 |
with gr.Row():
|
| 175 |
-
with gr.Column():
|
| 176 |
# Database Connection Section
|
| 177 |
gr.Markdown("## Database Connection")
|
| 178 |
|
|
@@ -246,13 +291,40 @@ def create_ui():
|
|
| 246 |
with gr.Row():
|
| 247 |
preview_btn = gr.Button("Preview Changes")
|
| 248 |
run_btn = gr.Button("Run Operation", variant="primary")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 249 |
|
| 250 |
-
with gr.Column():
|
|
|
|
|
|
|
|
|
|
| 251 |
output = gr.TextArea(
|
| 252 |
label="Operation Log",
|
| 253 |
placeholder="Operation results will appear here",
|
| 254 |
-
lines=
|
| 255 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 256 |
|
| 257 |
# Connect buttons to handlers
|
| 258 |
connect_btn.click(
|
|
@@ -270,19 +342,26 @@ def create_ui():
|
|
| 270 |
execute_sql_btn.click(
|
| 271 |
fn=execute_sql_file,
|
| 272 |
inputs=sql_file,
|
| 273 |
-
outputs=output
|
| 274 |
)
|
| 275 |
|
| 276 |
preview_btn.click(
|
| 277 |
fn=handle_merge,
|
| 278 |
inputs=[action, table, column, from_values, target_value, preview_checkbox],
|
| 279 |
-
outputs=output
|
| 280 |
)
|
| 281 |
|
| 282 |
run_btn.click(
|
| 283 |
fn=handle_merge,
|
| 284 |
inputs=[action, table, column, from_values, target_value, gr.Checkbox(value=False, visible=False)],
|
| 285 |
-
outputs=output
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 286 |
)
|
| 287 |
|
| 288 |
return app
|
|
|
|
| 5 |
from .db_connector import DBConnector
|
| 6 |
from .schema_inspector import SchemaInspector
|
| 7 |
from .merge_operations import MergeOperations
|
| 8 |
+
from .db_visualizer import DatabaseVisualizer
|
| 9 |
from .config import get_config
|
| 10 |
|
| 11 |
# Set up logging
|
|
|
|
| 18 |
db = None
|
| 19 |
inspector = None
|
| 20 |
merge_ops = None
|
| 21 |
+
visualizer = None
|
| 22 |
|
| 23 |
def connect_to_database(db_url):
|
| 24 |
"""Connect to the database with provided URL"""
|
| 25 |
+
global db, inspector, merge_ops, visualizer
|
| 26 |
|
| 27 |
try:
|
| 28 |
# Initialize new connection
|
|
|
|
| 34 |
|
| 35 |
inspector = SchemaInspector(db)
|
| 36 |
merge_ops = MergeOperations(db, inspector)
|
| 37 |
+
visualizer = DatabaseVisualizer(db, inspector)
|
| 38 |
|
| 39 |
# Test connection by fetching tables
|
| 40 |
tables = get_db_tables()
|
|
|
|
| 53 |
def handle_merge(action, table, column, from_values, target_value, preview_only=True):
|
| 54 |
"""Handler for Gradio interface"""
|
| 55 |
if not db:
|
| 56 |
+
return "Error: Not connected to database. Please connect first.", "", ""
|
| 57 |
|
| 58 |
if not table or not column:
|
| 59 |
+
return "Error: Table and column must be specified", "", ""
|
| 60 |
|
| 61 |
# Parse from_values as comma-separated list
|
| 62 |
from_values_list = [v.strip() for v in from_values.split(',')]
|
|
|
|
| 64 |
if action == "Merge Values":
|
| 65 |
if preview_only:
|
| 66 |
result = merge_ops.preview_merge(table, column, from_values_list, target_value)
|
| 67 |
+
return result["preview"], "", ""
|
| 68 |
else:
|
| 69 |
result = merge_ops.run_merge(table, column, from_values_list, target_value)
|
| 70 |
+
# Auto-generate visualization after successful operation
|
| 71 |
+
if result.get("success", False) and visualizer:
|
| 72 |
+
try:
|
| 73 |
+
text_summary = visualizer.generate_table_summary()
|
| 74 |
+
mermaid_diagram = visualizer.generate_mermaid_diagram()
|
| 75 |
+
return result["log"], text_summary, mermaid_diagram
|
| 76 |
+
except Exception as e:
|
| 77 |
+
logger.error(f"Error generating visualization after merge: {str(e)}")
|
| 78 |
+
return result["log"], "Error generating visualization", ""
|
| 79 |
+
return result["log"], "", ""
|
| 80 |
else:
|
| 81 |
+
return "Action not implemented yet", "", ""
|
| 82 |
|
| 83 |
def get_db_tables():
|
| 84 |
"""Get list of tables from the database for dropdown"""
|
|
|
|
| 128 |
global db
|
| 129 |
|
| 130 |
if not db:
|
| 131 |
+
return "Error: Not connected to database. Please connect first.", "", ""
|
| 132 |
|
| 133 |
if sql_file is None:
|
| 134 |
+
return "Error: No SQL file provided.", "", ""
|
| 135 |
|
| 136 |
try:
|
| 137 |
# Read the uploaded file
|
|
|
|
| 139 |
sql_content = f.read()
|
| 140 |
|
| 141 |
if not sql_content.strip():
|
| 142 |
+
return "Error: SQL file is empty.", "", ""
|
| 143 |
|
| 144 |
# Split SQL content by semicolons and execute each statement
|
| 145 |
sql_statements = [stmt.strip() for stmt in sql_content.split(';') if stmt.strip()]
|
|
|
|
| 164 |
|
| 165 |
session.commit()
|
| 166 |
results.insert(0, f"Successfully executed {len(sql_statements)} SQL statements.")
|
| 167 |
+
operation_log = "\n".join(results)
|
| 168 |
+
|
| 169 |
+
# Auto-generate visualization after successful SQL execution
|
| 170 |
+
if visualizer:
|
| 171 |
+
try:
|
| 172 |
+
text_summary = visualizer.generate_table_summary()
|
| 173 |
+
mermaid_diagram = visualizer.generate_mermaid_diagram()
|
| 174 |
+
return operation_log, text_summary, mermaid_diagram
|
| 175 |
+
except Exception as e:
|
| 176 |
+
logger.error(f"Error generating visualization after SQL execution: {str(e)}")
|
| 177 |
+
return operation_log, "Error generating visualization", ""
|
| 178 |
+
|
| 179 |
+
return operation_log, "", ""
|
| 180 |
|
| 181 |
except Exception as e:
|
| 182 |
session.rollback()
|
| 183 |
error_msg = f"Error executing SQL: {str(e)}"
|
| 184 |
logger.error(error_msg)
|
| 185 |
+
return error_msg, "", ""
|
| 186 |
finally:
|
| 187 |
session.close()
|
| 188 |
|
| 189 |
except Exception as e:
|
| 190 |
logger.error(f"Error reading SQL file: {str(e)}")
|
| 191 |
+
return f"Error reading SQL file: {str(e)}", "", ""
|
| 192 |
+
|
| 193 |
+
def generate_database_visualization():
|
| 194 |
+
"""Generate database visualization"""
|
| 195 |
+
if not visualizer:
|
| 196 |
+
return "Error: Not connected to database. Please connect first.", ""
|
| 197 |
+
|
| 198 |
+
try:
|
| 199 |
+
# Generate both Mermaid diagram and text summary
|
| 200 |
+
mermaid_diagram = visualizer.generate_mermaid_diagram()
|
| 201 |
+
text_summary = visualizer.generate_table_summary()
|
| 202 |
+
|
| 203 |
+
return text_summary, mermaid_diagram
|
| 204 |
+
|
| 205 |
+
except Exception as e:
|
| 206 |
+
error_msg = f"Error generating visualization: {str(e)}"
|
| 207 |
+
logger.error(error_msg)
|
| 208 |
+
return error_msg, ""
|
| 209 |
+
|
| 210 |
+
def refresh_visualization():
|
| 211 |
+
"""Refresh the database visualization"""
|
| 212 |
+
return generate_database_visualization()
|
| 213 |
|
| 214 |
def create_ui():
|
| 215 |
"""Create and configure the Gradio UI"""
|
|
|
|
| 217 |
gr.Markdown("# SchemaSync - Database Schema Manipulation Tool")
|
| 218 |
|
| 219 |
with gr.Row():
|
| 220 |
+
with gr.Column(scale=1):
|
| 221 |
# Database Connection Section
|
| 222 |
gr.Markdown("## Database Connection")
|
| 223 |
|
|
|
|
| 291 |
with gr.Row():
|
| 292 |
preview_btn = gr.Button("Preview Changes")
|
| 293 |
run_btn = gr.Button("Run Operation", variant="primary")
|
| 294 |
+
|
| 295 |
+
# Database Visualization Section
|
| 296 |
+
gr.Markdown("## Database Visualization")
|
| 297 |
+
|
| 298 |
+
visualize_btn = gr.Button("Generate Visualization", variant="secondary")
|
| 299 |
|
| 300 |
+
with gr.Column(scale=2):
|
| 301 |
+
# Operation Results
|
| 302 |
+
gr.Markdown("## Operation Results")
|
| 303 |
+
|
| 304 |
output = gr.TextArea(
|
| 305 |
label="Operation Log",
|
| 306 |
placeholder="Operation results will appear here",
|
| 307 |
+
lines=15
|
| 308 |
)
|
| 309 |
+
|
| 310 |
+
# Visualization Results
|
| 311 |
+
gr.Markdown("## Database Schema")
|
| 312 |
+
|
| 313 |
+
with gr.Tabs():
|
| 314 |
+
with gr.TabItem("Schema Summary"):
|
| 315 |
+
schema_summary = gr.Markdown(
|
| 316 |
+
value="Connect to a database and run operations or click 'Generate Visualization' to see the schema structure."
|
| 317 |
+
)
|
| 318 |
+
|
| 319 |
+
with gr.TabItem("ER Diagram"):
|
| 320 |
+
gr.Markdown("Copy the code below and paste it into [Mermaid Live Editor](https://mermaid.live) to view the interactive diagram.")
|
| 321 |
+
|
| 322 |
+
mermaid_code = gr.Code(
|
| 323 |
+
label="Mermaid Diagram Code",
|
| 324 |
+
language="markdown",
|
| 325 |
+
lines=15,
|
| 326 |
+
value="Connect to database and run operations to see diagram code here."
|
| 327 |
+
)
|
| 328 |
|
| 329 |
# Connect buttons to handlers
|
| 330 |
connect_btn.click(
|
|
|
|
| 342 |
execute_sql_btn.click(
|
| 343 |
fn=execute_sql_file,
|
| 344 |
inputs=sql_file,
|
| 345 |
+
outputs=[output, schema_summary, mermaid_code]
|
| 346 |
)
|
| 347 |
|
| 348 |
preview_btn.click(
|
| 349 |
fn=handle_merge,
|
| 350 |
inputs=[action, table, column, from_values, target_value, preview_checkbox],
|
| 351 |
+
outputs=[output, schema_summary, mermaid_code]
|
| 352 |
)
|
| 353 |
|
| 354 |
run_btn.click(
|
| 355 |
fn=handle_merge,
|
| 356 |
inputs=[action, table, column, from_values, target_value, gr.Checkbox(value=False, visible=False)],
|
| 357 |
+
outputs=[output, schema_summary, mermaid_code]
|
| 358 |
+
)
|
| 359 |
+
|
| 360 |
+
# Manual visualization generation
|
| 361 |
+
visualize_btn.click(
|
| 362 |
+
fn=generate_database_visualization,
|
| 363 |
+
inputs=None,
|
| 364 |
+
outputs=[schema_summary, mermaid_code]
|
| 365 |
)
|
| 366 |
|
| 367 |
return app
|
schema_sync/db_visualizer.py
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logging
|
| 2 |
+
import json
|
| 3 |
+
from sqlalchemy import text
|
| 4 |
+
|
| 5 |
+
logger = logging.getLogger(__name__)
|
| 6 |
+
|
| 7 |
+
class DatabaseVisualizer:
|
| 8 |
+
def __init__(self, db_connector, schema_inspector):
|
| 9 |
+
self.db = db_connector
|
| 10 |
+
self.inspector = schema_inspector
|
| 11 |
+
|
| 12 |
+
def get_database_schema(self):
|
| 13 |
+
"""Get complete database schema with tables, columns, and relationships"""
|
| 14 |
+
try:
|
| 15 |
+
# Get all tables
|
| 16 |
+
tables_query = """
|
| 17 |
+
SELECT table_name
|
| 18 |
+
FROM information_schema.tables
|
| 19 |
+
WHERE table_schema = 'public' AND table_type = 'BASE TABLE'
|
| 20 |
+
ORDER BY table_name
|
| 21 |
+
"""
|
| 22 |
+
tables_result = self.db.execute_query(tables_query)
|
| 23 |
+
tables = [row[0] for row in tables_result]
|
| 24 |
+
|
| 25 |
+
schema_data = {
|
| 26 |
+
"tables": {},
|
| 27 |
+
"relationships": []
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
# Get detailed info for each table
|
| 31 |
+
for table in tables:
|
| 32 |
+
# Get columns
|
| 33 |
+
columns = self.inspector.get_column_info(table)
|
| 34 |
+
|
| 35 |
+
# Get foreign keys
|
| 36 |
+
foreign_keys = self.inspector.get_foreign_keys(table)
|
| 37 |
+
|
| 38 |
+
# Get primary key
|
| 39 |
+
pk_query = f"""
|
| 40 |
+
SELECT column_name
|
| 41 |
+
FROM information_schema.key_column_usage
|
| 42 |
+
WHERE table_name = '{table}'
|
| 43 |
+
AND constraint_name IN (
|
| 44 |
+
SELECT constraint_name
|
| 45 |
+
FROM information_schema.table_constraints
|
| 46 |
+
WHERE table_name = '{table}'
|
| 47 |
+
AND constraint_type = 'PRIMARY KEY'
|
| 48 |
+
)
|
| 49 |
+
"""
|
| 50 |
+
pk_result = self.db.execute_query(pk_query)
|
| 51 |
+
primary_keys = [row[0] for row in pk_result]
|
| 52 |
+
|
| 53 |
+
# Store table info
|
| 54 |
+
schema_data["tables"][table] = {
|
| 55 |
+
"columns": columns,
|
| 56 |
+
"primary_keys": primary_keys,
|
| 57 |
+
"foreign_keys": foreign_keys
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
# Add relationships
|
| 61 |
+
for fk in foreign_keys:
|
| 62 |
+
relationship = {
|
| 63 |
+
"from_table": table,
|
| 64 |
+
"from_column": fk["column_name"],
|
| 65 |
+
"to_table": fk["foreign_table_name"],
|
| 66 |
+
"to_column": fk["foreign_column_name"]
|
| 67 |
+
}
|
| 68 |
+
schema_data["relationships"].append(relationship)
|
| 69 |
+
|
| 70 |
+
return schema_data
|
| 71 |
+
|
| 72 |
+
except Exception as e:
|
| 73 |
+
logger.error(f"Error getting database schema: {str(e)}")
|
| 74 |
+
return {"tables": {}, "relationships": []}
|
| 75 |
+
|
| 76 |
+
def generate_mermaid_diagram(self):
|
| 77 |
+
"""Generate a Mermaid ER diagram of the database"""
|
| 78 |
+
schema = self.get_database_schema()
|
| 79 |
+
|
| 80 |
+
if not schema["tables"]:
|
| 81 |
+
return "No tables found in database"
|
| 82 |
+
|
| 83 |
+
mermaid_lines = ["erDiagram"]
|
| 84 |
+
|
| 85 |
+
# Add tables and columns
|
| 86 |
+
for table_name, table_info in schema["tables"].items():
|
| 87 |
+
mermaid_lines.append(f" {table_name} {{")
|
| 88 |
+
|
| 89 |
+
for col in table_info["columns"]:
|
| 90 |
+
col_name = col["column_name"]
|
| 91 |
+
data_type = col["data_type"]
|
| 92 |
+
is_pk = col_name in table_info["primary_keys"]
|
| 93 |
+
is_fk = any(fk["column_name"] == col_name for fk in table_info["foreign_keys"])
|
| 94 |
+
|
| 95 |
+
# Add type indicators
|
| 96 |
+
type_indicator = ""
|
| 97 |
+
if is_pk:
|
| 98 |
+
type_indicator = " PK"
|
| 99 |
+
elif is_fk:
|
| 100 |
+
type_indicator = " FK"
|
| 101 |
+
|
| 102 |
+
mermaid_lines.append(f" {data_type} {col_name}{type_indicator}")
|
| 103 |
+
|
| 104 |
+
mermaid_lines.append(" }")
|
| 105 |
+
|
| 106 |
+
# Add relationships
|
| 107 |
+
for rel in schema["relationships"]:
|
| 108 |
+
# Mermaid relationship syntax: TableA ||--o{ TableB : relationship
|
| 109 |
+
mermaid_lines.append(
|
| 110 |
+
f" {rel['to_table']} ||--o{{ {rel['from_table']} : \"{rel['from_column']} -> {rel['to_column']}\""
|
| 111 |
+
)
|
| 112 |
+
|
| 113 |
+
return "\n".join(mermaid_lines)
|
| 114 |
+
|
| 115 |
+
def generate_table_summary(self):
|
| 116 |
+
"""Generate a text summary of database structure"""
|
| 117 |
+
schema = self.get_database_schema()
|
| 118 |
+
|
| 119 |
+
if not schema["tables"]:
|
| 120 |
+
return "No tables found in database"
|
| 121 |
+
|
| 122 |
+
summary_lines = ["# Database Schema Summary\n"]
|
| 123 |
+
|
| 124 |
+
# Table overview
|
| 125 |
+
summary_lines.append(f"**Total Tables:** {len(schema['tables'])}")
|
| 126 |
+
summary_lines.append(f"**Total Relationships:** {len(schema['relationships'])}\n")
|
| 127 |
+
|
| 128 |
+
# Detailed table info
|
| 129 |
+
summary_lines.append("## Tables\n")
|
| 130 |
+
|
| 131 |
+
for table_name, table_info in schema["tables"].items():
|
| 132 |
+
summary_lines.append(f"### {table_name}")
|
| 133 |
+
summary_lines.append(f"- **Columns:** {len(table_info['columns'])}")
|
| 134 |
+
summary_lines.append(f"- **Primary Keys:** {', '.join(table_info['primary_keys']) if table_info['primary_keys'] else 'None'}")
|
| 135 |
+
summary_lines.append(f"- **Foreign Keys:** {len(table_info['foreign_keys'])}")
|
| 136 |
+
|
| 137 |
+
# Column details
|
| 138 |
+
summary_lines.append("\n**Columns:**")
|
| 139 |
+
for col in table_info["columns"]:
|
| 140 |
+
nullable = "NULL" if col["is_nullable"] == "YES" else "NOT NULL"
|
| 141 |
+
summary_lines.append(f"- `{col['column_name']}` ({col['data_type']}) {nullable}")
|
| 142 |
+
|
| 143 |
+
summary_lines.append("")
|
| 144 |
+
|
| 145 |
+
# Relationships summary
|
| 146 |
+
if schema["relationships"]:
|
| 147 |
+
summary_lines.append("## Relationships\n")
|
| 148 |
+
for rel in schema["relationships"]:
|
| 149 |
+
summary_lines.append(
|
| 150 |
+
f"- `{rel['from_table']}.{rel['from_column']}` → `{rel['to_table']}.{rel['to_column']}`"
|
| 151 |
+
)
|
| 152 |
+
|
| 153 |
+
return "\n".join(summary_lines)
|