Initial Codes

This commit is contained in:
2026-05-21 15:46:41 -04:00
commit b01ad5ea40
40 changed files with 9102 additions and 0 deletions
+324
View File
@@ -0,0 +1,324 @@
"""
Table structure viewer — shows columns, indexes, foreign keys, and DDL.
"""
from PyQt6.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QTabWidget, QTableWidget,
QTableWidgetItem, QPlainTextEdit, QLabel, QPushButton, QHeaderView,
QMessageBox,
)
from PyQt6.QtCore import Qt, pyqtSignal
from PyQt6.QtGui import QColor, QFont
from app.ui.syntax_highlighter import SQLHighlighter
from app.ui.column_dialog import ColumnDialog
from app.utils.worker import SchemaWorker
class TableStructureView(QWidget):
status_message = pyqtSignal(str)
def __init__(self, driver, database: str, table: str, parent=None):
super().__init__(parent)
self._driver = driver
self._database = database
self._table = table
self._build_ui()
self._load_all()
def _build_ui(self):
root = QVBoxLayout(self)
root.setContentsMargins(0, 0, 0, 0)
# Header
hdr = QHBoxLayout()
hdr.setContentsMargins(8, 6, 8, 4)
title = QLabel(f"🏗️ {self._table}")
title.setObjectName("structureTitle")
f = title.font()
f.setBold(True)
f.setPointSize(12)
title.setFont(f)
refresh_btn = QPushButton("🔄 Refresh")
refresh_btn.setFixedWidth(90)
refresh_btn.clicked.connect(self._load_all)
hdr.addWidget(title)
hdr.addStretch()
hdr.addWidget(refresh_btn)
root.addLayout(hdr)
tabs = QTabWidget()
tabs.addTab(self._build_columns_tab(), "Columns")
tabs.addTab(self._build_indexes_tab(), "Indexes")
tabs.addTab(self._build_fks_tab(), "Foreign Keys")
tabs.addTab(self._build_ddl_tab(), "DDL")
root.addWidget(tabs)
# ── Tab builders ──────────────────────────────────────────────────────────
def _build_columns_tab(self) -> QWidget:
w = QWidget()
lay = QVBoxLayout(w)
lay.setContentsMargins(0, 4, 0, 0)
# Designer toolbar
toolbar = QHBoxLayout()
toolbar.setContentsMargins(4, 0, 4, 4)
self._add_col_btn = QPushButton(" Add Column")
self._add_col_btn.clicked.connect(self._add_column)
self._rename_col_btn = QPushButton("✏️ Rename")
self._rename_col_btn.setEnabled(False)
self._rename_col_btn.clicked.connect(self._rename_column)
self._drop_col_btn = QPushButton("🗑 Drop Column")
self._drop_col_btn.setObjectName("deleteBtn")
self._drop_col_btn.setEnabled(False)
self._drop_col_btn.clicked.connect(self._drop_column)
toolbar.addWidget(self._add_col_btn)
toolbar.addWidget(self._rename_col_btn)
toolbar.addWidget(self._drop_col_btn)
toolbar.addStretch()
lay.addLayout(toolbar)
self._col_table = self._make_table([
"Column", "Type", "Nullable", "Default", "PK", "FK", "Extra"
])
self._col_table.itemSelectionChanged.connect(self._on_col_selection)
lay.addWidget(self._col_table)
return w
def _build_indexes_tab(self) -> QWidget:
w = QWidget()
lay = QVBoxLayout(w)
lay.setContentsMargins(0, 0, 0, 0)
self._idx_table = self._make_table([
"Name", "Columns", "Unique", "Type"
])
lay.addWidget(self._idx_table)
return w
def _build_fks_tab(self) -> QWidget:
w = QWidget()
lay = QVBoxLayout(w)
lay.setContentsMargins(0, 0, 0, 0)
self._fk_table = self._make_table([
"Name", "Column", "References", "On Update", "On Delete"
])
lay.addWidget(self._fk_table)
return w
def _build_ddl_tab(self) -> QWidget:
w = QWidget()
lay = QVBoxLayout(w)
lay.setContentsMargins(0, 0, 0, 0)
self._ddl_view = QPlainTextEdit()
self._ddl_view.setReadOnly(True)
self._ddl_view.setFont(QFont("Consolas", 12))
SQLHighlighter(self._ddl_view.document())
lay.addWidget(self._ddl_view)
return w
@staticmethod
def _make_table(headers: list) -> QTableWidget:
t = QTableWidget(0, len(headers))
t.setHorizontalHeaderLabels(headers)
t.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
t.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents)
t.verticalHeader().setDefaultSectionSize(24)
t.setAlternatingRowColors(True)
t.setSelectionBehavior(QTableWidget.SelectionBehavior.SelectRows)
t.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers)
return t
# ── Data loading ──────────────────────────────────────────────────────────
def _load_all(self):
self._load_columns()
self._load_indexes()
self._load_fks()
self._load_ddl()
def _load_columns(self):
w = SchemaWorker(self._driver.get_columns,
self._database, self._table, parent=self)
w.result.connect(self._populate_columns)
w.error.connect(lambda e: self.status_message.emit(f"Error: {e}"))
w.start()
def _populate_columns(self, cols: list):
t = self._col_table
t.setRowCount(0)
for col in cols:
r = t.rowCount()
t.insertRow(r)
items = [
col.name,
col.data_type,
"YES" if col.nullable else "NO",
col.default or "",
"" if col.is_primary_key else "",
"" if col.is_foreign_key else "",
col.extra,
]
for c, val in enumerate(items):
item = QTableWidgetItem(str(val))
item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
if col.is_primary_key and c == 0:
item.setForeground(QColor("#f9e2af"))
elif col.is_foreign_key and c == 0:
item.setForeground(QColor("#89dceb"))
t.setItem(r, c, item)
def _load_indexes(self):
w = SchemaWorker(self._driver.get_indexes,
self._database, self._table, parent=self)
w.result.connect(self._populate_indexes)
w.error.connect(lambda e: self.status_message.emit(f"Error: {e}"))
w.start()
def _populate_indexes(self, indexes: list):
t = self._idx_table
t.setRowCount(0)
for idx in indexes:
r = t.rowCount()
t.insertRow(r)
vals = [
idx.name,
", ".join(idx.columns),
"" if idx.is_unique else "",
idx.index_type,
]
for c, val in enumerate(vals):
item = QTableWidgetItem(str(val))
item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
t.setItem(r, c, item)
def _load_fks(self):
w = SchemaWorker(self._driver.get_foreign_keys,
self._database, self._table, parent=self)
w.result.connect(self._populate_fks)
w.error.connect(lambda e: self.status_message.emit(f"Error: {e}"))
w.start()
def _populate_fks(self, fks: list):
t = self._fk_table
t.setRowCount(0)
for fk in fks:
r = t.rowCount()
t.insertRow(r)
vals = [
fk.name,
fk.column,
f"{fk.ref_table}.{fk.ref_column}",
fk.on_update,
fk.on_delete,
]
for c, val in enumerate(vals):
item = QTableWidgetItem(str(val))
item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
t.setItem(r, c, item)
def _load_ddl(self):
w = SchemaWorker(self._driver.get_table_ddl,
self._database, self._table, parent=self)
w.result.connect(self._ddl_view.setPlainText)
w.error.connect(lambda e: self._ddl_view.setPlainText(f"Error: {e}"))
w.start()
# ── Table designer ────────────────────────────────────────────────────────
def _on_col_selection(self):
has_sel = bool(self._col_table.selectedItems())
self._rename_col_btn.setEnabled(has_sel)
self._drop_col_btn.setEnabled(has_sel)
def _selected_col_name(self) -> str:
row = self._col_table.currentRow()
if row < 0:
return ""
item = self._col_table.item(row, 0)
return item.text() if item else ""
def _add_column(self):
dlg = ColumnDialog(mode="add",
db_type=getattr(self._driver, "db_type", ""),
parent=self)
if not dlg.exec():
return
w = SchemaWorker(
self._driver.add_column,
self._database, self._table,
dlg.column_name, dlg.column_type,
dlg.nullable,
dlg.default_value or None,
parent=self,
)
w.result.connect(lambda _: (
self.status_message.emit(
f"Column '{dlg.column_name}' added to {self._table}."
),
self._load_all(),
))
w.error.connect(lambda e: QMessageBox.critical(
self, "Add Column Error", f"Could not add column:\n{e}"
))
w.start()
def _drop_column(self):
col = self._selected_col_name()
if not col:
return
btn = QMessageBox.warning(
self, "Drop Column",
f"Drop column '{col}' from '{self._table}'?\n\nThis cannot be undone.",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
QMessageBox.StandardButton.No,
)
if btn != QMessageBox.StandardButton.Yes:
return
w = SchemaWorker(
self._driver.drop_column,
self._database, self._table, col,
parent=self,
)
w.result.connect(lambda _: (
self.status_message.emit(f"Column '{col}' dropped."),
self._load_all(),
))
w.error.connect(lambda e: QMessageBox.critical(
self, "Drop Column Error", f"Could not drop column:\n{e}"
))
w.start()
def _rename_column(self):
old_name = self._selected_col_name()
if not old_name:
return
dlg = ColumnDialog(mode="rename", column_name=old_name, parent=self)
if not dlg.exec():
return
new_name = dlg.column_name
if new_name == old_name:
return
w = SchemaWorker(
self._driver.rename_column,
self._database, self._table, old_name, new_name,
parent=self,
)
w.result.connect(lambda _: (
self.status_message.emit(
f"Column '{old_name}' renamed to '{new_name}'."
),
self._load_all(),
))
w.error.connect(lambda e: QMessageBox.critical(
self, "Rename Column Error", f"Could not rename column:\n{e}"
))
w.start()