Skip to content

LuciusChen/clutch

Repository files navigation

clutch — Interactive Database Client for Emacs

Overview

An Emacs database client with an interactive data browsing UI. Supports MySQL, PostgreSQL, and SQLite via pure Elisp backends, plus Oracle, SQL Server, DB2, Snowflake, and Amazon Redshift through a lightweight JVM sidecar (clutch-jdbc-agent, Java 17+).

  • mysql.el — pure Elisp MySQL wire protocol implementation (no external CLI needed)
  • pg.el — pure Elisp PostgreSQL wire protocol v3 implementation
  • clutch-db.el — generic database interface (cl-defgeneric dispatch)
  • clutch-db-mysql.el — MySQL backend adapter
  • clutch-db-pg.el — PostgreSQL backend adapter
  • clutch-db-sqlite.el — SQLite backend adapter (uses Emacs 29+ built-in sqlite)
  • clutch-db-jdbc.el — JDBC backend adapter (Oracle, SQL Server, DB2, Snowflake, Redshift, and any JDBC-compatible database)
  • clutch.el — interactive UI layer: column-paged result browser, record view, FK navigation, editing, transient menus, REPL
  • ob-clutch.el — Org-Babel integration (mysql/postgresql/sqlite)

assets/clutch-overview.png

Note: MySQL is the primary backend — thoroughly tested and used daily. Native MySQL support has been live-validated against MySQL 5.6, 8.0, and MariaDB 10.11 through the same mysql backend. PostgreSQL support is functional and compatible, but has not been tested as extensively in daily use. SQLite support requires Emacs 29+ (built-in sqlite-* functions); no external dependencies. The JDBC backend requires Java 17+ and clutch-jdbc-agent.jar (auto-downloaded on first use).

Features

Protocol Layer (mysql.el)

  • Pure Elisp — no external dependencies
  • MySQL wire protocol (HandshakeV10 + HandshakeResponse41)
  • mysql_native_password and caching_sha2_password authentication (caching_sha2_password auto-retries over TLS when the server requires a secure channel)
  • COM_QUERY (text protocol) for executing SQL
  • Prepared statements (COM_STMT_PREPARE / COM_STMT_EXECUTE / COM_STMT_CLOSE) with binary protocol
  • TLS/SSL support via Emacs built-in GnuTLS (STARTTLS upgrade)
  • Extended type system: DATE, TIME, DATETIME, TIMESTAMP, BIT with structured parsing
  • Convenience macros: with-mysql-connection, with-mysql-transaction
  • Utilities: mysql-ping, mysql-escape-identifier, mysql-escape-literal, mysql-connect/uri
  • Extensible type parsing via mysql-type-parsers
  • Proper error signaling with structured error types

Protocol Layer (pg.el)

  • Pure Elisp — no libpq or external CLI needed
  • PostgreSQL wire protocol v3
  • SCRAM-SHA-256 (SASL) and MD5 authentication
  • TLS/SSL support via Emacs built-in GnuTLS
  • OID-based type parsing (int, float, numeric, bool, date/time, json/jsonb, bytea)
  • Convenience macros: with-pg-connection, with-pg-transaction
  • Utilities: pg-ping, pg-escape-identifier, pg-escape-literal
  • Proper error signaling with structured error types

Interactive UI (clutch.el)

  • Column-paged result display (no horizontal scrolling)
  • SQL pagination (LIMIT/OFFSET) with fixed header and status bar
    • tab-line: row count, page, elapsed time, sort state, staged-change summary, aggregate status, no-PK guidance, and active filters (with nerd-icons, Unicode fallback)
    • header-line: column names with borders and underlined labels
  • After execution, focus stays in the SQL buffer while results refresh below
  • Last successfully executed SQL region is highlighted in the editor
  • Row numbers as a table column; sort/pin indicators via nerd-icons (with Unicode fallback)
  • Right-aligned numeric columns
  • Record buffer for single-row detail view
  • Foreign key detection and navigation
  • Inline cell editing with UPDATE generation
  • Inline INSERT (o) and DELETE (d) rows with confirmation
  • WHERE filtering (W), client-side fuzzy filter (/), SQL ORDER BY sorting (s)
  • Pin/unpin columns, adjust column widths
  • CSV/Org export, copy as INSERT
  • Schema browser with expandable column details (TAB to expand/collapse)
  • SQL completion: table/column names scoped to the current statement, with synchronous column loading kept conservative for large schemas, plus keywords
  • Eldoc: three-tier lookup — (1) schema-aware: table name shows database, column count, and table comment; column name shows table.column, type, nullability, PK/FK references, and comment; (2) live MySQL HELP lookup (MySQL only, result cached per session); (3) static reference covering ~150 SQL keywords and functions with call signatures and descriptions (works without a connection; MySQL/PG-specific entries annotated)
  • Mode-line spinner () while a query is executing, so you always know when clutch is waiting for the server
  • Dedicated query console per connection (clutch-query-console), with fast switching between open consoles (clutch-switch-console); console content persisted across Emacs sessions
  • Schema refresh state is explicit: eager backends refresh on connect, lazy backends start stale, query console buffer names surface schema... / schema~ / schema! / schema 42t style state markers, and the mode-line now turns schema~ / schema! into direct C-c C-s refresh/retry hints
  • Cache-backed schema actions also warn when the schema cache is stale, failed, or still refreshing, so completion / table prompts do not silently pretend cached metadata is current
  • The schema browser now surfaces the same recovery state at the top of the buffer, so stale / failed / refreshing remain visible even after you leave the console
  • Auto-reconnect: if the connection drops, clutch silently reconnects before the next query using the stored credentials
  • Value viewer (v): auto-detects JSON (pretty-printed with json-ts-mode), XML (formatted via xmllint if available, rendered with nxml-mode), or BLOB (concise text/hex preview); XML/BLOB size is shown in viewer header-line (without polluting content highlighting)
  • REPL mode (comint-based)

