Read-only connections
✓ functionalMake a connection genuinely read-only when browsing is all it should ever do.
Make a connection truly read-only: SQLly refuses anything that is not a read before it reaches the server. It is a good fit for a replica, an analyst-facing environment, or any server that should never be at the mercy of a stray script.
Where the gate sits
The check runs in SQLly, on the way out, and it runs per batch -
each GO-separated chunk is classified on its own. A refusal never
becomes a round trip: the statement is not sent, so there is nothing for the
server to log, roll back, or partially apply.
What counts as a read
- A statement must begin with
SELECTorWITH. AWITHbatch also has to contain aSELECTsomewhere, so a CTE feeding anINSERTis not mistaken for a query. USE <database>is allowed. Switching session context changes nothing in the data, and any real mutation after it is still caught.- A mutating keyword anywhere in the statement blocks it, not just at the front - which is what stops
SELECT … INTO-style and composed statements from slipping through.
Refusals say which statement and why - “statement starts with UPDATE, not SELECT”, “statement contains mutating keyword MERGE” - and the editor highlights the exact offending statement rather than the whole tab.
Read-shaped, but not harmless
Some statements return rows and still reach the server's file system, shell, or
module loader. A read-only connection exists so it is safe to point at production,
so those are refused too even though they technically start with SELECT:
| Engine | Refused artifacts |
|---|---|
| SQL Server | xp_cmdshell, sp_configure, sp_OACreate, sp_OAMethod, OPENROWSET, OPENDATASOURCE |
| PostgreSQL | pg_read_file, pg_read_binary_file, pg_ls_dir, lo_import, lo_export, and COPY (file and PROGRAM I/O), DO |
| MySQL / MariaDB | LOAD_FILE, OUTFILE, DUMPFILE, LOAD DATA/LOAD XML, INSTALL/UNINSTALL, CALL |
| SQLite | load_extension, readfile, writefile, ATTACH/DETACH |
Matching is tokenizer-driven, so a name inside a string literal or an ordinary
comment is inert, while quoted and bracketed spellings still match -
[xp_cmdshell] and "OPENROWSET" are caught. MySQL and
MariaDB executable comments (/*! … */,
/*M! … */) are deliberately exposed to the scan first:
they run on the server, so they cannot be allowed to smuggle a verb past it.
SELECT from.