Actual novelty

BtrQL facilitates code reuse via modules, relation types, extension methods, extension columns, and macros. These features let you write clean, shared logic that the compiler checks for you, while still generating explicit, predictable SQL.

Extensions draw inspiration from receiver-oriented languages: define a row-shape contract once, then attach multiple reusable relation methods and scalar column computations that work on any compatible relation.

Reusable relation logic

One receiver exposes multiple relation methods and computed columns.

query.btrql
using analyticstype OrderInput = [amount: DECIMAL, tax_rate: DECIMAL]extension OrderInput {  method taxable =    self      .where(amount > 0)  method withTax =    self      .addColumn(tax)  column tax: DECIMAL = amount * tax_rate  column gross: DECIMAL = amount + tax}view enriched_orders =  orders    .taxable()    .addColumn(tax, gross)
generated/postgresql.sql
CREATE VIEW "enriched_orders" ASSELECT  "order_id",  "amount",  "tax_rate",  "tax",  "_q5"."amount" + "_q5"."tax" AS "gross"FROM (  SELECT    "order_id",    "amount",    "tax_rate",    "_q3"."amount" * "_q3"."tax_rate" AS "tax"  FROM (    SELECT *    FROM (      SELECT        "orders"."order_id",        "orders"."amount",        "orders"."tax_rate"      FROM "analytics"."orders" AS "orders"    ) AS "_q1"    WHERE "amount" > 0  ) AS "_q3") AS "_q5";

Relation types and extension columns

A relation type names a reusable row contract. It can drive typed projections, grouped keys, column removal, and extension receivers.

type AuditCols = [id: INT, created_at: TIMESTAMP]type UserShape = AuditCols ++ [name: VARCHAR, active: BOOLEAN]users  .select[UserShape]

An extension can define multiple relation methods and reusable scalar columns. Extra receiver columns are allowed. A dependency is substituted into the scalar expression but does not become an output unless requested.

using analyticstype EngagementInput = [  posts_viewed: INT,  comments_written: INT]extension EngagementInput {  method activeOnly =    self      .where(is_engaged)  method withMinimumPosts()(minPosts: INT) =    self      .where(posts_viewed >= minPosts)  column engagement_score: INT = posts_viewed + comments_written * 5  column is_engaged: BOOLEAN =    posts_viewed > 10 and comments_written > 0  column engagement_label =    case when is_engaged then 'active' else 'inactive' end}view engaged_users =  users    .activeOnly()    .withMinimumPosts()(5)    .addColumn(engagement_score, engagement_label)

Both examples compile with the PostgreSQL and ClickHouse C# prototypes when their analytics schema metadata supplies the receiver columns. The former column recipe and addColumn[Type] forms are retained only as migration diagnostics.

Modules and projects

BtrQL modules are file-based. A source file such as input/reporting/shared.btrql becomes module reporting.shared, and configured source roots decide where module lookup begins.

import reporting.shared { base_users -> users_base }using analyticsview active_users =  users_base    .where(active == TRUE)

A project ties source files, generated output, target dialect, and schema metadata together.

{  "output-folder": "generated",  "target-dialect": "postgresql"}

The schema cache stores database metadata used for offline checks: schemas, tables, columns, types, and callable signatures. It does not store table rows. A project build follows imports in dependency order, writes generated SQL, and reports missing modules, duplicate names, import cycles, stale schema references, and diagnostics before deployment.

Post-compile actions

Project compiler integrations should expose enough result data for post-compile work. The main project compiler supports configured post-compile hooks after SQL generation and reports hook diagnostics and artifacts.

The native project APIs currently return compiled modules with source paths, output paths, SQL, diagnostics, output roots, and relation-shape maps after CompileDirectory(...), so callers can attach their own post-compile steps. Those APIs do not currently run configured hook files themselves; their editor build responses expose successful default post-compile status when no hook runner is configured.