Better Query Language

VS Code Extension

Familiarity

Familiar keywords + pipeline syntax. Minimum surprises in generated SQL.

query.btrql
using analyticsusers  .where(active == TRUE)  .addColumn(active -> active_flag)  .orderBy(created_at.desc)  .limit(10)
generated/query.sql
SELECT  "users"."id",  "users"."name",  "users"."active",  "users"."created_at",  "users"."active" AS "active_flag"FROM "analytics"."users" AS "users"WHERE "users"."active" = TRUEORDER BY "created_at" DESCLIMIT 10;

Modern programming language

Type safety

  • Static Structural Typing

  • Higher confidence in large codebases

  • Coding agents get feedback early

LSP server

  • Syntax highlighting

  • Autocomplete

  • BtrQL Outline

Debugger

  • Views/TVF/extension methods

  • Stored procedures not supported yet

  • Available via CLI/MCP for agents

Reusability

Less code - less bugs. Extension methods and columns stick to compatible relation types.

department_ops.btrql
using analytics// Any type that contains column dept_id of type INT// will now have this method available.extension [dept_id: INT] {  method withDepartmentName =    self      .innerJoin(department on dept_id == department_id)      .select(department_name)}
usage.btrql
using analyticsimport department_opsemployees  .withDepartmentName()  .select(name, department_name)

Alternatives

BtrQL aims to look transparent to those who know SQL but discoverable for newbies (via code completion and static type checking).

  • Strong First class support
  • Partial Possible, with caveats
  • Missing
BtrQL compared with ORMs, SQL builders, and PRQL
Capability BtrQL ORMs (Entity Framework, Hybernate) SQL builders(JOOQ,...) PRQL
Independent from particular PL/VM ecosystem
Portable source

Same btrql could be used to generate sql stored procedures that could be called from any programming language.

Runtime-bound

Queries are often generated in runtime using reflection or some platform dependent mechanism.

Library-bound

Closer to generated SQL, easier to understand by folks from different tech stack, but not portable.

Portable source

A standalone language compiled to SQL.

Static Structural typing
Structural rows

Row shape carries column names and types through transformations. Extension methods and columns define minimal contract for an argument type.

Nominal type system

Frameworks don't help here much. Some languages offer crutches.

Nominal type system

Frameworks don't help here much. Some languages offer crutches.

Missing

Quote from official roadmap "Currently PRQL compiles into SQL with no understanding of the underlying tables. "

Reusable query abstractions
Main focus of the project

Modules, extension methods and columns were created to make reusing query logic easier.

Runtime code generation

Which breaks transparency and makes reviewing actually executed SQL harder. Nominal type system limits what could be reused.

Runtime code generation

Which breaks transparency and makes reviewing actually executed SQL harder. Nominal type system limits what could be reused.

Doesn't seem like a focus of the project

No module system or extension-method/column equivalent.

Reviewable generated SQL before deployment
Direct output

Generated SQL is the reviewable before deployment and it's also quite similar to btrql.

Runtime code generation prevents it.

Generated SQL depends on mapping, lazy loading, and runtime code generation. But review-ability depends on complexity of your queries.

Close to SQL, more transparent.

Builder calls often mirror SQL clauses directly, unless you need flexibility, which requires runtime code generation - then only DBMS logs.

SQL is reviewable

Tooling
Tools for devs and agents

VSIX, LSP, CLI, MCP toolkit

Host tooling

IDE support comes from the host language and framework. Debugging queries are out of scope for them.

Host tooling

IDE support comes from the host language and framework. Debugging queries are out of scope for them.

Language tooling

LSP/MCP/Debugger are on the roadmap.

Type safety

Catching problems early, helps both developers and agents.

unknown_column.btrql
using analyticsusers  .select(id, missing_name)
extension_receiver_mismatch.btrql
using analyticsextension [id: INT, active: BOOLEAN] {  method onlyActive =    self      .where(active == TRUE)}view broken =  users    .select(id, name)    .onlyActive()
PROBLEMS
unknown_column.btrql Unknown column 'missing_name'. at 4:15
extension_receiver_mismatch.btrql No applicable sticky method 'onlyActive' found for the current row type. at 12:6