qub v0.44.9
There are great open-source SQL editors out there, but none of them ever really clicked for me. DataGrip was the one that gave me the feeling of productivity and control over my database workspace. But once my enterprise license got canceled, instead of picking a new one I decided to build myself an editor the way I always wanted it to be. So qub was built with an emphasis on SQL centricity, the basics done well, and a clean interface I enjoy looking at all day.
During development, to keep the interface consistent and easier to build, I created a Qt/QML component library (including fonts, icons, and theming) called Mahina, which I also open-sourced. If you're building desktop apps with Qt, it might save you from reimplementing the same primitives from scratch.
AI assistance was used to build this application, but all the code was human-reviewed.
/* @ai */ blocks. Supports Anthropic, OpenAI, and Ollama.
Connections
qub supports multiple simultaneous connections. Each connection lives in its own tab in the editor. You can switch freely between them while queries run in the background.
Creating a connection
From the welcome screen, click the + icon in the Data Sources list.
Choose a driver from the dropdown (PostgreSQL, MySQL, MariaDB, SQLite, ODBC, Oracle, Firebird).
Fill in host, port, database name, username, and password. For SQLite, pick a Source instead — open an existing file, create a new database, or Import CSV to build one from a delimited-text file.
Running the database in Docker? Click Discover Docker container and pick one from the list instead of typing any of that — see below.
Optionally assign a profile to enforce safety rules on this connection.
Click Test to verify the credentials, then Save to return to the list (its status dot shows whether the connection came up), or Save & Connect to jump into the workspace once the connection actually opens — if it fails, you stay on the form with the error and can fix and retry.
Discover a Docker container
When the docker CLI is on your PATH, a Discover Docker
container button appears on the form for a new server connection. It lists the
running containers that look like a database and have a published port, showing each
one's name, image, and port; picking one fills in the driver, host, port, database name,
username, and password from the container's own environment, and names the connection
after it if you left the name blank.
The port it fills in is the one mapped on the host, not the one inside the container, so the connection works from outside Docker. A container whose image sets no password in its environment prefills without one and says so — type it in yourself. The button is hidden for SQLite and when editing an existing connection, and qub only ever reads containers: it never starts, stops, or creates one.
Connecting and disconnecting
Each row in the Data Sources list has a plug button that toggles the connection: it connects when the connection is down and disconnects when it is up, without leaving the welcome screen. Clicking the row itself also connects, but additionally takes you into the workspace. Inside a workspace, a Disconnect button appears in the top bar while the active connection is up, and a Reconnect button while it is down.
Disconnecting closes the database session and tears down its SSH tunnel, if any. Reconnecting reads the password from the system keychain and re-establishes the tunnel automatically. With Auto-reconnect enabled in Settings, qub also reconnects on its own when a connection drops mid-query.
Passwords and the system keychain
qub never writes a password to its own files. Connection passwords, SSH passwords and
AI provider keys go to the credential store your operating system already runs, under
the service name qub: Keychain on macOS, the Secret Service (GNOME Keyring
or KWallet) on Linux, Credential Manager on Windows. That store is the one asking when
you see a prompt — qub has no password of its own to ask for.
Expect a prompt the first time a saved password is read, which is usually the first connection after installing, and again after an update:
| System | What you see |
|---|---|
| macOS | A dialog: "qub wants to use your confidential information stored in 'qub' in your keychain." Enter your login password and choose Always Allow so it stops asking. The permission is tied to that exact build of the app, so a new version of qub asks once more. |
| Linux | Usually nothing: on most desktops the login keyring is unlocked when you log in. If your keyring has its own password, or you use automatic login, a dialog asks to unlock it the first time qub stores or reads a password. |
| Windows | Nothing. Credential Manager stores the entry for your Windows account without prompting. |
Denying the prompt is not fatal, but qub gets no password back and the connection fails with whatever the database says about the credentials. Type the password into the connection form and connect again — you will be asked once more, and can allow it then. The same applies on a machine with no keyring service running at all: qub cannot save the password, so it has to be typed each session.
The entries are visible and revocable in the tools you already have — Keychain Access,
Seahorse or KDE Wallet Manager, Credential Manager — under the service qub,
keyed by connection name (ssh_… for a tunnel's password,
ai_… for a provider key). Deleting a connection in qub deletes its
entry with it.
Nothing else in qub is protected by that prompt: it guards the stored passwords, not the application. Anyone at an unlocked machine can open qub and use a connection that is already up.
Supported drivers
| Driver | Database | System dependency |
|---|---|---|
| QPSQL | PostgreSQL | libpq |
| QMYSQL | MySQL | libmysqlclient |
| QMARIADB | MariaDB | libmariadb |
| QSQLITE | SQLite | none (built-in) |
| QODBC | Any ODBC source | unixODBC |
| QOCI | Oracle | Oracle Instant Client |
| QIBASE | Firebird | libfbclient |
Which of those a copy of qub can actually load is decided by Qt, not by qub: the official Qt binaries the macOS and Windows builds are made with carry no MySQL/MariaDB driver at all, and the macOS ones none for Oracle or Firebird. The macOS DMG is the exception — it ships a MySQL/MariaDB plugin qub compiles itself. qub lists only the drivers the copy you are running found, so the connection form never offers one that would fail on you.
| Driver | Linux · AppImage | macOS · DMG | Windows · installer |
|---|---|---|---|
| QSQLITE | bundled | bundled | bundled |
| QPSQL | bundled | bundled | bundled |
| QODBC | bundled | bundled | system unixODBC |
| QMYSQL / QMARIADB | bundled | bundled | no plugin |
| QIBASE | bundled | no plugin | bring fbclient.dll |
| QOCI | not bundled | no plugin | bring Instant Client |
No plugin means nothing you install will help — only a qub built against a Qt compiled with that driver, which is what the macOS DMG does for MySQL: it carries a plugin built during packaging against MariaDB Connector/C, and the client library alongside it. Oracle is left out of the AppImage because its client library is not redistributable. Building qub from source, every client library is your system’s and every driver Qt built is available.
PostgreSQL: requires libpq on your system.
Fedora: sudo dnf install libpq. Ubuntu: sudo apt install libpq5.
SSL / TLS
Enable Use SSL in the connection form to encrypt the connection. You can provide a CA certificate, a client certificate, and a client private key for mutual TLS authentication. All certificate paths are stored per connection.
SSH tunnel
qub can route a database connection through an SSH tunnel — useful for databases
inside private networks that are not directly reachable. Create the tunnel first
under the SSH card on the welcome screen (host, port, username,
and a private key file — leave the key empty to use your default keys or a running
ssh-agent), then select it in the SSH connection dropdown of the
database connection form. Authentication is key-based: the tunnel runs
ssh non-interactively, so it never prompts for a password.
Test checks the SSH login on its own, without a database behind
it — in the form, before you save, and from the plug icon on each saved entry.
It logs in, reports what ssh said, and drops the session again; no
port is forwarded. The first connection to a host stops on its fingerprint, which
you confirm before qub writes it to ~/.ssh/known_hosts.
With a tunnel selected, the connection’s Host and
Port are read from the SSH host’s point of view, not from
yours: localhost means the SSH machine itself, and a private name
like db.internal is resolved over there. If the database runs on the
SSH host, that is 127.0.0.1 and its real port; if it runs elsewhere
on the private network, use the address the SSH host would use to reach it.
Import and export
Both connections and SSH configurations can be moved between machines as JSON files. Use the ↑ (export) and ↓ (import) icons in the Data Sources and SSH Connections headers. Connection exports include every setting — driver, host, SSL certificates, timeout, default schema, and the SSH tunnel link. Passwords live in the system keychain and are never written to the file; re-enter them after importing. Importing skips entries that already exist.
Import CSV as a table
qub can turn a delimited-text file into a queryable table. It sniffs the delimiter
(comma, tab, semicolon, or pipe — quoted fields, embedded delimiters, and newlines are
handled), infers a type for each column (INTEGER, REAL, or
TEXT), and imports the full file. Blank cells import as NULL.
Once imported, the data is queryable with plain SQL — joins, aggregates, filters, and the
profile and chart views all work on it. There are two ways in:
As a new data source. Add a connection, choose the SQLite driver, and set Source to Import CSV. Pick the file, name the table, toggle First row is header if needed, then Import & Connect (or Save). qub writes a standalone SQLite file and registers it as an ordinary connection that persists across restarts.
Into an existing SQLite connection. Click the CSV icon on any SQLite row in the Data Sources list to add the file as a new table in that same database. Because the tables share one database, you can join a second CSV against the first with plain SQL. Importing a table whose name already exists is rejected.
Database info & health
Click the ℹ button next to the + tab to open the Database window for the active connection. Its Overview tab shows the database's identity at a glance — version, size, table and view counts, index count, active connections, encoding, and collation. The Health tab shows live metric cards with sparklines (connections, rows read, commits, cache hit ratio, lock waits, database size — varying by driver), polled every 3 seconds while the window is open. It's a separate window, so you can move it to another monitor and keep an eye on the database while you work. Embedded databases like SQLite have no server counters; only the file size is tracked there.
The Alerts section at the bottom of the Health tab lets you get notified when a metric crosses a threshold. Pick a metric, choose above or below, enter a threshold (for example Active connections above 100 or Cache hit ratio below 90), and click Add. Each rule shows the metric's current value live, tinted red while it is in breach. When a metric first crosses its threshold qub raises a toast and writes a warning to the Activity Log, so the breach is still on record after you close the window, and logs a note when it recovers. Alerts are evaluated from the same 3-second poll as the metric cards, which only runs while the Database window is open — closing it stops the checking, it does not run in the background. Rules are saved per connection and persist across restarts; the toggle disables a rule without deleting it.
Schema graph
Open Schema graph from a schema row's ··· menu in the schema browser. It draws that schema's tables and the foreign keys between them in a separate window, which you can move to a second monitor while you keep writing queries. Tables with foreign keys are highlighted; isolated tables are muted. Click a node to read its name.
Drag the background to pan, wheel to zoom, or use the − / + / Fit buttons. Labels appear when the tables have room to carry them, so zoom in on a crowded corner rather than reading through overlapping text.
Above 150 tables the window says so instead of drawing: a graph that size is unreadable, and laying it out costs seconds of a frozen window. Where the tables that actually have foreign keys would fit, it offers to draw just those — usually the part worth looking at. A schema, not the whole connection, is the scope: a database with hundreds of tables has no picture that fits on a screen.
Compare schemas
Click the git-diff button next to the + tab to open the schema comparison window. Pick a Base (A) and a Compare (B) connection from the two dropdowns — both must be connected — and qub diffs their live schemas into an add/remove/change tree:
+ green — a schema, table, or column that exists only in B (added).
− red — one that exists only in A (removed).
~ amber — a table whose columns differ, or a column whose type, nullability, or primary-key membership changed. Changed columns show the before → after (e.g.
TEXT → VARCHAR,NULL → NOT NULL).
The summary strip tallies the changes, the ⇄ button swaps A and B, and Differences only hides everything that matches so you see just the drift. This is the quickest way to check whether staging and production have fallen out of sync.
Schema snapshots
Comparing two live connections is great when you have both, but sometimes you want to track how a single database changes over time. The camera button next to Compare schemas opens the schema snapshots window. Pick a connection, give the snapshot a name, and click Capture to save its schema to disk.
Snapshots are listed on the left with their connection, table count, and age. Select one, choose a Live connection to compare it against, and qub diffs the current schema against the saved snapshot using the same add/remove/change tree as Compare schemas — so you can see exactly what has drifted since the snapshot was taken (a migration you forgot to re-apply, a column someone dropped, and so on). Differences only hides everything that still matches. Snapshots persist across restarts until you delete them with the trash button.
Query editor
The editor occupies the upper portion of the workspace. It has SQL syntax highlighting, line numbers, and autocomplete for table and column names.
Command palette
Press Ctrl+P to open the command palette — a searchable launcher for every workspace action. Start typing to filter by name or group, use ↑/↓ to move, Enter to run, and Esc to dismiss. Each command lists its own keyboard shortcut, so the palette doubles as a way to learn them.
It covers running and explaining queries, formatting SQL, asking the AI, opening/closing tabs, opening and saving files, saving snippets, exporting results, viewing the selected row, toggling the activity log and panels, and opening this shortcuts reference.
Running queries
Click ▶ Run or press Ctrl+Enter. If text is selected, only the selection is executed. Otherwise the full buffer runs. Queries run asynchronously — the UI stays responsive while results load.
EXPLAIN (query plan)
Press Ctrl+E (or the lightning button in the editor toolbar) to explain the current query — the selection if you have one, otherwise the whole editor. The plan opens in the Explain tab beside Results, drawn as an indented tree of operations rather than raw text.
- Each node shows its operation, the table/index/join it touches, and cost/row metric chips.
- The costliest node (and full table scans) are highlighted, and tuning hints — sequential scans, badly-off row estimates — appear at the top.
- The plan is dialect-aware: PostgreSQL uses
EXPLAIN (FORMAT JSON)with per-node self-cost, SQLite usesEXPLAIN QUERY PLAN, and MySQL/MariaDB use classic tabularEXPLAIN. - By default the plan is estimated (the query is not run). For a
SELECT/WITHquery, the Analyze button re-runs it withEXPLAIN ANALYZEfor measured timings — this executes the statement, so it's offered for read-only queries only.
Explaining a query that contains parameters prompts for their values first, then explains the filled-in SQL.
Tabs
Open multiple query tabs per connection with Ctrl+T. Each tab has its own SQL buffer, result set, and execution state. Close a tab with Ctrl+W. Tab state is preserved when you switch connections and restored when you switch back.
SQL formatting
Press Ctrl+Shift+F to auto-format the SQL in the editor.
Find and replace
Ctrl+F opens the find bar. Ctrl+H opens find-and-replace. Press Escape to close.
Autocomplete
After typing 2 or more characters, qub offers schema-aware suggestions drawn from the connected database. Completion understands the statement under the cursor:
- Columns in scope — a bare word first suggests columns of the tables
named in the current statement's
FROM,JOIN,UPDATE, orINSERT INTO, tagged with their type and source table. - Qualified columns —
alias.ortable.resolves the qualifier (aliases first, then table names, schema-qualified names allowed) and lists only that table's columns. - Tables and keywords — matching table names and SQL keywords
(
SELECT,LEFT JOIN,GROUP BY, …) round out the list.
Use ↑ / ↓ to navigate and Enter or Tab to accept. Escape dismisses the popup. It only opens while you type: moving the caret into an existing word — by clicking or with the arrow keys — leaves it closed. Autocomplete can be turned off in Settings → Editor.
Schema insertion
Double-click a table or column name in the schema browser to insert it at the cursor position in the editor (requires Insert on double-click to be enabled in Settings).
Saving and opening files
Ctrl+S saves the current query to a .sql file on disk.
Ctrl+Shift+S saves it as a snippet.
Ctrl+O opens a file picker to load a SQL file into the editor.
Profile guards
If the active connection has a profile assigned, qub inspects
every query before it runs. Depending on the profile rules, it will either block
the query outright or ask for confirmation before executing destructive statements
such as DELETE, DROP, TRUNCATE, or
UPDATE without a WHERE clause.
Markdown blocks (literate SQL)
You can interleave narrative with your SQL by writing Markdown inside special
block comments. A block starts with /* @md and ends at the normal
*/:
/* @md # Creating the user The account has to exist before the FK on `orders` can reference it. */ CREATE TABLE users (id int PRIMARY KEY);
Because the narrative lives in a comment, the buffer stays a perfectly valid
.sql file — the database ignores it, and running, profiles, history,
and snippets all work unchanged.
Preview: Ctrl+5 (or Panels → Markdown preview) opens a rendered view beside the editor. Markdown blocks are formatted; the SQL between them appears as code blocks. The preview follows your typing live.
Export: choose Export as Markdown… from the editor's
··· menu to save the whole document as a .md file —
narrative becomes prose and each SQL chunk becomes a fenced
```sql code block, ready for a wiki, a README, or a pull request.
The suggested filename follows the query tab's name.
Export as Markdown + results… does the same but appends a Latest result section containing the current tab's result set rendered as a Markdown table (capped at 100 rows, with a note when more were returned) — handy for pasting a query and its output together into a report or review.
Query parameters
You can write queries with named or positional placeholders. When you run such a query, qub opens a parameter dialog where you fill in the values before execution. This is especially useful for snippets you reuse with different inputs.
Named parameters
Use :name syntax. Each unique name gets one input field in the dialog.
SELECT * FROM orders WHERE status = :status AND created_at BETWEEN :start_date AND :end_date
Positional parameters
Use $1, $2, … syntax (PostgreSQL style).
Parameters are filled in order.
SELECT id, total FROM orders WHERE customer_id = $1 AND total > $2 ORDER BY created_at DESC LIMIT $3
Value typing
Values are inserted as SQL literals with a little type inference so
comparisons and LIMIT work as expected:
- Numbers (
42,-3.14),true/false, andnullare inserted unquoted. - Everything else becomes a quoted, escaped string.
- A blank field becomes an empty string
''.
To force a value that looks like a number or keyword to be treated as text
(e.g. a ZIP code 01234, or the literal word true),
wrap it in single quotes in the field.
Remembered values
The values you enter are kept per tab, so re-running a parameterised query pre-fills the dialog with what you used last time. Values are also collected before an EXPLAIN of a parameterised query.
Named and positional parameters are mutually exclusive in a single query. If both are present, named parameters take priority.
Results & export
Query results appear in the lower portion of the workspace in a scrollable table. Column headers are fixed while you scroll horizontally. The status bar at the bottom shows the row count and execution time.
Status bar
The left of the status bar says where the next statement will land, read left to
right: the kind of database (with a check mark while the connection is up), the
database itself, and — on PostgreSQL and Oracle, where a schema is a separate
thing from a database — the schema an unqualified table name resolves to, each
with its own icon. A SET search_path or USE you run
moves it, so the schema shown is the live one rather than the one the connection
was created with.
Output console
Next to Results sits an Output tab — a console-style
stream of everything the active connection did in this session: each executed statement
with its outcome (SELECT · 500 rows · 142ms), connection and SSH tunnel
events, and errors inline in red with the failing SQL echoed underneath. A statement takes
two stamped lines — the SQL at the moment it was sent, the outcome at the moment it came
back — so the gap between them is on screen rather than in a duration you have to read.
The console is one selectable text buffer, not a list of rows: drag through it and copy
the three lines you want, or right-click for Copy, Select all and
Copy console (the whole session as plain text). It follows new
entries like a terminal; scroll up to read and it pauses, with a Latest button
to jump back to the live tail. The global
activity log (Ctrl+L) remains the
place to see all connections at once with filtering and export.
Chart
A third Chart tab sits beside Results and Output. Pick a chart type — Bar, Line, Area, or Scatter — and choose an X column (categories, or the x-axis for scatter) and a Y column (the numeric value). The plot is drawn straight from the rows currently in view, so filtering the results narrows the chart too. Non-numeric Y values are treated as zero; scatter plots use only rows where both X and Y are numeric. It's a quick way to eyeball a trend or distribution without exporting to a spreadsheet.
Column stats
Click any cell and the status bar shows live aggregates for that whole column,
computed over the rows currently in view (so filtering narrows them too) — much
like the selection summary at the bottom of a spreadsheet. Numeric columns report
Σ sum · avg · min · max · n; text columns report
n · nulls · distinct. It's a fast way to sanity-check a result without
writing a second GROUP BY query.
Profile
The Profile tab gives you a df.describe()-style
summary of the whole result set — one card per column, computed over the rows
currently in view (so filtering narrows it too). Each card shows the column's
inferred type (numeric, text, or empty), its
count · nulls · distinct counts, and a distribution:
numeric columns get min · median · mean · max plus a histogram;
text columns get their top values with frequency bars. It's a fast first look
at the shape of a dataset before you start slicing it with queries.
Explain
The Explain tab shows the query plan for the current statement as a visual tree — see EXPLAIN in the Query editor section. Press Ctrl+E or the editor's lightning button to fill it.
Pivot
The Pivot tab cross-tabulates the result set, spreadsheet pivot-table style. Choose a Rows field and a Columns field to group by, then an aggregation — Count, Sum, Avg, Min or Max — applied to a chosen value column (Count tallies rows and ignores the value column). Row, column and grand totals are computed automatically.
Like the other analysis tabs it works over the rows currently in view, so filtering the results re-pivots them. Cells with no matching rows are left blank, and numeric aggregates skip non-numeric or NULL values. Very high-cardinality fields are capped with a notice so a pivot can't explode into thousands of columns.
Checks (data quality)
The Checks tab lets you assert data-quality expectations about the result and see them pass or fail live. Click Add check, pick a column, and choose a check:
Not null / Not empty — no NULLs (and, for Not empty, no blank strings).
Unique — every value is distinct (duplicate rows are counted as violations).
Positive / Non-negative — numeric values are
> 0/≥ 0.In range — numeric values fall within a
min, maxyou supply.Max length — text is no longer than N characters.
Matches regex — every value matches a pattern (handy for emails, codes, IDs).
Each row shows a green pass or red fail with the number of offending rows and an
example value. The header tallies passed and failed checks. Checks evaluate over the
rows currently in view, so filtering re-runs them, and they are remembered per query
tab. This turns a quick SELECT into a lightweight data-quality gate
without leaving qub.
Diff (compare result sets)
The Diff tab compares the current result against a saved baseline — handy for checking what a query change did, or how data shifted between two runs. Click Set baseline to capture the rows in view, then re-run the query (or edit it and run again) and the diff updates live:
+ added — rows present now but not in the baseline.
− removed — rows that were in the baseline but are now gone.
~ changed — rows matched by a key column whose other values differ; each shown as
column: before → after.
Use Match by to pick the key column that identifies a row across runs (for example a primary key), or leave it on Whole row to compare rows exactly. Differences only hides unchanged rows. If the baseline and the current result have different columns, the diff says so rather than guessing. The baseline is remembered per query tab until you clear it.
To keep a baseline beyond the current tab, click Save… to store the result under a name (it's kept with the connection and the SQL that produced it). Later — even in a new session — click Load… to pick a saved snapshot for this connection and diff the current result against it. Saved snapshots are per connection, so the Load… menu only lists ones captured on the connection in view.
Inline editing
For single-table SELECT queries, results are editable directly in the table.
Click any cell to edit it; qub generates and executes an UPDATE statement
automatically on confirmation. Primary key columns are protected and shown as read-only.
Which table you are editing is read back out of the query itself, so it works on a
schema-qualified SELECT — including the one the quick-browse button
writes — and the generated UPDATE quotes its identifiers in the
connection's own dialect, backticks on MySQL and MariaDB.
Column widths
Columns start out sharing the width of the grid evenly, which is rarely what a result set wants: a column of two-letter codes gets the same room as one holding a timestamp. Drag the edge of a header to resize one by hand, double-click that edge to fit the column to its widest value, or right-click the header for Fit “column” to contents, Fit all columns to contents and Reset column widths. A fit measures the first 500 rows and stops at 600 px, so one cell holding a page of JSON cannot push every column after it off the screen.
Value inspector
To read a value that's too wide for the grid, open it in the inspector: double-click a cell (in read-only results) or right-click and choose View value…. The dialog shows the full contents in a scrollable, selectable monospace pane, and JSON objects and arrays are automatically pretty-printed (indented and on multiple lines). Copy copies exactly what's shown, so you can grab the formatted JSON. In editable single-table results, double-click still starts an inline edit — use the right-click menu to inspect instead.
Row viewer
For wide tables that scroll off-screen, right-click a cell and choose View row… — or press Ctrl+Shift+R with a cell selected — to see the whole record as a vertical field list (column → value). Click any value to open it in the value inspector (full text or pretty-printed JSON), and use the ‹ Prev / Next › buttons to step through the result set one record at a time. Fields that are foreign keys show a jump icon that opens the referenced row in a new tab, and Copy row copies the record as tab-separated values.
Foreign-key navigation
When a cell sits in a column that participates in a foreign key, its right-click menu offers one-click navigation that opens the related rows in a new query tab:
- Go to table row — the column is a foreign key, so this jumps
to the single referenced row in the parent table
(
SELECT * FROM parent WHERE pk = value). - Rows in table (column) — the column is referenced by other tables, so this lists the child rows pointing at the current one. One entry appears per referencing table.
Click a cell first (which also selects it), then right-click for its navigation options. The new tab is named after the table it lands in, so a trail of jumps reads as the tables you followed. If a tab already answers to that name, the new one keeps its Query n label rather than repeating it; double-click any tab to rename it. Foreign keys are read from the live schema of PostgreSQL, MySQL/MariaDB, and SQLite connections, and the generated query uses the connection's identifier-quoting dialect. Both ends of a key carry their schema, so on a connection exposing more than one the menu names the destination qualified (Go to analytics.dim_customer row) and the generated query qualifies it too — a bare name would resolve against the search path rather than against the schema the row you clicked came from. Keys that cross schemas are followed like any other.
Exporting results
| Format | How to export |
|---|---|
| CSV | Ctrl+Shift+E or toolbar button |
| TSV | Toolbar export menu → TSV |
| JSON | Toolbar export menu → JSON |
| Excel (.xlsx) | Toolbar export menu → Excel |
SQL INSERTs | Toolbar export menu → SQL INSERTs |
SQL INSERTs writes one INSERT INTO … VALUES … statement per
row — handy for moving data into another database. You're prompted for the target table
name (prefilled from the query's table when known), and identifier quoting and value
literals follow the active connection's dialect: MySQL/MariaDB use back-ticks, everyone
else double quotes; booleans, byte data, and backslashes are escaped per dialect. The
export covers the rows currently in view, so it respects any active filter.
Copying results
Right-click a cell or use the toolbar to copy the result set as CSV, JSON,
plain text, or SQL INSERT statements.
Result limit
The Limit picker in the toolbar caps the number of rows returned. Set it to None to retrieve all rows. The default can be changed in Settings.
Exporting is not bound by that display limit: when the visible result is truncated, an export re-runs the same query with no limit and writes the complete result set to the file (up to a 500,000-row safety ceiling). A toast confirms how many rows were written. Non-truncated results export straight from what's on screen.
Schema browser
The schema browser lives in the left panel of the workspace. It shows all schemas, tables, and columns for the active connection, and provides quick actions for inspecting your database structure.
Browsing tables
Click a table name to select it. Click the arrow to expand it and see its columns, along with type, primary key, and nullable indicators. Double-click a table or column to insert its name at the cursor in the editor (requires Insert on double-click in Settings).
Quick browse
Enable the quick-browse button in Settings (Show quick browse button).
A ▶ icon appears on hover next to each table and runs
SELECT * FROM table instantly, in a tab of its own, named after the
table — whatever you were writing in the current tab stays where it was. The row
limit applies just as it does to the Run button.
Table actions menu
Hover over any table row to reveal a ··· button on the right. Clicking it opens a menu with three actions:
-
Copy name
Copies the table name to the clipboard, qualified with its schema
(
analytics.orders) where the connection exposes more than one — the same name the quick-browse button would have queried. - Statistics Opens a popup with row count, size, pages, dead rows, vacuum and analyze timestamps (PostgreSQL), engine and timestamps (MySQL), and per-column statistics including distinct count, null fraction, average width, and correlation (PostgreSQL only). The lookup is pinned to the table's own schema, and the popup title names it the same way Copy name does — so where two schemas hold a table of the same name, what you are reading about is on screen.
-
View DDL
Opens a read-only popup showing the
CREATE TABLEstatement for that table, with a Copy button. UsesSHOW CREATE TABLEon MySQL,sqlite_masteron SQLite, and reconstructs frominformation_schemaon PostgreSQL. Like Statistics, it is pinned to the table's own schema, and the reconstructed statement carries the qualified name where there is more than one.
Schema actions menu
A schema row has a ··· button of its own, with Copy name — the schema name alone, unqualified.
Snippets
Snippets are reusable pieces of SQL — the queries and DDL you keep coming back to. They live in one global library shared by all workspaces.
Saving a snippet
In the editor, select some SQL and press Ctrl+Shift+S (with nothing selected, the whole tab is saved). The editor's ··· menu has the same Save as snippet action.
On the welcome screen, open the Snippets card and click + to write one from scratch.
Names must be unique within a folder — you'll get an inline error instead of a silent duplicate.
Folders
Type any folder name when saving — folders are just labels, so they spring into existence with their first snippet and disappear with their last. Existing folder names appear as one-click chips below the folder field, which keeps you from accidentally creating "DDL" and "ddl". Folders group the sidebar list under collapsible headers and the Home list under section headers.
Managing snippets
The Snippets card on the welcome screen lists the whole library. Click a row (or its pencil) to edit the name, folder, or SQL; the trash icon deletes after confirmation.
Import and export
Use the ↑ / ↓ icons in the Snippets header to save the library to a JSON file or load one from another machine. Importing skips snippets that already exist (same name and folder).
AI assistant
qub has two ways to generate SQL from natural language — an inline comment syntax that stays in your editor, and a floating command palette for quick one-off prompts. Both use the same configured AI provider and are schema-aware. The prompt also tells the model which database engine the active connection uses, so the generated SQL is written in the right dialect (PostgreSQL, MySQL, SQLite, …).
Command palette — Ctrl+K
Press Ctrl+K (or click the ✦ button in the query toolbar) to open the AI command palette. Type your prompt and press Enter. The generated SQL appears below the input with two options:
- Insert at cursor — inserts the generated SQL at the current cursor position.
- Replace editor — replaces the entire editor content.
Press Escape or click outside to close without inserting.
Inline prompts — /* @ai */
Write a prompt directly in the editor inside an @ai comment block —
the same block-comment convention as /* @md */ markdown blocks — then
press Ctrl+Shift+Enter. The block is replaced
in-place with the generated SQL. Prompts can span multiple lines:
/* @ai list all users who signed up in the last 7 days, grouped by country, sorted by count descending */
The block under the cursor is the one that runs (a lone block runs from anywhere). Inline prompts persist with the file and can be saved alongside your queries — useful for documenting intent or keeping a prompt you run regularly.
Both methods send your database schema (table names and column types) to the AI provider as context, so the generated SQL uses your actual table and column names.
Configuring a provider
Go to Settings → AI and choose a provider:
| Provider | What you need |
|---|---|
| Anthropic | An API key from console.anthropic.com |
| OpenAI | An API key from platform.openai.com |
| Ollama | A running Ollama instance; configure its URL in Settings (default: http://localhost:11434) |
API keys are stored in the system keychain, not in plain text. You can override the model name for any provider in Settings.
Workspaces
A workspace is a named work environment. It owns its open query tabs and a chosen subset of your global connections. Keep a Staging workspace and a Production workspace, and neither can accidentally run against the other's databases. qub creates a Default workspace for you, so you can start with one normal editing space and add more only when separate scopes are useful. Everything is saved continuously — close the app mid-task and the workspace reopens exactly as you left it.
Creating and switching
The Workspaces card on the welcome screen lists all workspaces with their tab counts and connections — click one to open it, or create, rename, and delete from there. Inside the editor, the workspace name in the toolbar opens a switcher menu with the same actions. Switching saves the current workspace first; nothing is lost.
Workspace connections
Each workspace contains an explicit subset of your global connections, and only those appear in its connection menus and schema browser. Manage the list via Workspace connections… in the switcher menu, or add one on the fly with Add connection to workspace… at the bottom of any connection dropdown. Opening a connection from the welcome screen adds it to the current workspace automatically.
This is a safety boundary: if a tab's connection is removed from the workspace (or deleted entirely), the tab keeps its SQL and stays editable, but running is blocked and a warning banner appears — qub never silently retargets a query at a different database. The banner's Add to workspace button restores access in one click.
Activity log
The activity log is a separate window recording what qub does behind the scenes: connection opens and failures (CONN), executed queries with timing and row counts (QUERY), SSH tunnel events (SSH), and AI requests (AI). Open it with Ctrl+L in the editor or the list icon in the welcome screen's top bar.
Click an entry to expand its details — a query entry shows the full SQL with an Open in editor button, an AI entry shows the prompt and generated SQL. Entries can be filtered by category and connection, and exported as JSON or CSV.
Slow queries
The Slow Queries tab of the same window ranks your executions by
total time spent. Structurally identical statements are grouped together by a
fingerprint — literal strings and numbers are collapsed to
?, so WHERE id = 1 and WHERE id = 42 count
as one query — and each group shows the number of calls, total, average and maximum
duration, how many runs failed, and when it last ran. Click a row to open its SQL in
the editor, or use the connection dropdown to focus on a single database.
This turns your ordinary query history into a lightweight profiler: the query at the top of the list is where your time is actually going, whether because it is slow or because you run it constantly.
Profiles
A profile is a named set of safety rules you can assign to a connection. Profiles help you avoid costly mistakes — especially on production databases — by either blocking certain query types outright or requiring explicit confirmation.
Creating a profile
Go to Settings → Profiles → Add Profile.
Give the profile a name (e.g. Production) and pick a color to identify it visually.
Enable the rules you want to enforce.
Click Save.
Available rules
-
Read-only mode
Blocks all write operations:
INSERT,UPDATE,DELETE,DROP,TRUNCATE,ALTER,CREATE, andREPLACE. -
Confirm DELETE
Shows a confirmation dialog before any
DELETE FROMstatement runs. -
Confirm DROP
Shows a confirmation dialog before
DROP TABLE,DROP DATABASE,DROP SCHEMA,DROP VIEW, andDROP INDEX. -
Confirm TRUNCATE
Shows a confirmation dialog before any
TRUNCATEstatement runs. -
Confirm UPDATE without WHERE
Shows a confirmation dialog when an
UPDATEhas noWHEREclause. - Allow Live Share On by default. Turn it off and the Live Share button is disabled while you are on a connection using this profile — and a session already running stops the moment you switch to one. It guards against inattention, not against you: anyone who can turn it off can also edit the profile.
Visual indicators
When a connection has a profile assigned, a colored stripe appears along the right edge of that connection's tab, and the profile name and color dot are shown in the status bar.
A common setup: a red Production profile with all confirm rules enabled, and a green Dev profile with no rules. The color makes the environment obvious at a glance.
Themes
qub supports full palette customization. Every color in the interface — backgrounds, surfaces, borders, text, and status colors — can be changed through the theme system. Themes are applied live and stored locally.
Built-in themes
The standard Mahina palette — clean blues, neutral greys, and warm surfaces.
A homage to one of the most beloved dark themes in the developer community, originally created by Zeno Rocha — someone whose work and commitment to the open-source community I deeply admire. Purple accents on a near-black canvas, exactly as it should be.
The classic precision-designed palette by Ethan Schoonover. Works beautifully in both light and dark mode.
A tribute to all women in computer science and technology — and in particular to the incredible people running the WoMakersCode program. The colors are taken directly from their iconic pixelated butterfly logo.
This theme exists because representation matters. If you are a woman in tech, know that you belong here.
Creating a custom theme
Go to Settings → Themes → New Theme.
Give your theme a name.
Use the color pickers to customize each palette token. Changes apply live as you pick.
Click Save. The theme appears in the list.
Click the ✓ icon next to it to activate it.
Import and export
Use Export (↑ icon) next to any theme to save it as a .json file.
Use Import (↓ icon in the section header) to load a theme someone
shared with you; a theme whose name already exists is rejected rather than duplicated.
Built-in themes cannot be edited, but you can export one as a starting point,
edit the name inside the file, and import your customized copy.
Settings
Access settings from the Settings tab on the welcome screen.
Appearance
| Setting | Description |
|---|---|
| Dark mode | Switch between light and dark mode. The active theme adapts automatically. |
Connection
| Setting | Description |
|---|---|
| Always close connection when leaving | Skip the "keep connections open?" dialog when leaving the workspace. |
| Auto-reconnect on disconnect | Automatically attempt to reconnect if the database connection drops. |
Editor
| Setting | Description |
|---|---|
| Font family | Monospace font used in the query editor. |
| Font size | Editor font size in pixels (10–24). |
| Font weight | Normal or medium weight. |
| Tab size | Number of spaces per tab (2, 4, or 8). |
| Insert spaces for Tab | Insert spaces instead of a tab character. |
| Line height | Compact, normal, or comfortable line spacing. |
| Highlight current line | Subtle highlight on the line the cursor is on. |
| Autocomplete | Enable or disable schema-aware autocomplete. |
| Keyword case in generated SQL | Whether the SQL qub writes for you (a table's browse button, a double-clicked table, a foreign-key jump) uses SELECT * FROM or select * from. It never recases SQL you typed, and never an identifier. |
| Default result limit | Default row cap applied to every query. |
Schema browser
| Setting | Description |
|---|---|
| Insert on double-click | Double-clicking a table or column inserts it at the cursor. |
| Show quick browse button | Show the ▶ play button on hover in the schema tree. |
History
| Setting | Description |
|---|---|
| History limit | Maximum number of history entries to keep (10–1000). |
AI
| Setting | Description |
|---|---|
| Provider | Anthropic, OpenAI, or Ollama. |
| API key | Stored in the system keychain, never in plain text. |
| Model | Override the default model for the selected provider. |
| Ollama URL | Base URL of your Ollama instance (default: http://localhost:11434). |
Sharing
| Setting | Description |
|---|---|
| Warn on start / stop | Show a confirmation before starting or stopping live share. |
| Pulse the button | Ring the Live Share button while a session runs. The button is red and reads "Stop Live Share" either way. |
| Stop sharing after | Minutes before a running session ends by itself. 0 leaves it up until you stop it. |
| TLS certificate | Path to a custom certificate file for HTTPS sharing. |
| TLS private key | Path to the private key matching the certificate. |
Profiles & Themes
| Setting | Description |
|---|---|
| Profiles | Create and manage safety rule sets. See Profiles. |
| Themes | Activate a built-in theme or create your own. See Themes. |
Keyboard shortcuts
Query editor
| Action | Shortcut |
|---|---|
| Run query (or selection) | Ctrl+Enter |
Run /* @ai */ block as AI prompt | Ctrl+Shift+Enter |
| EXPLAIN query | Ctrl+E |
| Format SQL | Ctrl+Shift+F |
| Find | Ctrl+F |
| Find and replace | Ctrl+H |
| Undo | Ctrl+Z |
| Redo | Ctrl+Shift+Z |
| Select all | Ctrl+A |
AI
| Action | Shortcut |
|---|---|
| Open AI command palette | Ctrl+K |
Run inline /* @ai */ prompt | Ctrl+Shift+Enter |
Tabs
| Action | Shortcut |
|---|---|
| New query tab | Ctrl+T |
| Close query tab | Ctrl+W |
Files
| Action | Shortcut |
|---|---|
| Save selection as snippet | Ctrl+S |
| Save to file | Ctrl+Shift+S |
| Open file | Ctrl+O |
| Export as CSV | Ctrl+Shift+E |
Panels
| Action | Shortcut |
|---|---|
| Toggle schema panel | Ctrl+1 |
| Toggle query panel | Ctrl+2 |
| Toggle results panel | Ctrl+3 |
| Toggle sidebar | Ctrl+4 |
| Toggle markdown preview | Ctrl+5 |
| Toggle activity log | Ctrl+L |
| Command palette | Ctrl+P |
| Show keyboard shortcuts | ? |