Requirements

  • Emacs 28.1+ for MySQL and PostgreSQL backends
  • Emacs 29.1+ for the SQLite backend (built-in sqlite-* functions)
  • MySQL 5.6+ / 8.x, PostgreSQL 12+, or SQLite 3 (via Emacs built-in)
  • MariaDB 10.11 has been live-validated through the native mysql backend; TLS with mysql_native_password has also been live-validated; other MariaDB versions are expected to be broadly compatible but are not yet part of the regular test matrix
  • Java 17+ for the JDBC backend (Oracle, SQL Server, DB2, Snowflake, Redshift)

For MySQL-family servers, core functionality (connect/query/schema browsing, editing, prepared statements, and clutch UI workflows) is expected to work on MySQL 5.6+ and on compatible MariaDB releases. Some newer server-side features remain version-specific:

  • MySQL 5.6 does not provide a native JSON column type
  • MariaDB exposes JSON as an alias over text with json_valid(...) checks
  • MariaDB 10.11 TLS has been validated with mysql_native_password; the older mysql_old_password plugin is not supported by the native client
  • newer auth plugins and TLS combinations vary by server family/version

Installation

Clone this repository and add it to your load-path (skip this if installed via a package manager):

(add-to-list 'load-path "/path/to/clutch")
(require 'clutch)

Interactive Client

Quick Start

1. Configure connections

(add-to-list 'load-path "/path/to/clutch")
(require 'clutch)

(setq clutch-connect-timeout-seconds 10
      clutch-read-idle-timeout-seconds 30
      clutch-query-timeout-seconds 20
      clutch-jdbc-rpc-timeout-seconds 15)

(setq clutch-connection-alist
      '(("dev-mysql"  . (:backend mysql
                          :host "127.0.0.1" :port 3306
                          :user "root" :password "test"
                          :database "mydb"
                          :connect-timeout 5
                          :read-idle-timeout 60))
        ("dev-pg"     . (:backend pg
                          :host "127.0.0.1" :port 5432
                          :user "postgres" :password "test"
                          :database "mydb"))
        ("dev-sqlite" . (:backend sqlite
                          :database "/path/to/my.db"))))

Each entry should specify :backend explicitly (mysql, pg, sqlite, or a JDBC driver symbol). Omitting :backend defaults to mysql, but explicit is clearer. SQLite connections only require :database (the file path); no host, port, or user. Use ":memory:" for a transient in-memory database (data is lost when disconnected). :password is optional for network backends — see *Password Management for auth-source integration. For networked backends, you can set :connect-timeout and :read-idle-timeout (seconds) per connection entry. JDBC connections also accept :query-timeout and :rpc-timeout. TLS can be enabled per entry with :tls t; see backend TLS sections above for verification defaults and CA trustfile configuration.

JDBC backends use a driver symbol as :backend instead of mysql=/=pg. See *JDBC Backend (clutch-db-jdbc.el) for the full list of supported databases and setup.

2. Open a query console

M-x clutch-query-console   ;; Select a saved connection → opens *clutch: NAME*
                            ;; and connects automatically
                            ;; Mode-line: MySQL[[email protected]:3306/mydb]
                            ;;         or PostgreSQL[[email protected]:5432/mydb]
                            ;;         or SQLite[?@?:?//path/to/my.db]

Repeated calls with the same name switch to the existing buffer instead of opening a new one. Use clutch-switch-console to jump between open consoles.

3. Write and execute SQL

SELECT * FROM users LIMIT 10;

Press C-c C-c to execute. If a region is selected, the selected SQL runs; otherwise the statement at point runs. Results appear in a split result buffer below.

Password Management

:password is optional in connection entries. When omitted (or nil), the password is looked up via Emacs’s built-in auth-source library, which supports:

  • ~/.authinfo / ~/.authinfo.gpg (netrc format)
  • pass (the Unix password manager), when auth-source-pass is enabled

~/.authinfo example

machine 127.0.0.1 login root password secret port 3306

Connection entry without hardcoded password:

("dev-mysql" . (:host "127.0.0.1" :port 3306 :user "root" :database "mydb"))

pass example — connection name as entry name (recommended)

When auth-source-pass is loaded, clutch automatically looks up a pass entry whose path ends with the connection name. The entry can live anywhere in the pass store — subdirectories are fine:

(require 'auth-source-pass)

(setq clutch-connection-alist
      '(("dev-mysql" . (:backend mysql
                         :host "db.example.com" :port 3306
                         :user "alice" :database "mydb"))))

