Validating generated SQL before it runs
3 October 2026
Never run model-written SQL directly. Put a validation step between the model and the database. It is cheap, and it catches a lot.
Parse it first
A SQL parser turns the text into a tree you can inspect. This lets you check structure instead of searching the string for keywords.
import sqlglot
from sqlglot import exp
ALLOWED_TABLES = {"orders", "customers", "products"}
def validate(sql: str) -> exp.Select:
tree = sqlglot.parse_one(sql, read="oracle")
if not isinstance(tree, exp.Select):
raise ValueError("Only SELECT statements are allowed")
tables = {t.name.lower() for t in tree.find_all(exp.Table)}
unknown = tables - ALLOWED_TABLES
if unknown:
raise ValueError(f"Tables not allowed: {sorted(unknown)}")
return tree
Set the read dialect to match your database.
Rules worth enforcing
- Read-only statements only
- An allowlist of tables, and ideally of columns
- A row limit added when the query has none
- A timeout on execution
- A database user that only has read permission
The last point matters most. Validation can have gaps, so the database permissions should make a harmful query impossible even if one slips through.
Check the plan if you can
Many databases can explain a query without running it. A very expensive plan, such as a full scan over a huge table, can be rejected before it uses any resources.
Feed errors back
If validation fails, send the error message back to the model and let it try again. Limit this to a few attempts so it cannot loop.