under the hood

How QuerySQL works

A native Rust backend, one dialect abstraction for SQLite and Postgres, and an edit model built around staging changes before anything is written.

Architecture

QuerySQL is a Tauri app: a Rust backend paired with a React and TypeScript frontend, rendered in the operating system's own webview rather than a bundled Chromium runtime. The two sides communicate over Tauri's command layer.

Database access lives behind a DbConnection enum in src-tauri/src/db/. SQLite is implemented with rusqlite; Postgres with the postgres crate. Both dialects expose the same shape — schema introspection, query execution, paginated table reads, and transactional row mutations — so the frontend doesn't need to know which one it's talking to. That shared interface is also what makes adding another dialect later (SQL Server, for instance) a matter of implementing the same commands rather than reworking the app.

Postgres implementation notes

A few deliberate tradeoffs in how the Postgres dialect talks to the wire protocol.

Area
How it works
 
Reads
Arbitrary queries and table-page fetches use Postgres's simple query protocol, which returns every value as text. That sidesteps decoding dozens of Postgres OIDs into JSON, at the cost of numbers and booleans arriving as strings rather than native JSON types.
Postgres
Writes
Row inserts, updates, and deletes from the table editor use the extended protocol, with every parameter bound as TEXT and cast to the destination column's real type in the SQL itself ($1::int4, $1::timestamp, etc.) — the standard way to send dynamically-typed values through tokio-postgres without knowing column types at compile time.
Postgres
Tables without a primary key
Postgres has no stable per-row identifier equivalent to SQLite's rowidctid changes across updates and vacuums — so QuerySQL falls back to using every column as a composite identity for edits.
Postgres
Schema scope
Only the public schema is introspected for now.
Postgres

SQL injection guards

Identifiers can't be parameterized the way values can, so both dialects validate them before they ever reach a query string.

Table and column names are checked against sqlite_master / PRAGMA table_info on SQLite, or information_schema on Postgres, before being interpolated into generated SQL — for the paginated table view and the row mutation commands, where identifiers have to be built dynamically. Query values themselves go through parameterized queries the normal way.

The staged edit model

Why the table editor works the way it does.

Every edit is staged first. Fix a typo, add a row, drop a record — nothing reaches the database until you commit. Change your mind? Discard, and it's like it never happened.

What's stored, and where

Everything QuerySQL remembers between launches stays on your machine.

Recent connections and query history are persisted through tauri-plugin-store to JSON files in the app's config directory. Postgres passwords are never included — recent Postgres connections are saved without one, so reconnecting from the list always re-prompts. Theme preference is stored in localStorage.

The frontend keeps open connections, query and table tabs, and theme state in Zustand stores, with a CodeMirror-powered editor for schema-aware autocomplete and a virtualized grid for large result sets.