Store the password under any path that ends with the connection name:

pass insert mysql/dev-mysql   ;; or just: pass insert dev-mysql

The pass store reveals only mysql/dev-mysql — no host or username. Use :pass-entry "other-name" to override the suffix when the entry name must differ from the connection name.

For JDBC connections (including Org-Babel blocks), an explicit :pass-entry that resolves to no password now fails fast in Emacs instead of sending a null password to the driver. If you use pass, make sure auth-source-pass is enabled first.

Fallback: standard auth-source lookup

If no pass entry matches the connection name, clutch falls back to standard auth-source-search by :host, :user, and :port. This covers ~/.authinfo, ~/.authinfo.gpg, and pass entries named after the host. See the auth-source manual for details.

Working with SQL files

You can also open any .sql file, enable clutch-mode, and connect manually — the query console is not required.

1. Open a .sql file
2. M-x clutch-mode        — enable clutch (inherits sql-mode syntax/fontification)
3. C-c C-e                 — select a saved connection or enter params manually
                             Mode-line shows MySQL[[email protected]:3306/mydb]
4. C-c C-c                 — execute query at point

To activate clutch-mode automatically for .sql files, add to your config:

(add-to-list 'auto-mode-alist '("\\.sql\\'" . clutch-mode))

.mysql files activate clutch-mode automatically without any configuration.

SQL Editing (clutch-mode) Key Bindings

KeyAction
C-c C-cExecute region or query at point
C-c C-rExecute selected region
C-c C-bExecute entire buffer
C-c C-eConnect / reconnect
C-c C-tList all tables
C-c C-jBrowse table data
C-c C-dDESCRIBE table at point (errors if not on a table name)
C-c C-pPreview execution (dry-run)
C-c C-sRefresh schema for current connection
C-c ?Transient menu
TABComplete table/column name

Result Browser (clutch-result-mode) Key Bindings

