@tanstack/db
Version:
A reactive client store for building super fast apps on sync
2,341 lines • 74.3 kB
text/typescript
import {
concat as concatOperator,
distinct,
filter,
join as joinOperator,
map,
reduce,
serializeValue,
tap,
} from '@tanstack/db-ivm'
import { optimizeQuery } from '../optimizer.js'
import {
CollectionInputNotFoundError,
DistinctRequiresSelectError,
DuplicateAliasInSubqueryError,
FnSelectWithGroupByError,
HavingRequiresGroupByError,
LimitOffsetRequireOrderByError,
UnsupportedFromTypeError,
} from '../../errors.js'
import { VIRTUAL_PROP_NAMES } from '../../virtual-props.js'
import {
ConditionalSelect,
IncludesSubquery,
PropRef,
Value as ValClass,
collectCollectionSources,
getWhereExpression,
isExpressionLike,
} from '../ir.js'
import { ensureIndexForField } from '../../indexes/auto-index.js'
import { deepEquals } from '../../utils.js'
import {
compileExpression,
isCaseWhenConditionTrue,
toBooleanPredicate,
} from './evaluators.js'
import { processJoins, registerLazyDemandPlan } from './joins.js'
import { containsAggregate, processGroupBy } from './group-by.js'
import { getLazyLoadTargets } from './lazy-targets.js'
import { processOrderBy } from './order-by.js'
import { processSelect } from './select.js'
import type { CollectionSubscription } from '../../collection/subscription.js'
import type { OrderByOptimizationInfo } from './order-by.js'
import type {
BasicExpression,
CollectionRef,
IncludesMaterialization,
QueryIR,
QueryRef,
UnionAll,
UnionFrom,
} from '../ir.js'
import type { LazyCollectionCallbacks } from './joins.js'
import type { Collection } from '../../collection/index.js'
import type {
KeyedStream,
NamespacedAndKeyedStream,
NamespacedRow,
ResultStream,
} from '../../types.js'
import type { QueryCache, QueryMapping, WindowOptions } from './types.js'
export type { WindowOptions } from './types.js'
/** Symbol used to tag parent $selected with routing metadata for includes */
export const INCLUDES_ROUTING = Symbol(`includesRouting`)
export const INCLUDES_PUBLIC_KEY = Symbol(`includesPublicKey`)
export const FN_SELECT_STATE = Symbol(`fnSelectState`)
const SKIP_INCLUDE = Symbol(`skipInclude`)
type ConditionalSelectGuard = {
condition: BasicExpression
expected: boolean
}
type SourceInclude = {
sourceAlias: string
include: IncludesCompilationResult
}
type ProjectedSourceIncludePath = {
path: Array<string>
guards: Array<ConditionalSelectGuard>
}
/**
* Result of compiling an includes subquery, including the child pipeline
* and metadata needed to route child results to parent-scoped Collections.
*/
export interface IncludesCompilationResult {
/** Filtered child pipeline (post inner-join with parent keys) */
pipeline: ResultStream
/** Result field name on parent (e.g., "issues") */
fieldName: string
/** Path where the included value is written in the parent result */
resultPath: Array<string>
/** Parent-side correlation ref (e.g., project.id) */
correlationField: PropRef
/** Child-side correlation ref (e.g., issue.projectId) */
childCorrelationField: PropRef
/** Whether the child query has an ORDER BY clause */
hasOrderBy: boolean
/** Full compilation result for the child query (for nested includes + alias tracking) */
childCompilationResult: CompilationResult
/** Parent-side projection refs for parent-referencing filters */
parentProjection?: Array<PropRef>
/** How the output layer materializes the child result on the parent row */
materialization: IncludesMaterialization
/** Internal field used to unwrap scalar child selects */
scalarField?: string
}
/**
* Result of query compilation including both the pipeline and source-specific WHERE clauses
*/
export interface CompilationResult {
/** The ID of the main collection */
collectionId: string
/** The compiled query pipeline (D2 stream) */
pipeline: ResultStream
/** Map of opaque source IDs to their WHERE clauses for index optimization */
sourceWhereClauses: Map<string, BasicExpression<boolean>>
/**
* Maps each source alias to its collection ID. Enables per-alias subscriptions for self-joins.
* Example: `{ employee: 'employees-col-id', manager: 'employees-col-id' }`
*/
aliasToCollectionId: Record<string, string>
/**
* Flattened mapping from outer alias to innermost alias for subqueries.
* Always provides one-hop lookups, never recursive chains.
*
* Example: `{ activeUser: 'user' }` when `.from({ activeUser: subquery })`
* where the subquery uses `.from({ user: collection })`.
*
* For deeply nested subqueries, the mapping goes directly to the innermost alias:
* `{ author: 'user' }` (not `{ author: 'activeUser' }`), so `aliasRemapping[alias]`
* always resolves in a single lookup.
*
* Used to resolve subscriptions during lazy loading when join aliases differ from
* the inner aliases where collection subscriptions were created.
*/
aliasRemapping: Record<string, string>
/** Child pipelines for includes subqueries */
includes?: Array<IncludesCompilationResult>
}
/**
* Compiles a query IR into a D2 pipeline
* @param rawQuery The query IR to compile
* @param inputs Mapping of source aliases to input streams (e.g., `{ employee: input1, manager: input2 }`)
* @param collections Mapping of collection IDs to Collection instances
* @param subscriptions Mapping of source aliases to CollectionSubscription instances
* @param callbacks Mapping of source aliases to lazy loading callbacks
* @param lazySources Set of source identities that should load data lazily
* @param optimizableOrderByCollections Map of source IDs to order-by optimization info
* @param cache Optional cache for compiled subqueries (used internally for recursion)
* @param queryMapping Optional mapping from optimized queries to original queries
* @returns A CompilationResult with the pipeline, source WHERE clauses, and alias metadata
*/
export function compileQuery(
rawQuery: QueryIR,
inputs: Record<string, KeyedStream>,
collections: Record<string, Collection<any, any, any, any, any>>,
subscriptions: Record<string, CollectionSubscription>,
callbacks: Record<string, LazyCollectionCallbacks>,
lazySources: Set<string>,
optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,
setWindowFn: (windowFn: (options: WindowOptions) => void) => void,
cache: QueryCache = new WeakMap(),
queryMapping: QueryMapping = new WeakMap(),
// For includes: parent key stream to inner-join with this query's FROM
parentKeyStream?: KeyedStream,
childCorrelationField?: PropRef,
): CompilationResult {
// Check if the original raw query has already been compiled
const cachedResult =
parentKeyStream === undefined ? cache.get(rawQuery) : undefined
if (cachedResult) {
return cachedResult
}
// Validate the raw query BEFORE optimization to check user's original structure.
// This must happen before optimization because the optimizer may create internal
// subqueries (e.g., for predicate pushdown) that reuse aliases, which is fine.
validateQueryStructure(rawQuery)
// Optimize the query before compilation
const { optimizedQuery, sourceWhereClauses } = optimizeQuery(rawQuery)
// Use a mutable binding so we can shallow-clone select before includes mutation
let query = optimizedQuery
// Create mapping from optimized query to original for caching
queryMapping.set(query, rawQuery)
mapNestedQueries(query, rawQuery, queryMapping)
// Create a copy of the inputs map to avoid modifying the original
const allInputs = { ...inputs }
bindSourceInputs(rawQuery, allInputs)
// Track alias to collection id relationships discovered during compilation.
// This includes all user-declared aliases plus inner aliases from subqueries.
const aliasToCollectionId: Record<string, string> = {}
// Track alias remapping for subqueries (outer alias → inner alias)
// e.g., when .join({ activeUser: subquery }) where subquery uses .from({ user: collection })
// we store: aliasRemapping['activeUser'] = 'user'
const aliasRemapping: Record<string, string> = {}
// Create a map of source aliases to input streams.
// Inputs MUST be keyed by alias (e.g., `{ employee: input1, manager: input2 }`),
// not by collection ID. This enables per-alias subscriptions where different aliases
// of the same collection (e.g., self-joins) maintain independent filtered streams.
const sources: Record<string, KeyedStream> = {}
// Process the FROM clause to get the source stream.
const {
alias: mainSource,
collectionId: mainCollectionId,
pipeline: initialPipeline,
sources: fromSources,
sourceIncludes,
directIncludes,
isUnionFrom,
} = processFromClause(
query.from,
allInputs,
collections,
subscriptions,
callbacks,
lazySources,
optimizableOrderByCollections,
setWindowFn,
cache,
queryMapping,
aliasToCollectionId,
aliasRemapping,
sourceWhereClauses,
)
Object.assign(sources, fromSources)
// If this is an includes child query, inner-join the raw input with parent keys.
// This filters the child collection to only rows matching parents in the result set.
// The inner join happens BEFORE namespace wrapping / WHERE / SELECT / ORDER BY,
// so the child pipeline only processes rows that match parents.
let pipeline: NamespacedAndKeyedStream = initialPipeline
const childCorrelationAlias = childCorrelationField?.path[0]
const joinsParentAfterJoins =
!isUnionFrom &&
parentKeyStream !== undefined &&
childCorrelationField !== undefined &&
childCorrelationAlias !== mainSource
if (
!isUnionFrom &&
parentKeyStream &&
childCorrelationField &&
!joinsParentAfterJoins
) {
const mainInput = sources[mainSource]!
let filteredMainInput = mainInput
// Re-key child input by correlation field: [correlationValue, [childKey, childRow]]
const childFieldPath = childCorrelationField.path.slice(1) // remove alias prefix
const childRekeyed = mainInput.pipe(
map(([key, row]: [unknown, any]) => {
const correlationValue = getNestedValue(row, childFieldPath)
return [correlationValue, [key, row]] as [unknown, [unknown, any]]
}),
)
// Inner join: only children whose correlation key exists in parent keys pass through
const joined = childRekeyed.pipe(joinOperator(parentKeyStream, `inner`))
// Extract: [correlationValue, [[childKey, childRow], parentContext]] → [childKey, childRow]
// Tag the row with __correlationKey for output routing
// If parentSide is non-null (parent context projected), attach as __parentContext
filteredMainInput = joined.pipe(
filter(([_correlationValue, [childSide]]: any) => {
return childSide != null
}),
map(([correlationValue, [childSide, parentSide]]: any) => {
const [childKey, childRow] = childSide
const tagged: any = {
...childRow,
__correlationKey: correlationValue,
[INCLUDES_PUBLIC_KEY]: childKey,
}
if (parentSide != null) {
tagged.__parentContext = parentSide
}
const effectiveKey =
parentSide != null ? serializeValue([childKey, parentSide]) : childKey
return [effectiveKey, tagged]
}),
)
// Update sources so the rest of the pipeline uses the filtered input
sources[mainSource] = filteredMainInput
pipeline = wrapInputWithAlias(filteredMainInput, mainSource)
}
// Process JOIN clauses if they exist
if (query.join && query.join.length > 0) {
pipeline = processJoins(
pipeline,
query.join,
sources,
mainCollectionId,
mainSource,
allInputs,
cache,
queryMapping,
collections,
subscriptions,
callbacks,
lazySources,
optimizableOrderByCollections,
setWindowFn,
rawQuery,
compileQuery,
aliasToCollectionId,
aliasRemapping,
sourceWhereClauses,
parentKeyStream !== undefined && !joinsParentAfterJoins,
)
}
// A correlation field owned by a joined source does not exist on the main
// input. Join the fully namespaced child relation with its parent routes
// here, after the source join has made that field available.
if (joinsParentAfterJoins) {
const compiledChildCorrelation = compileExpression(childCorrelationField)
pipeline = pipeline.pipe(
map(
([key, row]) =>
[compiledChildCorrelation(row), [key, row]] as [
unknown,
[unknown, typeof row],
],
),
joinOperator(parentKeyStream, `inner`),
filter(([_correlationValue, [childSide]]) => childSide != null),
map(([correlationValue, [childSide, parentSide]]) => {
const [childKey, row] = childSide as [unknown, NamespacedRow]
const namespaced = { ...row } as Record<string, any>
namespaced[mainSource] = {
...namespaced[mainSource],
__correlationKey: correlationValue,
[INCLUDES_PUBLIC_KEY]: childKey,
}
if (parentSide != null) {
Object.assign(namespaced, parentSide)
namespaced.__parentContext = parentSide
}
const effectiveKey =
parentSide != null ? serializeValue([childKey, parentSide]) : childKey
return [effectiveKey, namespaced]
}),
) as NamespacedAndKeyedStream
}
// Process the WHERE clause if it exists
if (query.where && query.where.length > 0) {
// Apply each WHERE condition as a filter (they are ANDed together)
for (const where of query.where) {
const whereExpression = getWhereExpression(where)
const compiledWhere = compileExpression(whereExpression)
pipeline = pipeline.pipe(
filter(([_key, namespacedRow]) => {
return toBooleanPredicate(compiledWhere(namespacedRow))
}),
)
}
}
// Process functional WHERE clauses if they exist
if (query.fnWhere && query.fnWhere.length > 0) {
for (const fnWhere of query.fnWhere) {
pipeline = pipeline.pipe(
filter(([_key, namespacedRow]) => {
return toBooleanPredicate(fnWhere(namespacedRow))
}),
)
}
}
// Extract includes from SELECT, compile child pipelines, and replace with placeholders.
// This must happen AFTER WHERE (so parent pipeline is filtered) but BEFORE processSelect
// (so IncludesSubquery nodes are stripped before select compilation).
const includesResults: Array<IncludesCompilationResult> = !query.select
? [...directIncludes]
: []
const includesRoutingFns: Array<{
fieldName: string
getRouting: (nsRow: any) => {
active: boolean
correlationKey: unknown
parentContext: Record<string, any> | null
}
}> = []
for (const { sourceAlias, include } of sourceIncludes) {
const projectedPaths =
query.select != null
? findProjectedSourceIncludePaths(
query.select,
sourceAlias,
include.resultPath,
)
: query.fnSelect
? []
: [
{
path: [sourceAlias, ...include.resultPath],
guards: [],
},
]
if (projectedPaths.length === 0) {
continue
}
for (const { path: resultPath, guards } of projectedPaths) {
const fieldName = getUniqueIncludesRoutingKey(
`${sourceAlias}.${resultPath.join(`.`)}`,
includesRoutingFns,
)
const compiledGuards = guards.map((guard) => ({
condition: compileExpression(guard.condition),
expected: guard.expected,
}))
includesResults.push({
...include,
fieldName,
resultPath,
})
includesRoutingFns.push({
fieldName,
getRouting: (nsRow: any) => {
if (!matchesConditionalSelectGuards(compiledGuards, nsRow)) {
return { active: false, correlationKey: null, parentContext: null }
}
return (
nsRow[sourceAlias]?.[INCLUDES_ROUTING]?.[include.fieldName] ?? {
active: false,
correlationKey: null,
parentContext: null,
}
)
},
})
}
}
if (query.select && directIncludes.length > 0) {
for (const include of directIncludes) {
const projectedPaths = findProjectedResultIncludePaths(
query.select,
include.resultPath,
)
for (const { path: resultPath, guards } of projectedPaths) {
const fieldName = getUniqueIncludesRoutingKey(
resultPath.join(`.`),
includesRoutingFns,
)
const compiledGuards = guards.map((guard) => ({
condition: compileExpression(guard.condition),
expected: guard.expected,
}))
includesResults.push({
...include,
fieldName,
resultPath,
})
includesRoutingFns.push({
fieldName,
getRouting: (nsRow: any) => {
if (!matchesConditionalSelectGuards(compiledGuards, nsRow)) {
return {
active: false,
correlationKey: null,
parentContext: null,
}
}
return (
nsRow[INCLUDES_ROUTING]?.[include.fieldName] ?? {
active: false,
correlationKey: null,
parentContext: null,
}
)
},
})
}
}
}
if (query.select) {
const includesEntries = extractIncludesFromSelect(query.select)
if (includesEntries.length > 0) {
query = { ...query, select: { ...query.select } }
}
for (const { key, path, subquery, guards } of includesEntries) {
const fieldName = getUniqueIncludesRoutingKey(key, includesRoutingFns)
// Branch parent pipeline: map to [correlationValue, parentContext]
// When parentProjection exists, project referenced parent fields; otherwise null (zero overhead)
const compiledCorrelation = compileExpression(subquery.correlationField)
const compiledGuards = guards.map((guard) => ({
condition: compileExpression(guard.condition),
expected: guard.expected,
}))
let parentKeys: any
if (subquery.parentProjection && subquery.parentProjection.length > 0) {
const compiledProjections = subquery.parentProjection.map((ref) => ({
alias: ref.path[0]!,
field: ref.path.slice(1),
compiled: compileExpression(ref),
}))
parentKeys = pipeline.pipe(
map(([_key, nsRow]: any) => {
if (!matchesConditionalSelectGuards(compiledGuards, nsRow)) {
return [SKIP_INCLUDE, null] as any
}
const parentContext: Record<string, Record<string, any>> = {}
for (const proj of compiledProjections) {
if (!parentContext[proj.alias]) {
parentContext[proj.alias] = {}
}
const value = proj.compiled(nsRow)
// Set nested field in the alias namespace
let target = parentContext[proj.alias]!
for (let i = 0; i < proj.field.length - 1; i++) {
if (!target[proj.field[i]!]) {
target[proj.field[i]!] = {}
}
target = target[proj.field[i]!]
}
target[proj.field[proj.field.length - 1]!] = value
}
return [compiledCorrelation(nsRow), parentContext] as any
}),
)
} else {
parentKeys = pipeline.pipe(
map(([_key, nsRow]: any) => {
if (!matchesConditionalSelectGuards(compiledGuards, nsRow)) {
return [SKIP_INCLUDE, null] as any
}
return [compiledCorrelation(nsRow), null] as any
}),
)
}
parentKeys = parentKeys.pipe(
filter(([correlationValue]: any) => correlationValue !== SKIP_INCLUDE),
)
// Deduplicate: when multiple parents share the same correlation key (and
// parentContext), clamp multiplicity to 1 so the inner join doesn't
// produce duplicate child entries that cause incorrect deletions.
parentKeys = parentKeys.pipe(
reduce((values: Array<[any, number]>) =>
values.map(([v, mult]) => [v, mult > 0 ? 1 : 0] as [any, number]),
),
)
// --- Includes lazy loading (mirrors join lazy loading in joins.ts) ---
// Resolve the child correlation field to concrete collection targets so
// subquery and union child sources can load by branch when it is safe.
const childSourceAlias = subquery.childCorrelationField.path[0]!
const directChildCollection =
subquery.query.from.type === `collectionRef`
? subquery.query.from.collection
: undefined
const lazyTargets = getLazyLoadTargets(
subquery.query,
subquery.query.from,
childSourceAlias,
subquery.childCorrelationField,
directChildCollection,
aliasRemapping,
)
if (lazyTargets.length > 0) {
// 1. Mark child source as lazy so CollectionSubscriber skips initial full load
for (const target of lazyTargets) {
lazySources.add(target.sourceId)
}
// 2. Ensure an index on the correlation field for efficient lookups
for (const target of lazyTargets) {
const targetFieldName = target.path[0]
if (targetFieldName) {
ensureIndexForField(targetFieldName, target.path, target.collection)
}
}
const initialKeys = getStaticDemandKeys(
rawQuery,
subquery.correlationField,
)
const demandPlans = lazyTargets.map((target) =>
registerLazyDemandPlan(callbacks, target, initialKeys),
)
const demandWeights = new Map<
string,
{ key: unknown; weight: number }
>()
// Keep the async demand adapter in sync with the current parent-key
// relation. Retired keys stop participating in readiness immediately.
parentKeys = parentKeys.pipe(
tap((data: any) => {
for (const [[correlationValue], weight] of data.getInner()) {
if (correlationValue == null) continue
const encoded = serializeValue(correlationValue)
const previous = demandWeights.get(encoded)
const nextWeight = (previous?.weight ?? 0) + weight
if (nextWeight === 0) {
demandWeights.delete(encoded)
} else {
demandWeights.set(encoded, {
key: correlationValue,
weight: nextWeight,
})
}
}
const keys = new Set(
[...demandWeights.values()]
.filter(({ weight }) => weight > 0)
.map(({ key: demandedKey }) => demandedKey),
)
for (let index = 0; index < lazyTargets.length; index++) {
const target = lazyTargets[index]!
const plan = demandPlans[index]!
callbacks[target.sourceId]?.setDemand?.(plan, keys)
}
}),
)
}
// If parent filters exist, append them to the child query's WHERE
const childQuery =
subquery.parentFilters && subquery.parentFilters.length > 0
? {
...subquery.query,
where: [
...(subquery.query.where || []),
...subquery.parentFilters,
],
}
: subquery.query
// Recursively compile child query WITH the parent key stream
const childResult = compileQuery(
childQuery,
allInputs,
collections,
subscriptions,
callbacks,
lazySources,
optimizableOrderByCollections,
setWindowFn,
cache,
queryMapping,
parentKeys,
subquery.childCorrelationField,
)
// Merge child's alias metadata into parent's
Object.assign(aliasToCollectionId, childResult.aliasToCollectionId)
Object.assign(aliasRemapping, childResult.aliasRemapping)
for (const [alias, whereClause] of childResult.sourceWhereClauses) {
sourceWhereClauses.set(alias, whereClause)
}
includesResults.push({
pipeline: childResult.pipeline,
fieldName,
resultPath: path,
correlationField: subquery.correlationField,
childCorrelationField: subquery.childCorrelationField,
hasOrderBy: !!(
subquery.query.orderBy && subquery.query.orderBy.length > 0
),
childCompilationResult: childResult,
parentProjection: subquery.parentProjection,
materialization: subquery.materialization,
scalarField: subquery.scalarField,
})
// Capture routing function for INCLUDES_ROUTING tagging
if (subquery.parentProjection && subquery.parentProjection.length > 0) {
const compiledProjs = subquery.parentProjection.map((ref) => ({
alias: ref.path[0]!,
field: ref.path.slice(1),
compiled: compileExpression(ref),
}))
const compiledCorr = compiledCorrelation
const compiledRoutingGuards = compiledGuards
includesRoutingFns.push({
fieldName,
getRouting: (nsRow: any) => {
if (!matchesConditionalSelectGuards(compiledRoutingGuards, nsRow)) {
return {
active: false,
correlationKey: null,
parentContext: null,
}
}
const parentContext: Record<string, Record<string, any>> = {}
for (const proj of compiledProjs) {
if (!parentContext[proj.alias]) {
parentContext[proj.alias] = {}
}
const value = proj.compiled(nsRow)
let target = parentContext[proj.alias]!
for (let i = 0; i < proj.field.length - 1; i++) {
if (!target[proj.field[i]!]) {
target[proj.field[i]!] = {}
}
target = target[proj.field[i]!]
}
target[proj.field[proj.field.length - 1]!] = value
}
return {
active: true,
correlationKey: compiledCorr(nsRow),
parentContext,
}
},
})
} else {
const compiledRoutingGuards = compiledGuards
includesRoutingFns.push({
fieldName,
getRouting: (nsRow: any) => {
if (!matchesConditionalSelectGuards(compiledRoutingGuards, nsRow)) {
return {
active: false,
correlationKey: null,
parentContext: null,
}
}
return {
active: true,
correlationKey: compiledCorrelation(nsRow),
parentContext: null,
}
},
})
}
// Replace includes entry in select with a null placeholder
query = {
...query,
select: replaceIncludesInSelect(query.select!, path),
}
}
}
if (
query.distinct &&
!query.fnSelect &&
!query.select &&
query.from.type !== `unionAll`
) {
throw new DistinctRequiresSelectError()
}
if (query.fnSelect && query.groupBy && query.groupBy.length > 0) {
throw new FnSelectWithGroupByError()
}
// Process the SELECT clause early - always create $selected
// This eliminates duplication and allows for DISTINCT implementation
if (query.fnSelect) {
// Handle functional select - apply the function to transform the row
pipeline = pipeline.pipe(
map(([key, namespacedRow]) => {
const selectResults = query.fnSelect!(namespacedRow)
let selected = selectResults
if (selectResults && typeof selectResults === `object`) {
selected = Array.isArray(selectResults)
? [...selectResults]
: { ...selectResults }
const routing = (namespacedRow as any)[INCLUDES_ROUTING]
if (routing) {
selected[INCLUDES_ROUTING] = routing
}
if (directIncludes.length > 0) {
Object.defineProperty(selected, FN_SELECT_STATE, {
value: {
sourceRow: namespacedRow,
fnSelect: query.fnSelect!,
},
enumerable: true,
configurable: true,
})
}
}
return [
key,
{
...namespacedRow,
$selected: selected,
},
] as [string, typeof namespacedRow & { $selected: any }]
}),
)
} else if (query.select) {
pipeline = processSelect(pipeline, query.select, allInputs)
} else {
// If no SELECT clause, create $selected with the main table data
pipeline = pipeline.pipe(
map(([key, namespacedRow]) => {
const selectResults =
!isUnionFrom && !query.join && !query.groupBy
? namespacedRow[mainSource]
: namespacedRow
return [
key,
{
...namespacedRow,
$selected: selectResults,
},
] as [string, typeof namespacedRow & { $selected: any }]
}),
)
}
// Tag $selected with routing metadata so the materialization graph can route
// children without depending on the user's projection.
if (includesRoutingFns.length > 0) {
pipeline = pipeline.pipe(
map(([key, namespacedRow]: any) => {
const routing: Record<
string,
{
active: boolean
correlationKey: unknown
parentContext: Record<string, any> | null
}
> = {}
for (const { fieldName, getRouting } of includesRoutingFns) {
routing[fieldName] = getRouting(namespacedRow)
}
const selected = Array.isArray(namespacedRow.$selected)
? [...namespacedRow.$selected]
: { ...namespacedRow.$selected }
selected[INCLUDES_ROUTING] = routing
return [key, { ...namespacedRow, $selected: selected }]
}),
)
}
// Process the GROUP BY clause if it exists.
// When in includes mode (parentKeyStream), pass mainSource so that groupBy
// preserves __correlationKey for per-parent aggregation.
const groupByMainSource = parentKeyStream ? mainSource : undefined
if (query.groupBy && query.groupBy.length > 0) {
pipeline = processGroupBy(
pipeline,
query.groupBy,
query.having,
query.select,
query.fnHaving,
mainCollectionId,
groupByMainSource,
)
} else if (query.select) {
// Check if SELECT contains aggregates but no GROUP BY (implicit single-group aggregation)
const hasAggregates = Object.values(query.select).some(
(expr) => expr.type === `agg` || containsAggregate(expr),
)
if (hasAggregates) {
// Handle implicit single-group aggregation
pipeline = processGroupBy(
pipeline,
[], // Empty group by means single group
query.having,
query.select,
query.fnHaving,
mainCollectionId,
groupByMainSource,
)
}
}
// Process the HAVING clause if it exists (only applies after GROUP BY)
if (query.having && (!query.groupBy || query.groupBy.length === 0)) {
// Check if we have aggregates in SELECT that would trigger implicit grouping
const hasAggregates = query.select
? Object.values(query.select).some((expr) => expr.type === `agg`)
: false
if (!hasAggregates) {
throw new HavingRequiresGroupByError()
}
}
// Process functional HAVING clauses outside of GROUP BY (treat as additional WHERE filters)
if (
query.fnHaving &&
query.fnHaving.length > 0 &&
(!query.groupBy || query.groupBy.length === 0)
) {
// If there's no GROUP BY but there are fnHaving clauses, apply them as filters
for (const fnHaving of query.fnHaving) {
pipeline = pipeline.pipe(
filter(([_key, namespacedRow]) => {
return fnHaving(namespacedRow)
}),
)
}
}
// Normalize every logical row before DISTINCT and ordering. Those operators
// track visibility by row key, so an insert-before-delete replacement with
// the same key would otherwise keep the old value and hide route or order
// changes. Joined contributors may differ in unselected namespaces; only
// the public value and its route/order inputs must be congruent.
if (!query.select || !containsAggregate(query.select)) {
pipeline = canonicalizeSelectedRows(
pipeline,
query,
mainSource,
parentKeyStream !== undefined,
)
}
const keyedSourceWhereClauses = keyWhereClausesBySource(
rawQuery,
sourceWhereClauses,
aliasRemapping,
)
// Process the DISTINCT clause if it exists
if (query.distinct) {
pipeline = pipeline.pipe(distinct(([_key, row]) => row.$selected))
}
// Process orderBy parameter if it exists
if (query.orderBy && query.orderBy.length > 0) {
// When in includes mode with limit/offset, use grouped ordering so that
// the limit is applied per parent (per correlation key), not globally.
const includesGroupKeyFn =
parentKeyStream &&
(query.limit !== undefined || query.offset !== undefined)
? (_key: unknown, row: unknown) => {
const correlationKey =
(row as any)?.[mainSource]?.__correlationKey ??
(row as any)?.__correlationKey
const parentContext = (row as any)?.__parentContext
if (parentContext != null) {
return serializeValue([correlationKey, parentContext])
}
return correlationKey
}
: undefined
const orderedPipeline = processOrderBy(
rawQuery,
pipeline,
query.orderBy,
query.select || {},
collections[mainCollectionId]!,
optimizableOrderByCollections,
setWindowFn,
query.limit,
query.offset,
includesGroupKeyFn,
)
// Final step: extract the $selected and include orderBy index
const resultPipeline: ResultStream = orderedPipeline.pipe(
map(([key, [row, orderByIndex]]) => {
// Extract the final results from $selected and include orderBy index
const raw = (row as any).$selected
const finalResults = attachVirtualPropsToSelected(
unwrapValue(raw),
row as Record<string, any>,
)
// When in includes mode, embed the correlation key and parentContext
if (parentKeyStream) {
const correlationKey =
(row as any)[mainSource]?.__correlationKey ??
(row as any).__correlationKey
const parentContext = (row as any).__parentContext ?? null
const publicKey = getIncludesPublicKey(row, mainSource, key)
const routedResults = stripInternalCorrelation(finalResults)
return [
key,
[
routedResults,
orderByIndex,
correlationKey,
parentContext,
publicKey,
],
] as any
}
return [key, [finalResults, orderByIndex]] as [unknown, [any, string]]
}),
) as ResultStream
// Cache the result before returning (use original query as key)
const compilationResult: CompilationResult = {
collectionId: mainCollectionId,
pipeline: resultPipeline,
sourceWhereClauses: keyedSourceWhereClauses,
aliasToCollectionId,
aliasRemapping,
includes: includesResults.length > 0 ? includesResults : undefined,
}
if (parentKeyStream === undefined) cache.set(rawQuery, compilationResult)
return compilationResult
} else if (query.limit !== undefined || query.offset !== undefined) {
// If there's a limit or offset without orderBy, throw an error
throw new LimitOffsetRequireOrderByError()
}
// Final step: extract the $selected and return tuple format (no orderBy)
const resultPipeline: ResultStream = pipeline.pipe(
map(([key, row]) => {
// Extract the final results from $selected and return [key, [results, undefined]]
const raw = (row as any).$selected
const finalResults = attachVirtualPropsToSelected(
unwrapValue(raw),
row as Record<string, any>,
)
// When in includes mode, embed the correlation key and parentContext
if (parentKeyStream) {
const correlationKey =
(row as any)[mainSource]?.__correlationKey ??
(row as any).__correlationKey
const parentContext = (row as any).__parentContext ?? null
const publicKey = getIncludesPublicKey(row, mainSource, key)
const routedResults = stripInternalCorrelation(finalResults)
return [
key,
[routedResults, undefined, correlationKey, parentContext, publicKey],
] as any
}
return [key, [finalResults, undefined]] as [
unknown,
[any, string | undefined],
]
}),
)
// Cache the result before returning (use original query as key)
const compilationResult: CompilationResult = {
collectionId: mainCollectionId,
pipeline: resultPipeline,
sourceWhereClauses: keyedSourceWhereClauses,
aliasToCollectionId,
aliasRemapping,
includes: includesResults.length > 0 ? includesResults : undefined,
}
if (parentKeyStream === undefined) cache.set(rawQuery, compilationResult)
return compilationResult
}
function keyWhereClausesBySource(
query: QueryIR,
clauses: Map<string, BasicExpression<boolean>>,
aliasRemapping: Record<string, string>,
): Map<string, BasicExpression<boolean>> {
const sources = collectCollectionSources(query)
const sourceIds = new Set(sources.map(({ sourceId }) => sourceId))
const result = new Map<string, BasicExpression<boolean>>()
for (const [key, clause] of clauses) {
if (sourceIds.has(key)) {
result.set(key, clause)
continue
}
const alias = aliasRemapping[key] ?? key
for (const source of sources) {
if (source.alias === alias) result.set(source.sourceId, clause)
}
}
return result
}
function bindSourceInputs(
query: QueryIR,
inputs: Record<string, KeyedStream>,
): void {
for (const source of collectCollectionSources(query)) {
const input = inputs[source.sourceId] ?? inputs[source.alias]
if (!input) continue
inputs[source.sourceId] = input
inputs[source.alias] = input
}
}
function canonicalizeSelectedRows(
pipeline: NamespacedAndKeyedStream,
query: QueryIR,
mainSource: string,
isIncludedRelation: boolean,
): NamespacedAndKeyedStream {
const compiledOrder = (query.orderBy ?? []).map(({ expression }) =>
compileExpression(expression),
)
const signature = (row: any) => ({
value: row.$selected,
routing: row.$selected?.[INCLUDES_ROUTING],
outerCorrelation: isIncludedRelation
? row[mainSource]?.__correlationKey
: undefined,
parentContext: isIncludedRelation
? (row.__parentContext ?? row[mainSource]?.__parentContext ?? null)
: undefined,
order: compiledOrder.map((evaluate) => evaluate(row)),
})
return pipeline.pipe(
reduce((values: Array<[any, number]>) => {
const totalMultiplicity = values.reduce(
(total, [, multiplicity]) => total + multiplicity,
0,
)
if (totalMultiplicity === 0) return []
if (totalMultiplicity < 0) {
throw new Error(`Query row has negative multiplicity`)
}
const visible = values.find(([, multiplicity]) => multiplicity > 0)?.[0]
if (!visible) throw new Error(`Query row has no positive contributor`)
const visibleSignature = signature(visible)
for (const [candidate, multiplicity] of values) {
if (
multiplicity > 0 &&
!deepEquals(visibleSignature, signature(candidate))
) {
throw new Error(
`Query contributors with the same row key are not congruent`,
)
}
}
return [[visible, 1]]
}),
) as NamespacedAndKeyedStream
}
/**
* Collects aliases used for DIRECT collection references (not subqueries).
* Used to validate that subqueries don't reuse parent query collection aliases.
* Only direct CollectionRef aliases matter - QueryRef aliases don't cause conflicts.
*/
function collectDirectCollectionAliases(query: QueryIR): Set<string> {
const aliases = new Set<string>()
// Collect FROM alias only if it's a direct collection reference
for (const source of getFromSources(query.from)) {
if (source.type === `collectionRef`) {
aliases.add(source.alias)
}
}
// Collect JOIN aliases only for direct collection references
if (query.join) {
for (const joinClause of query.join) {
if (joinClause.from.type === `collectionRef`) {
aliases.add(joinClause.from.alias)
}
}
}
return aliases
}
/**
* Validates the structure of a query and its subqueries.
* Checks that subqueries don't reuse collection aliases from parent queries.
* This must be called on the RAW query before optimization.
*/
function validateQueryStructure(
query: QueryIR,
parentCollectionAliases: Set<string> = new Set(),
): void {
// Collect direct collection aliases from this query level
const currentLevelAliases = collectDirectCollectionAliases(query)
// Check if any current alias conflicts with parent aliases
for (const alias of currentLevelAliases) {
if (parentCollectionAliases.has(alias)) {
throw new DuplicateAliasInSubqueryError(
alias,
Array.from(parentCollectionAliases),
)
}
}
// Combine parent and current aliases for checking nested subqueries
const combinedAliases = new Set([
...parentCollectionAliases,
...currentLevelAliases,
])
// Recursively validate FROM subqueries
if (query.from.type === `unionAll`) {
for (const branch of query.from.queries) {
validateQueryStructure(branch, combinedAliases)
}
} else {
for (const source of getFromSources(query.from)) {
if (source.type === `queryRef`) {
validateQueryStructure(source.query, combinedAliases)
}
}
}
// Recursively validate JOIN subqueries
if (query.join) {
for (const joinClause of query.join) {
if (joinClause.from.type === `queryRef`) {
validateQueryStructure(joinClause.from.query, combinedAliases)
}
}
}
if (query.select) {
for (const { subquery } of extractIncludesFromSelect(query.select)) {
validateQueryStructure(subquery.query, combinedAliases)
}
}
}
/**
* Processes the FROM clause, handling direct collection references and subqueries.
* Populates `aliasToCollectionId` and `aliasRemapping` for per-alias subscription tracking.
*/
function processFromClause(
from: CollectionRef | QueryRef | UnionFrom | UnionAll,
allInputs: Record<string, KeyedStream>,
collections: Record<string, Collection>,
subscriptions: Record<string, CollectionSubscription>,
callbacks: Record<string, LazyCollectionCallbacks>,
lazySources: Set<string>,
optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,
setWindowFn: (windowFn: (options: WindowOptions) => void) => void,
cache: QueryCache,
queryMapping: QueryMapping,
aliasToCollectionId: Record<string, string>,
aliasRemapping: Record<string, string>,
sourceWhereClauses: Map<string, BasicExpression<boolean>>,
): {
alias: string
pipeline: NamespacedAndKeyedStream
collectionId: string
sources: Record<string, KeyedStream>
sourceIncludes: Array<SourceInclude>
directIncludes: Array<IncludesCompilationResult>
isUnionFrom: boolean
} {
if (from.type === `unionAll`) {
return processUnionAll(
from,
allInputs,
collections,
subscriptions,
callbacks,
lazySources,
optimizableOrderByCollections,
setWindowFn,
cache,
queryMapping,
aliasToCollectionId,
aliasRemapping,
sourceWhereClauses,
)
}
if (from.type !== `unionFrom`) {
const { alias, input, collectionId, sourceIncludes } = processFrom(
from,
allInputs,
collections,
subscriptions,
callbacks,
lazySources,
optimizableOrderByCollections,
setWindowFn,
cache,
queryMapping,
aliasToCollectionId,
aliasRemapping,
sourceWhereClauses,
)
return {
alias,
pipeline: wrapInputWithAlias(input, alias),
collectionId,
sources: { [alias]: input },
sourceIncludes,
directIncludes: [],
isUnionFrom: false,
}
}
if (from.sources.length === 0) {
throw new UnsupportedFromTypeError(`empty unionFrom`)
}
const sources: Record<string, KeyedStream> = {}
const sourceIncludes: Array<SourceInclude> = []
let pipeline: NamespacedAndKeyedStream | undefined
let mainAlias = ``
let mainCollectionId = ``
for (const source of from.sources) {
const {
alias,
input,
collectionId,
sourceIncludes: childSourceIncludes,
} = processFrom(
source,
allInputs,
collections,
subscriptions,
callbacks,
lazySources,
optimizableOrderByCollections,
setWindowFn,
cache,
queryMapping,
aliasToCollectionId,
aliasRemapping,
sourceWhereClauses,
)
if (!mainAlias) {
mainAlias = alias
mainCollectionId = collectionId
}
sources[alias] = input
sourceIncludes.push(...childSourceIncludes)
const branch = wrapInputWithAlias(input, alias).pipe(
map(([key, row]) => {
return [`${alias}:${encodeKeyForUnionBranch(key)}`, row] as [
string,
typeof row,
]
}),
)
pipeline = pipeline ? pipeline.pipe(concatOperator(branch)) : branch
}
return {
alias: mainAlias,
pipeline: pipeline!,
collectionId: mainCollectionId,
sources,
sourceIncludes,
directIncludes: [],
isUnionFrom: true,
}
}
function processUnionAll(
from: UnionAll,
allInputs: Record<string, KeyedStream>,
collections: Record<string, Collection>,
subscriptions: Record<string, CollectionSubscription>,
callbacks: Record<string, LazyCollectionCallbacks>,
lazySources: Set<string>,
optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,
setWindowFn: (windowFn: (options: WindowOptions) => void) => void,
cache: QueryCache,
queryMapping: QueryMapping,
aliasToCollectionId: Record<string, string>,
aliasRemapping: Record<string, string>,
sourceWhereClauses: Map<string, BasicExpression<boolean>>,
): {
alias: string
pipeline: NamespacedAndKeyedStream
collectionId: string
sources: Record<string, KeyedStream>
sourceIncludes: Array<SourceInclude>
directIncludes: Array<IncludesCompilationResult>
isUnionFrom: boolean
} {
if (from.queries.length === 0) {
throw new UnsupportedFromTypeError(`empty unionAll`)
}
const sources: Record<string, KeyedStream> = {}
const sourceIncludes: Array<SourceInclude> = []
const directIncludes: Array<IncludesCompilationResult> = []
let pipeline: NamespacedAndKeyedStream | undefined
let mainCollectionId = ``
const branchAliases = new Set<string>()
for (let index = 0; index < from.queries.length; index++) {
const branch = from.queries[index]!
for (const source of getAllSources(branch)) {
if (branchAliases.has(source.alias)) {
throw new Error(
`Duplicate source alias "${source.alias}" in unionAll query branches. ` +
`Use distinct aliases in each branch before passing them to unionAll().`,
)
}
branchAliases.add(source.alias)
}
const branchResult = compileQuery(
branch,
allInputs,
collections,
subscriptions,
callbacks,
lazySources,
optimizableOrderByCollections,
setWindowFn,
cache,
queryMapping,
)
if (!mainCollectionId) {
mainCollectionId = branchResult.collectionId
}
Object.assign(aliasToCollectionId, branchResult.aliasToCollectionId)
Object.assign(aliasRemapping, branchResult.aliasRemapping)
directIncludes.push(...(branchResult.includes ?? []))
Object.assign(sources, allInputs)
for (const [alias, where] of branchResult.sourceWhereClauses) {
sourceWhereClauses.set(alias, where)
}
const branchPipeline = branchResult.pipeline.pipe(
map(([key, [row]]) => {
return [`${index}:${encodeKeyForUnionBranch(key)}`, row] as [
string,
Record<string, any>,
]
}),
)
pipeline = pipeline
? pipeline.pipe(concatOperator(branchPipeline))
: branchPipeline
}
return {
alias: ``,
pipeline: pipeline!,
collectionId: mainCollectionId,
sources,
sourceIncludes,
directIncludes,
isUnionFrom: true,
}
}
function wrapInputWithAlias(
input: KeyedStream,
alias: string,
): NamespacedAndKeyedStream {
return input.pipe(
map(([key, row]) => {
// Initialize the record with a nested structure.
// If __parentContext exists (from parent-referencing includes), merge parent
// aliases into the namespaced row so WHERE can resolve parent refs.
const { __parentContext, ...cleanRow } = row as any
const nsRow: Record<string, any> = { [alias]: cleanRow }
if (__parentContext) {
Object.assign(nsRow, __parentContext)
;(nsRow as any).__parentContext = __parentContext
}
return [key, nsRow] as [unknown, Record<string, typeof row>]
}),
)
}
function encodeKeyForUnionBranch(key: unknown): string {
if (typeof key === `string`) {
return `string:${key}`
}
if (typeof key === `number`) {
return `number:${String(key)}`
}
if (typeof key === `bigint`) {
return `bigint:${String(key)}`
}
return `${typeof key}:${JSON.stringify(key)}`
}
function processFrom(
from: CollectionRef | QueryRef,
allInputs: Record<string, KeyedStream>,
collections: Record<string, Collection>,
subscriptions: Record<string, CollectionSubscription>,
callbacks: Record<string, LazyCollectionCallbacks>,
lazySources: Set<string>,
optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,
setWindowFn: (windowFn: (options: WindowOptions) => void) => void,
cache: QueryCache,
queryMapping: QueryMapping,
aliasToCollectionId: Record<string, string>,
aliasRemapping: Record<string, string>,
sourceWhereClauses: Map<string, BasicExpression<boolean>>,
): {
alias: string
input: KeyedStream
collectionId: string
sourceIncludes: Array<SourceInclude>
} {
switch (from.type) {
case `collectionRef`: {
const input = allInputs[from.sourceId] ?? allInputs[from.alias]
if (!input) {
throw new CollectionInputNotFoundError(
from.alias,
from.collection.id,
Object.keys(allInputs),
)
}
aliasToCollectionId[from.alias] = from.collection.id
return {
alias: from.alias,
input,
collectionId: from.collection.id,
sourceIncludes: [],
}
}
case `queryRef`: {
// Find the original query for caching purposes
const originalQuery = queryMapping.get(from.query) || from.query
// Recursively compile the sub-query with cache
const subQueryResult = compileQuery(
originalQuery,
allInputs,
collections,
subscriptions,
callbacks,
lazySources,
optimizableOrderByCollections,
setWindowFn,
cache,
queryMapping,
)
// Pull up alias mappings from subquery to parent scope.
// This includes both the innermost alias-to-collection mappings AND
// any existing remappings from nested subquery levels.
Object.assign(aliasToCollectionId, subQueryResult.aliasToCollectionId)
Object.assign(aliasRemapping, subQueryResult.aliasRemapping)
// Pull up source WHERE clauses from subquery to parent scope.
// This enables loadSubset to receive the correct where clauses for subquery collections.
//
// IMPORTANT: Skip pull-up for optimizer-created subqueries. These are detected when:
// 1. The outer alias (from.alias) matches the inner alias (from.query.from.alias)
// 2. The subquery was found in queryMapping (it's a user-defined subquery, not optimizer-created)
//
// For optimizer-created subqueries, the parent already has the sourceWhereClauses
// extracted from the original raw query, so pulling up would be redundant.
// More importantly, pulling up for optimizer-created subqueries can cause issues
// when the optimizer has restructured the query.
const isUserDefinedSubquery = queryMapping.has(from.query)
const subqueryFromAlias = getFirstFromAlias(from.query.from)
const isOptimizerCreated =
!isUserDefinedSubquery && from.alias === subqueryFromAlias
if (!isOptimizerCreated) {
for (const [alias, whereClause] of subQueryResult.sourceWhereClauses) {
sourceWhereClauses.set(alias, whereClause)
}
}
// Create a FLATTENED remapping from outer alias to innermost alias.
// For nested subqueries, this ensures one-hop lookups (not recursive chains).
//
// Example with 3-level nesting:
// Inner: .from({ user: usersCollection })
// Middle: .from({ activeUser: innerSubquery }) → creates: activeUser → user
// Outer: .from({ author: middleSubquery }) → creates: author → user (not author → activeUser)
//
// The key insight: We search through the PULLED-UP aliasToCollectionId (which contains
// the innermost 'user' alias), so we always map directly to the deepest level.
// This means aliasRemapping[alias] is always a single lookup, never recursive.
// Needed for subscription resolution during lazy loading.
const innerAlias = Object.keys(subQueryResult.aliasToCollectionId).find(
(alias) =>
subQueryResult.aliasToCollectionId[alias] ===
subQueryResult.collectionId,
)
if (innerAlias && innerAlias !== from.alias) {
aliasRemapping[from.alias] = innerAlias
}
// Extract the pipeline from the compilation result
const subQueryInput = subQueryResult.pipeline
// Subqueries may return [key, [value, orderByIndex]] (with ORDER BY) or [key, value] (without ORDER BY)
// We need to extract just the value for use in parent queries
const extractedInput = subQueryInput.pipe(
map((data: any) => {
const [key, [value, _orderByIndex]] = data
// Unwrap Value expressions that might have leaked through as the entire row
const unwrapped = unwrapValue(value)
return [key, unwrapped] as [unknown, any]
}),
)
return {
alias: from.alias,
input: extractedInput,
collectionId: subQueryResult.collectionId,
sourceIncludes:
subQueryResult.includes?.map((include) => ({
sourceAlias: from.alias,
include,
})) ?? [],
}
}
default:
throw new UnsupportedFromTypeError((from as any).type)
}
}
// Helper to check if a value is a Value expression
function isValue(raw: any): boolean {
return (
raw instanceof ValClass ||
(raw && typeof raw === `object` && `type` in raw && raw.type === `val`)
)
}
// Helper to unwrap a Value expression or return the value itself
function unwrapValue(value: any): any {
return isValue(value) ? value.value : value
}
function attachVirtualPropsToSelected(
selected: any,
row: Record<string, any>,
): any {
if (!selected || typeof selected !== `object`) {
return selected
}
let needsMerge = false
for (const prop of VIRTUAL_PROP_NAMES) {
if (selected[prop] == null && prop in row) {
needsMerge = true
break
}
}
if (!needsMerge) {
return selected
}
const result = Array.isArray(selected) ? [...selected] : { ...selected }
for (const prop of VIRTUAL_PROP_NAMES) {
if (selected[prop] == null && prop in row) {
result[prop] = row[prop]
}
}
return result
}
function stripInternalCorrelation(selected: any): any {
if (
!selected ||
typeof selected !== `object` ||
(!(`__correlationKey` in selected) &&
!(`__parentContext` in selected) &&
!(INCLUDES_PUBLIC_KEY in selected))
) {
return selected
}
const result = Array.isArray(selected) ? [...selected] : { ...selected }
delete result.__correlationKey
delete result.__parentContext
delete result[INCLUDES_PUBLIC_KEY]
return result
}
function getIncludesPublicKey(
row: Record<string, any>,
mainSource: string,
fallback: unknown,
): unknown {
return row[mainSource]?.[INCLUDES_PUBLIC_KEY] ?? fallback
}
/**
* Recursively maps optimized subqueries to their original queries for proper caching.
* This ensures that when we encounter the same QueryRef object in different contexts,
* we can find the original query to check the cache.
*/
function mapNestedQueries(
optimizedQuery: QueryIR,
originalQuery: QueryIR,
queryMapping: QueryMapping,
): void {
mapNestedFromQueries(optimizedQuery.from, originalQuery.from, queryMapping)
// Map JOIN clauses if they exist
if (optimizedQuery.join && originalQuery.join) {
for (
let i = 0;
i < optimizedQuery.join.length && i < originalQuery.join.length;
i++
) {
const optimizedJoin = optimizedQuery.join[i]!
const originalJoin = originalQuery.join[i]!
if (
optimizedJoin.from.type === `queryRef` &&
originalJoin.from.type === `queryRef`
) {
queryMapping.set(optimizedJoin.from.query, originalJoin.from.query)
// Recursively map nested queries in joins
mapNestedQueries(
optimizedJoin.from.query,
originalJoin.from.query,
queryMapping,
)
}
}
}
}
function getRefFromAlias(
query: QueryIR,
alias: string,
): CollectionRef | QueryRef | void {
for (const source of getFromSources(query.from)) {
if (source.alias === alias) {
return source
}
}
for (const join of query.join || []) {
if (join.from.alias === alias) {
return join.from
}
}
}
function getFromSources(
from: QueryIR[`from`],
): Array<CollectionRef | QueryRef> {
if (from.type === `unionFrom`) {
return from.sources
}
if (from.type === `unionAll`) {
return []
}
return [from]
}
function getAllSources(query: QueryIR): Array<CollectionRef | QueryRef> {
return [
...getFromSources(query.from),
...(query.join?.map((join) => join.from) ?? []),
]
}
function getFirstFromAlias(from: QueryIR[`from`]): string {
return getFromSources(from)[0]?.alias ?? ``
}
function findProjectedSourceIncludePaths(
select: Record<string, any>,
sourceAlias: string,
sourcePath: Array<string>,
): Array<ProjectedSourceIncludePath> {
const targetPath = [sourceAlias, ...sourcePath]
return findProjectedIncludePaths(select, targetPath)
}
function findProjectedResultIncludePaths(
select: Record<string, any>,
resultPath: Array<string>,
): Array<ProjectedSourceIncludePath> {
return findProjectedIncludePaths(select, resultPath)
}
function findProjectedIncludePaths(
select: Record<string, any>,
targetPath: Array<string>,
): Array<ProjectedSourceIncludePath> {
const resultPaths: Array<ProjectedSourceIncludePath> = []
const visitSelectObject = (
obj: Record<string, any>,
prefix: Array<string>,
guards: Array<ConditionalSelectGuard>,
) => {
for (const [key, value] of Object.entries(obj)) {
if (key.startsWith(`__SPREAD_SENTINEL__`)) {
visitSpreadSentinel(key, value, prefix, guards)
continue
}
visitSelectValue(value, [...prefix, key], guards)
}
}
const visitSpreadSentinel = (
key: string,
value: any,
path: Array<string>,
guards: Array<ConditionalSelectGuard>,
) => {
const rest = key.slice(`__SPREAD_SENTINEL__`.length)
const splitIndex = rest.lastIndexOf(`__`)
const pathStr = splitIndex >= 0 ? rest.slice(0, splitIndex) : rest
const isRefExpr =
value &&
typeof value === `object` &&
`type` in value &&
value.type === `ref`
const sourcePath = isRefExpr
? (value as PropRef).path
: pathStr.split(`.`).filter(Boolean)
if (pathStartsWith(targetPath, sourcePath)) {
resultPaths.push({
path: [...path, ...targetPath.slice(sourcePath.length)],
guards,
})
}
}
const visitSelectValue = (
value: any,
path: Array<string>,
guards: Array<ConditionalSelectGuard>,
) => {
if (value instanceof PropRef && pathStartsWith(targetPath, value.path)) {
resultPaths.push({
path: [...path, ...targetPath.slice(value.path.length)],
guards,
})
return
}
if (value instanceof ConditionalSelect) {
const previousBranchGuards: Array<ConditionalSelectGuard> = []
for (const branch of value.branches) {
visitSelectValue(branch.value, path, [
...guards,
...previousBranchGuards,
{ condition: branch.condition, expected: true },
])
previousBranchGuards.push({
condition: branch.condition,
expected: false,
})
}
if (value.defaultValue !== undefined) {
visitSelectValue(value.defaultValue, path, [
...guards,
...previousBranchGuards,
])
}
return
}
if (isNestedSelectObject(value)) {
visitSelectObject(value, path, guards)
}
}
visitSelectObject(select, [], [])
return resultPaths
}
function pathStartsWith(path: Array<string>, prefix: Array<string>): boolean {
return (
prefix.length <= path.length && prefix.every((part, i) => path[i] === part)
)
}
function mapNestedFromQueries(
optimizedFrom: QueryIR[`from`],
originalFrom: QueryIR[`from`],
queryMapping: QueryMapping,
): void {
if (optimizedFrom.type === `unionAll` && originalFrom.type === `unionAll`) {
for (
let i = 0;
i < optimizedFrom.queries.length && i < originalFrom.queries.length;
i++
) {
const optimizedBranch = optimizedFrom.queries[i]!
const originalBranch = originalFrom.queries[i]!
queryMapping.set(optimizedBranch, originalBranch)
mapNestedQueries(optimizedBranch, originalBranch, queryMapping)
}
return
}
const optimizedSources = getFromSources(optimizedFrom)
const originalSources = getFromSources(originalFrom)
for (
let i = 0;
i < optimizedSources.length && i < originalSources.length;
i++
) {
const optimizedSource = optimizedSources[i]!
const originalSource = originalSources[i]!
if (
optimizedSource.type === `queryRef` &&
originalSource.type === `queryRef`
) {
queryMapping.set(optimizedSource.query, originalSource.query)
mapNestedQueries(
optimizedSource.query,
originalSource.query,
queryMapping,
)
}
}
}
/**
* Follows the given reference in a query
* until its finds the root field the reference points to.
* @returns The collection, its alias, and the path to the root field in this collection
*/
export function followRef(
query: QueryIR,
ref: PropRef<any>,
collection: Collection,
): { collection: Collection; path: Array<string> } | void {
if (ref.path.length === 0) {
return
}
if (ref.path.length === 1) {
// This field should be part of this collection
const field = ref.path[0]!
// is it part of the select clause?
if (query.select) {
const selectedField = query.select[field]
if (selectedField && selectedField.type === `ref`) {
return followRef(query, selectedField, collection)
}
}
// Either this field is not part of the select clause
// and thus it must be part of the collection itself
// or it is part of the select but is not a reference
// so we can stop here and don't have to follow it
return { collection, path: [field] }
}
if (ref.path.length > 1) {
// This is a nested field
const [alias, ...rest] = ref.path
const aliasRef = getRefFromAlias(query, alias!)
if (!aliasRef) {
return
}
if (aliasRef.type === `queryRef`) {
return followRef(aliasRef.query, new PropRef(rest), collection)
} else {
// This is a reference to a collection
// we can't follow it further
// so the field must be on the collection itself
return { collection: aliasRef.collection, path: rest }
}
}
}
/**
* Walks a Select object to find IncludesSubquery entries.
* Plain nested objects still reject includes, but ConditionalSelect branches can
* contain guarded nested includes that are only materialized when the branch
* condition is true.
*/
function extractIncludesFromSelect(select: Record<string, any>): Array<{
key: string
path: Array<string>
subquery: IncludesSubquery
guards: Array<ConditionalSelectGuard>
}> {
const results: Array<{
key: string
path: Array<string>
subquery: IncludesSubquery
guards: Array<ConditionalSelectGuard>
}> = []
for (const [key, value] of Object.entries(select)) {
if (key.startsWith(`__SPREAD_SENTINEL__`)) continue
if (value instanceof IncludesSubquery) {
results.push({
key: getIncludesRoutingKey([key], results),
path: [key],
subquery: value,
guards: [],
})
} else if (value instanceof ConditionalSelect) {
collectIncludesFromConditionalSelect(value, [key], [], results)
} else if (isNestedSelectObject(value)) {
// Check nested objects for IncludesSubquery — not supported yet
assertNoNestedIncludes(value, key)
}
}
return results
}
function collectIncludesFromConditionalSelect(
conditional: ConditionalSelect,
prefixPath: Array<string>,
guards: Array<ConditionalSelectGuard>,
results: Array<{
key: string
path: Array<string>
subquery: IncludesSubquery
guards: Array<ConditionalSelectGuard>
}>,
): void {
const previousBranchGuards: Array<ConditionalSelectGuard> = []
for (const branch of conditional.branches) {
collectIncludesFromSelectValue(
branch.value,
prefixPath,
[
...guards,
...previousBranchGuards,
{ condition: branch.condition, expected: true },
],
results,
)
previousBranchGuards.push({
condition: branch.condition,
expected: false,
})
}
if (conditional.defaultValue !== undefined) {
collectIncludesFromSelectValue(
conditional.defaultValue,
prefixPath,
[...guards, ...previousBranchGuards],
results,
)
}
}
function collectIncludesFromSelectValue(
value: any,
prefixPath: Array<string>,
guards: Array<ConditionalSelectGuard>,
results: Array<{
key: string
path: Array<string>
subquery: IncludesSubquery
guards: Array<ConditionalSelectGuard>
}>,
): void {
if (value instanceof IncludesSubquery) {
const key = getIncludesRoutingKey(prefixPath, results)
results.push({ key, path: prefixPath, subquery: value, guards })
return
}
if (value instanceof ConditionalSelect) {
collectIncludesFromConditionalSelect(value, prefixPath, guards, results)
return
}
if (!isNestedSelectObject(value)) {
return
}
for (const [key, child] of Object.entries(value)) {
if (key.startsWith(`__SPREAD_SENTINEL__`)) continue
collectIncludesFromSelectValue(child, [...prefixPath, key], guards, results)
}
}
function getIncludesRoutingKey(
path: Array<string>,
entries: Array<{ key: string }>,
): string {
return getUniqueIncludesRoutingKey(path.join(`.`), entries)
}
function getUniqueIncludesRoutingKey(
baseKey: string,
entries: Array<{ key?: string; fieldName?: string }>,
): string {
const hasKey = (key: string) =>
entries.some((entry) => (entry.key ?? entry.fieldName) === key)
if (!hasKey(baseKey)) {
return baseKey
}
let suffix = entries.length
let key = `${baseKey}#${suffix}`
while (hasKey(key)) {
suffix++
key = `${baseKey}#${suffix}`
}
return key
}
/** Check if a value is a nested plain object in a select (not an IR expression node) */
function isNestedSelectObject(value: any): value is Record<string, any> {
return (
value != null &&
typeof value === `object` &&
!Array.isArray(value) &&
!isExpressionLike(value) &&
value.__refProxy !== true
)
}
function assertNoNestedIncludes(
obj: Record<string, any>,
parentPath: string,
): void {
for (const [key, value] of Object.entries(obj)) {
if (key.startsWith(`__SPREAD_SENTINEL__`)) continue
if (value instanceof IncludesSubquery) {
throw new Error(
`Includes subqueries must be at the top level of select(). ` +
`Found nested includes at "${parentPath}.${key}".`,
)
}
if (isNestedSelectObject(value)) {
assertNoNestedIncludes(value, `${parentPath}.${key}`)
}
}
}
/**
* Replaces an IncludesSubquery entry in the select object with a null Value placeholder.
* This ensures processSelect() doesn't encounter it.
*/
function replaceIncludesInSelect(
select: Record<string, any>,
path: Array<string>,
): Record<string, any> {
return replaceIncludesInSelectValue(select, path, new ValClass(null)).value
}
function replaceIncludesInSelectValue(
value: any,
path: Array<string>,
replacement: ValClass,
): { value: any; replaced: boolean } {
if (path.length === 0) {
return replaceIncludesValue(value, replacement)
}
if (value instanceof ConditionalSelect) {
return replaceIncludesInConditionalSelect(value, path, replacement)
}
if (!isNestedSelectObject(value)) {
return { value, replaced: false }
}
if (path.length === 1) {
const field = path[0]!
const result = replaceIncludesValue(value[field], replacement)
if (!result.replaced) {
return { value, replaced: false }
}
return {
value: {
...value,
[field]: result.value,
},
replaced: true,
}
}
const [head, ...rest] = path
const result = replaceIncludesInSelectValue(value[head!], rest, replacement)
if (!result.replaced) {
return { value, replaced: false }
}
return {
value: {
...value,
[head!]: result.value,
},
replaced: true,
}
}
function replaceIncludesValue(
value: any,
replacement: ValClass,
): { value: any; replaced: boolean } {
if (value instanceof IncludesSubquery) {
return { value: replacement, replaced: true }
}
if (value instanceof ConditionalSelect) {
return replaceIncludesInConditionalSelect(value, [], replacement)
}
return { value, replaced: false }
}
function replaceIncludesInConditionalSelect(
conditional: ConditionalSelect,
path: Array<string>,
replacement: ValClass,
): { value: ConditionalSelect; replaced: boolean } {
let replaced = false
const branches = conditional.branches.map((branch) => {
const result =
path.length === 0
? replaceIncludesValue(branch.value, replacement)
: replaceIncludesInSelectValue(branch.value, path, replacement)
if (!result.replaced) {
return branch
}
replaced = true
return { ...branch, value: result.value }
})
let defaultValue = conditional.defaultValue
if (conditional.defaultValue !== undefined) {
const result =
path.length === 0
? replaceIncludesValue(conditional.defaultValue, replacement)
: replaceIncludesInSelectValue(
conditional.defaultValue,
path,
replacement,
)
if (result.replaced) {
replaced = true
defaultValue = result.value
}
}
if (!replaced) {
return { value: conditional, replaced: false }
}
return {
value: new ConditionalSelect(branches, defaultValue),
replaced: true,
}
}
/**
* Gets a nested value from an object by path segments.
* For v1 with single-level correlation fields (e.g., `projectId`), it's just `obj[path[0]]`.
*/
function getNestedValue(obj: any, path: Array<string>): any {
let value = obj
for (const segment of path) {
if (value == null) return value
value = value[segment]
}
return value
}
function matchesConditionalSelectGuards(
guards: Array<{
condition: (row: any) => any
expected: boolean
}>,
row: any,
): boolean {
return guards.every(
(guard) => isCaseWhenConditionTrue(guard.condition(row)) === guard.expected,
)
}
export type CompileQueryFn = typeof compileQuery
function getStaticDemandKeys(query: QueryIR, ref: PropRef): Set<unknown> {
const constraints: Array<Set<unknown>> = []
const visit = (expression: BasicExpression): void => {
if (expression.type !== `func`) return
if (expression.name === `and`) {
expression.args.forEach(visit)
return
}
if (expression.name !== `eq` && expression.name !== `in`) return
const [left, right] = expression.args
const value =
left?.type === `ref` &&
pathsEqual(left.path, ref.path) &&
right instanceof ValClass
? right.value
: right?.type === `ref` &&
pathsEqual(right.path, ref.path) &&
left instanceof ValClass
? left.value
: undefined
if (value === undefined) return
constraints.push(new Set(Array.isArray(value) ? value : [value]))
}
query.where?.forEach((where) => visit(getWhereExpression(where)))
if (constraints.length === 0) return new Set()
return new Set(
[...constraints[0]!].filter((value) =>
constraints.slice(1).every((constraint) => constraint.has(value)),
),
)
}
function pathsEqual(left: Array<string>, right: Array<string>): boolean {
return (
left.length === right.length &&
left.every((segment, index) => segment === right[index])
)
}