Files
DBClient/CLAUDE.md
T
2026-05-21 15:46:41 -04:00

6.4 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

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 Mocha dark theme applied globally at startup in main.py. Widget-specific overrides belong here, not as inline setStyleSheet() calls.


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.


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)
  • Results panel (sortable QAbstractTableModel, export CSV/JSON/SQL INSERT, pagination, execution time)
  • Table viewer (paginated grid 50/100/All, add/edit/delete rows via dialog, WHERE filter, bcrypt password hashing)
  • 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)
  • 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)

Not Yet Implemented

  • (All planned features are now complete)