Around 2014, I needed users to complete some data that ended up in a data warehouse. The standard solution is an Excel file with a note saying "please fill this in". But I am not standard, so I decided to write an entire web application that would hand out forms and tables to users. And because JSON is beautiful and declaring things is beautiful, everything had to be declarative.

My secret weapon was HTML::Mason (later Mason 2), a Perl templating system built around components. In the early 2000s, compared to PHP, it was in another league. Everything was possible with it. It was a technology for few, but a great one, and I'm fairly sure sites like Amazon and Booking.com ran on it. And it was going to run my own corporate portal as well! I called the project Sentosa Autoforms, after the Singaporean island with beautiful imported sand.

Sentosa Autoforms

It worked on my computer, and I even used it for real. But it was complicated, since it mixed backend, databases, frontend, APIs and more, and it wasn't really elegant. So I decided to split it into more manageable modules. The first one was the SQL library, which didn't look bad at all, and had some elegance in it. The other modules never came. I have plenty of great and unfinished ideas!

After the Honestbee accident, I realized I could convert Perl to Python quite easily, and add some pythonic elegance along the way. So in January 2020, the Sentosa SQL Library became a Python library.

Then I left it alone, aging like fine wine. I used it a few times, saw a handful of visits on GitHub, and kept telling myself I would document it properly someday, which of course I never did.

The Over-Engineered ETL

Meanwhile, life went on, mostly in its administrative form. Day to day I deal with never-ending paperwork. Renewing boring software maintenance contracts, for example, means writing boring justifications: why we are buying it, how much we paid last year, when the original purchase was approved.

A standard person would keep the files in a tidy folder and write everything down in an Excel sheet.

But I am not standard, let alone tidy, so instead of being reasonable I built an over-engineered ETL system that downloads corporate documents automatically: first the titles, then the PDFs, then the text, which gets extracted and processed. Everything ended up in a database, which I query using Adminer, the lightweight web client for databases, with my own Markdown plugin—because of course I had to write a plugin.

Then, AI showed up.

The first thing I did was use it to extract metadata—tags, names, deadlines—from all of those documents. It’s oddly satisfying to bring order to chaos. But once the database was clean and filled up, I started wondering: how on earth can I actually query this database using natural language?

The SQL Injection of the Future

I knew LLMs could write SQL. You feed them the schema, explain what you need, and they hand you back a nicely formatted query that works like magic—at least most of the time.

But how do you integrate that query into a real application? I am quite sure that many developers are tempted to start with something like this:

select * from metadata where {llm_query} 

But hey. That looks exactly like the SQL injection of the future, and I wouldn't do it. After all, I am not very stupid. At least, not that very stupid.

Six Years Later

Then I remembered. I had a Python SQL library sitting alone in my repository, already able to build secure queries with placeholders.

Could an LLM write filters for it? Would that be a good idea?

Yes, I tried it and it could. It was a perfect match.

The LLM didn’t need to know SQL. It only had to output a simple JSON payload: “I want the column ‘age’ where the value is greater than 30.”

{ "action": "select", "source": "users", "filters": [ { "field": "age", "operator": ">", "value": 30 } ] } 

My library would do the rest: validate the JSON, check the allowed columns, and compile it into safe, parameterized SQL.

The LLM never sees or writes SQL. It only produces a simple JSON structure. The library then validates the fields against an allow-list, binds every value as a parameter, and generates the final query. Even if the model hallucinates a weird operator or a non-existent column, nothing dangerous reaches the database.

I renamed it filtersql, extended it, made it more elegant and secure, and finally gave it the README it deserved, six years later.