An AI with SQL and good manners

2026-09-13 · 5 min

"Give a language model a database connection and it will find the one query you did not think of. Before lunch."
A database administrator, recovering
A penguin bouncer at a velvet rope checks a robot's SQL statement on a clipboard before letting it into a library of spreadsheets
Every statement is read before it runs. Read-only queries over your own tables go in; everything else is turned away with a note saying why.

Letting an assistant write SQL over your spreadsheet is the whole point of tableski. It is also how you end up with a model that helpfully tidies your data by deleting it. The assistant is not malicious; it is eager, and eager is enough. So the hosted service treats every query as coming from someone it does not trust, and decides what the statement may do before a single row is read.

Decisions on the statement, not on the text

The naive way is to scan the SQL for scary words. It fails in both directions: SELECT * FROM dropbox_exports contains "drop", and a comment or a clever alias hides the real intent from any pattern. tableski parses the statement with the same parser the engine uses and looks at the tree:

Then the engine itself gets the same instruction a second time, through its own switches that disable data definition and data manipulation. Two fences, one message to the client.

Read-only is not the same as harmless

A query can be perfectly read-only and still take the machine down: a cross join of two tables, a result with a hundred million rows, an expression that builds a gigabyte string. So every query is also bounded. There is a memory pool it cannot grow past, a wall-clock limit after which it is cancelled, and a cap on the rows and bytes it may hand back. A result that hits the cap comes back cut, with a line saying so and suggesting a LIMIT or an aggregate. An export that would be cut is refused instead, because a partial file that looks whole is worse than no file.

One shape needed special treatment. A cartesian product, a cross join or a join without an equality, yields work so quickly per unit of progress that a timeout cannot interrupt it in time. Those are refused before they start, on the plan the engine produces, with a note to join on a column with an equals sign.

The hostile suite

All of this is a test file that runs before every change: about fifty statements, from CREATE EXTERNAL TABLE pointing at /etc/passwd through homoglyph table names, null bytes, two thousand nested parentheses, a megabyte of SQL, an infinite recursive CTE and a two-hundred-way union, plus a set of file names for the export tool that try to leave their folder. Each must be refused or bounded within ten seconds, and the server must answer the next question afterwards. When we found that the optimizer would happily spend nineteen seconds folding repeat('x', 200000000) at planning time, that became a case too, and planning moved inside the same time limit as execution.

What the assistant sees

Plain errors that say what to do: rejected: DROP TABLE is not allowed for untrusted clients; only read-only queries over registered tables run here. Models read that and correct themselves; they are good at following instructions that arrive as answers. The open-source binary you run on your own laptop trusts you by default, because it is your data and your machine; the same guard is one flag away, --untrusted-sql, when you serve others.