Skip to content
Tinker
Download

The SQL editor

An AppKit text view with highlighting, alias-aware autocomplete and a formatter that keeps every token. Each tab carries its own session, and its results sit under the editor or beside it.

Two statements run with ⌘⌥R. Each produced its own result tab; the status line under the panes shows the SQL that made the rows on screen.

Writing

  • Line-number gutter, current-line highlight, bracket matching, auto-indent, ⌘/ to toggle comments, ⌘D to duplicate a line.
  • Highlighting comes from Tinker’s own SQL tokenizer (DBSQL), which also drives the statement splitter and the formatter, so all three agree on where a statement begins and ends.
  • Autocomplete opens after one character, or on ⌥Esc when you want it sooner: keywords, tables in the current schema, functions, and the columns of tables referenced in the statement. It knows your aliases: after FROM orders t, typing t. lists that table’s columns with their native types beside them.
  • The match is fuzzy in the way an editor’s is: typing cst finds customers.
  • Functions come from a catalog per engine — 281 on MySQL, 291 on PostgreSQL, 128 on SQLite — grouped the way each engine’s own documentation groups them. Typing DA offers DATE, DATE_FORMAT, DAY, DAYNAME…, each with its argument list and its category; choosing one inserts DATE() with the caret between the parentheses, while a function that takes none, such as CURRENT_TIMESTAMP, is inserted bare. Known function names are coloured in the editor.
  • ⌘⇧I formats conservatively: uppercase keywords, one clause per line, indented sub-selects. Every token survives.
  • A server error with a known position becomes a red squiggle under the offending token until the text changes.
Typing t. after FROM orders t lists that table’s columns — id, customer_id, total, placed_at — each with the type the server declared, not a guess from the name.
The same list, asked for functions: DA on PostgreSQL offers date_bin, date_part, date_trunc, DATABASE, current_database… with the arguments each one takes and the group the engine’s own documentation files it under. The match is fuzzy, which is why justify_days is in the list.

The tab’s session

Two pickers in the tab toolbar choose the connection and where in it the tab runs. On MySQL the second lists the databases, and choosing one issues USE db. On PostgreSQL it is one list of database › schema: every database of the server, with the one the tab sits on opened out into its schemas. Choosing a schema sets search_path; choosing another database opens that database’s own session, because PostgreSQL cannot change database on an open connection, and the completion list, the history and the result grids follow it there. Statements run in that session, so you write SELECT * FROM t, not SELECT * FROM db.t. The pickers show what the session actually holds after the switch; a failed switch leaves the previous choice selected and reports the server’s words.

A statement that moves the session moves the tab with it: in USE staging; DELETE FROM t the DELETE runs in staging, and the picker says so. Moving a tab to another connection or database asks first when it would roll back an open transaction or drop uncommitted edits, and cannot happen while a statement is running. A new tab waits to be used before it connects: until its pop-up is opened, a statement run or a word typed, the database pop-up reads “Choose…” and no server is woken.

Running

  • ⌘R runs the statement under the cursor, ⌘⌃R the selection, and ⌘⌥R every statement on the page in order. Return stays Return, which is why the run keys moved off it (ADR-0041).
  • Each statement produces its own result tab. A non-SELECT produces a message such as “UPDATE 3 rows in 12 ms”.
  • The splitter handles MySQL DELIMITER $$ procedures, dollar-quoted PostgreSQL bodies and a 2,000-statement dump pasted in one go.
  • ⌘. cancels on the server (pg_cancel_backend, KILL QUERY) and returns within a second. The connection remains usable afterwards.
  • Auto-commit is a toggle in the toolbar. With it off, the status bar shows “Transaction open” in orange and Commit and Rollback become active — or TRANSACTION · NO WRITES, when all the transaction has run is a read, with a tooltip saying what the server is still holding. Closing the tab with an open transaction asks what to do. On a production connection there is no toggle at all: the toolbar carries a MANUAL COMMIT mark, and every statement you type that writes waits for Commit or Rollback. An edit made in the result grid goes through the review sheet instead, where Execute is the whole commit.

The editor beside its results

A query tab is an editor above its results, or an editor beside them — Split in the editor’s toolbar, beside Run and Beautify, and a setting that decides which way new tabs open. Side by side suits a wide display and a tall result: the statement stays in view while the rows, the chart or the map get the full height of the window.

Result panes

PaneWhat it shows
RowsThe result set in the grid. One pane per result set the statement produced.
ChartThe same rows drawn: bar, line, area, pie or scatter. Offered only when the result has a column that measures something.
MapA geometry column on Apple Maps. Offered only when the result has one.
MessageThe statement, what the server said, and the elapsed time.
ProfilePer-stage timings where the engine offers them: MySQL’s SHOW PROFILE. On PostgreSQL the pane explains there is no equivalent that does not re-run the statement and offers EXPLAIN (ANALYZE) as an explicit action for a SELECT.
StatusSession counters: SHOW SESSION STATUS on MySQL, the pg_stat_database row on PostgreSQL.

Profile and Status are read when their pane is opened, not after every statement. Both cost a round trip, and a pane nobody looks at should cost nothing. Chart and Map are never offered for a result that cannot fill them, so the picker never shows a pane that would be empty.

The Chart pane

A million rows grouped by the server into twelve, drawn where they landed. The readout above the plot names the point under the pointer.

Any result with something to measure can be drawn without leaving the tab: pick the shape, the value and what to group it by, and choose whether rows sharing a label are summed, averaged, counted, or drawn one each. It starts at Sum when labels repeat and at each row when they do not. It is the answer to the question the rows were asked for in the first place — is it going up? — and it is one click from the grid rather than a copy into a spreadsheet.

  • A key is not a measurement. A primary key, a UUID, or a column named id or ending in _id is offered as a label and never as a value, because summing a key says nothing.
  • Hovering reads out the point under the pointer, so a bar you are looking at has a number rather than an estimate off the axis.
  • Bars and pie slices take a hue per category from an eight-colour palette chosen separately for light and dark surfaces and checked for colour-blind separation. A line, an area or a scatter keeps one colour, because it draws one series.
  • A chart has a budget of marks. At most 50 bars (or 20, or 100, by choice), 8 slices, 1,000 points of a line and 2,000 of a scatter. Where a sum means something the rest becomes Other, and a line is thinned by min–max buckets, so its peaks and troughs survive.
  • The bar above the plot says what was drawn — “Top 50 of 1,000 rows, rest not drawn” — so a partial picture can never read as the whole.
A wide result opened straight on Chart, every label its own. Fifty bars are drawn and the bar says so; the rest are not drawn rather than squeezed into a smear.

The plot is worked out off the main thread, from a copy of only the two or three columns it needs and up to 100,000 rows of the result, and it is thrown away if the choice changes before it is done. The highlight under the pointer is a layer of its own, so moving over the chart does not redraw it. That is why the Chart tab opens at once on a large result instead of stalling the window.

Errors

Server errors appear in a non-modal banner above the results: red left border, monospaced message, a Copy button. The message is the server’s, verbatim, with its SQLSTATE. When the position is known the editor jumps to the token. Tinker never rewrites, summarises or retries an error on your behalf.

History and snippets

Every executed statement is recorded with its connection, database, timestamp, duration and row count or error, capped at 10,000 entries and searchable from ⌘Y. Double-click puts one back in the editor. Snippets (⌘⇧K) hold the statements you type every week.