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
+195
View File
@@ -0,0 +1,195 @@
"""
Query history panel — logs every executed query with timestamp and status.
Persists to ~/.dbclient/history.db (SQLite).
"""
import sqlite3
import datetime
from pathlib import Path
from PyQt6.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QTableWidget, QTableWidgetItem,
QLineEdit, QPushButton, QHeaderView, QAbstractItemView, QMenu,
QLabel,
)
from PyQt6.QtCore import Qt, pyqtSignal
from PyQt6.QtGui import QColor, QFont
_HISTORY_DB = Path.home() / ".dbclient" / "history.db"
_MAX_HISTORY = 500
def _open_db() -> sqlite3.Connection:
_HISTORY_DB.parent.mkdir(parents=True, exist_ok=True)
conn = sqlite3.connect(_HISTORY_DB)
conn.execute("""
CREATE TABLE IF NOT EXISTS history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ts TEXT NOT NULL,
db_type TEXT,
database TEXT,
sql TEXT,
duration REAL,
status TEXT
)
""")
conn.commit()
return conn
def record_query(db_type: str, database: str, sql: str,
duration: float, status: str = "OK") -> None:
"""Insert a history record (called from query worker result slot)."""
try:
conn = _open_db()
conn.execute("""
INSERT INTO history (ts, db_type, database, sql, duration, status)
VALUES (?, ?, ?, ?, ?, ?)
""", (
datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
db_type, database, sql[:2000], round(duration, 4), status
))
# Prune
conn.execute(f"""
DELETE FROM history WHERE id NOT IN (
SELECT id FROM history ORDER BY id DESC LIMIT {_MAX_HISTORY}
)
""")
conn.commit()
conn.close()
except Exception:
pass
class QueryHistoryPanel(QWidget):
"""Shows query history and emits signals to replay queries."""
run_query = pyqtSignal(str) # emitted when user re-runs a history item
def __init__(self, parent=None):
super().__init__(parent)
self._build_ui()
self.refresh()
def _build_ui(self):
root = QVBoxLayout(self)
root.setContentsMargins(0, 0, 0, 0)
root.setSpacing(0)
# Toolbar
tb = QHBoxLayout()
tb.setContentsMargins(6, 6, 6, 4)
self._search = QLineEdit()
self._search.setPlaceholderText("🔍 Search history…")
self._search.textChanged.connect(self._filter)
refresh_btn = QPushButton("🔄")
refresh_btn.setFixedWidth(34)
refresh_btn.setToolTip("Refresh")
refresh_btn.clicked.connect(self.refresh)
clear_btn = QPushButton("🧹")
clear_btn.setFixedWidth(34)
clear_btn.setToolTip("Clear all history")
clear_btn.clicked.connect(self._clear_history)
tb.addWidget(self._search, 1)
tb.addWidget(refresh_btn)
tb.addWidget(clear_btn)
root.addLayout(tb)
# Table
self._table = QTableWidget(0, 5)
self._table.setHorizontalHeaderLabels(
["Timestamp", "Database", "Duration", "Status", "SQL"])
self._table.horizontalHeader().setSectionResizeMode(
QHeaderView.ResizeMode.ResizeToContents)
self._table.horizontalHeader().setSectionResizeMode(
4, QHeaderView.ResizeMode.Stretch)
self._table.verticalHeader().setDefaultSectionSize(24)
self._table.setSelectionBehavior(
QAbstractItemView.SelectionBehavior.SelectRows)
self._table.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers)
self._table.setAlternatingRowColors(True)
self._table.setContextMenuPolicy(
Qt.ContextMenuPolicy.CustomContextMenu)
self._table.customContextMenuRequested.connect(self._context_menu)
self._table.doubleClicked.connect(self._on_double_click)
root.addWidget(self._table)
# ── Data ─────────────────────────────────────────────────────────────────
def refresh(self):
self._load(self._search.text())
def _load(self, search: str = ""):
try:
conn = _open_db()
if search:
rows = conn.execute("""
SELECT ts, database, duration, status, sql
FROM history WHERE sql LIKE ? ORDER BY id DESC
""", (f"%{search}%",)).fetchall()
else:
rows = conn.execute("""
SELECT ts, database, duration, status, sql
FROM history ORDER BY id DESC
""").fetchall()
conn.close()
except Exception:
rows = []
self._table.setRowCount(0)
for ts, db, dur, status, sql in rows:
r = self._table.rowCount()
self._table.insertRow(r)
items = [
ts or "",
db or "",
f"{dur:.3f}s" if dur else "",
status or "",
(sql or "").replace("\n", " ")[:200],
]
for c, val in enumerate(items):
item = QTableWidgetItem(val)
if c == 3 and status == "ERROR":
item.setForeground(QColor("#f38ba8"))
self._table.setItem(r, c, item)
def _filter(self, text: str):
self._load(text)
def _clear_history(self):
try:
conn = _open_db()
conn.execute("DELETE FROM history")
conn.commit()
conn.close()
except Exception:
pass
self._table.setRowCount(0)
# ── Interactions ──────────────────────────────────────────────────────────
def _on_double_click(self, idx):
row = idx.row()
sql_item = self._table.item(row, 4)
if sql_item:
self.run_query.emit(sql_item.text())
def _context_menu(self, pos):
row = self._table.rowAt(pos.y())
if row < 0:
return
sql = self._table.item(row, 4)
if not sql:
return
menu = QMenu(self)
menu.addAction("▶ Run Query", lambda: self.run_query.emit(sql.text()))
menu.addAction("📋 Copy SQL", lambda: self._copy(sql.text()))
menu.exec(self._table.viewport().mapToGlobal(pos))
@staticmethod
def _copy(text: str):
from PyQt6.QtWidgets import QApplication
QApplication.clipboard().setText(text)