Files
DBClient/CLAUDE.md
T
2026-05-21 17:25:15 -04:00

11 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Overview

DBClient is a desktop database client built with Python + PyQt6, supporting MySQL, PostgreSQL, SQLite, and MSSQL. Inspired by DBeaver / TablePlus.


Development Commands

Install dependencies:

pip install -r requirements.txt

Key runtime dependencies: PyQt6, sqlparse>=0.5.0, bcrypt, keyring, cryptography.

Run the application:

python main.py

There is no test suite or linter config set up.

Build distributable (Windows):

pip install pyinstaller
python build_app.py           # one-folder bundle → dist/DBClient/
python build_app.py --onefile # single .exe       → dist/DBClient.exe
python build_app.py --clean   # wipe build/ and dist/ first

The PyInstaller spec is DBClient.spec; add new resource files / hidden imports there.


Architecture

Driver Abstraction (app/drivers/)

All database operations go through BaseDriver (app/drivers/base.py). The four concrete drivers (mysql, postgres, sqlite, mssql) each implement the same interface — the UI never imports a specific driver directly. Use the factory get_driver(db_type, config) from app/drivers/__init__.py. Adding a new DB type means subclassing BaseDriver and registering it in the factory.

Key data types defined in base.py: ColumnInfo, IndexInfo, ForeignKeyInfo, TableInfo.

Async DB Operations (app/utils/worker.py)

All database I/O runs in background threads. The pattern is:

  1. Create a QueryWorker / SchemaWorker / TableDataWorker (subclasses of QRunnable/QThread)
  2. Connect signals.result / signals.error to UI slots
  3. The worker calls the driver and emits results — never call driver methods directly from the GUI thread.

All worker errors are logged with exc_info=True (full traceback) via app.utils.logger.

Logging (app/utils/logger.py)

Call get_logger(__name__) anywhere to get a module logger. setup_logging() is called once in main.py and initialises:

  • TimedRotatingFileHandler~/.dbclient/logs/dbclient.log (daily rotation, 7-day retention)
  • StreamHandler to stderr (WARNING+ only, for dev)
  • sys.excepthook and threading.excepthook to capture uncaught exceptions with full tracebacks
  • qInstallMessageHandler to capture Qt internal warnings

View logs inside the app via Help → View App Logs (Ctrl+L).

Signal/Slot Flow

SchemaBrowser (left sidebar) emits signals when the user double-clicks a table or selects a database. MainWindow connects these to open workspace tabs (TableViewer, TableStructure, SQLEditor). New UI interactions should follow this pattern: sidebar emits, main window routes, tabs receive.

Data Persistence

All user data lives under ~/.dbclient/:

  • connections.json — connection profiles (passwords omitted; stored in OS keychain via keyring)
  • settings.json — app-wide preferences (theme, font size, page size, timeouts)
  • history.db — SQLite store for query history

app/config/connections.py handles profile CRUD + keyring integration. app/config/settings.py is a settings singleton.

UI Structure

MainWindow (app/main_window.py) owns a QSplitter with SchemaBrowser on the left and a QTabWidget workspace on the right. Workspace tabs are created dynamically: SQLEditor for query tabs, TableViewer for data browsing, TableStructure for DDL inspection.

The ResultTableModel (app/models/result_table_model.py) is a QAbstractTableModel — query results should always go through it rather than populating QTableWidget directly.

Styling

resources/style.qss is a Catppuccin Frappé dark theme applied globally at startup in main.py. Widget-specific overrides belong here, not as inline setStyleSheet() calls. Key palette values: base #303446, surface0 #414559, surface1 #51576d, text #c6d0f5, blue #8caaee, green #a6d189, red #e78284, peach #ef9f76, mauve #ca9ee6, sky #99d1db.

Recent Files (app/config/recent_files.py)

load_recent() / add_recent(path) / clear_recent() persist to ~/.dbclient/recent_files.json (max 10 entries, dead paths filtered on load). Called from MainWindow File menu.

Settings Cascade

PreferencesDialog emits settings_appliedMainWindow._apply_settings_to_open_tabs() iterates workspace tabs and calls widget.apply_settings() on each. SQLEditorWidget.apply_settings() cascades to each EditorTab, which cascades to CodeEditor. Add apply_settings() to any new workspace widget that reads from get_settings().


Known Quirks

TableViewer selection signals

QItemSelectionModel.selectionChanged becomes unreliable after beginResetModel/endResetModel cycles (triggered on every data load). Edit/Delete button enabling is driven by QTableView.clicked (primary, mouse) and selectionChanged (secondary, keyboard). Do not remove the clicked connection — removing it re-breaks the buttons.

NULL-aware WHERE clauses

All four drivers have a _where(where: dict) → (clause_str, params) static helper that emits col IS NULL for None values instead of col = NULL. All update_row and delete_row calls go through this helper. Do not bypass it.

Password hashing in RowDialog

RowDialog detects password-like column names (via _is_password_col()) and hashes plain-text input with bcrypt before storing. In edit mode, leaving a password field empty omits that column from the UPDATE so the existing hash is preserved.

ResultsPanel multi-tab architecture

