Natural language to validated SQL

Text2SQL Analytics System

Turns natural-language questions into SQL with Google Gemini, then sanitizes, runs and caches the query against a normalized PostgreSQL schema. Includes connection pooling and audit trails.

Stack
  • FastAPI
  • PostgreSQL
  • Google Gemini

Fig. 1 · From a question to rows

Synthetic data

customers(id, name, city)orders(id, customer_id, total, placed_at)

Generated SQL (pre-computed)

SELECT c.city, SUM(o.total) AS revenue
FROM orders o
JOIN customers c ON c.id = o.customer_id
WHERE o.placed_at >= '2026-03-01'
  AND o.placed_at <  '2026-04-01'
GROUP BY c.city
ORDER BY revenue DESC
LIMIT 3;

Validation

  • Single statement: passed
  • Read-only (SELECT): passed
  • Known tables and columns only: passed
  • Row limit present: passed

Result (synthetic rows)

cityrevenue
Lakeport18,420.00
Ashford15,075.50
Brightwater12,960.25

Synthetic. Invented schema, pre-written SQL and made-up rows. The checks illustrate the idea of sanitizing generated SQL; they are not a specification of the project’s rules.

54 / 54

core system tests passing

A 100% pass rate on the project's 54 core system tests. It covers that test suite. It isn't a proof that every question gets correct or safe SQL.

The idea

Most people who have a question about their data can’t write the SQL to answer it. A language model can write that SQL for them, but the generated query is untrusted input and has to be checked before it runs.

What I built

  • Natural language to SQL with Google Gemini.
  • Data normalization to 1NF, 2NF and 3NF, so the model queries a clean schema.
  • SQL sanitization that checks generated queries before they run.
  • Query caching, so a repeated question can skip generating the SQL again.
  • Connection pooling to PostgreSQL.
  • Audit trails.

Testing

The system passes all 54 core system tests. That’s what the tests cover. It doesn’t mean every generated query will be correct or that sanitization makes it impossible to misuse.

About the demo above

The walkthrough uses a small made-up schema and pre-computed outputs. Nothing calls a model or queries a database from this page. It shows how the pipeline works, including what happens when a request asks for something it shouldn’t.

Site index