KeyAction
RETOpen record view
TAB / S-TABNext cell / previous cell
n / pNext row / previous row (same column)
N / PNext page / previous page (SQL pagination)
M-> / M-<Last page / first page
#Query total row count
AAggregate numeric values (region/current cell; C-u with region enters visual refine mode)
]Next column page
[Previous column page
===Widen current column
-Narrow current column
C-c pPin current column
C-c PUnpin column
WWHERE filter (press again to clear)
/Client-side fuzzy filter (empty to clear)
sSort by column (toggles ASC/DESC on repeat)
SSort descending
CJump to column
gRe-execute query
C-c 'Edit / re-edit at point
iStage new row for insertion
dStage row(s) for deletion (supports region)
C-c C-cCommit all pending changes (INSERT/UPDATE/DELETE)
C-c C-kDiscard pending change at point
C-c C-pPreview execution (pending batch if any; otherwise effective query)
cOpen copy transient menu (choose TSV / CSV / INSERT / UPDATE; toggle -r to refine)
vView cell value (JSON / XML / BLOB preview — auto-detected)
eExport all rows (CSV / INSERT / UPDATE copy/file)
fToggle fullscreen
C-c ?Transient menu

Tip: c supports both regular region and rectangular selection (C-x SPC). Tip: use A after C-x SPC to aggregate selected numeric cells quickly. Tip: the result-buffer status line uses compact staged-change tokens: E-<n> for pending edits, D-<n> for pending deletions, and I-<n> for pending inserts. When a query result is not updateable because no primary key was detected, the same line shows PK missing and E/D off. Tip: pending changes can be previewed with C-c C-p and copied/saved from the result transient as the exact staged SQL batch.

Insert Buffer Keys

The insert buffer is intentionally form-like: TAB moves between fields, while completion stays on Emacs’ completion keys.

KeyAction
RETAccept current field and move to next field
TAB / S-TABNext field / previous field
M-TAB / C-M-iComplete current field value (prefers CAPF; falls back to chooser)
C-c 'Edit current JSON field in a dedicated buffer
C-c .Set current date/time field to “now”
C-c C-cStage new row / update staged insert
C-c C-kCancel

Field labels are read-only, aligned to a shared value column, and may show tags such as generated, default…, enum, bool, json, and required. The active field line is highlighted. Insert buffers also validate locally as you type: obvious enum / bool / json / numeric / date-time mistakes are shown inline on the current field before staging.

JSON fields still support direct editing in the insert buffer, but C-c ' opens an on-demand child editor for more comfortable multi-line editing. Saving that child editor writes compact JSON back into the insert form, so the primary flow remains result buffer -> insert buffer.

Visual Refine Mode (c -r / C-u A)

The copy transient (c) offers a -r switch for refine mode. With an active region, enabling -r before choosing a format lets you interactively exclude rows or columns before the copy is performed. The tab-line at the top of the result buffer is replaced with a color-coded hint for the duration of the session.

Step 1 — select a region, then press c.
         The transient menu appears.

Step 2 — press -r to toggle refine mode (shown in the Options section).

Step 3 — press t / c / i to choose the format (TSV / CSV / INSERT).
         The rect is highlighted; tab-line shows available keys.

  REFINE  m row   x col   RET confirm   C-g cancel     ← tab-line

  id   name    email              joined
  ─────────────────────────────────────────────
  1  ░ Alice ░ [email protected] ░ 2023-01-15  ┐
  2  ░ Bob   ░ [email protected]   ░ 2023-03-22  ├─ rect (highlighted)
  3  ░ Carol ░ [email protected] ░ 2024-07-01  ┘
  4    Dave    [email protected]    2024-09-10     (outside rect)

Step 4 — press m on row 2 to exclude it (dim + strikethrough).

  1  ░ Alice ░ [email protected] ░ 2023-01-15
  2    Bob     [email protected]     2023-03-22   ← excluded
  3  ░ Carol ░ [email protected] ░ 2024-07-01

Step 5 — press x on the email column to exclude it.

  1  ░ Alice ░ ~~email~~         ░ 2023-01-15
  3  ░ Carol ░ ~~email~~         ░ 2024-07-01

Step 6 — press RET to confirm.
         Data is copied in the chosen format.
         Result: rows {1, 3} × cols {id, name, joined}.
KeyAction
mToggle exclusion of row at point
xToggle exclusion of column at point
RETConfirm — execute copy / aggregate
C-gCancel — discard all exclusions

Record View (clutch-record-mode) Key Bindings

KeyAction
RETExpand long field / follow FK
n / pNext row / previous row
vView field value (JSON / XML / BLOB preview)
gRefresh
qQuit
C-c ?Transient menu

Typical Workflows

Query + Pagination + Filter + Sort

1. Write SQL:  SELECT * FROM orders;
2. C-c C-c    Execute → results displayed with pagination (default 500 rows/page)
              → tab-line shows: Σ 500 of ? rows • 1 / 1 • ⏱ 42ms
3. N / P      Next page / previous page
4. #          Query total rows → tab-line shows "Σ 500 of 1234 rows"
5. ] / [      Move across column pages when result has many columns
              → pinned columns stay visible while column pages switch
6. W          Enter filter: status = 'pending'
              → Appends WHERE clause, re-queries from page 1
7. /          Client-side filter: type a pattern to narrow visible rows instantly
8. s          Sort by current column (SQL ORDER BY), re-queries from page 1
              → tab-line adds current ORDER BY state
9. A          Aggregate selected cells/current cell
              → tab-line adds aggregate status (sum/avg/min/max + [rows/cells/skipped])
10. W         Enter empty string → clears filter, restores original query

Foreign Key Navigation

1. SELECT * FROM orders;     → C-c C-c to execute
2. Move cursor to a value in the user_id column (FK columns are underlined)
3. RET → opens record view
4. Press RET on an FK field → automatically runs SELECT * FROM users WHERE id = <value>

Editing Data

1. SELECT * FROM users;      → C-c C-c to execute
2. Move cursor to the cell to modify
3. C-c '                      → opens edit buffer; JSON columns jump straight into the JSON sub-editor
4. Header shows edit-relevant tags like [enum], [json], or [datetime]
                               M-TAB completes enum / bool-like values when available
                               non-JSON edit buffers still use C-c ' for the JSON sub-editor fallback
                               C-c . sets temporal columns to now
5. Edit the value, C-c C-c to stage (C-c C-k to cancel)
                               enum / bool / numeric / temporal fields validate locally as you edit
                               JSON validation follows the same model but waits for a short idle pause
                               validation shows a compact inline token instead of a long full-line message
                               C-c C-c reuses the same validation rules before staging
                               tab-line shows staged edit/delete/insert summary
6. Repeat for other cells — modified cells are highlighted
7. C-c C-c in result buffer  → confirmation prompt showing the UPDATE statements

Insert / Delete Rows

1. SELECT * FROM users;      → C-c C-c to execute
2. i                          → opens insert buffer with all column fields
3. RET / TAB / S-TAB          → move quickly between fields
                               field labels are read-only, aligned, and can show tags like [enum], [json], [default=...]
4. M-TAB / C-M-i              → complete enum / bool-like fields (falls back to a chooser if needed)
5. C-c ' on a JSON field      → open a dedicated JSON editor when the value gets large
6. C-c .                      → set current date/time field to "now"
7. Inline validation          → field-level mistakes show up immediately while editing
                               JSON validation waits for a short idle pause
                               validation uses compact inline tokens so long errors do not deform the form layout
8. C-c C-c                    → validate locally, then stage (C-c C-k cancels)
                               staged rows appear as ghost rows in result buffer
                               left marker shows =I= and status line shows =I-<n>=
                               missing generated/defaulted columns render as <generated> / <default>
9. C-c ' on a pending insert row → re-open that staged insert with its previous values
10. Use normal Emacs region selection to select rows
11. d                          → stage current row or selected region for deletion
                               left marker shows =D= and status line shows =D-<n>=
                               deleted rows are visually marked in the result buffer
12. C-c C-k                   → discard pending change at point
13. g with pending changes    → prompts "Discard pending changes and re-run query?"
14. C-c C-c in result buffer → confirmation prompt showing all statements
                               commits in order: INSERT → UPDATE → DELETE
                               edited rows show =E= in the left marker column and =E-<n>= in the status line

REPL Mode

M-x clutch-repl          ;; Open REPL buffer (*clutch REPL*)
C-c C-e                     ;; Connect to database
db> SELECT * FROM users;     ;; Type SQL, auto-executes on ;
                             ;; Multi-line input supported (continuation prompt     ->)

Query Console

clutch-query-console opens a dedicated buffer *clutch: NAME* for a saved connection, enables clutch-mode, and connects automatically. Repeated calls with the same name switch to the existing buffer rather than creating a new one.

clutch-switch-console lists all open console buffers via completing-read for fast switching between active connections.

Console persistence

Buffer content is automatically saved to clutch-console-directory (default: ~/.emacs.d/clutch/) and restored the next time the same console is opened. Each connection gets its own file (e.g. dev-mysql.sql). Content is saved when the buffer is killed or when Emacs exits.

Suggested global bindings:

(global-set-key (kbd "C-c d") #'clutch-query-console)
(global-set-key (kbd "C-c D") #'clutch-switch-console)

Schema Browser

C-c C-t                     ;; List all tables
C-c C-j                     ;; Browse table data (select table, insert SELECT into console)
TAB                          ;; Expand/collapse table columns (type, PK, FK)
E / C                        ;; Expand all / Collapse all
RET                          ;; Show DDL for table at point
v                            ;; Browse table data at point (insert SELECT into console)
g                            ;; Refresh

Embark Integration (optional)

If embark is installed, clutch registers a clutch-table target type so table names can be acted on with a consistent action map:

KeyAction
bBrowse table (SELECT *)
dDescribe table (show DDL)
wCopy table name to kill ring

Table names are recognised as targets in three contexts:

  • =clutch-schema-mode= buffer — table name on the current line
  • =clutch-mode= (SQL buffer) — symbol at point, validated against the schema cache
  • =completing-read= minibuffer — current candidate in clutch-browse-table or clutch-describe-table

Example: invoke C-c C-j (browse table), navigate to a candidate, then press the Embark key and d to describe it instead — without cancelling and re-running.

JDBC Backend (clutch-db-jdbc.el)

clutch-db-jdbc.el adds support for databases that have no pure Elisp wire protocol implementation. It delegates all SQL execution and metadata queries to clutch-jdbc-agent, a minimal JVM sidecar process that speaks a one-JSON-object-per-line protocol on stdin/stdout. Java 17+ is required.

Supported Databases

Backend symbolDatabaseDriver auto-installDefault port
oracleOracle DatabaseMaven Central1521
sqlserverMicrosoft SQL ServerMaven Central1433
redshiftAmazon RedshiftMaven Central5439
snowflakeSnowflakeMaven Central
db2IBM DB2Manual download50000

Any other JDBC-compatible database (ClickHouse, TiDB, Hive, etc.) can be used by passing :url "jdbc:xxx://..." directly and placing the driver jar in the drivers directory.

Setup

(require 'clutch-db-jdbc)

;; Download clutch-jdbc-agent.jar (once):
(clutch-jdbc-ensure-agent)

;; Optional: allow a locally built jar with checksum verification disabled.
;; Keep this nil only if you intentionally manage the jar yourself.
(setq clutch-jdbc-agent-sha256 nil)

;; Install a JDBC driver (Oracle, SQL Server, Snowflake, Redshift):
(clutch-jdbc-install-driver 'oracle)      ; ojdbc8.jar + orai18n.jar from Maven Central
(clutch-jdbc-install-driver 'oracle-11)   ; ojdbc11.jar + orai18n.jar for newer Oracle-only setups
(clutch-jdbc-install-driver 'sqlserver)   ; mssql-jdbc.jar (JRE-classified SQL Server driver) from Maven Central
(clutch-jdbc-install-driver 'snowflake)   ; snowflake-jdbc.jar from Maven Central
(clutch-jdbc-install-driver 'redshift)    ; redshift-jdbc.jar from Maven Central
;; DB2: download db2jcc4.jar from IBM manually and place in clutch-jdbc-agents-dir/drivers/

Driver jars are stored in ~/.emacs.d/clutch-jdbc/drivers/ (configurable via clutch-jdbc-agent-dir).

The agent jar itself is verified against the release SHA-256 before startup. This catches the common case where a local clutch-jdbc-agent-0.1.3.jar exists but does not actually match the current release asset. Running clutch-jdbc-ensure-agent also removes stale versioned clutch-jdbc-agent-*.jar files from the same directory.

If you intentionally use a locally built jar, set clutch-jdbc-agent-sha256 to nil to disable verification. That is the escape hatch for custom development builds.

Oracle installs now also pull orai18n.jar automatically, which is required for CJK database character sets such as ZHS16GBK. If you install drivers while the shared JDBC agent is already running, clutch will restart that agent on the next JDBC use; reconnecting is enough, you do not need to restart Emacs.

Oracle installs also keep the active JDBC line unambiguous: clutch-jdbc-install-driver 'oracle enables ojdbc8.jar and disables ojdbc11.jar by removing it if present; clutch-jdbc-install-driver 'oracle-11 does the inverse. This avoids loading both Oracle driver lines at once.

SQL Server installs use Microsoft’s classifier-based Maven artifacts (for example mssql-jdbc-13.4.0.jre11.jar) and normalize the local filename to mssql-jdbc.jar. If you manage the driver manually, use the JRE-classified artifact from Maven Central rather than a nonexistent plain mssql-jdbc-<version>.jar path.

Connection Examples

(setq clutch-connection-alist
      '(;; Oracle (service name style)
        ("prod-oracle" . (:backend oracle
                           :host "db.corp.com" :port 1521
                           :database "ORCL"           ; service name or SID
                           :user "scott" :pass-entry "prod-oracle"
                           :connect-timeout 8
                           :read-idle-timeout 30
                           :query-timeout 20
                           :rpc-timeout 25))

        ;; Oracle (SID style — use :sid instead of :database)
        ("prod-oracle-sid" . (:backend oracle
                               :host "db.corp.com" :port 1521
                               :sid "ORCL"
                               :user "scott" :pass-entry "prod-oracle"))

        ;; Microsoft SQL Server
        ("prod-mssql" . (:backend sqlserver
                          :host "sqlserver.corp.com" :port 1433
                          :database "AdventureWorks"
                          :user "sa" :pass-entry "prod-mssql"
                          :connect-timeout 8
                          :query-timeout 15))

        ;; Amazon Redshift
        ("prod-redshift" . (:backend redshift
                             :host "cluster.abc123.us-east-1.redshift.amazonaws.com"
                             :port 5439 :database "analytics"
                             :user "awsuser" :pass-entry "prod-redshift"))

        ;; Snowflake
        ("prod-snowflake" . (:backend snowflake
                              :host "myorg-myaccount"  ; account identifier
                              :database "MYDB"
                              :user "myuser" :pass-entry "prod-snowflake"))

        ;; Generic JDBC (any database with a JDBC driver jar in drivers/)
        ;; Use :url to bypass URL construction; pick any registered JDBC symbol as :backend.
        ("clickhouse" . (:backend sqlserver
                          :url "jdbc:clickhouse://ch.corp.com:8123/default"
                          :user "default" :password ""
                          :rpc-timeout 10))))

JDBC connections use the same :pass-entry auth-source integration as other backends. See *Password Management for details.

Oracle-specific Notes

  • Oracle driver default: clutch-jdbc-install-driver 'oracle now installs ojdbc8 by default. It is the safest choice across Oracle 11g/12c/19c and avoids the known 11g LOB compatibility problems seen with ojdbc11.
  • Oracle 11g: keep using ojdbc8. If ojdbc11.jar is present from an older setup, reinstall with (clutch-jdbc-install-driver 'oracle) to re-enable ojdbc8.jar and remove the conflicting ojdbc11.jar copy automatically.
  • Oracle 19c+ only: if you specifically want the newer driver line, install it with (clutch-jdbc-install-driver 'oracle-11).
  • SID vs service name: use :sid "MYSID" for SID-style connections (@host:port:SID); use :database "myservice" for service name style (@//host:port/service). Mixing them up causes the connection to hang. When in doubt, check with your DBA or use :url for full control.
  • Schema is automatically set to the uppercase username (Oracle convention).
  • Oracle/JDBC completion does not rely on a full eager schema refresh. Table names and column names are resolved through Oracle-specific prefix lookups, so connect stays non-blocking and completion does not have to enumerate the whole schema up front.
  • After connect (and after schema-affecting DDL), Oracle/JDBC also starts a background full table refresh. Completion stays responsive because it keeps using the prefix fast path, while the console’s schema~ state is eventually backfilled to schema Nt once the cache snapshot finishes loading.
  • Pagination uses ROWNUM subquery, compatible with all Oracle versions (9i+).
  • CJK character sets (e.g. ZHS16GBK) require orai18n.jar in addition to the active Oracle JDBC jar (ojdbc8.jar or ojdbc11.jar).
  • Oracle DATE columns include a time component; the agent always uses getTimestamp() to avoid silent data loss.

Additional JDBC Connection Properties

Pass arbitrary JDBC connection properties via :props:

("mssql-encrypt" . (:backend sqlserver
                     :host "sqlserver.corp.com" :port 1433
                     :database "mydb" :user "sa" :password "secret"
                     :props (("encrypt" . "true")
                             ("trustServerCertificate" . "true"))))

Transient Menus

Requires the transient package (built-in since Emacs 28.1).

Press C-c ? to open the transient menu. The Schema group contains List tables, Describe table, and Browse table.

In the result buffer, the Edit group in the transient menu provides the unified staged-commit workflow: i stages an insert, d stages a deletion, C-c C-k discards a pending change at point, and C-c C-c commits all pending changes (INSERT → UPDATE → DELETE) in a single confirmation.

Org-Babel Integration

Execute SQL directly in org files. All backends (MySQL, PostgreSQL, SQLite, and JDBC databases) are supported via ob-clutch.el.

Configuration:

(require 'ob-clutch)
;; For JDBC backends also load:
(require 'clutch-db-jdbc)

(with-eval-after-load 'org
  (org-babel-do-load-languages
   'org-babel-load-languages
   '((mysql . t)
     (postgresql . t)
     (sqlite . t)
     (clutch . t))))   ; generic block — needed for JDBC backends

Using a saved connection (works for any backend including JDBC):

#+begin_src mysql :connection dev
SELECT * FROM users LIMIT 5;
#+end_src
#+begin_src clutch :connection prod-oracle
SELECT * FROM employees WHERE ROWNUM <= 10;
#+end_src

Or with inline connection parameters:

#+begin_src mysql :host 127.0.0.1 :port 3306 :user root :password test :database mydb
SHOW TABLES;
#+end_src

PostgreSQL and SQLite blocks:

#+begin_src postgresql :connection dev-pg
SELECT now();
#+end_src
#+begin_src sqlite :database /tmp/demo.db
SELECT name FROM sqlite_master WHERE type = 'table';
#+end_src

Generic form with explicit backend — required for JDBC databases:

#+begin_src clutch :backend pg :host 127.0.0.1 :port 5432 :user postgres :password test :database mydb
SELECT version();
#+end_src
#+begin_src clutch :backend oracle :host db.corp.com :port 1521 :user scott :password tiger :database ORCL
SELECT table_name FROM user_tables;
#+end_src
#+begin_src clutch :backend sqlserver :host sqlserver.corp.com :port 1433 :user sa :password secret :database mydb
SELECT TOP 10 * FROM sys.tables;
#+end_src

Compatibility entry files:

  • ob-mysql.el, ob-postgresql.el, ob-sqlite.el — thin language entry points for the named block types.
  • ob-clutch.el — core execution logic; also registers the generic clutch block type.

C-c C-c executes the source block; results are inserted as an org table.

Header Arguments

ArgumentDescription
:connectionName from clutch-connection-alist (overrides all inline params)
:backendmysql, pg, sqlite, oracle, sqlserver, redshift, etc.
:hostDatabase host (default: 127.0.0.1)
:portDatabase port (omit to use driver default)
:userDatabase user
:databaseDatabase name or file path (SQLite)
:pass-entryauth-source entry name for password lookup

Connection Caching

Connections are cached by parameter hash and reused across source block evaluations in the same Emacs session. All cached connections are cleanly disconnected when Emacs exits (via kill-emacs-hook).

Query Timeout and Interrupt

  • clutch-connect-timeout-seconds controls connect timeout (default: 10s).
  • clutch-read-idle-timeout-seconds controls query I/O idle timeout (default: 30s).
  • clutch-query-timeout-seconds controls database-side statement timeout for JDBC and native PostgreSQL queries (default: 30s).
  • clutch-jdbc-rpc-timeout-seconds controls Emacs <-> JDBC agent RPC timeout (default: 30s).
  • MySQL connections can override via :connect-timeout and :read-idle-timeout.
  • PostgreSQL connections can override via :connect-timeout, :read-idle-timeout, and :query-timeout.
  • JDBC connections can override via :connect-timeout, :read-idle-timeout, :query-timeout, and :rpc-timeout.
  • During long-running queries, press C-g to interrupt.
    • clutch will drop the current connection to avoid protocol desync.
    • the next command auto-reconnects when saved connection params are available.
  • High-risk DML (UPDATE/DELETE without top-level WHERE) requires typed confirmation (YES) before execution.

Protocol Layer Usage (mysql.el)

(require 'mysql)

;; Connect
(setq conn (mysql-connect :host "127.0.0.1"
                           :port 3306
                           :user "root"
                           :password "secret"
                           :database "mydb"))

;; Query
(let ((result (mysql-query conn "SELECT * FROM users LIMIT 10")))
  (mysql-result-columns result)  ;; column metadata
  (mysql-result-rows result))    ;; list of rows (list of values)

;; DML
(let ((result (mysql-query conn "INSERT INTO users (name) VALUES ('alice')")))
  (mysql-result-affected-rows result)  ;; => 1
  (mysql-result-last-insert-id result)) ;; => auto-increment id

;; Disconnect
(mysql-disconnect conn)

TLS Connection

When :tls t is used, certificate and hostname verification are enabled by default (secure-by-default). If verification fails, connection setup aborts with an error; it does not silently downgrade to insecure mode.

For MySQL 8.x servers using caching_sha2_password, clutch will also retry the handshake over TLS when the server rejects cleartext-free full authentication on an insecure channel. That retry still honors verification settings: for local or self-signed dev servers, either configure mysql-tls-trustfiles or set mysql-tls-verify-server to nil explicitly.

To trust private/internal CAs, configure:

  • mysql-tls-trustfiles (list of CA certificate files)
  • mysql-tls-verify-server (defaults to t; set to nil only for local/dev)
  • mysql-tls-keylist for client certificates when required
;; Example: custom CA for private PKI
(setq mysql-tls-trustfiles '("/etc/ssl/certs/internal-ca.pem"))

(setq conn (mysql-connect :host "127.0.0.1"
                           :port 3306
                           :user "root"
                           :password "secret"
                           :database "mydb"
                           :tls t))

URI Connection

(setq conn (mysql-connect/uri "mysql://root:[email protected]:3306/mydb"))

Convenience Macros

;; Auto-close connection
(with-mysql-connection conn (:host "127.0.0.1" :user "root"
                             :password "secret" :database "mydb")
  (mysql-query conn "SELECT 1"))

;; Transaction with auto-rollback on error
(with-mysql-transaction conn
  (mysql-query conn "INSERT INTO users (name) VALUES ('alice')")
  (mysql-query conn "INSERT INTO users (name) VALUES ('bob')"))

Prepared Statements

(let ((stmt (mysql-prepare conn "SELECT * FROM users WHERE id = ?")))
  (let ((result (mysql-execute stmt 42)))
    (mysql-result-rows result))
  (mysql-stmt-close stmt))

Utilities

(mysql-ping conn)                          ;; => t
(mysql-escape-identifier "my`table")       ;; => "`my``table`"
(mysql-escape-literal "it's a \"test\"")   ;; => "'it\\'s a \\\"test\\\"'"

Protocol Layer Usage (pg.el)

(require 'pg)

;; Connect
(setq conn (pg-connect :host "127.0.0.1"
                        :port 5432
                        :user "postgres"
                        :password "secret"
                        :database "mydb"))

;; Query
(let ((result (pg-query conn "SELECT * FROM users LIMIT 10")))
  (pg-result-columns result)  ;; column metadata
  (pg-result-rows result))    ;; list of rows (list of values)

;; DML
(let ((result (pg-query conn "INSERT INTO users (name) VALUES ('alice')")))
  (pg-result-affected-rows result))  ;; => 1

;; Disconnect
(pg-disconnect conn)

TLS Connection

When :tls t is used, certificate and hostname verification are enabled by default (secure-by-default). If verification fails, connection setup aborts with an error; it does not silently downgrade to insecure mode.

To trust private/internal CAs, configure:

  • pg-tls-trustfiles (list of CA certificate files)
  • pg-tls-verify-server (defaults to t; set to nil only for local/dev)
  • pg-tls-keylist for client certificates when required
;; Example: custom CA for private PKI
(setq pg-tls-trustfiles '("/etc/ssl/certs/internal-ca.pem"))

(setq conn (pg-connect :host "127.0.0.1"
                        :port 5432
                        :user "postgres"
                        :password "secret"
                        :database "mydb"
                        :tls t))

Convenience Macros

;; Auto-close connection
(with-pg-connection conn (:host "127.0.0.1" :user "postgres"
                          :password "secret" :database "mydb")
  (pg-query conn "SELECT 1"))

;; Transaction with auto-rollback on error
(with-pg-transaction conn
  (pg-query conn "INSERT INTO users (name) VALUES ('alice')")
  (pg-query conn "INSERT INTO users (name) VALUES ('bob')"))

Testing

MySQL tests (require a running MySQL)

Start a MySQL instance:

docker run --rm -e MYSQL_ROOT_PASSWORD=testpass -e MYSQL_USER=testuser \
  -e MYSQL_PASSWORD=testpass -e MYSQL_DATABASE=testdb -p 3307:3306 mysql:8

Load test data and run tests:

mysql -h 127.0.0.1 -P 3307 -u testuser -ptestpass testdb < test-wide-table.sql
emacs --batch -L . -l test/test-run.el

PostgreSQL tests (require a running PostgreSQL)

Start a PostgreSQL instance:

docker run --rm -e POSTGRES_USER=testuser -e POSTGRES_PASSWORD=testpass \
  -e POSTGRES_DB=testdb -p 5433:5432 postgres:16-alpine

Run tests:

emacs --batch -L . -l test/pg-test.el

Unit tests (message encoding, type parsing, auth) run without a server. Live tests require the Docker instance above.

Roadmap

  • SQL rewrite engine (AST-based) for safer, semantics-aware transformations across complex queries (CTE/UNION/nested SELECT) and dialect differences. Current filtering uses a safe derived-table wrapper; full AST rewriting is planned for future iterations.

License

GPL-3.0-or-later. See LICENSE.

About

Interactive database client for Emacs — MySQL, PostgreSQL, SQLite (pure Elisp) plus Oracle, SQL Server, DB2, Snowflake & Redshift via JDBC; paginated result browser, inline editing, schema browser, and REPL.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors