#+TITLE: clutch Interactive Client Guide * SQL Editing (clutch-mode) Key Bindings | Key | Action | |-------------+----------------------------| | =C-c C-c= | Execute region, or the current =;=-delimited statement / query at point | | =C-c C-r= | Execute selected region; multiple =;=-delimited statements run sequentially | | =C-c C-b= | Execute entire buffer; multiple =;=-delimited statements run sequentially | | =C-c C-e= | Connect; query consoles reconnect their current connection | | =C-c C-z= | Show the current connection's existing result buffer | | =C-c C-j= | Jump to configured primary object types and run the default action | | =C-c C-d= | Describe object at point, or prompt | | =C-c C-o= | Show object actions for the current object, or prompt | | =C-c C-p= | Preview execution (dry-run) | | =C-c C-l= | Switch current schema/database on the active connection | | =C-c C-s= | Refresh schema for current connection now | | =C-c C-m= | Commit transaction (manual-commit connections only) | | =C-c C-u= | Roll back transaction (manual-commit connections only) | | =C-c C-a= | Toggle auto-commit on/off when supported | | =C-c TAB= | Invoke SQL identifier completion explicitly, including empty column positions | | =C-c ?= | Transient menu | =C-c C-n= is disabled in =clutch-mode= because the inherited =sql-mode= command requires a SQLi process. Use =C-c C-c= to execute the statement at point. =C-c C-z= reports when no result buffer exists for the current connection; it does not execute a query or open a REPL. Standard editing integrations still apply in =clutch-mode=: xref lookup uses =M-.= through the buffer-local xref backend for SQL alias navigation, so =M-.= on =u= or =u.name= jumps to the alias definition in the current statement. Schema-qualified table names such as =schema.table= are not treated as aliases; use =C-c C-d= or =C-c C-j= for object lookup and table description. CAPF completion handles SQL identifiers, including empty column positions such as =select | from users=. =TAB= completes columns after a table or alias dot, then falls back to indentation elsewhere; other completion stays on normal CAPF keys such as =M-TAB= inherited from Emacs. Cross-schema completion is enabled by explicitly qualifying a table, without a setting or switching the session schema. On native PostgreSQL/MySQL and JDBC Oracle/SQL Server/DuckDB, =FROM schema.= or =JOIN schema.= fetches that schema's table names asynchronously; =SELECT p. FROM schema.table p= fetches only that table's column names. The first attempt can have no candidates while metadata loads; invoke completion again when it finishes. Subsequent attempts use the cache, including empty results and failed lookups, until the normal metadata refresh, namespace change or reconnect invalidates it. Other schemas are not scanned, and unqualified names retain the existing current-schema behavior. The SQL context parser still has its existing limits for complex or quoted names containing spaces; this does not add a full SQL parser or automatic schema-name discovery. Table-name completion is within the connection's catalog, not a list of every database. DuckDB requires a file-backed database for this metadata path; its separate metadata session cannot see objects in the query session's private in-memory database. FROM lists also support comma-separated sources, as in =FROM schema1.table1 a, schema2.= and =SELECT b. FROM schema1.table1 a, schema2.table2 b=. Derived-table aliases complete the subquery output in SELECT and ON, as in =SELECT b. FROM schema1.table1 a JOIN (SELECT * FROM schema2.table2) b ON a.id=b.id=. Explicit projections expose only the selected names, including =name AS label=, direct-column implicit aliases and expressions with an explicit =AS= alias; =*= and =p.*= expand the selected sources using the existing column-metadata path. Nested derived tables and set-operation branches (=UNION=, =EXCEPT=, =INTERSECT= and =MINUS= on Oracle connections only) keep their output names and alias scopes separate; the output uses its first branch's column names. Leading comments, the standard =SELECT ALL= modifier and named projections without FROM followed by clauses such as LIMIT are supported. This is completion support, not writable-result inference or new derived-column eldoc. CTE output resolution, derived-table column-name lists, dialect-specific projection modifiers and expressions without explicit output names remain outside this lightweight parser; complex quoted table/alias names retain the existing limitations. SQL execution status marks the statement's starting line with a colored dot: a fringe bitmap on graphical displays, or =●= in the left margin on text terminals. Both use the same success/failure faces. Terminal SQL buffers reserve enough margin width for the dot automatically and retain it between executions; existing wider margins and right margins are preserved. When =C-c C-c= picks the statement at point, with no active region, the built-in =pulse= also flashes the range it picked, without moving point; the Redis command at point flashes the same way. The flash marks what Clutch picked, not that it ran: it comes before any confirmation, so a statement you decline has still flashed. A region or the whole buffer, including each statement of a batch, does not flash, since its text was chosen. The flash fades out where =pulse-flag= lets it pulse; with =pulse-flag= nil, its default where pulse cannot read the frame's colors, as on many terminals, it stays until the next key press. =clutch-pulse-statement-at-point= set to nil turns it off, and the face =pulse-highlight-start-face= sets its color. No additional mode or configuration is required. =M-x clutch-export-query= and Run → Export query to file (=C-c ?=, then =e=) export the selected region, or the statement at point, directly to a file. The minibuffer asks for CSV/TSV, encoding and destination, and asks before overwriting an existing file. Export accepts one SELECT, including a SELECT-only WITH query, and refuses multiple statements, SELECT INTO and data-changing CTEs. It uses the query's columns without adding editing identity columns, leaves the existing result and its staged edits intact, and does not open a result grid. Files include a header even for an empty result. On PostgreSQL, when the installed pgsql.el provides =pgsql-copy-out-async=, the query runs once as =COPY ... TO STDOUT= in CSV format, keeping the NULL, quoting and header conventions described below, and PostgreSQL writes each value in its own text format: booleans as =t= and =f=, =bytea= as =\x= hex, arrays as ={...}=, =json= and =jsonb= as the server prints them, and =timestamptz= in the session time zone with its offset. XTDB, older pgsql.el and the other backends fetch pages as described below. The encoding, incomplete-value checks and atomic file replacement follow the result exporter described below. Query export offers =clutch-export-default-format= (=csv= by default) and the existing =clutch-csv-export-default-coding-system=. File exports, including the result menu, offer =clutch-export-default-directory= when set, and CSV and TSV file exports also offer =clutch-export-default-file-name=. Otherwise they use the current buffer's directory and the format's file name, such as =export.csv= or =export.tsv=. These are prompt defaults, so each export can choose something else; a configured file name keeps its extension even when the other of CSV and TSV is chosen. Choose an existing directory. For example: #+begin_src emacs-lisp (setq clutch-export-default-format 'tsv clutch-csv-export-default-coding-system 'utf-8 clutch-export-default-directory (expand-file-name "~/exports/") clutch-export-default-file-name "latest.tsv") #+end_src These options can also be buffer-local for project-specific destinations. Without an explicit SQL row limit, paged query export and all-row result export retrieve pages of =clutch-export-page-size= rows (default 2000), independently of =clutch-result-max-rows= for display. The export size must be a positive integer; for example, =(setq clutch-export-page-size 5000)= increases it. Larger pages reduce repeated query work but use more memory and can prolong synchronous JDBC remaining-row fetches; smaller pages suit wide rows or slow remote connections. With LIMIT/OFFSET, TOP or FETCH, it runs the bounded query once and formats its rows in batches, so the backend may hold that whole result in memory. Give paged exports an ORDER BY on a unique key: pages are separate executions, so without one a page can repeat or skip rows, as when PostgreSQL starts each sequential scan of a large table at a different block (=synchronize_seqscans=), and they do not guarantee a consistent snapshot if the data changes. The source buffer's mode line counts elapsed time while the export runs, and =C-g= there requests cancellation. If the backend refuses, Clutch reports that it is waiting for the current query's result, then stops without fetching another page or replacing the destination. The connection remains occupied until that result arrives. MySQL, PostgreSQL and JDBC statement waits are asynchronous; file writes, and formatting outside a PostgreSQL COPY stream, still run in Emacs, JDBC remaining-row fetches are synchronous, and other backends such as SQLite can block. Compression may also hold the whole output in memory, as below. * Result Browser (clutch-result-mode) Key Bindings | Key | Action | |----------------+-----------------------------------------------| | =RET= | Open record view | | =TAB= / =S-TAB= | Next cell / previous cell | | =n= / =p= | Next row / previous row (same column) | | =M-n= / =M-p= | Alias for next row / previous row | | =N= / =P= | Next page / previous page (SQL pagination) | | =M->= / =M-<= | Last row window / first page | | =#= | Query total row count | | =A= | Aggregate numeric values (region/current cell; =C-u= with region enters visual refine mode) | | =]= | Scroll right (snap to next column border) | | =[= | Scroll left (snap to previous column border) | | === | Widen current column | | =-= | Narrow current column | | =W= | WHERE filter: a column and its condition, or a whole condition; an empty condition clears | | =/= | Client-side substring filter (empty to clear) | | =s= | Cycle current-column sort: unsorted / ASC / DESC | | =C= | Jump to visible column | | ={= / =}= | First visible data column / last visible data column | | =?= | Show column type info at point | | =g= | Re-execute query | | =C-c '= | Edit / re-edit at point | | =i= | Stage new row for insertion | | =I= | Clone current row into a prefilled insert form (without PK values) | | =d= | Stage row(s) for deletion (supports region) | | =C-c C-c= | Submit staged changes (INSERT/UPDATE/DELETE) | | =C-c C-m= | Commit the current server transaction | | =C-c C-u= | Roll back the current server transaction | | =C-c C-a= | Toggle auto-commit when supported | | =C-c C-k= | Discard staged change at point | | =C-c C-p= | Preview execution (staged batch if any; otherwise effective query) | | =c= | Open copy transient menu (choose TSV / CSV / aligned Org table / INSERT / UPDATE; =-r= controls refinement and =-h= controls tabular headers) | | =k= | Copy SQL, table metadata, and bounded result sample for an external agent | | =v= | Open the full cell value (JSON / XML / BLOB — auto-detected) | | =|= | Pipe the current cell through a shell command | | =e= | Open export transient menu (choose CSV / TSV / INSERT / UPDATE; =-h= controls headers and =-f= switches Clipboard / File) | | =f= | Toggle fullscreen | | =C-c ?= | Transient menu | The =e= exporter uses the same Transient interaction model as =c=. Copy identifies the current-cell or selected-cell scope; Export identifies all result rows and explicitly notes when a local filter is ignored. Export does not adopt the selection or the =/= filter. Header defaults to Yes for CSV and TSV, while Destination defaults to Clipboard and can be switched to File before choosing a format. For pageable queries the exporter retrieves every page rather than stopping at the displayed row limit; SQL =WHERE= and explicit =LIMIT=, =OFFSET=, =TOP= and =FETCH= clauses remain part of the query. Ordinary column, table and alias names such as =top= or =fetch= do not disable pagination. CSV and TSV files prompt for an encoding and default to UTF-8 with BOM for spreadsheet compatibility. By default, CSV/TSV copy and export write SQL NULL as an unquoted empty field, an empty string as =""=, and literal =NULL= text as =NULL=. This keeps the three values distinct in the output; importers must recognize this convention rather than treating every empty field as empty text. It is independent of encoding and applies to both query and result export. Oracle already treats empty character strings as SQL NULL, so they use the NULL representation there. =clutch-export-null-value-text= changes the unquoted NULL marker. Values containing the separator, a double quote or a line break are quoted, with double quotes doubled inside, and so is ordinary text matching the NULL marker. Choose a NULL marker without separators, double quotes or line breaks, and configure the importer to match. These settings affect CSV/TSV copying as well as both file exporters; they do not change result display or insert-form import. For example: #+begin_src emacs-lisp (setq clutch-export-null-value-text "NULL") #+end_src This writes SQL NULL as =NULL=, empty text as an unquoted empty field, and literal =NULL= text as ="NULL"=. =clutch-csv-delimiter= sets the CSV field separator, a comma by default, for copying and both file exporters; PostgreSQL's streamed query export passes it to =COPY=. Fields containing it are quoted. TSV always uses tabs. For example, for spreadsheets that expect semicolons: #+begin_src emacs-lisp (setq clutch-csv-delimiter ?\;) #+end_src The =/= filter searches only the loaded page. The footer preserves the unfiltered page range and total, and separately reports how many page rows match. A zero-match page explains how to change or clear the filter. Transaction state and staged counts precede row statistics and sorting so long secondary labels do not hide them first; long sort/filter expressions are shortened with full hover text. File exports prompt for their destination before fetching rows. CSV, TSV, INSERT and UPDATE output is formatted in batches and written about a megabyte at a time, with one header and one BOM when selected. Symbolic links are preserved and output goes to their final target. Filename-based transformations such as =.gz= compression run once on the complete encoded output and may hold it in memory. The destination is replaced only after success; errors and =C-g= preserve an existing file. Clipboard output and native document =insertMany= helpers still require the complete output in memory. Bounded or nonpageable backend queries may also materialize their result before formatting. Tip: =c= supports both regular region and rectangular selection (=C-x SPC=). Its Header option defaults to Yes for TSV, CSV, and Org tables; switch it to No to copy data rows only. Tip: use =A= after =C-x SPC= to aggregate selected numeric cells quickly. Tip: =v= opens the full current value in a normal buffer. Set =clutch-cell-preview-style= to =child-frame= for an automatic formatted preview that appears after =clutch-cell-preview-delay= while point rests on a visibly truncated cell and grows up to =clutch-cell-preview-max-size=; moving to another cell immediately hides stale preview content, fully displayed cells do not open a preview, the preview uses a theme-relative contrasting background and border, and unsupported displays silently do nothing. Tip: short JSON/XML cells render inline with lightweight token faces; long values show a raw prefix with an ellipsis and can be opened with =v=. Tip: the result-buffer status line uses compact staged-change tokens: =E-= for staged edits, =D-= for staged deletions, and =I-= for staged inserts. When a query result is not updateable because no safe row identity is available, the same line shows =row editing unavailable= and =E/D off= without placing raw backend errors in the footer; =C-c '= and delete commands still report a precise metadata failure with debug-buffer guidance when discovery failed. Editable result buffers choose row identity in this order: primary key, non-null unique key, then backend row locator when available. Row locator support is backend-specific: PostgreSQL tables can use =ctid=, SQLite rowid tables can use =rowid=, and Oracle JDBC tables can use =ROWID=. Physical row locators may change after UPDATE, so refresh order is still controlled only by the SQL query; use an explicit =ORDER BY= when row order matters. Aggregate or grouped result sets, such as =SELECT count(1) ...=, do not receive hidden row-identity columns and remain read-only for row mutation commands. A simple query of CTEs that read one table edits that table: the table's row identity is carried out through each CTE, so the query need not select the key, and column aliases and CTE column lists map back to the table's columns. Such a result stays read-only when a CTE on the way joins, groups, aggregates or combines rows, is recursive, is read more than once, or has the name of a column, as in =WITH name AS (SELECT name FROM people) SELECT * FROM name=, because Clutch counts every use of the name; renaming the CTE makes it editable. A CTE column list over =SELECT *=, as in =WITH c (k, v) AS (SELECT * FROM t)=, keeps rows deletable but no column editable, because only the table knows the order of =*=. A query of a table as of another time, with a temporal clause such as =FOR SYSTEM_TIME ALL= or Oracle's =AS OF TIMESTAMP=, stays read-only, because its rows may be past versions while an edit by key changes the current row. Tip: SQL-backed result refreshes (=g=, =W=, sort, paging) keep the column header visible; while the query runs, the footer's elapsed-time segment counts its running time instead of the table header being replaced with the query-console connection header. Sort and paging refreshes also keep manually adjusted column widths when the result columns are unchanged. Tip: Press =s= to cycle sort state for the result column at point. Simple table results, including a simple query of a CTE, use server-side SQL =ORDER BY=. UNION, grouped, derived, and other results that Clutch cannot safely rewrite use a client-side sort of the current page; the footer marks this scope as =page=. Use =C= to jump to a different visible column first, or click its header to cycle that column. The cycle is unsorted, ascending, descending, then unsorted again. Header icons appear after column names and use the original-size nerd-icons sort glyphs; when nerd-icons is unavailable, text arrows preserve the sort marker. Tip: Result Browser owns its virtual row-number column and disables native display line numbers whenever a result is opened or refreshed. Use ={= and =}= for data-grid edges; =C-a= and =C-e= retain their standard physical-line behavior and may move into the row prefix or beyond the final cell. Native isearch also finds column names. When a search starts, Clutch inserts a real header row at the top of the result buffer, and removes it when the search ends, so the header is never part of the buffer while you browse, select, copy or aggregate. Use native =C-r= to search backward from data to a column name; =C-s= reaches the header after wrapping, as in an ordinary buffer. Matching a header brings point to that column, horizontally scrolling it into view. =RET= ends the search and leaves point on that column's first data cell, and =C-g= returns to the original position. Complete column names remain searchable even when displayed with truncation. Search also continues to find matching data values; it is not restricted to column names. While point is in the header during a search, the window displays the real row and suppresses the fixed header-line copy. The cursor follows your normal Emacs blinking settings, and changing text scale keeps the header aligned with the data. No additional window or ETAF dependency is used. Other search commands, such as =occur= or =consult-line=, only see the header if they run isearch. Tip: staged changes can be previewed with =C-c C-p= and copied/saved from the result transient as the exact staged SQL batch. Tip: INSERT copy/export from joined, derived, or otherwise ambiguous result queries uses =MY_TABLE= as a placeholder target instead of guessing a wrong real table name. Tip: staged mutations and the server transaction are two separate layers. Submission follows the current transaction mode, like DataGrip's data editor: in Auto mode, =C-c C-c= (=clutch-result-submit=) submits and automatically commits the staged batch on transaction-capable backends; in Manual mode, it wraps the batch in a savepoint inside the current transaction, =C-c C-m= (=clutch-commit=) makes successful work durable, and =C-c C-u= rolls the transaction back. If a later staged statement fails, the savepoint removes this submission's earlier statements while preserving work already in the transaction, so the retained batch is safe to correct and retry. Multiple staged statements never require changing transaction mode first. If recovery fails or any commit outcome is unknown, =Tx: Uncertain= blocks further queries, commit, and transaction-mode changes until an explicit rollback or reconnect. Recovery makes the session usable again, but an uncertain commit still requires checking the database before retrying retained work. The automatic reconnect after a lost connection keeps the console in the mode it was in, Manual or Auto, and so do reopening a console whose connection was lost, a schema switch that reconnects and =C-c C-e= that connects the session's own connection again; =C-c C-e= that connects elsewhere starts a new session in the mode the connection starts in: Auto, or Manual on Oracle or with =:manual-commit t=. On native MySQL and PostgreSQL, and on XTDB, the transaction's state follows the server's report of an open transaction: a write inside one is uncommitted work in Auto mode too, as after a typed =BEGIN=, shown as =Tx: Auto*= and asked about before the session closes, and only a report of no open transaction, or a commit or rollback that Clutch runs, clears it. Switching to Manual mode keeps such a transaction open, uncommitted work and all, for =C-c C-m= or =C-c C-u= to end. Tip: a result remembers where its query resolved table names, as the server reports it: the database and schema, on PostgreSQL the whole effective path (=current_schemas(true)=), and on DuckDB the catalog, schema and =search_path=. Submitting a change, loading a page, counting rows and exporting all rows run SQL that names the result's tables as the query did, so once the connection resolves names elsewhere, as after =clutch-switch-schema= or an automatic reconnect that restored another path, they are refused, and so is staging a change. Switching back lifts the refusal, and so does running the query again (=g=), though where the result buffer is named after the database, as on MySQL, the new result opens in the current database's buffer. A table that the query names with its schema is no exception: DuckDB, for one, looks that schema up in the current catalog. A move that Clutch does not follow, such as those listed under Object Workflow, is not noticed here either, and on PostgreSQL a schema on the path that is created or dropped, or the temporary schema that a first temporary table adds, is noticed only after the next statement that may change the path. ** Insert Buffer Values An untouched blank insert field is omitted, allowing the database to supply its default. =C-c C-n= explicitly selects SQL NULL, =C-c C-e= selects an empty string, and =C-c C-d= returns the field to omission/default. Typing =NULL= inserts the literal text. Cloned rows and reopened staged inserts preserve the distinction between empty strings and SQL NULL. The state markers are visual overlays, not text sent to the database. INSERT submission and copy/export target a simple query's table, schema included, and name it as the query does, as UPDATE and DELETE do; a result without row identity whose query a server-side filter has wrapped names it as the database stores it. Incomplete JDBC CLOB previews remain viewable but cannot be edited, cloned as data, or exported as complete values. JDBC binary values that contain only BLOB length metadata appear as == in the table and == in the viewer. They are unavailable as complete data and are refused by copy/export and mutation paths. Select an explicit hex or text conversion in SQL to export those bytes, for example =RAWTOHEX(col)= on Oracle or =CONVERT(VARCHAR(MAX), col, 2)= on SQL Server. Decoded complete JSON/XML text BLOBs remain usable. ** Edit Buffer Keys Single-cell edit buffers use the same staged-mutation vocabulary as result buffers: =C-c C-c= stages the edited value, and the result buffer's =C-c C-c= submits all staged row changes. Closing a result buffer with staged edits, inserts or deletes asks before discarding them. Declining or pressing =C-g= keeps the buffer and its changes; accepting does not submit changes or end a database transaction. | Key | Action | |-------------------+------------------------------------------------| | =M-TAB= / =C-M-i= | Complete enum / bool-like values | | =C-c '= | Edit current JSON field in a dedicated buffer | | =C-c .= | Set current date/time field to "now" | | =C-c C-n= | Set database =NULL= | | =C-c C-d= | Set database =DEFAULT= | | =C-c C-c= | Stage edited value | | =C-c C-k= | Cancel | Database =NULL= and =DEFAULT= display as == and == with the same placeholder face. The edit header shows =Set NULL= only for nullable columns and =Set DEFAULT= only when the column declares a default and the backend supports it. An empty buffer remains an empty string, while typing =NULL= or =DEFAULT= stages literal text; use =C-c C-n= or =C-c C-d= for the database values. #+CAPTION: Result browser with staged markers, sort state, and query timing. [[file:screenshots/result-browser-basic.png]] ** Per-column displayers Result buffers can replace the visible text for a specific table/column pair without changing the raw cell value. The displayer receives the raw Elisp value stored in =clutch-full-value= and may return a propertized string. A =nil= return falls back to the default renderer. Truncation, padding, cell faces, the full viewer (=v=), and optional automatic child-frame previews still use the normal clutch pipeline. #+begin_src emacs-lisp ;; Define ordinary functions for reusable business display rules. (defun my-clutch-status-displayer (value) (pcase value (0 (propertize "pending" 'face 'warning)) (1 (propertize "active" 'face 'success)) (2 (propertize "done" 'face 'shadow)))) ;; Register them explicitly for each table/column pair where the rule applies. (dolist (table '("orders" "tasks" "jobs")) (clutch-register-column-displayer table "status" #'my-clutch-status-displayer)) #+end_src Table and column names are matched case-insensitively. Use =clutch-unregister-column-displayer= to remove one registration. Use the registration functions or Customize for runtime changes so existing result buffers invalidate cached cell displays. Keep displayers pure and cheap: they run on the result rendering path, though rendered cell strings are cached while the result state is unchanged. A displayer should not query the database, read files, start processes, mutate Clutch state, or schedule asynchronous work. Return =nil= for values the function does not handle so the default renderer can display them normally. If a generic rule depends on SQL type, schema metadata, or backend-specific state, prefer Clutch's built-in renderer or schema/cache layer over a column displayer. ** Insert Buffer Keys 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-y= | Import TSV / CSV from region or kill ring | | =C-c C-c= | Stage new row / update staged insert | | =C-c C-k= | Cancel | Insert buffers show every field by default, including generated/defaulted ones. 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. =C-c C-y= imports delimited text directly into the form. A single imported row prefills the current insert buffer; multiple imported rows become staged inserts immediately. Header-based imports map by column name; otherwise clutch maps positionally using the fields currently visible in the insert form. 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. Opening and cancelling the child editor leaves the parent field's value and NULL/default/empty state unchanged. #+CAPTION: Insert buffer with aligned labels, inline metadata tags, and the value column focused. [[file:screenshots/insert-buffer-basic.png]] ** Visual Refine Mode (=c -r= / =C-u A=) The copy transient (=c=) shows =Refine selection (No|Yes)= on =-r=. With an active region, enabling refinement before choosing a format lets you interactively exclude rows or columns before the copy is performed. The mode-line at the bottom of the result buffer is replaced with a color-coded hint for the duration of the session. The refine switch can be toggled before a region is active; choosing a copy format with refinement still requires a region and reports that clearly. The region can be set with =C-x SPC= (rectangle mark), a normal mark (=C-SPC=), or mouse selection — all three are accepted. Regardless of how the region was set, clutch resolves the start and end cells and expands the selection into a full rectangle covering all rows and columns in that range. Typical flow: 1. Select a region, then press =c= to open the copy transient. 2. Toggle =-r= and choose the target format (=t= / =c= / =i=). 3. In refine mode, use =m= to exclude rows and =x= to exclude columns. 4. Press =RET= to confirm and copy only the remaining rectangle. #+CAPTION: Refine mode for excluding rows and columns before copy. [[file:screenshots/refine-mode-basic.png]] | 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 | * Record View (clutch-record-mode) Key Bindings | Key | Action | |-------------+-------------------------------| | =RET= | Expand long field / follow FK | | =n= / =p= | Next row / previous row | | =C-c '= | Edit / re-edit current field | | =C-c C-k= | Discard staged change at field | | =C-c C-m= | Commit the current server transaction | | =C-c C-u= | Roll back the current server transaction | | =C-c C-a= | Toggle auto-commit when supported | | =I= | Clone current record into a prefilled insert form (without PK values) | | =v= | Open the full field value (JSON / XML / BLOB) | | =g= | Refresh | | =q= | Quit | | =C-c ?= | Transient menu | The Record transient labels =RET= from the field at point: =Follow FK=, =Expand=, =Collapse=, or =Show value=. It is inapt when point is not on a field. Staging a field edit with =C-c C-c= returns to the same field in the Record view. * Describe View (clutch-describe-mode) Key Bindings | Key | Action | |-------------+-------------------------------| | =s= | Show DDL / source | | =g= | Refresh describe buffer | | =C-c C-d= | Describe object at point, or prompt | | =C-c C-o= | Show object actions | * Typical Workflows ** Query + Pagination + Filter + Sort #+begin_src 1. Write SQL: SELECT * FROM orders; 2. C-c C-c Execute → results displayed with pagination (default 500 rows/page) → mode-line shows: Σ 1-500 of 501+ rows • ⏱ 42ms 3. N / P Next page / previous page 4. M-> Jump to the last row window, e.g. "Σ 735-1234 of 1234 rows" 5. # Query total rows → mode-line shows "Σ 1-500 of 1234 rows" 6. ] / [ Page horizontally with column-aligned snapping; point moves to the first column of the new view, so TAB continues from there → all columns stay searchable because they remain in the buffer 7. W Pick column (defaults to column at point), enter value or condition e.g., select "status" then type: 'pending' → WHERE status = 'pending' e.g., select "amount" then type: > 100 → WHERE amount > 100 → Appends WHERE clause, re-queries from page 1 8. / Client-side filter: type a pattern to narrow visible rows instantly 9. s Cycle current-column sort: unsorted → ASC → DESC → unsorted Use C to jump to another visible column first; clicking a header uses the same cycle → mode-line shows ORDER BY state, or "page" for local query-result sorting 10. A Aggregate selected cells/current cell → mode-line adds aggregate status (sum/avg/min/max + [rows/cells/skipped]) 11. W Enter empty string → clears filter, restores original query #+end_src ** Foreign Key Navigation #+begin_src 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 = #+end_src ** Editing Data #+begin_src 1. SELECT * FROM users; → C-c C-c to execute 2. Move cursor to the cell to modify 3. C-c ' → opens edit buffer from a result cell or record-view field; 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 C-c C-n sets database NULL when the column is nullable C-c C-d sets database DEFAULT when supported for that column 5. Edit the value, C-c C-c to stage (C-c C-k to cancel) empty text is an empty string; NULL and DEFAULT use their explicit commands 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 mode-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 #+end_src ** Insert / Delete Rows #+begin_src 1. SELECT * FROM users; → C-c C-c to execute 2. i → opens an insert buffer for the result table 3. I → clones the current row into a prefilled insert buffer without PK values 4. RET / TAB / S-TAB → move quickly between fields field labels are read-only, aligned, and can show tags like [enum], [json], [default=...] 5. C-c C-y → import TSV / CSV into the form; multi-row import stages rows directly 6. M-TAB / C-M-i → complete enum / bool-like fields (falls back to a chooser if needed) 7. C-c ' on a JSON field → open a dedicated JSON editor when the value gets large 8. C-c . → set current date/time field to "now" 9. 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 10. 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-= missing generated/defaulted columns render as / 12. C-c ' on a staged insert row → re-open that staged insert with its previous values 13. Use normal Emacs region selection to select rows 14. d → stage current row or selected region for deletion left marker shows =D= and status line shows =D-= deleted rows are visually marked in the result buffer 15. C-c C-k → discard staged change at point 16. g with staged changes → prompts "Discard staged changes and re-run query?" 17. C-c C-c in result buffer → confirmation prompt showing all statements submits in order: INSERT → UPDATE → DELETE edited rows show =E= in the left marker column and =E-= in the status line #+end_src The confirmation preview always shows fully rendered SQL text. Native MySQL, PostgreSQL, SQLite, and JDBC backends execute staged DML through parameterized prepared/bound values under the hood; only the preview renders readable SQL literals. * REPL Mode #+begin_src 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 ; ;; SELECT results open in the result buffer ;; Multi-line input supported (continuation prompt ->) #+end_src | Key | Action | |-------------+-------------------------------| | =C-c C-e= | Connect | | =C-c C-m= | Commit transaction when supported | | =C-c C-u= | Roll back transaction when supported | | =C-c C-a= | Toggle auto-commit when supported | | =C-c C-j= | Jump to object | | =C-c C-d= | Describe object | | =C-c C-o= | Object actions | | =C-c C-l= | Switch current schema/database | Useful =M-x= entry points outside the main keymaps: - =clutch-describe-refresh= — refresh the current describe buffer - =clutch-object-show-ddl-or-source= — show object DDL/source directly - =clutch-describe-dwim= / =clutch-act-dwim= — resolve objects and choose describe, browse, or backend-specific actions - =clutch-result-copy-pending-sql= / =clutch-result-save-pending-sql= — export the exact staged DML batch - =clutch-copy-context-for-agent= — copy the current SQL, connection context, referenced table metadata, and the visible columns from the latest matching result sample as Markdown for an external agent; also available as =k= in the main and result transients * External Agent Context =clutch-copy-context-for-agent= is the lightweight handoff point for ChatGPT, Claude, DeepSeek, or another external assistant. It copies Markdown to the kill ring from the current clutch buffer: - in =clutch-mode=, the active region or current DWIM SQL statement - in =clutch-result-mode=, the effective result query, including an active SQL =WHERE= filter - connection/backend/schema information - referenced table metadata from the existing object describe path, including comments, columns, primary-key/foreign-key tags, and any warmed related object metadata such as indexes - a bounded sample from the current result buffer, or from the latest result buffer produced by the query console when its SQL still matches; hidden row identity columns are omitted The command does not run the SQL being copied. It only uses clutch metadata APIs and rows already present in a matching result buffer. Metadata lookup errors are written into the copied Markdown instead of being hidden, so the external agent can see which context is missing. * Query Console =clutch-query-console= opens a dedicated buffer =*clutch: NAME*=, enables =clutch-mode=, and connects automatically. Its unified picker lists open consoles and saved connections; an open saved console appears once, while open temporary and SQLite consoles remain directly selectable. Pressing RET with no matching connection starts a temporary connection flow. SQLite is part of that flow and reads a database file instead of host/user credentials. =clutch-query-sqlite-file= jumps straight to the SQLite-file shortcut. Repeated calls with the same connection switch to the existing buffer rather than creating a new one. Connection happens before the buffer is shown: if the initial connect or a reconnect of an existing disconnected console fails, clutch raises the error without leaving a visible disconnected console buffer behind. Inside a query console, =C-c C-e= connects the console's session anew with the console's own connection: its results and the session's other buffers move to the new connection, which stays in the commit mode the console was in, and uncommitted work lost with the old one is reported. Once the console's saved entry changed in anything but =:password= or =:pass-entry=, a password written into a =:url= included, =C-c C-e= connects the console alone, in the mode that entry starts in, and the results stay off the new connection, so nothing staged in them reaches another server; the next =C-c C-e= moves the session again. For saved consoles, if the saved connection entry was removed from =clutch-connection-alist=, clutch raises a clear error instead of silently reconnecting to some other database. For ad hoc consoles, reconnect uses the original temporary parameters; SQLite reuses the original file path. A schema or database chosen since then, with =clutch-switch-schema= or a statement such as =USE=, =SET search_path= or a Redis =SELECT=, gives way to the one these parameters name; the automatic reconnect after an idle drop keeps it, and so does reopening a console whose connection was lost with =clutch-query-console=. Such a reopen reconnects as the automatic reconnect does, with the console's own parameters: it moves the console's results to the new connection and reports uncommitted work lost. A change made since to the saved entry, such as a new inline password, is therefore picked up by =C-c C-e= and not by the reopen. To move to a different saved or temporary connection, call =clutch-query-console= again; press RET with no matching connection to create another temporary connection. In ordinary =clutch-mode= or REPL buffers, =C-c C-e= keeps the generic connect picker: select a saved connection, or press RET with no matching connection to enter temporary params. Picking the connection the buffer's session is on connects the session anew, as in a console: its results move to the new connection, and the commit mode stays. Picking another connects the buffer alone, in the mode that connection starts in. An indirect edit (=clutch-edit-indirect=) shares the session of its console: =C-c C-e= there moves that session, console and all, when it picks the session's connection, and otherwise connects the edit alone and leaves the console as it was. The session the edit then has is its own: =C-c C-e= elsewhere asks about its uncommitted work and ends it, as in other buffers, =C-c '= runs SQL in the edit, and leaving the edit with =C-c C-k=, or killing it, ends the session. An edit opened from such an edit shares its session, and leaving it leaves the session as it was. For SQLite files, use the query-console path instead of opening the database file itself as an editor buffer. A result whose session ended, as after =clutch-disconnect= or killing its console, keeps its rows to read, copy as text and export as loaded. The commands that need its connection say it is closed: refreshing, paging, counting, sorting on the server and filtering it, editing a cell, loading a column's details, previewing its SQL and copying rows as =INSERT= or =UPDATE= statements or MongoDB document mutation snippets. After a lost session, editing a cell, copying =UPDATE= statements and submitting staged changes reconnect before loading metadata or starting the batch, keeping Auto or Manual mode and the staged changes. They then check where the result's query ran again, so a reconnect that restores another database, schema or path refuses the action. An uncertain submission requires explicit rollback or reconnect before retrying. SQL previews that need no source-column metadata and copying rows as =INSERT= statements still work without reconnecting; a lost MongoDB connection also still copies document mutation snippets from loaded rows. ** Paste whitespace cleanup Text pasted into a query console is automatically cleaned: trailing whitespace, mixed tabs/spaces, and CRLF line endings are fixed in the pasted region only — existing SQL in the buffer is not touched. Disable with =(setq clutch-console-yank-cleanup nil)=. ** Console persistence Buffer content is automatically saved to =clutch-console-directory= (default: =~/.emacs.d/clutch/=) and restored the next time the same connection is opened. clutch saves console SQL under a hash of the connection identity, not the saved connection name. Renaming a saved connection reuses the open console buffer and keeps the same saved SQL. If no identity-keyed file exists yet, clutch reads the old alias-keyed file as a migration fallback. Content is saved when the buffer is killed or when Emacs exits. Suggested global bindings: #+begin_src emacs-lisp (global-set-key (kbd "C-c d") #'clutch-query-console) #+end_src * Object Workflow #+begin_src C-c C-j ;; Main object entrypoint C-c C-d ;; Describe object at point, or prompt C-c C-o ;; Show object actions for the current object, or prompt #+end_src The main navigation path is object-centric rather than tree-centric. =C-c C-j= is the single conceptual object entrypoint. It resolves an object at point when possible; otherwise it opens a flat annotated object picker. By default the primary set is =TABLE= / =VIEW= / =SYNONYM= / =COLLECTION= / =KEY=, configurable via =clutch-primary-object-types=. The picker returns quickly with schema snapshot objects and any already-warmed metadata, then keeps warming slower categories such as indexes, sequences, and routines in the background after schema refreshes. Once an object is resolved, =RET= runs the object's default action. In query consoles and the REPL, when point is already on a table-like object, =C-c C-j= opens the picker prefilled with that name instead of inserting browse SQL immediately; =RET= stays the explicit browse trigger. =C-c C-d= is a describe-focused wrapper over the same resolution logic. It resolves the object at point when possible; otherwise it prompts with the same flat picker. It opens the shared describe buffer for the target object. That buffer is only a view over the same object model; it does not define a second action universe. =C-c C-o= is the direct “show me actions” wrapper over the same object model. It resolves the object at point when possible; otherwise it prompts, then opens the shared action UI. The action set is intentionally small and shared across Embark and Transient: #+CAPTION: Object describe buffer with action hints and column metadata. [[file:screenshots/object-describe-basic.png]] - =d=: describe object - =s=: show DDL / source - =j=: jump to the target object for objects with explicit target metadata such as indexes, triggers, and synonyms - =n=: copy object name - =f=: copy fully-qualified object name =C-c C-l= / =clutch-switch-schema= is the one public namespace entrypoint. Each backend owns its candidates, current value, switch operation, and connection-state projection; the command layer only orchestrates an in-session change or a reconnect. Oracle's JDBC adapter changes business schemas, PostgreSQL uses =SET search_path TO ...=, MySQL uses =USE database=, MongoDB changes its logical database from names returned by =mongodb.el=, which the automatic reconnect returns to while it authenticates where the connection first did, and file-backed DuckDB uses =USE= for schemas in the current catalog. ClickHouse exposes the same command but returns reconnect parameters because its HTTP-backed connection cannot rely on session-local =USE=; a =:url= names the database switched to in them as well. Generic JDBC URLs gain no switching merely from JDBC metadata, and Redis offers no candidates because it cannot reliably enumerate all configured logical databases through an ACL-safe command, though a =SELECT= run in its console moves the session as a switch would. A =USE= run in a MySQL console and a =SET search_path= run in a PostgreSQL console move the session as a switch would, too, and on PostgreSQL the automatic reconnect after an idle drop restores the whole path rather than its first schema, once no open transaction can still undo it: a lost connection rolls an open transaction back, and the path with it. That holds for a schema chosen with =clutch-switch-schema= too, which in Manual mode runs inside the transaction a statement has opened. Clutch asks PostgreSQL for the path after any =SET=, =RESET=, =DISCARD= or =PREPARE= and after the end of a transaction, since a rollback undoes a =SET= made inside it and any end, =PREPARE TRANSACTION= among them, undoes =SET LOCAL=; =SELECT set_config=, =DO= and =CALL= are not followed, and statements sent together in one string, as the REPL sends several typed at once, count by the first one. DDL that moves the schema a path resolves to, such as creating a schema named after the current role or dropping the current one, is not followed either, nor is a =COMMIT= that fails and so rolls back; the mode line catches up after the next statement that may move the path, the next =clutch-commit= or =clutch-rollback=, or a return to Auto mode. A reconnect does not restore a role set with =SET ROLE=, so a path that starts with ="$user"= can resolve to another schema. Clutch asks MySQL for the current database after any =USE= or =DROP=, so a =DROP DATABASE= of the current one leaves the console with none, as it leaves the server's session, and the automatic reconnect then connects without one; a =DROP= run through =CALL= or =EXECUTE= is not followed. Clutch asks Oracle for the current schema after any =ALTER=, so an =ALTER SESSION SET CURRENT_SCHEMA= moves the console as =clutch-switch-schema= does; a schema set inside a PL/SQL block or a procedure is not followed. Clutch asks DuckDB for its catalog and schema after any =USE=, =SET= or =RESET=, and the automatic reconnect returns there; it refuses, naming the database, when that is an attached or an in-memory one, which a new connection cannot reach, and =C-c C-e= connects anew. On Redis, a =SELECT= queued after =MULTI= and run by =EXEC= is not followed, nor =RESET=, which returns the session to database 0. For JDBC backends, [[https://github.com/LuciusChen/clutch-jdbc-agent][clutch-jdbc-agent]] now isolates foreground SQL from metadata traffic by keeping separate primary and metadata JDBC sessions under one logical clutch connection. This avoids Oracle-style contention between user queries and background schema/object introspection. Default actions only depend on the object type: - tables / views / synonyms: insert =SELECT *= into a query console - collections: browse documents through the native MongoDB query surface - keys: browse the value through the native Redis command surface - procedures / functions / triggers: show source - indexes / sequences: show DDL For JDBC/Oracle connections, =C-c C-j= shows annotated object candidates in the minibuffer. The object name remains the primary candidate text; source/type is shown as a gray annotation on the right (for example =PUBLIC/synonym= or =APP/synonym=). When duplicate names need disambiguation, the target owner is appended as extra detail. If you use [[https://github.com/oantolin/orderless][orderless]], annotation search works naturally here: =&public=, =&synonym=, =&view=, etc. * Embark Integration (optional) If [[https://github.com/oantolin/embark][embark]] is installed, clutch registers a =clutch-object= target type so objects can be acted on with the same shared action set used by the native =transient= fallback: | Key | Action | |-----+--------| | =d= | Describe object | | =s= | Show definition | | =i= | Show index insight (MongoDB collections) | | =e= | Explain sample query (MongoDB collections) | | =v= | Show validation (MongoDB collections) | | =t= | Show stats (MongoDB collections) | | =j= | Jump to target object (only for target-capable objects such as synonyms, indexes, and triggers) | | =n= | Copy object name | | =f= | Copy fully-qualified object name | Objects are recognised as targets in four contexts: - *=clutch-mode= (SQL buffer)* — symbol at point, resolved against known objects - *=clutch-repl-mode=* — symbol at point - *=completing-read= minibuffer* — current candidate in =C-c C-j= / =C-c C-d= / =C-c C-o= - *describe / definition buffers* — the object currently shown in that buffer Without Embark, =C-c C-o= opens a =transient= menu with the same object-driven actions from the current SQL, REPL, minibuffer, describe buffer, or definition buffer context. Object-specific actions are marked unavailable in the transient when the current object type cannot support them. Embark only exposes secondary object actions. Default actions such as =C-c C-j= itself and table-like row browsing stay on =RET= rather than being duplicated in the Embark menu. Actions that depend on object capabilities, such as =jump target=, only appear for objects that actually carry explicit target metadata. * Transient Menus Requires the =transient= package (built-in since Emacs 28.1). Press =C-c ?= to open the transient menu. The main menu is headed by the connection it acts on. The *Objects* group contains =Jump to object=, =Describe object=, =Object actions=, =Switch schema/db=, and =Refresh schema=. Finite state is displayed as parenthesized choices, with the active choice highlighted. For example, the connection menu shows =Auto-commit (manual|auto)=, and the result menu shows current filter, sort, copy-refinement, and layout states. The *Connection* group contains =Connect=, =Query console=, =SQLite file=, =Prepare SSH= (=S=), and =Disconnect=. The *Transaction* group, with =Commit=, =Rollback= and =Auto-commit=, appears only on connections that have transaction controls. *Run* holds the execute and export commands, and *Tools* holds =REPL=, =Indirect edit= and =Copy agent context=. =Prepare SSH= opens an interactive OpenSSH setup session for host-key confirmation or key passphrase entry before retrying the normal non-interactive connect path. In the result buffer, the *Edit* heading includes the current staged count. Submit, discard, and staged-SQL copy/save actions appear only while changes are staged. The unified staged-submit workflow uses =i= to stage an insert, =d= to stage a deletion, =C-c C-k= to discard a staged change at point, and =C-c C-c= to submit all staged changes (INSERT → UPDATE → DELETE) in a single confirmation. The result and record transients expose an *Inspect* group in which =v= opens the full value in a normal buffer. Automatic child-frame previews are a display preference rather than a transient action: set =clutch-cell-preview-style= to =child-frame= to preview a truncated result or collapsed Record cell after point rests on it, without moving focus. * Faces Customize these through =M-x customize-group RET clutch RET=: | Face | Purpose | |------+---------| | =clutch-field-name-face= | Shared field/column-name face used by result headers, insert labels, and record labels | | =clutch-header-face= / =clutch-header-active-face= | Non-field header text and the active result header cell | | =clutch-border-face= | Table borders and separators | | =clutch-null-face= | NULL cell values | | =clutch-modified-face= / =clutch-pending-delete-face= / =clutch-pending-insert-face= | Staged edit/delete/insert markers in result buffers | | =clutch-fk-face= | Foreign-key cell values | | =clutch-executed-sql-marker-face= / =clutch-failed-sql-marker-face= | Last SQL execution marker in SQL buffers | | =clutch-running-sql-marker-face= / =clutch-cancelling-sql-marker-face= | Marker of SQL that runs or is being cancelled | | =clutch-running-sql-face= / =clutch-cancelling-sql-face= | Background of the text of SQL that runs without blocking or is being cancelled | | =clutch-error-summary-face= | SQL execution error summaries in result buffers | | =clutch-object-type-face= | Object type labels in describe/object UIs | | =clutch-object-source-face= / =clutch-object-public-source-face= | Object source/schema annotations | | =clutch-insert-field-tag-face= | Insert-form metadata tags | | =clutch-insert-field-error-face= / =clutch-insert-inline-error-face= | Insert/edit validation feedback | | =clutch-insert-active-field-face= / =clutch-insert-active-field-name-face= | Active field highlighting in insert buffers | * 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= limits how long a statement may run on JDBC and native PostgreSQL connections (default: nil, no limit; a limit configured on the server still applies). - =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=. - Statements on native MySQL and PostgreSQL connections and on JDBC connections run without blocking Emacs. MySQL needs a mysql.el that provides =mysql-query-async= and PostgreSQL pgsql.el 0.2.0 or later; with an older library their statements block as before. - While a statement runs, other buffers stay usable, its first line in the console shows an amber dot in the fringe (=●= in the margin of a terminal), its text has the background of =clutch-running-sql-face=, from =secondary-selection=, and the mode line counts the elapsed time. Text typed inside the statement while it runs takes the background too; text typed before or after it does not. The background goes when the statement finishes. - =C-g= in the console, REPL or result buffer requests cancellation of the running statement. So does a quit that reaches the command loop in that buffer, as a =C-g= typed while Emacs is busy does on MS-Windows, where it quits whatever runs next, such as redisplay. The marker turns into a red square and the background into =clutch-cancelling-sql-face=, from =isearch-fail=, until the server answers, and the statement then reports the server's verdict: the cancellation error, or its result if it succeeded. Even if the backend refuses cancellation, the statements after it in a batch do not run; the current statement can still finish and, in Auto mode, commit its changes. A second =C-g=, or =C-g= with nothing running, quits as usual. - While a long script or a large export runs, the SQL buffer is redisplayed repeatedly to show progress, and =display-line-numbers-mode= makes each redisplay costlier: it roughly doubled the time of a 3,200-statement batch of =UPDATE=s (about 14 s to 28 s) and added one to a few seconds to a 180 MB export. Turn it off in that buffer with =M-x display-line-numbers-mode= before such a run, or keep it out of SQL buffers with =(add-hook 'clutch-mode-hook (lambda () (display-line-numbers-mode -1)))=. - Paging a result, sorting it on the server and counting its rows run the same way. The result keeps its page, sort and staged changes until the new page or count arrives, and a failure or =C-g= leaves it as it was. Staging an edit is refused until then, since the arriving page would drop it. - Exporting all rows of a result runs the same way, one page after another, and =C-g= stops it. A file export replaces its destination only once every page has arrived, so a failure, =C-g= or killing the result buffer leaves the destination as it was; the clipboard receives the rows once they have all arrived. An export still running when Emacs exits leaves its temporary file beside the destination. - On a JDBC connection, the rows a result still owes when its statement finishes are fetched before the result is shown. =C-g= during that fetch stops it and closes the connection, whose cursor is then in an unknown state; the next statement reconnects. - Until the statement finishes, its connection refuses commands that need it, such as another statement, paging, submitting edits, switching schema or connecting elsewhere with =clutch-connect=. - A batch from =clutch-execute-region= or =clutch-execute-buffer= runs one statement after another the same way. Every risky statement in the batch is confirmed before the first one runs. - The client-side idle timeout does not apply, and by default no statement timeout does either: a statement runs until it finishes or =C-g= cancels it, holding its connection, any locks it takes and, on JDBC, one of the agent's execution threads until then. =clutch-query-timeout-seconds= or =:query-timeout= limits statements on PostgreSQL and JDBC, and a limit configured on the server still applies. JDBC fetches the remaining rows of a result page synchronously once the statement has finished; that fetch, like other requests Emacs waits for, stays within =clutch-jdbc-rpc-timeout-seconds=. A page rarely has rows left after the first batch of =clutch-jdbc-fetch-size=, but a query with a larger =LIMIT=, or an export of one, blocks while they arrive, and =C-g= then closes the connection. - =clutch-disconnect= while a statement runs closes its connection at once, and the statement reports in the echo area that its outcome is unknown: the server may still finish it and, in Auto mode, commit it. On JDBC the agent drops the connection without waiting for the statement. - A statement that ends after its buffer has left its connection, as after =clutch-disconnect=, marks its line and gives its outcome in the echo area, but draws no result or error page: the buffer's result buffer now belongs to another connection, or to none. - Other statements block while they run; press =C-g= to interrupt. - On JDBC backends, clutch first sends a protocol-level =cancel= for the running statement. - On native MySQL, clutch opens a helper connection, issues =KILL QUERY= for the original connection id, drains the interrupted response, and keeps the same session usable. - On native PostgreSQL, clutch sends a protocol-level =CancelRequest=, drains the main socket back to =ReadyForQuery=, and keeps the same session usable. - When that backend-specific cancel succeeds, the connection stays usable and you can run the next SQL immediately on the same session. - clutch only drops the connection when interrupt recovery fails, for example if cancel times out, the agent has exited, or the JDBC session is already dead. - Backends without explicit interrupt support still fall back to disconnect/reconnect semantics. - =M-x clutch-debug-mode= is the opt-in deep-debug workflow and is off by default. When enabled, clutch records a redacted trace of recent connect/query/interrupt/object/metadata events in =*clutch-debug*=, and JDBC also requests an additional redacted backend debug payload (currently stack trace + request context). Turning the mode on starts a fresh capture window. =clutch-debug-event-limit= bounds the total number of =Trace Event= entries in that buffer; problem records and historical problems are retained independently. - The supported troubleshooting workflow is: 1. enable =M-x clutch-debug-mode= 2. reproduce the failure 3. inspect =*clutch-debug*= - =*clutch-debug*= is the sole supported troubleshooting surface. It is a single session log, not a per-buffer report command, and it should be read instead of raw stderr buffers. - When the failing path involved hidden/internal SQL, the same debug buffer also shows generated SQL together with the related JDBC agent stderr tail and backend debug payload. - High-risk DML (=UPDATE/DELETE= without top-level =WHERE=) requires typed confirmation (=YES=) before execution. ** Additional Customization - =clutch-cell-preview-style= enables automatic child-frame previews when the result or Record cell at point is visibly truncated; it defaults to =nil=. - =clutch-cell-preview-delay= controls how long point must rest on a truncated cell before its child-frame preview appears (default 0.25 seconds); moving to another cell hides the old preview immediately. - =clutch-cell-preview-max-size= bounds the preview relative to its parent frame (default width 65%, height 45%). - =clutch-column-width-max= caps the display width of one result column (default 30); =clutch-column-width-step= is how much ~=~ and ~-~ widen or narrow it (default 2); =clutch-column-padding= sets the spaces on each side of a cell (default 1). - =clutch-result-window-height= sets the height of a new result window as a fraction of the frame (default 0.33); an existing result window keeps its height. - =clutch-sql-product= is the =sql-mode= product used for object definition buffers when the connection does not identify one (default =mysql=). - =clutch-column-displayers= controls the per-table/per-column result displayer registry. - =clutch-object-warmup-idle-delay-seconds= delays background object warmup after schema refresh. - =clutch-csv-export-default-coding-system= sets the default CSV and TSV encoding (UTF-8 with BOM by default for Excel compatibility). - =clutch-export-default-format= sets the format offered by query export (=csv= or =tsv=). - =clutch-export-default-directory= sets the offered file export directory; =nil= uses the current buffer's directory. - =clutch-export-default-file-name= sets the offered CSV and TSV file name; =nil= uses the format's default. - =clutch-export-null-value-text= sets the unquoted CSV/TSV NULL marker (an empty field by default). - =clutch-csv-delimiter= sets the CSV field separator for copy and export (a comma by default). - =clutch-jdbc-fetch-size= controls JDBC cursor batch size from 1 through 10,000 rows (default 500). - =clutch-primary-object-types= chooses which object types =C-c C-j= treats as the primary jump set. - =clutch-sql-completion-case-style= controls whether SQL completion preserves, lowercases, or uppercases inserted keywords. Identifiers retain their catalog spelling and are escaped using the connection's SQL dialect when accepted. Unquoted prefixes still match names needing quotes; completing inside existing double quotes, backticks or brackets (on SQLite and SQL Server) replaces the surrounding quotes with the backend's identifier syntax. Qualifiers already written in the query remain unchanged. Derived projection names retain their original SQL quoting and case-folding semantics rather than acquiring catalog quotes; a column whose name is also a keyword remains an identifier in column completion. - =clutch-agent-context-max-result-rows= controls how many current result rows are copied by =clutch-copy-context-for-agent=. - =clutch-agent-context-max-cell-width= controls per-cell truncation in copied external-agent context.