The data grid
One implementation serves table tabs and query results: an AppKit NSTableView with fixed row heights, server-side paging and an edit buffer that turns into SQL you read before it runs.
Pages, not endless scroll
A table tab shows one page of at most 1,000 rows. The pager in the status bar moves first, previous, next and last, and names the row range on screen. Moving between pages re-reads from the server; the grid never holds more than the current page. When a table has a single-column integer primary key and no user sort, deep pages switch from OFFSET to keyset paging, because OFFSET degrades. The status bar tooltip tells you which was used.
Query results are different: a result set is what the statement returned, so it streams in batches as rows arrive. Beyond 200,000 rows the stream pauses and a banner offers to load more, export the remainder straight to a file, or cancel.
~. A filter with an exact count, or a short final page, shows the real number. An estimate is never presented as a count.Sorting and filtering
orders, joined by OR. The SQL toggle on the right shows the generated WHERE clause.Click a column header to cycle none, ascending, descending; ⇧-click adds a secondary sort. ⌘⇧F opens the filter bar. Each condition is a column, an operator and a value; conditions combine with AND or OR. Operators cover =, ≠, <, ≤, >, ≥, contains, starts with, ends with, is null, is not null, in (…) and between. Values are always sent as parameters, never interpolated. Sort and filter both run on the server and reset to page 1, because both change what page 2 would mean. The state persists per table per connection.
Editing
Editing is enabled when the table has a primary key or a unique NOT NULL index. Otherwise the grid is read-only and the status bar says why. Double-click or ↩ starts an inline edit with an editor that fits the column type: a text field for strings and numbers, a toggle for booleans, a date and time picker with a raw-text fallback for temporal types, and the cell inspector for JSON and long text. ⌘⌫ sets NULL, Esc cancels, Tab moves right.
Edits are held in a buffer and coloured: changed cells yellow, new rows green, rows marked for deletion red with strikethrough. Nothing is sent until you commit (⌘⇧S) or discard. With auto-commit on, a non-production connection writes each edit as it is made; production connections always buffer.
The commit preview
Commit generates statements and shows every one of them in a sheet, syntax-highlighted, with a count summary. Only after you press Execute do they run, inside one transaction. On success the buffer clears and the affected rows refresh in place. On any failure everything rolls back, the buffer stays intact, and the error is shown against the statement that caused it.
UPDATE orders SET status = $1 WHERE id = $2; -- only changed columns; WHERE uses the original PK values DELETE FROM orders WHERE id = $1; INSERT INTO orders (customer_id, total) VALUES ($1, $2) RETURNING *;
Selection, copy and paste
- Cell, row, column and rectangular range selection, like a spreadsheet.
⌘Ccopies TSV.⌘⌥Ccopies the selected rows as INSERT statements in the connection’s dialect. The context menu also offers CSV, JSON, a Markdown table and a WHERE-IN list.⌘Vpastes TSV into an editable grid starting at the anchor cell, creating rows past the end. Invalid values are flagged, never dropped.
The cell inspector
⌘⌥I opens the inspector: column name, native type, constraints and the raw value, editable.The inspector shows the focused cell in full: JSON pretty-printed with a Copy formatted button, binary as a hex dump with Save as file, and long text without the 512-character in-cell truncation. Values that carry precision, such as decimal and timestamp, keep the server’s text end to end. They are never routed through Double or Date.