A native database client for Omarchy.
A connection manager, a schema browser, a paged result grid with staged
edits and a SQL editor, for MySQL/MariaDB, PostgreSQL and Redis — over a
direct socket or through an SSH tunnel.
A production MariaDB connection: the bar is the theme's red, the footer says 1-300 of 10,559 rows and means it, and the console log shows the exact SQL omatable just ran — timestamps and duration included.
One binary. Qt 6 Widgets straight over the vendor client libraries — MariaDB Connector/C, libpq, hiredis — painted live in whatever Omarchy theme you are running, at whatever text size you have set. No Electron, no Java, no Python runtime, no subscription.
- A 200,000-row table opens as fast as an empty one. Nothing ever loads a whole table: every result is a page — 300 rows, with a real count beside it — so memory is bounded by construction, not by luck.
- The grid never writes as you type. Edits, inserts and deletions
accumulate visibly, show you the exact SQL they will send
(
Ctrl+Shift+P), and go to the server as one transaction (Ctrl+S). No "are you sure" on every cell — one deliberate commit instead. - Production is read-only until you say otherwise. Protection follows the connection's environment, not a toolbar control you have to remember to arm — and the connection bar is filled with the environment's colour, so which server am I about to change is answered before you ask it.
- Every statement is inspectable, including ours. The console log shows the SQL omatable itself issues, introspection queries included. If the app sends it, you can read it.
- The tunnel is OpenSSH. A real
sshchild process, so~/.ssh/config,ProxyJump,known_hosts, certificates, FIDO keys and the 1Password agent all just work — omatable implements no SSH at all. - Arrive without retyping. Omatable reads Sequel Ace's and DBeaver's connection files, so a connection set built elsewhere arrives working.
- Omarchy through and through. Colours from the live theme, text size from
omarchy display text size, both applied the instant they change — and row heights, indents and paddings all derive from the text size, because a grid is mostly rows.
On Omarchy:
git clone https://github.com/omacom-io/omatable
cd omatable
./install-omarchy # deps through omarchy-pkg-add, installs into ~/.localAs an Arch package:
cd pkgbuild && makepkg -fsiRuntime dependencies: qt6-base, mariadb-libs, postgresql-libs,
hiredis, libsecret, openssl, openssh.
Optional: mariadb-clients and postgresql, for database backup and
restore. Without them those two actions say which package to install;
everything else works.
Omatable also registers as the handler for database URLs:
xdg-open postgres://user@host/db opens it as an ad-hoc connection that is
never saved and never touches the keyring.
A connection is created in one form that carries its own driver selector, so
there is no separate chooser step. Host and port are prefilled, and the name
is derived from user@host/database when left empty. Launch to first row of
data is ten interactions, and that number is a budget the test suite holds.
Every connection has an environment, and it drives both the colour of the connection bar and what the connection permits:
| Environment | Colour | Writes |
|---|---|---|
| Development | theme accent |
permitted |
| Staging | theme orange / yellow |
confirmed first |
| Production | theme red |
refused until unlocked for the session |
The three colours come from your theme's own palette, and are pulled apart automatically on a theme whose accent, orange and red are three shades of the same warm colour — so red still means production under all 22 shipped themes, without a hex literal fighting your colours.
An unlock is explicit, lasts only that session, and is shown in the connection
bar for as long as it lasts. Whether a statement is a read is decided
conservatively and syntactically: anything that is not plainly a
SELECT/EXPLAIN/SHOW/DESCRIBE — a DELETE inside a CTE, a
SELECT … FOR UPDATE, a batch with one write in it — is a write. And no
dialog ever stands between you and a statement you typed: a production refusal
is a line in the message pane with an Unlock control beside it.
The tunnel forwards to a unix socket, not a loopback port, in a mode-0700
directory under $XDG_RUNTIME_DIR — a forwarded TCP port is reachable by
every process on the machine while it is up, and a socket in a private
directory is not. When a jump host restarts underneath it, the tunnel is
rebuilt silently on next use. When it cannot be, the error names the cause —
refused forwarding, unknown host key, auth rejected, no agent, host
unreachable — rather than a bare Failed to create tunnel, and the ssh
child's stderr is kept and shown on request.
Double-click a table and you get its first page, with 1-300 of 200,000 and
page controls in the footer. Filter, sort and page compose into one statement,
and the count is the filtered count.
The grid never writes as you type. Double-click a cell and type, + Row
or Ctrl+I for a new row, Delete row from the grid's menu for an old one —
all three accumulate, visibly, and none of them has touched the server. An
edited cell is tinted in place, an inserted row appears under the page with
its untouched cells reading DEFAULT, and a row staged for deletion is tinted
across its full width.
Ctrl+Shift+P previews the SQL. Ctrl+S applies it as one transaction: a
batch that fails at any statement rolls back whole. Values are sent as bound
parameters, so a numeric shown as 128.40 is written back as 128.40.
Ctrl+Shift+Delete discards everything staged.
A row that changed underneath you is detected, not overwritten — every
update is preceded, inside the same transaction, by a locking check that the
row still holds what the page read. A table with no primary or unique key
cannot be edited here, and the footer says so rather than generating a
WHERE that would match more rows than you selected.
Ctrl+F over an open table, and filtering to one row is five interactions:
Ctrl+F, type the column name, Tab, type the value, Ctrl+Return. The
column field is a typeahead and the operator defaults from the column's type,
so in the common case it is never touched.
Operators follow the type — contains and begins with on text, between on
numbers and timestamps, an enum's own labels as a picker, containment on JSON,
and is null only on a column that can be null. Conditions combine with AND
and each can be turned off (Ctrl+B) without losing what was typed into it.
When the builder runs out, Raw SQL sits in the same column list and is
appended to the WHERE as written.
Values are bound parameters, so a filter value of
'; DROP TABLE customers; -- matches nothing and drops nothing.
Ctrl+Alt+] over an open table, and its shape is editable: columns, indexes
and foreign keys, staged exactly like the grid — the same three keys, the same
three tints, the same one transaction. Adding a column is eight interactions.
The preview says which kind of apply you are getting: PostgreSQL wraps DDL in the transaction and rolls a failed batch back whole, MariaDB commits each statement as it runs, and neither pretends to be the other. Destructive changes are named above their statement rather than blocked.
Ctrl+E from anywhere opens the editor on the current connection —
no feature in this app is allowed to stand between you and writing SQL.
Ctrl+Return runs the selection or the statement under the cursor; Ctrl+.
cancels. Several statements produce several result tabs. The server's own
error text is shown unchanged, with the line it came from. Completion knows
your schema — tables, columns behind a dot, and the keywords of the engine you
are on.
Every statement you run is kept in the sidebar's History tab across
restarts, and Save query… puts one in Queries.
A driver, not a second application. db0 opens as a grid whose columns are
key, value, type and ttl, filtered with the same filter bar as any
table and paged over SCAN rather than LIMIT — so browsing a keyspace feels
exactly like browsing a table, because it is the same grid.
Tabs belong to a connection, not to the window: switching connection switches
the whole set, and switching back finds it where you left it. Ctrl+Shift+D
splits the pane, once.
Omatable always opens on the connection list. It does not reconnect to whatever you were last looking at — opening a server is a click you make on purpose, and there is no environment for which that is the wrong rule.
The window writes ~/.local/state/omatable/session.json when it closes, and
it holds a window layout — geometry, the sidebar width, whether the console
was showing. Nothing in that file is a secret and nothing in it names a
connection, and a missing or corrupt one starts clean rather than failing to
launch.
Omatable reads Sequel Ace's Favorites.plist and DBeaver's
data-sources.json (with the passwords beside it), so a connection set built
elsewhere arrives without retyping. Each import is best-effort and says what
it could not read rather than failing whole.
Two different things, deliberately named differently:
- Your connections go into one sealed file through
...besideNew connection. The backup carries the passwords, pulled out of the keyring at export time and put back on restore, so a connection set that moves machine arrives working. The passphrase is not optional, and SSH keys are never included — only the path to them. - A database is dumped and restored through
Back up database...andRestore database..., which shell out tomariadb-dump/mysqldumpandpg_dump/pg_restore— the vendor tools, not a re-implementation. Whole-database granularity,--no-datafor schema-only, and no scheduling. Each job opens its own tunnel; the password travels in the child's environment, never on its command line.
This is a database client, so you should be able to check this before trusting it.
| Secret | Where it lives |
|---|---|
| Database passwords, SSH passwords, key passphrases | The Secret Service keyring, keyed by connection id |
| Hosts, ports, users, SSH options, environment tags, the path to a private key | ~/.config/omatable/connections.json |
| Private keys | Never read, never copied, never exported. Only the path travels |
| A connection backup's passwords | Inside the sealed envelope, under PBKDF2-HMAC-SHA256 at 600,000 rounds and AES-256-GCM |
And where they never go:
- Never on a command line.
mariadb-dumpgets the password throughMYSQL_PWDor a mode-0600 defaults file andpg_dumpthroughPGPASSWORD, in the child's environment;sshgets it over a private socket. A test asserts that no child process command line contains a password, because/proc/<pid>/cmdlineis readable by every process on the machine. - Never in
connections.json, and never in plaintext on disk as a fallback. With no keyring available the password is kept for that session only, and the form says so. - Never in
session.json, which is asserted rather than assumed. - Never in the console log, which is the sneaky one — it shows every statement, and a connection string is a statement.
F1 shows this table inside the app, and a test fails the build if the two
ever disagree — a shortcut with no row and a row with no shortcut are the same
bug.
Omatable never binds Super. Super belongs to Hyprland: on stock Omarchy 184 of its 225 bindings use it.
| Key | |
|---|---|
Ctrl+Shift+K / Ctrl+K |
Switch connection / switch database |
Ctrl+Shift+W |
Disconnect, back to the connection list |
Ctrl+P |
Quick open: any object by name |
Ctrl+R |
Reload what is in front of you |
Ctrl+T / Ctrl+W |
New query tab / close the tab |
Ctrl+[ / Ctrl+] |
Previous / next tab, wrapping |
Ctrl+1…Ctrl+9 |
Jump to a tab |
Ctrl+Shift+D |
Split the pane, and unsplit it |
Ctrl+Shift+C |
Show or hide the console log |
Ctrl+Alt+[ / Ctrl+Alt+] |
Data view / structure view |
Space |
Row detail |
Ctrl+I |
Insert a row, or add a filter condition |
Ctrl+F |
Filter the table, or search the tree |
Ctrl+Return |
Apply the filters, or run the statement |
Ctrl+Shift+P / Ctrl+S / Ctrl+Shift+Delete |
Preview / apply / discard staged changes |
Ctrl+E |
Open a query editor |
Ctrl+. |
Cancel the running statement |
F1 |
These shortcuts |
| Path | What |
|---|---|
~/.config/omatable/connections.json |
Connections. No passwords, ever |
~/.local/state/omatable/session.json |
Window geometry, sidebar width, console visibility |
~/.config/omatable/history/<id>.json |
Query history, per connection |
~/.config/omatable/queries/ |
Saved queries |
$XDG_RUNTIME_DIR/omatable/<id>/ |
The tunnel's forward socket, mode 0700 |
| The Secret Service keyring | Every password and passphrase |
Omatable follows the live Omarchy theme with no restart: omarchy theme set <name> re-colours the running window and omarchy display text size
re-scales it. Row heights, the tree indent and every padding derive from the
resolved text size, so the grid stays readable at every setting rather than
growing text inside a fixed row. Anything painted in a theme colour picks its
own label colour by WCAG contrast rather than assuming white, and light themes
are read as light, not assumed away.
On a machine with no Omarchy themes installed at all, omatable falls back to a built-in palette and looks right anyway.
make check # configure, build, the offscreen suite, then both linters
make build # build only
make install # install into ~/.localBuild dependencies (Arch): qt6-base qt6-svg cmake ninja pkgconf mariadb-libs postgresql-libs hiredis libsecret openssl. qt6-svg is needed only by the
icon suite, which rasterises the shipped icon; the app itself never renders
one.
The suite runs headless against real servers rather than mocks — a driver test that mocks a server proves the mock:
make containers # MariaDB, PostgreSQL, Redis and an SSH jump host, on loopback
make smoke # the suite
make containers-down # destroy them, volumes includedThe containers exist to prove what nothing else can: that the SSH tunnel carries a real connection end to end, and that paging over 200,000 rows stays bounded.
Engines beyond MySQL/MariaDB, PostgreSQL and Redis; per-table backup granularity; scheduled backups; importing connections from clients other than Sequel Ace and DBeaver.
Keep make check green — the suites are the specification, and both linters
fail the build on a finding. The client libraries do the protocol and OpenSSH
does the tunnelling: never implement a wire format, a handshake or SSH that a
vendor library or ssh already provides. Each driver's C headers are included
only under its own src/drivers/<name>/, and make contained fails the build
on a violation. And never put a secret on a command line — there is a test for
that too.
MIT — see LICENSE.
Built with Qt 6 under the LGPLv3, linked dynamically, over MariaDB Connector/C, libpq and hiredis. Sequel Ace and DBeaver are their respective owners' projects; omatable shares no code with any other database client and interoperates only through file formats.
