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 implementationclutch-db.el— generic database interface (cl-defgenericdispatch)clutch-db-mysql.el— MySQL backend adapterclutch-db-pg.el— PostgreSQL backend adapterclutch-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, REPLob-clutch.el— Org-Babel integration (mysql/postgresql/sqlite)
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).
- Pure Elisp — no external dependencies
- MySQL wire protocol (HandshakeV10 + HandshakeResponse41)
mysql_native_passwordandcaching_sha2_passwordauthentication (caching_sha2_passwordauto-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
- Pure Elisp — no
libpqor 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
- Column-paged result display (no horizontal scrolling)
- SQL pagination (
LIMIT/OFFSET) with fixed header and status bartab-line: row count, page, elapsed time, sort state, staged-change summary, aggregate status, no-PK guidance, and active filters (withnerd-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 (/), SQLORDER BYsorting (s) - Pin/unpin columns, adjust column widths
- CSV/Org export, copy as INSERT
- Schema browser with expandable column details (
TABto 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 MySQLHELPlookup (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 42tstyle state markers, and the mode-line now turnsschema~/schema!into directC-c C-srefresh/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/refreshingremain 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 withjson-ts-mode), XML (formatted viaxmllintif available, rendered withnxml-mode), or BLOB (concise text/hex preview); XML/BLOB size is shown in viewer header-line (without polluting content highlighting) - REPL mode (comint-based)
- 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
mysqlbackend; TLS withmysql_native_passwordhas 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
JSONas an alias over text withjson_valid(...)checks - MariaDB 10.11 TLS has been validated with
mysql_native_password; the oldermysql_old_passwordplugin is not supported by the native client - newer auth plugins and TLS combinations vary by server family/version
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)(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.
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.
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 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), whenauth-source-passis enabled
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"))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-mysqlThe 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.
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.
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.
| Key | Action |
|---|---|
C-c C-c | Execute region or query at point |
C-c C-r | Execute selected region |
C-c C-b | Execute entire buffer |
C-c C-e | Connect / reconnect |
C-c C-t | List all tables |
C-c C-j | Browse table data |
C-c C-d | DESCRIBE table at point (errors if not on a table name) |
C-c C-p | Preview execution (dry-run) |
C-c C-s | Refresh schema for current connection |
C-c ? | Transient menu |
TAB | Complete table/column name |
| Key | Action |
|---|---|
RET | Open record view |
TAB / S-TAB | Next cell / previous cell |
n / p | Next row / previous row (same column) |
N / P | Next page / previous page (SQL pagination) |
M-> / M-< | Last page / first page |
# | Query total row count |
A | Aggregate 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 p | Pin current column |
C-c P | Unpin column |
W | WHERE filter (press again to clear) |
/ | Client-side fuzzy filter (empty to clear) |
s | Sort by column (toggles ASC/DESC on repeat) |
S | Sort descending |
C | Jump to column |
g | Re-execute query |
C-c ' | Edit / re-edit at point |
i | Stage new row for insertion |
d | Stage row(s) for deletion (supports region) |
C-c C-c | Commit all pending changes (INSERT/UPDATE/DELETE) |
C-c C-k | Discard pending change at point |
C-c C-p | Preview execution (pending batch if any; otherwise effective query) |
c | Open copy transient menu (choose TSV / CSV / INSERT / UPDATE; toggle -r to refine) |
v | View cell value (JSON / XML / BLOB preview — auto-detected) |
e | Export all rows (CSV / INSERT / UPDATE copy/file) |
f | Toggle 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.
The insert buffer is intentionally form-like: TAB moves between fields,
while completion stays on Emacs’ completion keys.
| Key | Action |
|---|---|
RET | Accept current field and move to next field |
TAB / S-TAB | Next field / previous field |
M-TAB / C-M-i | Complete 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-c | Stage new row / update staged insert |
C-c C-k | Cancel |
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.
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}.
| Key | Action |
|---|---|
m | Toggle exclusion of row at point |
x | Toggle exclusion of column at point |
RET | Confirm — execute copy / aggregate |
C-g | Cancel — discard all exclusions |
| Key | Action |
|---|---|
RET | Expand long field / follow FK |
n / p | Next row / previous row |
v | View field value (JSON / XML / BLOB preview) |
g | Refresh |
q | Quit |
C-c ? | Transient menu |
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
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>
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
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
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 ->)
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.
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)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
If embark is installed, clutch registers a clutch-table target type so table
names can be acted on with a consistent action map:
| Key | Action |
|---|---|
b | Browse table (SELECT *) |
d | Describe table (show DDL) |
w | Copy 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-tableorclutch-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.
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.
| Backend symbol | Database | Driver auto-install | Default port |
|---|---|---|---|
oracle | Oracle Database | Maven Central | 1521 |
sqlserver | Microsoft SQL Server | Maven Central | 1433 |
redshift | Amazon Redshift | Maven Central | 5439 |
snowflake | Snowflake | Maven Central | — |
db2 | IBM DB2 | Manual download | 50000 |
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.
(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.
(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 driver default:
clutch-jdbc-install-driver 'oraclenow installsojdbc8by default. It is the safest choice across Oracle 11g/12c/19c and avoids the known 11g LOB compatibility problems seen withojdbc11. - Oracle 11g: keep using
ojdbc8. Ifojdbc11.jaris present from an older setup, reinstall with(clutch-jdbc-install-driver 'oracle)to re-enableojdbc8.jarand remove the conflictingojdbc11.jarcopy 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:urlfor 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 toschema Ntonce the cache snapshot finishes loading. - Pagination uses
ROWNUMsubquery, compatible with all Oracle versions (9i+). - CJK character sets (e.g. ZHS16GBK) require
orai18n.jarin addition to the active Oracle JDBC jar (ojdbc8.jarorojdbc11.jar). - Oracle
DATEcolumns include a time component; the agent always usesgetTimestamp()to avoid silent data loss.
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"))))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.
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 backendsUsing 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 genericclutchblock type.
C-c C-c executes the source block; results are inserted as an org table.
| Argument | Description |
|---|---|
:connection | Name from clutch-connection-alist (overrides all inline params) |
:backend | mysql, pg, sqlite, oracle, sqlserver, redshift, etc. |
:host | Database host (default: 127.0.0.1) |
:port | Database port (omit to use driver default) |
:user | Database user |
:database | Database name or file path (SQLite) |
:pass-entry | auth-source entry name for password lookup |
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).
clutch-connect-timeout-secondscontrols connect timeout (default: 10s).clutch-read-idle-timeout-secondscontrols query I/O idle timeout (default: 30s).clutch-query-timeout-secondscontrols database-side statement timeout for JDBC and native PostgreSQL queries (default: 30s).clutch-jdbc-rpc-timeout-secondscontrols Emacs <-> JDBC agent RPC timeout (default: 30s).- MySQL connections can override via
:connect-timeoutand: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-gto 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/DELETEwithout top-levelWHERE) requires typed confirmation (YES) before execution.
(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)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 tot; set tonilonly for local/dev)mysql-tls-keylistfor 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))(setq conn (mysql-connect/uri "mysql://root:[email protected]:3306/mydb"));; 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')"))(let ((stmt (mysql-prepare conn "SELECT * FROM users WHERE id = ?")))
(let ((result (mysql-execute stmt 42)))
(mysql-result-rows result))
(mysql-stmt-close stmt))(mysql-ping conn) ;; => t
(mysql-escape-identifier "my`table") ;; => "`my``table`"
(mysql-escape-literal "it's a \"test\"") ;; => "'it\\'s a \\\"test\\\"'"(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)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 tot; set tonilonly for local/dev)pg-tls-keylistfor 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));; 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')"))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:8Load 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.elStart a PostgreSQL instance:
docker run --rm -e POSTGRES_USER=testuser -e POSTGRES_PASSWORD=testpass \
-e POSTGRES_DB=testdb -p 5433:5432 postgres:16-alpineRun tests:
emacs --batch -L . -l test/pg-test.elUnit tests (message encoding, type parsing, auth) run without a server. Live tests require the Docker instance above.
- 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.
GPL-3.0-or-later. See LICENSE.
