Query Model
Source scope
Learn why a column must belong to a source in the query, and how to refer to an enclosing query.
A column carries the identity of the source that provides it. Qubu checks that
identity when you assemble a query. The source must be supplied by FROM or a
join for its columns to appear in the projection, a predicate, or another clause.
Add the source that owns a column
Qubu reports a missing source when a query selects a column from a table that does not appear in the query:
import { from, integer, select, table, text } from "qubu"
const users = table("users", {
id: integer(),
})
const posts = table("posts", {
id: integer(),
title: text(),
})
select({ id: users.id }, from(posts))
// Type error: users is not available in this query scope
Add the source that owns the column, or join it with a condition that refers to both sources:
import { eq, from, innerJoin, integer, select, table } from "qubu"
const users = table("users", {
id: integer(),
})
const posts = table("posts", {
id: integer(),
authorId: integer(),
})
const query = select(
{ userId: users.id, postId: posts.id },
from(users),
innerJoin(posts, eq(users.id, posts.authorId)),
)
Use the source identity you introduced
Aliases, CTEs, derived queries, and custom sources expose new source identities. Use their columns after wrapping the original source:
import { alias, from, integer, select, table, text } from "qubu"
const users = table("users", {
id: integer(),
name: text(),
})
const author = alias(users, "author")
const query = select({ name: author.name }, from(author))
author.name belongs to the author source. users.name is a different source
identity after aliasing, even though both columns refer to the same table.
The same rule applies to a CTE or derived query. A query's selected row becomes the set of columns exposed by its new source:
import { alias, from, lower, select } from "qubu"
const names = select({ name: lower(users.name) }, from(users))
const namesSource = alias(names, "names")
const query = select({ name: namesSource.name }, from(namesSource))
Use Compose queries for the full CTE, derived-table, scalar-subquery, and set-operation workflow.
Correlate an inner query
Use correlate() when an inner query intentionally reads a source from its
enclosing query. correlate() changes type checking but emits no SQL:
import { correlate, crossJoin, eq, from, integer, lateral, select, table, where } from "qubu"
const users = table("users", { id: integer() })
const posts = table("posts", {
id: integer(),
authorId: integer(),
})
const recentPost = select(
{ id: posts.id },
from(posts),
correlate(users),
where(eq(posts.authorId, users.id)),
)
const recent = lateral(recentPost, "recent_post")
const query = select({ userId: users.id, postId: recent.id }, from(users), crossJoin(recent))
The inner query consumes posts locally. The enclosing users source satisfies
its outer requirement. The same requirement flows through scalar(),
exists(), and inQuery().
Read next
- Result shapes and cardinality explains projection names, outer-join nullability, and scalar subqueries.
- Fragments and metadata explains the type facts that carry source requirements through custom expressions and clauses.
- Extend Qubu shows how to publish a custom source or clause.