I'll spare you the trouble of reading this whole thing. This project is similar to sqlc where you write SQL and it generates typed code. The main difference is a richer annotation system to give more control over what gets generated.
But why?
So the SQL itself stays "plain sql", without macros or generator-only syntax leaking into queries. You can copy a query and run it directly against the database. The extra information needed for code generation lives in comments instead.
And because everything nowadays needs an AI angle. My thesis is that LLMs are pretty damn good at SQL, and if we keep them close to the raw queries it removes one layer of indirection for them to work through. There is no ORM or query builder in the middle, and the database schema gives them a concrete way to catch mistakes.
Alright, so here's the longer version. I've been a long-time sqlc user and for the most part it has worked okay. The idea is simple, write SQL queries you want the database to run and then generate type-safe application code needed to call it. The compiler does the heavy lifting ensuring queries match the schema.
There are pros to this approach:
- Real SQL!
- I will never forget the beast of an analytics query a coworker wrote with a SQL-builder. There was just no way to make heads or tails of what even the general shape of the query looked like.
- Compiler catches type and schema mismatches.
- No ORM or query-builder abstraction.
- Queries evolve with the schema.
- If a migration breaks something, code generation fails at build time.
Again, give credit where credit is due. sqlc proves this model works and it made writing SQL directly a practical alternative to ORMs. So I do think this is the right direction.
But, if you squint there's a bit of tension in those query files (and this is true for any project that tackles this problem), it describes two different things at once:
- The SQL statements the database executes.
- The code the generator should create.
SQL describes the query itself exceptionally well, haha you already knew that. But it does not say what the generated code should be called, whether it returns no rows, one row, many rows, what it's argument should be called, whether it is batched, or what type should hold the result (inferred, but difficult to get right 100% of the time).
As code generators grow more capable they need more of this extra information.
That's the crux of it, encoding more information into the raw SQL queries means risking those queries no longer being able to work with editors, formatters and tools (think psql).
It's an idea that has been brewing in my head for some time, to see how far we could take a comment-based annotation system to drive code generation, while keeping the SQL queries plain.
This has worked surprisingly well in goose (a migration tool). Devs and coding agents quickly get over having to annotate their migration files with -- +goose up.
That's it for now. I'll follow this up with more technical posts.
Stay tuned!