mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-19 08:01:25 +00:00
2d0e65b7ca
* add schema * give claude the schema * use summarized schema * add indexes * add usage comments * Remove backend/schema.sql from remote
154 lines
6.3 KiB
Python
154 lines
6.3 KiB
Python
# This script is used to summarize the database schema.
|
|
# You can use pg_dump to dump the schema to a file.
|
|
# pg_dump --file "schema.sql" --host "localhost" --port "5432" --username "postgres" --no-password --format=c --large-objects --schema-only --no-owner --no-privileges --no-tablespaces --no-unlogged-table-data --no-comments --no-publications --no-subscriptions --no-security-labels --no-toast-compression --no-table-access-method --verbose --schema "public" "windmill"
|
|
# Then you can run python summarize_schema.py schema.sql to get the summarized schema.
|
|
|
|
import re
|
|
import sys
|
|
from collections import defaultdict
|
|
|
|
def summarize_schema(file_path):
|
|
"""
|
|
Parses a PostgreSQL dump file and extracts a summarized schema.
|
|
"""
|
|
tables = defaultdict(lambda: {'columns': [], 'pks': set(), 'fks': [], 'indexes': []})
|
|
enums = defaultdict(list)
|
|
|
|
# Use state variables to parse multi-line definitions
|
|
current_table = None
|
|
current_enum = None
|
|
|
|
with open(file_path, 'r', encoding='utf-8') as f:
|
|
for line in f:
|
|
line = line.strip()
|
|
|
|
# --- State Resets ---
|
|
if line.startswith(');'):
|
|
current_table = None
|
|
current_enum = None
|
|
continue
|
|
|
|
# --- Parse ENUM definitions ---
|
|
match_enum = re.match(r"CREATE TYPE public\.(\w+) AS ENUM \($", line)
|
|
if match_enum:
|
|
current_enum = match_enum.group(1)
|
|
continue
|
|
|
|
if current_enum:
|
|
# Extract enum values, which are typically like 'value',
|
|
value = line.strip("',")
|
|
if value and not value.startswith('--'):
|
|
enums[current_enum].append(value)
|
|
continue
|
|
|
|
# --- Parse TABLE definitions ---
|
|
match_table = re.match(r"CREATE TABLE public\.(\w+) \($", line)
|
|
if match_table:
|
|
current_table = match_table.group(1)
|
|
continue
|
|
|
|
if current_table:
|
|
# Parse columns within a CREATE TABLE block
|
|
# e.g., "column_name type NOT NULL,"
|
|
# e.g., "id bigint NOT NULL,"
|
|
match_column = re.match(r'^"?(\w+)"?\s+([\w\d\.\[\]\(\)]+)', line)
|
|
if match_column:
|
|
col_name = match_column.group(1)
|
|
col_type = match_column.group(2)
|
|
tables[current_table]['columns'].append(f"{col_name} ({col_type})")
|
|
|
|
# Parse PRIMARY KEY defined inside the table
|
|
match_pk = re.search(r"CONSTRAINT \w+ PRIMARY KEY \((.+)\)", line)
|
|
if match_pk:
|
|
# Handle multiple PK columns: "col1, col2, col3"
|
|
pk_cols = [p.strip().strip('"') for p in match_pk.group(1).split(',')]
|
|
tables[current_table]['pks'].update(pk_cols)
|
|
continue
|
|
|
|
# --- Parse Foreign Keys (defined outside CREATE TABLE) ---
|
|
match_fk = re.match(r"ALTER TABLE ONLY public\.(\w+)\s+ADD CONSTRAINT \w+ FOREIGN KEY \(([\w,\s\"]+)\) REFERENCES public\.(\w+)\(([\w,\s\"]+)\);", line)
|
|
if match_fk:
|
|
from_table, from_cols, to_table, to_cols = match_fk.groups()
|
|
# Clean up column names
|
|
from_cols_clean = ', '.join([c.strip().strip('"') for c in from_cols.split(',')])
|
|
to_cols_clean = ', '.join([c.strip().strip('"') for c in to_cols.split(',')])
|
|
|
|
fk_string = f"({from_cols_clean}) -> {to_table}({to_cols_clean})"
|
|
tables[from_table]['fks'].append(fk_string)
|
|
|
|
# --- Parse Index definitions ---
|
|
match_index = re.match(r"CREATE (UNIQUE )?INDEX (\w+) ON public\.(\w+) USING (\w+) \((.+)\);", line)
|
|
if match_index:
|
|
is_unique = match_index.group(1) is not None
|
|
index_name = match_index.group(2)
|
|
table_name = match_index.group(3)
|
|
index_type = match_index.group(4)
|
|
columns = match_index.group(5)
|
|
|
|
# Clean up column expressions
|
|
columns_clean = columns.replace('"', '')
|
|
|
|
unique_str = "UNIQUE " if is_unique else ""
|
|
index_string = f"{unique_str}INDEX {index_name} ({index_type}) ON ({columns_clean})"
|
|
tables[table_name]['indexes'].append(index_string)
|
|
|
|
return enums, tables
|
|
|
|
def format_output(enums, tables):
|
|
"""
|
|
Formats the parsed schema data into a clean, readable string.
|
|
"""
|
|
output = []
|
|
|
|
output.append("### Simplified Database Schema ###")
|
|
output.append("\n--- Custom Data Types (ENUMs) ---\n")
|
|
if not enums:
|
|
output.append("No custom ENUM types found.")
|
|
else:
|
|
for name, values in sorted(enums.items()):
|
|
output.append(f"{name}:")
|
|
for v in values:
|
|
output.append(f" - {v}")
|
|
output.append("")
|
|
|
|
output.append("\n--- Tables and Relationships ---\n")
|
|
if not tables:
|
|
output.append("No tables found.")
|
|
else:
|
|
for name, data in sorted(tables.items()):
|
|
output.append(f"TABLE: {name}")
|
|
for col in data['columns']:
|
|
col_name = col.split(' ')[0]
|
|
marker = " (PK)" if col_name in data['pks'] else ""
|
|
output.append(f" - {col}{marker}")
|
|
|
|
if data['fks']:
|
|
output.append(" Relationships:")
|
|
for fk in data['fks']:
|
|
output.append(f" - {fk}")
|
|
|
|
if data['indexes']:
|
|
output.append(" Indexes:")
|
|
for idx in data['indexes']:
|
|
output.append(f" - {idx}")
|
|
output.append("-" * 20)
|
|
|
|
return "\n".join(output)
|
|
|
|
if __name__ == "__main__":
|
|
if len(sys.argv) != 2:
|
|
print(f"Usage: python {sys.argv[0]} <path_to_dump.sql>")
|
|
sys.exit(1)
|
|
|
|
input_file = sys.argv[1]
|
|
|
|
try:
|
|
enums_data, tables_data = summarize_schema(input_file)
|
|
formatted_summary = format_output(enums_data, tables_data)
|
|
print(formatted_summary)
|
|
except FileNotFoundError:
|
|
print(f"Error: The file '{input_file}' was not found.")
|
|
sys.exit(1)
|
|
except Exception as e:
|
|
print(f"An unexpected error occurred: {e}")
|
|
sys.exit(1) |