Guides

Compose queries

Reuse a query's inferred row shape as a typed source for CTEs, derived tables, subqueries, and set operations.

Turn a query into a CTE

cte() exposes the selected fields of a query as columns on a new source. Attach it with withCte() and use the CTE source in from():

import { cte, eq, from, integer, select, table, text, where, withCte } from "qubu"

const users = table("users", {
  id: integer(),
  name: text(),
})

const activeUsers = cte(
  "active_users",
  select({ id: users.id, name: users.name }, from(users), where(eq(users.id, 7))),
)

const report = select({ displayName: activeUsers.name }, withCte(activeUsers), from(activeUsers))

activeUsers.name is a typed column derived from the first query's row shape. The rendered statement includes the WITH clause before SELECT. Selected camelCase keys use snake_case while they belong to the CTE relation; the outer result projection aliases them back to camelCase for the returned row.

Attach the same clause to an insert, update, or delete when the mutation reads through the CTE. For example, an insert can consume a filtered CTE through insertSelect():

import { insertInto, insertSelect, returning } from "qubu"

const archivedUsers = table("archived_users", { name: text() })
const archive = insertInto(
  archivedUsers,
  insertSelect(select({ name: activeUsers.name }, from(activeUsers)), ["name"]),
  withCte(activeUsers),
  returning({ name: archivedUsers.name }),
)

Qubu renders WITH ... INSERT and keeps CTE parameters before mutation parameters. For update and delete predicates, select from the CTE in a scalar, IN, or EXISTS subquery; withCte() defines the relation but does not add it directly to the mutation target's column scope. Recursive CTEs use the same pattern and render WITH RECURSIVE before the mutation keyword.

Build a recursive CTE

recursiveCte() uses the anchor projection as the contract for a recursive member. The callback receives a typed self-reference; introduce it through from() or a join before selecting its fields:

import { add, cast, from, integer, lt, recursiveCte, select, value, where, withCte } from "qubu"

const numbers = recursiveCte("numbers", select({ value: cast(value(1), integer()) }), (self) =>
  select({ value: add(self.value, 1) }, from(self), where(lt(self.value, 3))),
)

const query = select({ value: numbers.value }, withCte(numbers), from(numbers))

The anchor names the fields, application types, nullability, and SQL domains that the returned source exposes. The member must project those same fields with compatible types. Give bound anchor values an explicit SQL type with cast() when the database cannot infer it from surrounding columns; PostgreSQL requires this for recursive CTE anchors. Qubu renders WITH RECURSIVE, an explicit relation column list, and anchor UNION ALL member; ordinary and recursive CTEs can share one withCte() clause.

Use a derived table

Alias a query when it should be used as an inline source:

import { alias, from, lower, select } from "qubu"
import type { SqlTypeOf } from "qubu"

const names = select({ name: lower(users.name) }, from(users))
const namesSource = alias(names, "names")

const query = select({ name: namesSource.name }, from(namesSource))
type NameSqlDomain = SqlTypeOf<typeof namesSource.name>
// SqlText

The alias gets the selected query's fields, while the new source identity keeps scope checks from confusing namesSource.name with users.name.

Its SQL domain is retained too. The projected lower(users.name) remains SqlText through this query alias, and the same preservation applies to a CTE, so downstream text operations remain checked without redeclaring the field.

Nest a scalar subquery

scalar() turns a query with exactly one selected field into an expression:

import { from, scalar, select, value } from "qubu"

const firstId = select({ id: users.id }, from(users))
const query = select(
  {
    name: users.name,
    firstId: scalar(firstId),
  },
  from(users),
)

scalar() throws at runtime when the query selects more than one field. Its type is the selected field's value type, widened with null when the query may return no rows. An ordinary select and fetchFirst(1) are both nullable: the limit proves at most one row, not that a row exists. A source-free select such as select({ value: value(42) }) is known to produce exactly one row.

Qubu does not treat an arbitrary predicate as proof of exactness. Use exists(), notExists(), or inQuery() for boolean subquery predicates.

Combine compatible queries

Set operations preserve the left query's row shape. Both queries must select compatible rows:

import { eq, from, select, unionAll, where } from "qubu"

const first = select({ id: users.id }, from(users))
const second = select({ id: users.id }, from(users), where(eq(users.id, 7)))

const allUsers = unionAll(first, second)

Use union, unionAll, intersect, or except depending on the SQL operation. Each input renderer still contributes its runtime parameters, which are collected in traversal order.

Prefer values over a builder chain

Build reusable pieces as ordinary values and pass them into the final query:

import { desc, eq, from, orderBy, select, where } from "qubu"

const byId = where(eq(users.id, 7))
const newest = orderBy(desc(users.id))

const query = select({ id: users.id, name: users.name }, from(users), byId, newest)

This makes it possible to share a predicate or projection without mutating a query object. The final select() call remains the place where source scope and result shape are checked. Qubu also accepts these independent values in another order, but SQL order is the canonical visual style for finished query code.

Constrain a reusable fragment by required fields

Use TableLike when a fragment requires a physical table and SourceLike when aliases, CTEs, derived tables, or custom sources are also valid. Both are lower-bound constraints: the source may contain additional fields, and the generic function retains its exact source identity.

For an application-level requirement, describe the required JavaScript row:

import { eq, where } from "qubu"
import type { TableLike } from "qubu"

function byStringId<TTable extends TableLike<{ id: string }>>(table: TTable, id: string) {
  return where(eq(table.columns.id, id))
}

{ id: string } means a non-null selected string. It accepts a table with extra fields and rejects string | null, but it does not distinguish SqlText from SqlUuid because both have a JavaScript output of string.

Use FieldLike when the fragment depends on SQL semantics:

import { eq, where } from "qubu"
import type { FieldLike, SourceLike, SqlTextLike } from "qubu"

type NonNullTextId = FieldLike<{
  sqlType: SqlTextLike
  nullable: false
}>

function byTextId<TSource extends SourceLike<{ id: NonNullTextId }>>(source: TSource, id: string) {
  return where(eq(source.columns.id, id))
}

This version accepts known text-like and permissive SqlUnknown fields. It rejects nullable text and known non-text domains such as SqlUuid. Add an output property to the FieldLike descriptor when the fragment also needs a specific JavaScript result type.