1dineshc0@gmail.com
← Blog

Building bq-write: SQL generation that understands your schema

·3 min read

At work we use BigQuery heavily. We have a Node.js backend with TypeORM entities, and whenever I needed to write a complex query — joins across several tables, aggregations, filtering on enum columns — I'd open Gemini Cloud Assist and paste in a bunch of entity files to give it context.

It worked. But I was doing the same thing every time: find the relevant entities, paste them in, write the prompt, get SQL, tweak it.

So I built bq-write.

What it does

bq-write is a CLI that scans your ORM entity files and uses them as context when generating SQL. You describe what you want in plain English, it figures out the schema from your codebase, and returns a query.

npx bq-write "total evaluations per project for last 30 days, exclude deleted"

The key part: it reads your actual TypeORM (or similar) entity definitions — column names, types, relationships, enums — so the generated SQL uses the real column names and understands what's nullable, what's an enum, what joins make sense.

The problem with generic SQL generation

LLMs are good at SQL in the abstract. But without schema context they guess. They'll reference columns that don't exist, miss that a status field is an enum with specific values, or write a join that's logically wrong because they don't know the relationship direction.

The fix is simple: give it the schema. The hard part was making that automatic.

How it works

  1. Discovers entity files in your project (configurable glob pattern)
  2. Reads the relevant ones based on your query — using embeddings to pick the most relevant entities rather than dumping everything
  3. Passes them as context to the LLM along with your query
  4. Streams the SQL back to your terminal

It supports both OpenAI and Claude (Anthropic) as the underlying model. You configure which one via a .bq-write config file or env vars.

What I learned

The main insight was that entity selection matters more than I expected. If you have 50+ entities and you pass all of them, the context gets noisy and the model gets confused. Picking the 3-5 most relevant ones based on semantic similarity to the query produces noticeably better results.

The other thing: streaming matters for UX. BigQuery SQL for complex queries can be 50-100 lines. Waiting 8 seconds for a response with no feedback feels broken. Streaming output line-by-line makes it feel fast even when it isn't.


It's on npm if you use BigQuery and TypeORM. It's become part of my daily workflow and a few engineers and PMs at work use it now.