My AI agents write Oracle SQL all day: fix scripts, diagnostics, one-off reports for a 2.3M-line legacy system. Their most common failure isn't bad logic. It is SQL that references a table or column that almost exists. Oracle has had the fix for decades, it costs one statement per query, and it never executes anything: EXPLAIN PLAN.
The failure mode: names that almost exist
A language model doesn't know your schema. It knows what schemas usually look like. So on a 20-year-old database with thousands of tables, it produces names that are plausible instead of real: POLICY_STATUS when the column is STATUS_CD, CUSTOMERS when the table has been CUSTOMER (singular) since 1998, a join through a link table that was dropped two versions ago.
These are the worst kind of errors, because they look right. A human reviewer skims the script, the naming convention matches, everything reads fine. The error only surfaces when the script runs, and in my case fix scripts run at more than 20 customer installations. That is exactly the place where you do not want to discover an invented column.
Why you cannot just run it to check
The obvious test, execute it and see, is not available. These scripts are UPDATEs and DELETEs against production-like data. Wrapping everything in a transaction and rolling back sort of works, but it fires triggers, takes locks, burns sequence numbers and takes time on big tables.
I wanted a check that touches nothing and still uses the real schema. It already exists.
EXPLAIN PLAN parses without executing
EXPLAIN PLAN FOR
UPDATE policy
SET status_cd = 'ACTIVE'
WHERE policy_id = :b1;
Oracle takes the statement through the full parse: it resolves every table and column against the live data dictionary, checks your privileges, builds an execution plan and writes it to PLAN_TABLE. What it never does is execute. No rows change, no triggers fire, no locks are held. It is safe to run for an UPDATE, a DELETE, a MERGE.
If the model invented a table, you get an answer in milliseconds:
ORA-00942: table or view does not exist
If it invented a column:
ORA-00904: "POLICY_STATUS": invalid identifier
That is a lint result, produced by the one parser that actually knows the schema: the database's own.
Wiring it into the agent loop
In my workflow every generated script goes through this gate before a human sees it, statement by statement, against the dev database:
for stmt in split_statements(script):
try:
cursor.execute(f"EXPLAIN PLAN FOR {stmt}")
except oracledb.DatabaseError as e:
errors.append((stmt, str(e)))
The errors go straight back to the model with the original task. Most name-level failures disappear in one retry, without me reading anything. What reaches me is a script whose every identifier is real.
The tool the agents call runs in a read-only safe mode: SELECT, DESCRIBE and EXPLAIN PLAN are allowed, everything else (DDL, DML, GRANT, COMMIT) is blocked before it reaches the database. So even a badly confused agent can't turn the lint step into a write.
The free bonus: you also get the plan
The check produces an execution plan as a side effect:
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);
So the same gate that catches invented columns also shows you the full table scan on a 41-million-row table before anything runs. Two problems, one statement.
What it does not catch
A green lint is not a correct script. Here is what this gate misses:
-
Valid names, wrong logic.
DELETE FROM policy WHERE status_cd = 'A'parses perfectly and can still delete the wrong rows. This gate replaces nothing at the review level. -
PL/SQL blocks.
EXPLAIN PLANtakes single SQL statements. For packages and procedures I compile against a scratch schema instead; that is a different gate. -
Data assumptions. The parser checks that a column exists, not that
'A'is a value that ever appears in it. - Schema drift. I parse against the dev schema. A customer installation two versions behind can still disagree. Closest schema wins, not a guarantee.
The same idea works outside Oracle: PostgreSQL parses and plans with PREPARE or plain EXPLAIN, SQL Server has SET PARSEONLY ON. Any database that can plan a statement without running it can lint one.
Wrapping up
LLM SQL fails most often at the name level, and name resolution is exactly what the database parser already does. EXPLAIN PLAN turns that parser into a lint step: every generated statement checked against the live schema in milliseconds, with nothing executed and an error message the model can act on. Of all the guardrails around my agents, this one has the best ratio of effort to failures caught. It was one afternoon of work.



Top comments (0)