ResultsPanel uses a QStackedWidget with two pages: page 0 = QTabWidget (result tabs), page 1 = loading spinner. Single-query results hide the tab bar; script results (show_script_results()) show named tabs. Each tab owns its own ResultTableModel + QSortFilterProxyModel. Export buttons operate on _current_model() from the active tab.

SQL formatter

EditorTab._format_sql() uses sqlparse.format(..., reindent=True, keyword_case='upper'). Requires sqlparse>=0.5.0 in requirements.txt.

Column statistics worker

_StatsWorker in app/ui/column_stats_dialog.py runs COUNT/COUNT(col)/COUNT(DISTINCT)/MIN/MAX in one query and AVG in a second (AVG silently returns None for non-numeric columns). Both queries are quoted with "col" and "table" to handle reserved words.


Implementation Status

Complete

  • Connection manager (add/edit/delete, test, color coding, keyring storage)
  • Schema browser (live tree: connections → databases → tables/views/functions/procedures/triggers, search, context menus, delete connection from active node)
  • SQL editor (multi-tab, syntax highlighting, line numbers, Ctrl+/ comment toggle, F5/Ctrl+Enter execute)
  • SQL auto-complete (app/ui/sql_completer.py): schema-aware popup, dot-notation (schema.table, table.col), Ctrl+Space trigger
  • Find/Replace bar in SQL editor (app/ui/find_bar.py): Ctrl+F find, Ctrl+H replace, wrap-around, case-sensitive toggle
  • SQL formatter: ≡ Format button in editor toolbar using sqlparse (reindent + uppercase keywords)
  • Results panel (sortable QAbstractTableModel, export CSV/JSON/SQL INSERT, pagination, execution time)
  • Multi-result tabs: scripts with multiple SELECTs show each result in a named sub-tab within ResultsPanel
  • Table viewer (paginated grid 50/100/All, add/edit/delete rows via dialog, WHERE filter, bcrypt password hashing)
  • Enhanced table context menu: "Filter by this value" (auto-populates WHERE filter), "Copy row as SQL INSERT"
  • Column statistics dialog (app/ui/column_stats_dialog.py): right-click column header → async COUNT/NULL/DISTINCT/MIN/MAX/AVG
  • Table structure view (columns, indexes, FKs, DDL with syntax highlighting, add/drop/rename column designer)
  • Query history (auto-log with timestamp/duration/status, search, replay, persisted in history.db)
  • All 4 DB drivers (MySQL, PostgreSQL, SQLite, MSSQL)
  • Process list viewer (app/ui/process_list.py) with 5 s auto-refresh and kill query
  • Import CSV/JSON dialog (app/ui/import_dialog.py) with preview and progress bar
  • Database dump export (app/ui/dump_dialog.py): schema/data/both, table selector, progress bar
  • Tools menu in MainWindow (Process List, Import CSV/JSON, Export Database Dump, User Management, Preferences)
  • Preferences dialog (app/ui/preferences_dialog.py): Ctrl+, / Tools → Preferences; font family/size/word-wrap, page size, timeout, history limit, auto-commit; live-applies to all open editors via settings_applied signal
  • PyInstaller packaging (DBClient.spec + build_app.py)
  • User & privilege management (app/ui/user_manager.py): list/create/drop users, GRANT/REVOKE per-DB (MySQL + PostgreSQL)
  • EXPLAIN plan diagram (app/ui/explain_view.py): visual node tree + raw table; opened from SQL editor "🔎 Explain" button
  • Application logging (app/utils/logger.py + app/ui/log_viewer.py): rotating file logs, unhandled exception hooks, in-app log viewer (Help → View App Logs)
  • Keyboard shortcuts dialog (app/ui/shortcuts_dialog.py): Help → Keyboard Shortcuts; categorised tree of all key bindings
  • Tab context menus: workspace tabs (Rename/Close/Close Others/Close to Right) + SQL query tabs (Rename/Duplicate/Close/Close Others)
  • Smart status bar: right-side connection indicator + 60-char cell value preview (updates on cell click)
  • Connection tab coloring: workspace tab labels colored by connection profile color
  • File open/save/recent: Ctrl+O open SQL file, Ctrl+S save, File → Open Recent (persisted to ~/.dbclient/recent_files.json, max 10)
  • Ctrl+W to close current workspace tab
  • Catppuccin Frappé theme: full palette upgrade from Mocha; all colors updated in resources/style.qss, syntax highlighter, completer popup, and inline Python stylesheets

Not Yet Implemented

High impact:

  • Auto-reconnect on dropped connection (detect broken pipe, reconnect transparently)
  • Quick result filter: live filter bar above the results table (client-side, no re-query)
  • SQL snippets / bookmarks: save and recall frequently used query fragments
  • ERD viewer: visual entity-relationship diagram from live schema

Medium impact:

  • Transaction panel: explicit BEGIN / COMMIT / ROLLBACK controls with in-flight indicator
  • Global schema search: search across all tables/columns/procedures in a database
  • Light theme: Catppuccin Latte or similar, toggle from Preferences
  • Schema diff: compare two database schemas and show structural differences

Small polish:

  • Row numbers column in results grid (virtual, not from DB)
  • Connection ping indicator: periodic heartbeat dot in tab / status bar
  • Frozen / pinned columns in table viewer (keep PK column always visible while scrolling)
  • Keyboard navigation in RowDialog / connection dialogs (Tab order, Enter to confirm)