from __future__ import annotations

import datetime as dt
import math
import re
from collections import Counter
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, cast

from ...metamodel.builtins import builtins as builtin_registry, is_entity_type, is_metatype, is_primitive, is_value_type
from ...metamodel.metamodel import Aggregate, Construct, Data, Exists, Literal, Logical, Lookup, Not, NumberType, Relation, Task, TypeNode, Union, Update, Value, Var
from .analyzer import SQL2Analysis
from .ir import (
    SQL2BinaryExpr,
    SQL2CastExpr,
    SQL2ColumnExpr,
    SQL2CreateViewOp,
    SQL2ExistsExpr,
    SQL2Expr,
    SQL2FromItem,
    SQL2FunctionExpr,
    SQL2Join,
    SQL2LiteralExpr,
    SQL2NullExpr,
    SQL2OrderByExpr,
    SQL2ProgramIR,
    SQL2Query,
    SQL2RecursiveQuery,
    SQL2SemiNaiveLoop,
    SQL2SemiNaiveTable,
    SQL2SelectItem,
    SQL2SelectQuery,
    SQL2StratumProgram,
    SQL2SubquerySource,
    SQL2TableSource,
    SQL2UnionQuery,
    SQL2WindowExpr,
)
from .model_analysis import TableKind
from .task_semantics import task_var_ids
from .type_inference import infer_function_return_sql_type, normalize_sql_type, same_sql_type

if TYPE_CHECKING:
    from .analyzer import SQL2FragmentProjectionAssemblyPlan, SQL2FragmentProjectionSpec


@dataclass
class _RuleCtx:
    from_item: SQL2FromItem | None = None
    joins: list[SQL2Join] = field(default_factory=list)
    where: list[SQL2Expr] = field(default_factory=list)
    var_map: dict[int, SQL2Expr] = field(default_factory=dict)
    lookup_alias_by_signature: dict[tuple[str, tuple[tuple[str, SQL2Expr], ...]], str] = field(default_factory=dict)
    alias_table_name_by_alias: dict[str, str] = field(default_factory=dict)
    optional_projection_var_ids_by_task_id: dict[int, tuple[int, ...]] = field(default_factory=dict)
    branch_projection_var_ids_by_task_id: dict[int, tuple[int, ...]] = field(default_factory=dict)
    union_mode_by_task_id: dict[int, str] = field(default_factory=dict)
    future_var_ids_by_task_id: dict[int, tuple[int, ...]] = field(default_factory=dict)
    bound_union_output_var_ids_by_branch_signature: dict[tuple[tuple[object, ...], ...], tuple[int, ...]] = field(
        default_factory=dict
    )
    seed_union_scope_from_bound_aliases: bool = False
    used_aliases: set[str] = field(default_factory=set)
    alias_counter: int = 0

    def new_alias(self, prefix: str) -> str:
        safe = re.sub(r"[^A-Za-z0-9_]", "_", prefix) or "t"
        while True:
            self.alias_counter += 1
            alias = f"{safe}_{self.alias_counter}"
            if alias in self.used_aliases:
                continue
            self.used_aliases.add(alias)
            return alias


class SQL2Lowerer:
    """Mechanical lowering from SQL2 normalized metamodel artifacts to SQL2 IR."""

    _BINARY_OPS = {"=", "!=", "<", "<=", ">", ">="}

    def __init__(self, dialect=None):
        self._source_schema_name: str | None = None
        self._table_source_overrides: dict[str, str] = {}
        self._physical_table_name_by_logical: dict[str, str] = {}
        dialect_name = type(dialect).__name__.lower()
        self._prefer_native_row_hash_identity = dialect_name.startswith("duckdb")

    def lower_model(
        self,
        analysis: SQL2Analysis,
        *,
        schema_name: str | None = None,
        source_schema_name: str | None = None,
    ) -> SQL2ProgramIR:
        source_schema = source_schema_name if source_schema_name is not None else schema_name
        self._physical_table_name_by_logical = self._build_physical_table_name_map(analysis)
        grouped = [
            (stratum_id, group, ix)
            for stratum_id, groups in sorted(analysis.normalized.groups_by_stratum.items())
            for ix, group in enumerate(groups)
        ]
        preseed_ops = self._lower_preseed_table_ops(analysis, schema_name=schema_name)
        data_ops = self._lower_data_table_ops(analysis, schema_name=schema_name)
        table_counts = Counter(group.table_name for _stratum_id, group, _ix in grouped)
        table_output_columns_by_name = {
            plan.table_name: plan.output_columns
            for plan in analysis.normalized.table_plans
        }
        table_key_columns_by_name = {
            plan.table_name: set(plan.key_columns)
            for plan in analysis.normalized.table_plans
        }

        previous_source_schema = self._source_schema_name
        previous_source_overrides = self._table_source_overrides
        self._source_schema_name = source_schema
        self._table_source_overrides = {}
        try:
            strata: list[SQL2StratumProgram] = []
            materialized_tables: set[str] = set()
            table_snapshot_query_by_name: dict[str, SQL2Query] = {}
            if preseed_ops:
                strata.append(SQL2StratumProgram(stratum_id=-2, operations=tuple(preseed_ops)))
                materialized_tables.update(op.name.rsplit(".", 1)[-1] for op in preseed_ops)
                table_snapshot_query_by_name.update(
                    {
                        op.name.rsplit(".", 1)[-1]: op.query
                        for op in preseed_ops
                    }
                )
            if data_ops:
                strata.append(SQL2StratumProgram(stratum_id=-1, operations=tuple(data_ops)))
                materialized_tables.update(
                    analysis.table_shape.table_name_by_relation_id.get(table.relation.id, table.name)
                    for table in analysis.model_analysis.table_info.values()
                    if table.kind == TableKind.DATA
                )
                for table in analysis.model_analysis.table_info.values():
                    if table.kind != TableKind.DATA:
                        continue
                    if not isinstance(table.relation, Data):
                        continue
                    table_name = analysis.table_shape.table_name_by_relation_id.get(table.relation.id, table.name)
                    table_snapshot_query_by_name[table_name] = self._data_table_query(
                        table.relation,
                        column_names=tuple(column.name for column in table.columns),
                    )
            for stratum_id, groups in sorted(analysis.normalized.groups_by_stratum.items()):
                pre_operations: list[SQL2CreateViewOp] = []
                post_operations: list[SQL2CreateViewOp] = []
                semi_naive_loops: list[SQL2SemiNaiveLoop] = []
                recursive_node_ids = analysis.graph.recursive_node_ids
                recursive_table_order: list[str] = []
                recursive_table_set: set[str] = set()
                for group in groups:
                    if any(node_id in recursive_node_ids for node_id in group.node_ids):
                        if group.table_name not in recursive_table_set:
                            recursive_table_set.add(group.table_name)
                            recursive_table_order.append(group.table_name)

                def depends_on_recursive_table(group) -> bool:
                    if group.table_name in recursive_table_set:
                        return False
                    return any(table_name in recursive_table_set for table_name in group.read_table_names)

                split_table_output_columns: dict[str, tuple[str, ...]] = {}
                split_table_key_columns: dict[str, set[str]] = {}
                split_table_accumulator: dict[str, str] = {}
                split_table_base_source: dict[str, str] = {}
                split_table_contributions: dict[str, list[SQL2Query]] = {}
                split_table_state_ordinal: dict[str, int] = {}
                split_table_order: list[str] = []
                deferred_groups: list = []
                for group in groups:
                    if group.table_name in recursive_table_set:
                        continue
                    if depends_on_recursive_table(group):
                        deferred_groups.append(group)
                        continue
                    if table_counts[group.table_name] == 1:
                        overrides = dict(split_table_accumulator)
                        if group.table_name in materialized_tables:
                            output_columns = table_output_columns_by_name.get(group.table_name, ())
                            key_columns = table_key_columns_by_name.get(group.table_name, set())
                            prior_query = table_snapshot_query_by_name.get(group.table_name)
                            seed_view_name = self._deferred_seed_view_name(
                                stratum_id=stratum_id,
                                table_name=group.table_name,
                                schema_name=schema_name,
                            )
                            if prior_query is not None:
                                seed_query = self._merge_table_views(
                                    source_views=(),
                                    source_queries=(prior_query,),
                                    output_columns=output_columns,
                                    key_columns=key_columns,
                                )
                            else:
                                seed_query = self._merge_table_views(
                                    source_views=(
                                        self._public_view_name(
                                            table_name=group.table_name,
                                            schema_name=schema_name,
                                        ),
                                    ),
                                    output_columns=output_columns,
                                    key_columns=key_columns,
                                )
                            pre_operations.append(
                                SQL2CreateViewOp(
                                    name=seed_view_name,
                                    query=seed_query,
                                )
                            )
                            overrides[group.table_name] = seed_view_name
                        self._table_source_overrides = overrides
                        query = self._lower_group_query(analysis, group)
                        view_name = self._public_view_name(table_name=group.table_name, schema_name=schema_name)
                        pre_operations.append(SQL2CreateViewOp(name=view_name, query=query))
                        materialized_tables.add(group.table_name)
                        table_snapshot_query_by_name[group.table_name] = query
                    else:
                        output_columns = table_output_columns_by_name.get(group.table_name, ())
                        key_columns = table_key_columns_by_name.get(group.table_name, set())
                        split_table_output_columns[group.table_name] = output_columns
                        split_table_key_columns[group.table_name] = key_columns
                        if group.table_name not in split_table_contributions:
                            split_table_contributions[group.table_name] = []
                            split_table_state_ordinal[group.table_name] = 0
                            if group.table_name in materialized_tables:
                                prior_query = table_snapshot_query_by_name.get(group.table_name)
                                seed_view_name = self._deferred_seed_view_name(
                                    stratum_id=stratum_id,
                                    table_name=group.table_name,
                                    schema_name=schema_name,
                                )
                                if prior_query is not None:
                                    seed_query = self._merge_table_views(
                                        source_views=(),
                                        source_queries=(prior_query,),
                                        output_columns=output_columns,
                                        key_columns=key_columns,
                                    )
                                else:
                                    seed_query = self._merge_table_views(
                                        source_views=(
                                            self._public_view_name(
                                                table_name=group.table_name,
                                                schema_name=schema_name,
                                            ),
                                        ),
                                        output_columns=output_columns,
                                        key_columns=key_columns,
                                    )
                                pre_operations.append(
                                    SQL2CreateViewOp(
                                        name=seed_view_name,
                                        query=seed_query,
                                    )
                                )
                                split_table_base_source[group.table_name] = seed_view_name
                                split_table_accumulator[group.table_name] = seed_view_name
                            else:
                                if group.table_name in group.read_table_names:
                                    seed_view_name = self._initial_seed_view_name(
                                        stratum_id=stratum_id,
                                        table_name=group.table_name,
                                        schema_name=schema_name,
                                    )
                                    pre_operations.append(
                                        SQL2CreateViewOp(
                                            name=seed_view_name,
                                            query=self._empty_rowset_query_for_table(
                                                analysis=analysis,
                                                table_name=group.table_name,
                                                output_columns=output_columns,
                                            ),
                                        )
                                    )
                                    split_table_base_source[group.table_name] = seed_view_name
                                    split_table_accumulator[group.table_name] = seed_view_name
                        split_table_state_ordinal[group.table_name] += 1
                        ordinal = split_table_state_ordinal[group.table_name]
                        state_view_name = self._state_view_name_for_group(
                            table_name=group.table_name,
                            stratum_id=stratum_id,
                            ordinal=ordinal,
                            schema_name=schema_name,
                        )
                        # Deterministic same-stratum visibility: each split-table
                        # contribution updates a monotonic internal state source.
                        self._table_source_overrides = dict(split_table_accumulator)
                        query = self._lower_group_query(analysis, group)
                        split_table_contributions[group.table_name].append(query)
                        base_source = split_table_base_source.get(group.table_name)
                        state_query = self._merge_table_views(
                            source_views=(base_source,) if base_source is not None else (),
                            source_queries=tuple(split_table_contributions[group.table_name]),
                            output_columns=output_columns,
                            key_columns=key_columns,
                        )
                        pre_operations.append(SQL2CreateViewOp(name=state_view_name, query=state_query))
                        split_table_accumulator[group.table_name] = state_view_name
                        if group.table_name not in split_table_order:
                            split_table_order.append(group.table_name)

                self._table_source_overrides = dict(split_table_accumulator)
                for table_name in split_table_order:
                    output_columns = split_table_output_columns.get(table_name, ())
                    key_columns = split_table_key_columns.get(table_name, set())
                    public_view_name = self._public_view_name(table_name=table_name, schema_name=schema_name)
                    public_query = self._merge_table_views(
                        source_views=(split_table_accumulator[table_name],),
                        output_columns=output_columns,
                        key_columns=key_columns,
                    )
                    pre_operations.append(
                        SQL2CreateViewOp(
                            name=public_view_name,
                            query=public_query,
                        )
                    )
                    materialized_tables.add(table_name)
                    table_snapshot_query_by_name[table_name] = public_query
                deferred_table_names = {group.table_name for group in deferred_groups}
                for table_name in sorted(deferred_table_names):
                    if table_counts[table_name] == 1 or table_name in split_table_accumulator:
                        continue
                    output_columns = split_table_output_columns.get(
                        table_name,
                        table_output_columns_by_name.get(table_name, ()),
                    )
                    key_columns = split_table_key_columns.get(
                        table_name,
                        table_key_columns_by_name.get(table_name, set()),
                    )
                    base_source = None
                    base_query = table_snapshot_query_by_name.get(table_name)
                    if base_query is None and table_name in materialized_tables:
                        base_source = self._public_view_name(
                            table_name=table_name,
                            schema_name=schema_name,
                        )
                    if base_query is not None or base_source is not None:
                        seed_view_name = self._deferred_seed_view_name(
                            stratum_id=stratum_id,
                            table_name=table_name,
                            schema_name=schema_name,
                        )
                        seed_query = self._merge_table_views(
                            source_views=(base_source,) if base_source is not None else (),
                            source_queries=(base_query,) if base_query is not None else (),
                            output_columns=output_columns,
                            key_columns=key_columns,
                        )
                    else:
                        seed_view_name = self._initial_seed_view_name(
                            stratum_id=stratum_id,
                            table_name=table_name,
                            schema_name=schema_name,
                        )
                        seed_query = self._empty_rowset_query_for_table(
                            analysis=analysis,
                            table_name=table_name,
                            output_columns=output_columns,
                        )
                    pre_operations.append(SQL2CreateViewOp(name=seed_view_name, query=seed_query))
                    split_table_base_source[table_name] = seed_view_name
                    split_table_accumulator[table_name] = seed_view_name
                recursive_loop = self._build_recursive_loop_for_stratum(
                    analysis=analysis,
                    stratum_id=stratum_id,
                    groups=tuple(groups),
                    recursive_table_order=tuple(recursive_table_order),
                    split_table_accumulator=split_table_accumulator,
                    materialized_tables=materialized_tables,
                    table_output_columns_by_name=table_output_columns_by_name,
                    table_key_columns_by_name=table_key_columns_by_name,
                    schema_name=schema_name,
                )
                if recursive_loop is not None:
                    semi_naive_loops.append(recursive_loop)
                    materialized_tables.update(table.table_name.rsplit(".", 1)[-1] for table in recursive_loop.tables)
                    for table in recursive_loop.tables:
                        table_name = table.table_name.rsplit(".", 1)[-1]
                        table_snapshot_query_by_name[table_name] = table.final_query
                deferred_tables_touched: set[str] = set()
                for group in deferred_groups:
                    deferred_tables_touched.add(group.table_name)
                    post_overrides: dict[str, str] = dict(split_table_accumulator)
                    for table_name in materialized_tables:
                        post_overrides.setdefault(
                            table_name,
                            self._public_view_name(table_name=table_name, schema_name=schema_name),
                        )
                    if table_counts[group.table_name] == 1:
                        self._table_source_overrides = post_overrides
                        query = self._lower_group_query(analysis, group)
                        view_name = self._public_view_name(table_name=group.table_name, schema_name=schema_name)
                        post_operations.append(SQL2CreateViewOp(name=view_name, query=query))
                        materialized_tables.add(group.table_name)
                        table_snapshot_query_by_name[group.table_name] = query
                        continue

                    output_columns = table_output_columns_by_name.get(group.table_name, ())
                    key_columns = table_key_columns_by_name.get(group.table_name, set())
                    split_table_output_columns[group.table_name] = output_columns
                    split_table_key_columns[group.table_name] = key_columns
                    split_table_state_ordinal.setdefault(group.table_name, 0)
                    split_table_state_ordinal[group.table_name] += 1
                    ordinal = split_table_state_ordinal[group.table_name]
                    state_view_name = self._state_view_name_for_group(
                        table_name=group.table_name,
                        stratum_id=stratum_id,
                        ordinal=ordinal,
                        schema_name=schema_name,
                    )
                    self._table_source_overrides = post_overrides
                    query = self._lower_group_query(analysis, group)
                    base_source = split_table_accumulator.get(group.table_name)
                    if base_source is None and group.table_name in materialized_tables:
                        base_source = self._public_view_name(
                            table_name=group.table_name,
                            schema_name=schema_name,
                        )
                    state_query = self._merge_table_views(
                        source_views=(base_source,) if base_source is not None else (),
                        source_queries=(query,),
                        output_columns=output_columns,
                        key_columns=key_columns,
                    )
                    post_operations.append(SQL2CreateViewOp(name=state_view_name, query=state_query))
                    split_table_accumulator[group.table_name] = state_view_name

                for table_name in sorted(deferred_tables_touched):
                    if table_name not in split_table_accumulator:
                        continue
                    output_columns = split_table_output_columns.get(table_name, ())
                    key_columns = split_table_key_columns.get(table_name, set())
                    public_view_name = self._public_view_name(table_name=table_name, schema_name=schema_name)
                    public_query = self._merge_table_views(
                        source_views=(split_table_accumulator[table_name],),
                        output_columns=output_columns,
                        key_columns=key_columns,
                    )
                    post_operations.append(
                        SQL2CreateViewOp(
                            name=public_view_name,
                            query=public_query,
                        )
                    )
                    materialized_tables.add(table_name)
                    table_snapshot_query_by_name[table_name] = public_query
                if pre_operations or semi_naive_loops:
                    strata.append(
                        SQL2StratumProgram(
                            stratum_id=stratum_id,
                            operations=tuple(pre_operations),
                            semi_naive_loops=tuple(semi_naive_loops),
                        )
                    )
                if post_operations:
                    strata.append(
                        SQL2StratumProgram(
                            stratum_id=stratum_id,
                            operations=tuple(post_operations),
                            semi_naive_loops=(),
                        )
                    )

            empty_table_ops: list[SQL2CreateViewOp] = []
            for table in sorted(
                analysis.model_analysis.table_info.values(),
                key=lambda info: (
                    analysis.table_shape.table_name_by_relation_id.get(info.relation.id, info.name),
                    info.relation.id,
                ),
            ):
                table_name = analysis.table_shape.table_name_by_relation_id.get(table.relation.id, table.name)
                if table_name in materialized_tables:
                    continue
                if table.kind not in {TableKind.CONCEPT, TableKind.RELATIONSHIP}:
                    continue
                column_names = tuple(column.name for column in table.columns)
                empty_query = self._empty_rowset_query_for_table(
                    analysis=analysis,
                    table_name=table_name,
                    output_columns=column_names,
                )
                empty_table_ops.append(
                    SQL2CreateViewOp(
                        name=self._public_view_name(table_name=table_name, schema_name=schema_name),
                        query=empty_query,
                    )
                )
                materialized_tables.add(table_name)
                table_snapshot_query_by_name[table_name] = empty_query
            if empty_table_ops:
                next_stratum_id = (max((stratum.stratum_id for stratum in strata), default=-1) + 1)
                strata.append(
                    SQL2StratumProgram(
                        stratum_id=next_stratum_id,
                        operations=tuple(empty_table_ops),
                        semi_naive_loops=(),
                    )
                )

            entity_empty_ops: list[SQL2CreateViewOp] = []
            for relation in sorted(analysis.model_analysis.model.types, key=lambda rel: (rel.name, rel.id)):
                if self._is_builtin_relation(relation):
                    continue
                if is_primitive(relation) or is_value_type(relation) or is_metatype(relation):
                    continue
                table_info = analysis.model_analysis.get_table_info(relation)
                if table_info is not None and table_info.kind in {
                    TableKind.EXTERNAL,
                    TableKind.DATA,
                    TableKind.OUTPUT,
                }:
                    continue
                table_name = analysis.table_shape.table_name_by_relation_id.get(relation.id, relation.name)
                if table_name in materialized_tables:
                    continue
                inherited_columns = self._inherited_empty_columns(analysis=analysis, relation=relation)
                if not inherited_columns:
                    continue
                empty_query = self._empty_rowset_query(inherited_columns)
                entity_empty_ops.append(
                    SQL2CreateViewOp(
                        name=self._public_view_name(table_name=table_name, schema_name=schema_name),
                        query=empty_query,
                    )
                )
                materialized_tables.add(table_name)
                table_snapshot_query_by_name[table_name] = empty_query
            if entity_empty_ops:
                next_stratum_id = (max((stratum.stratum_id for stratum in strata), default=-1) + 1)
                strata.append(
                    SQL2StratumProgram(
                        stratum_id=next_stratum_id,
                        operations=tuple(entity_empty_ops),
                        semi_naive_loops=(),
                    )
                )
            return SQL2ProgramIR(strata=tuple(strata))
        finally:
            self._source_schema_name = previous_source_schema
            self._table_source_overrides = previous_source_overrides

    def _lower_preseed_table_ops(
        self,
        analysis: SQL2Analysis,
        *,
        schema_name: str | None,
    ) -> list[SQL2CreateViewOp]:
        preseed_ops: list[SQL2CreateViewOp] = []
        for table_plan in analysis.normalized.preseed_tables:
            query = self._empty_rowset_query_for_table(
                analysis=analysis,
                table_name=table_plan.table_name,
                output_columns=table_plan.output_columns,
            )
            preseed_ops.append(
                SQL2CreateViewOp(
                    name=self._public_view_name(table_name=table_plan.table_name, schema_name=schema_name),
                    query=query,
                )
            )
        return preseed_ops

    def _is_builtin_relation(self, relation: Relation) -> bool:
        for library in builtin_registry._libraries.values():
            candidate = library.data.get(relation.name)
            if candidate is not None:
                return True
        return False

    def _lower_data_table_ops(self, analysis: SQL2Analysis, *, schema_name: str | None) -> list[SQL2CreateViewOp]:
        data_ops: list[SQL2CreateViewOp] = []
        table_infos = sorted(
            (table for table in analysis.model_analysis.table_info.values() if table.kind == TableKind.DATA),
            key=lambda table: (
                analysis.table_shape.table_name_by_relation_id.get(table.relation.id, table.name),
                table.relation.id,
            ),
        )
        for table in table_infos:
            if not isinstance(table.relation, Data):
                continue
            data_query = self._data_table_query(table.relation, column_names=tuple(column.name for column in table.columns))
            table_name = analysis.table_shape.table_name_by_relation_id.get(table.relation.id, table.name)
            view_name = self._public_view_name(table_name=table_name, schema_name=schema_name)
            data_ops.append(SQL2CreateViewOp(name=view_name, query=data_query))
        return data_ops

    def _data_table_query(self, relation: Data, *, column_names: tuple[str, ...]) -> SQL2Query:
        if not column_names:
            return SQL2SelectQuery(select_items=(SQL2SelectItem(expr=SQL2LiteralExpr(value=1), alias="empty"),))

        data_frame = relation.data.reset_index(drop=True)
        rows: list[SQL2SelectQuery] = []
        for row_ix, row_values in enumerate(data_frame.itertuples(index=False, name=None)):
            select_items: list[SQL2SelectItem] = []
            value_ix = 0
            for column_name in column_names:
                if column_name == "_id":
                    value = row_ix
                else:
                    value = row_values[value_ix] if value_ix < len(row_values) else None
                    value_ix += 1
                select_items.append(
                    SQL2SelectItem(
                        expr=SQL2LiteralExpr(value=self._normalize_data_literal(value)),
                        alias=column_name,
                    )
                )
            rows.append(SQL2SelectQuery(select_items=tuple(select_items)))

        if not rows:
            return SQL2SelectQuery(
                select_items=tuple(
                    SQL2SelectItem(expr=SQL2NullExpr(), alias=column_name)
                    for column_name in column_names
                ),
                where=(SQL2BinaryExpr(op="=", left=SQL2LiteralExpr(value=1), right=SQL2LiteralExpr(value=0)),),
            )
        if len(rows) == 1:
            return rows[0]
        return SQL2UnionQuery(parts=tuple(rows), distinct=False)

    def _inline_data_query_for_table_name(self, *, analysis: SQL2Analysis, table_name: str) -> SQL2Query | None:
        for table in analysis.model_analysis.table_info.values():
            if table.kind != TableKind.DATA:
                continue
            resolved_name = analysis.table_shape.table_name_by_relation_id.get(table.relation.id, table.name)
            if not isinstance(table.relation, Data):
                continue
            physical_name = self._physical_table_name_by_logical.get(resolved_name, resolved_name)
            candidate_names = {resolved_name, physical_name}
            if self._source_schema_name:
                candidate_names.add(f"{self._source_schema_name}.{physical_name}")
            if table_name not in candidate_names:
                continue
            return self._data_table_query(
                table.relation,
                column_names=tuple(column.name for column in table.columns),
            )
        return None

    def _normalize_data_literal(self, value):
        if value is None:
            return None
        if isinstance(value, float) and math.isnan(value):
            return None
        item_method = getattr(value, "item", None)
        if callable(item_method) and not isinstance(value, (bytes, bytearray, str)):
            try:
                value = item_method()
            except Exception:
                pass
        if type(value).__name__ == "NAType":
            return None
        return value

    @dataclass(frozen=True)
    class CoalescedProjectionSource:
        relation_ids: tuple[int, ...]
        query: SQL2SelectQuery
        key_field: str
        output_anchor_join_type: str
        var_columns: tuple[tuple[int, str], ...]

    @dataclass(frozen=True)
    class _LoweredChannelQuery:
        query: SQL2SelectQuery
        resolved_projection_var_ids: tuple[int, ...]

    def lower_coalesced_projection_sources(
        self,
        analysis: SQL2Analysis,
        *,
        projection_specs: tuple["SQL2FragmentProjectionSpec", ...],
        projection_assembly: "SQL2FragmentProjectionAssemblyPlan",
        source_schema_name: str | None = None,
        projection_var_ids: tuple[int, ...] = (),
        relation_task_var_ids_by_relation_id: dict[int, tuple[int, ...]] | None = None,
    ) -> tuple[CoalescedProjectionSource, ...]:
        relation_task_var_ids = relation_task_var_ids_by_relation_id or {}
        relation_id_set = {spec.relation_id for spec in projection_specs if spec.value_field_names}
        if not relation_id_set:
            return ()
        spec_by_relation_id = {spec.relation_id: spec for spec in projection_specs}
        previous_source_schema = self._source_schema_name
        if source_schema_name is not None:
            self._source_schema_name = source_schema_name
        relation_channel_queries: dict[int, list[SQL2Lowerer._LoweredChannelQuery]] = {
            relation_id: [] for relation_id in relation_id_set
        }
        try:
            for _stratum_id, groups in sorted(analysis.normalized.groups_by_stratum.items()):
                for group in groups:
                    for channel in group.channels:
                        relation_id = channel.target_relation_id
                        if relation_id not in relation_channel_queries:
                            continue
                        lowered_query = self._lower_channel_query_with_projection_meta(
                            analysis,
                            channel,
                            projection_var_ids=projection_var_ids,
                        )
                        relation_channel_queries[relation_id].append(lowered_query)
        finally:
            self._source_schema_name = previous_source_schema

        output: list[SQL2Lowerer.CoalescedProjectionSource] = []
        for source_group in projection_assembly.source_groups:
            present_relation_ids = tuple(
                relation_id
                for relation_id in source_group.relation_ids
                if relation_id in relation_channel_queries and relation_channel_queries[relation_id]
            )
            if not present_relation_ids:
                continue
            source_queries: list[SQL2SelectQuery] = []
            output_columns: list[str] = []
            seen_output_columns: set[str] = set()
            key_columns: set[str] = set()
            var_columns: dict[int, str] = {}
            hidden_var_ids: list[int] = []
            seen_hidden_var_ids: set[int] = set()
            hidden_var_presence_count: dict[int, int] = {}
            key_field: str | None = None
            for relation_id in present_relation_ids:
                spec = spec_by_relation_id.get(relation_id)
                if spec is None:
                    continue
                relation_queries = relation_channel_queries[relation_id]
                expected_var_ids = set(relation_task_var_ids.get(relation_id, ()))
                if not expected_var_ids:
                    expected_var_ids = set(projection_var_ids)
                for lowered_query in relation_queries:
                    source_queries.append(lowered_query.query)
                    for var_id in lowered_query.resolved_projection_var_ids:
                        if var_id not in expected_var_ids:
                            continue
                        hidden_var_presence_count[var_id] = hidden_var_presence_count.get(var_id, 0) + 1
                        if var_id in seen_hidden_var_ids:
                            continue
                        seen_hidden_var_ids.add(var_id)
                        hidden_var_ids.append(var_id)
                relation_aliases = {
                    item.alias
                    for lowered_query in relation_queries
                    for item in lowered_query.query.select_items
                    if item.alias is not None
                }
                preferred_key_field = spec.relation_key_field_name
                if preferred_key_field not in relation_aliases:
                    raise ValueError(
                        f"Projection relation {relation_id!r} missing expected key alias {preferred_key_field!r}."
                    )
                resolved_key_field = preferred_key_field
                if key_field is None:
                    key_field = resolved_key_field
                if key_field != resolved_key_field:
                    raise ValueError(
                        f"Projection source-group key mismatch in analyzer source group {source_group.group_id!r}."
                    )
                key_columns.add(key_field)
                if key_field not in seen_output_columns:
                    seen_output_columns.add(key_field)
                    output_columns.append(key_field)
                for _var_id, field_name, is_primary in spec.key_var_columns:
                    key_var_id = _var_id
                    key_alias = key_field if is_primary else field_name
                    if key_alias in relation_aliases:
                        var_columns.setdefault(key_var_id, key_alias)
                        key_columns.add(key_alias)
                        if key_alias not in seen_output_columns:
                            seen_output_columns.add(key_alias)
                            output_columns.append(key_alias)
                for value_field_name in spec.value_field_names:
                    if value_field_name in relation_aliases and value_field_name not in seen_output_columns:
                        seen_output_columns.add(value_field_name)
                        output_columns.append(value_field_name)
            if key_field is None or not source_queries:
                continue
            source_query_count = len(source_queries)
            for var_id in hidden_var_ids:
                alias = f"v_{var_id}"
                var_columns.setdefault(var_id, alias)
                if hidden_var_presence_count.get(var_id, 0) == source_query_count:
                    key_columns.add(alias)
                if alias not in seen_output_columns:
                    seen_output_columns.add(alias)
                    output_columns.append(alias)
            inferred_column_sql_types = self._infer_output_column_sql_types(
                source_queries=tuple(source_queries),
                output_columns=tuple(output_columns),
            )
            projected_queries = tuple(
                self._project_query_to_output_columns(
                    query=query,
                    output_columns=tuple(output_columns),
                    alias=f"_p{ix}",
                )
                for ix, query in enumerate(source_queries)
            )
            coalesced_query = self._merge_select_queries(
                source_queries=projected_queries,
                output_columns=tuple(output_columns),
                key_columns=key_columns,
                output_column_sql_types=inferred_column_sql_types,
            )
            assert isinstance(coalesced_query, SQL2SelectQuery)
            output.append(
                SQL2Lowerer.CoalescedProjectionSource(
                    relation_ids=present_relation_ids,
                    query=coalesced_query,
                    key_field=key_field,
                    output_anchor_join_type=source_group.output_anchor_join_type,
                    var_columns=tuple(sorted(var_columns.items())),
                )
            )
        return tuple(output)

    def lower_relation_query(
        self,
        analysis: SQL2Analysis,
        relation_id: int,
        *,
        source_schema_name: str | None = None,
        projection_var_ids: tuple[int, ...] = (),
    ) -> SQL2Query | None:
        previous_source_schema = self._source_schema_name
        if source_schema_name is not None:
            self._source_schema_name = source_schema_name
        try:
            channel_queries: list[SQL2SelectQuery] = []
            for _stratum_id, groups in sorted(analysis.normalized.groups_by_stratum.items()):
                for group in groups:
                    for channel in group.channels:
                        if channel.target_relation_id != relation_id:
                            continue
                        channel_queries.append(
                            self._lower_channel_query(
                                analysis,
                                channel,
                                projection_var_ids=projection_var_ids,
                            )
                        )
            if not channel_queries:
                return None
            if len(channel_queries) == 1:
                return channel_queries[0]
            return SQL2UnionQuery(parts=tuple(channel_queries), distinct=True)
        finally:
            self._source_schema_name = previous_source_schema

    def _lower_group_query(self, analysis: SQL2Analysis, group) -> SQL2Query:
        channel_query_by_id = {
            channel.channel_id: self._lower_channel_query(analysis, channel)
            for channel in group.channels
        }
        if not channel_query_by_id:
            return SQL2SelectQuery(select_items=(SQL2SelectItem(SQL2LiteralExpr(1), alias="empty"),))
        if len(channel_query_by_id) == 1:
            return next(iter(channel_query_by_id.values()))
        return self._merge_channel_queries(group=group, channel_query_by_id=channel_query_by_id)

    def _merge_channel_queries(self, *, group, channel_query_by_id: dict[int, SQL2SelectQuery]) -> SQL2Query:
        plan = group.channel_combine_plan
        grouped_queries = tuple(
            tuple(channel_query_by_id[channel_id] for channel_id in channel_ids)
            for channel_ids in plan.collapsed_channel_groups
        )
        if plan.strategy == "set_union":
            return SQL2UnionQuery(
                parts=tuple(query for query_group in grouped_queries for query in query_group),
                distinct=True,
            )
        collapsed_queries: tuple[SQL2SelectQuery, ...] = tuple(
            query_group[0] if len(query_group) == 1 else self._collapse_planned_channel_queries(
                query_group,
                output_columns=plan.output_columns,
            )
            for query_group in grouped_queries
        )
        collapsed_queries = self._collapse_additional_compatible_queries(
            collapsed_queries=collapsed_queries,
            output_columns=plan.output_columns,
        )
        return self._merge_select_queries(
            source_queries=collapsed_queries,
            output_columns=plan.output_columns,
            key_columns=set(plan.key_columns),
        )

    def _collapse_additional_compatible_queries(
        self,
        *,
        collapsed_queries: tuple[SQL2SelectQuery, ...],
        output_columns: tuple[str, ...],
    ) -> tuple[SQL2SelectQuery, ...]:
        if len(collapsed_queries) <= 1:
            return collapsed_queries

        # Conservative second-pass collapse: only for simple row-preserving selects.
        eligible = tuple(
            not query.distinct and not query.group_by and not query.having and not query.joins
            for query in collapsed_queries
        )
        if not any(eligible):
            return collapsed_queries

        assignments_by_ix = {
            ix: {
                item.alias: item.expr
                for item in query.select_items
                if item.alias is not None
            }
            for ix, query in enumerate(collapsed_queries)
        }
        used: set[int] = set()
        out: list[SQL2SelectQuery] = []
        for ix, query in enumerate(collapsed_queries):
            if ix in used:
                continue
            if not eligible[ix]:
                out.append(query)
                used.add(ix)
                continue
            cluster = [ix]
            used.add(ix)
            for jx in range(ix + 1, len(collapsed_queries)):
                if jx in used or not eligible[jx]:
                    continue
                other = collapsed_queries[jx]
                if (
                    other.from_item != query.from_item
                    or other.where != query.where
                ):
                    continue
                existing_assignments = [assignments_by_ix[kx] for kx in cluster]
                if not self._select_assignments_compatible(
                    existing=existing_assignments,
                    candidate=assignments_by_ix[jx],
                    output_columns=output_columns,
                ):
                    continue
                cluster.append(jx)
                used.add(jx)
            if len(cluster) == 1:
                out.append(query)
                continue
            out.append(
                self._collapse_planned_channel_queries(
                    tuple(collapsed_queries[kx] for kx in cluster),
                    output_columns=output_columns,
                )
            )
        return tuple(out)

    def _select_assignments_compatible(
        self,
        *,
        existing: list[dict[str, SQL2Expr]],
        candidate: dict[str, SQL2Expr],
        output_columns: tuple[str, ...],
    ) -> bool:
        for column in output_columns:
            existing_non_null: SQL2Expr | None = None
            for assignment in existing:
                expr = assignment.get(column, SQL2NullExpr())
                if self._expr_is_nullish(expr):
                    continue
                if existing_non_null is None:
                    existing_non_null = expr
                    continue
                if expr != existing_non_null:
                    return False
            candidate_expr = candidate.get(column, SQL2NullExpr())
            if self._expr_is_nullish(candidate_expr):
                continue
            if existing_non_null is not None and candidate_expr != existing_non_null:
                return False
        return True

    def _expr_is_nullish(self, expr: SQL2Expr) -> bool:
        return (
            isinstance(expr, SQL2NullExpr)
            or (isinstance(expr, SQL2LiteralExpr) and expr.value is None)
        )

    def _collapse_planned_channel_queries(
        self,
        queries: tuple[SQL2SelectQuery, ...],
        *,
        output_columns: tuple[str, ...],
    ) -> SQL2SelectQuery:
        if not queries:
            raise ValueError("SQL2 normalize emitted no channel queries to collapse.")
        if len(queries) == 1:
            return queries[0]
        base = queries[0]
        for query in queries[1:]:
            if (
                query.from_item != base.from_item
                or query.joins != base.joins
                or query.where != base.where
                or query.group_by != base.group_by
                or query.having != base.having
                or query.distinct != base.distinct
            ):
                raise ValueError("SQL2 normalize emitted incompatible channel collapse plan.")
        select_maps = tuple(
            {
                item.alias: item.expr
                for item in query.select_items
                if item.alias is not None
            }
            for query in queries
        )
        merged_items: list[SQL2SelectItem] = []
        for column in output_columns:
            non_null_exprs: list[SQL2Expr] = []
            for select_map in select_maps:
                expr = select_map.get(column, SQL2NullExpr())
                if isinstance(expr, SQL2NullExpr):
                    continue
                if expr not in non_null_exprs:
                    non_null_exprs.append(expr)
            if not non_null_exprs:
                expr = SQL2NullExpr()
            elif len(non_null_exprs) == 1:
                expr = non_null_exprs[0]
            else:
                expr = SQL2FunctionExpr(name="COALESCE", args=tuple(non_null_exprs))
            merged_items.append(SQL2SelectItem(expr=expr, alias=column))
        return SQL2SelectQuery(
            select_items=tuple(merged_items),
            from_item=base.from_item,
            joins=base.joins,
            where=base.where,
            group_by=base.group_by,
            having=base.having,
            distinct=base.distinct,
        )

    def _merge_table_views(
        self,
        *,
        source_views: tuple[str, ...],
        source_queries: tuple[SQL2Query, ...] = (),
        output_columns: tuple[str, ...],
        key_columns: set[str],
    ) -> SQL2Query:
        if not source_views and not source_queries:
            return self._empty_rowset_query(output_columns)
        view_queries: list[SQL2SelectQuery] = []
        alias_ix = 0
        for view_name in source_views:
            alias = f"_v{alias_ix}"
            alias_ix += 1
            select_items = tuple(
                SQL2SelectItem(
                    expr=SQL2ColumnExpr(table_alias=alias, column_name=column_name),
                    alias=column_name,
                )
                for column_name in output_columns
            )
            view_queries.append(
                SQL2SelectQuery(
                    select_items=select_items,
                    from_item=SQL2FromItem(source=SQL2TableSource(name=view_name), alias=alias),
                )
            )
        for query in source_queries:
            alias = f"_v{alias_ix}"
            alias_ix += 1
            projected = self._project_query_to_output_columns(
                query=query,
                output_columns=output_columns,
                alias=alias,
            )
            view_queries.append(projected)
        return self._merge_select_queries(
            source_queries=tuple(view_queries),
            output_columns=output_columns,
            key_columns=key_columns,
        )

    def _build_recursive_loop_for_stratum(
        self,
        analysis: SQL2Analysis,
        *,
        stratum_id: int,
        groups: tuple,
        recursive_table_order: tuple[str, ...],
        split_table_accumulator: dict[str, str],
        materialized_tables: set[str],
        table_output_columns_by_name: dict[str, tuple[str, ...]],
        table_key_columns_by_name: dict[str, set[str]],
        schema_name: str | None,
    ) -> SQL2SemiNaiveLoop | None:
        if not recursive_table_order:
            return None

        groups_by_table: dict[str, list] = {}
        recursive_node_ids = analysis.graph.recursive_node_ids
        for group in groups:
            groups_by_table.setdefault(group.table_name, []).append(group)

        marker_by_table = {
            table_name: self._recursive_marker_name(stratum_id=stratum_id, table_name=table_name)
            for table_name in recursive_table_order
        }
        accum_name_by_table = {
            table_name: self._recursive_temp_name(stratum_id=stratum_id, table_name=table_name, kind="accum")
            for table_name in recursive_table_order
        }
        delta_name_by_table = {
            table_name: self._recursive_temp_name(stratum_id=stratum_id, table_name=table_name, kind="delta")
            for table_name in recursive_table_order
        }
        next_delta_name_by_table = {
            table_name: self._recursive_temp_name(stratum_id=stratum_id, table_name=table_name, kind="delta_next")
            for table_name in recursive_table_order
        }
        stable_overrides = {
            table_name: self._public_view_name(table_name=table_name, schema_name=schema_name)
            for table_name in split_table_accumulator
        }

        loop_tables: list[SQL2SemiNaiveTable] = []
        for table_name in recursive_table_order:
            table_groups = groups_by_table.get(table_name, [])
            if not table_groups:
                continue
            output_columns = table_output_columns_by_name.get(table_name, ())
            key_columns = table_key_columns_by_name.get(table_name, set())

            non_recursive_groups = [
                group
                for group in table_groups
                if not any(node_id in recursive_node_ids for node_id in group.node_ids)
            ]
            recursive_groups = [
                group
                for group in table_groups
                if any(node_id in recursive_node_ids for node_id in group.node_ids)
            ]

            base_source = (
                self._public_view_name(table_name=table_name, schema_name=schema_name)
                if table_name in materialized_tables
                else None
            )
            base_queries: list[SQL2Query] = []
            for group in non_recursive_groups:
                overrides = dict(stable_overrides)
                for recursive_table in recursive_table_order:
                    if recursive_table in materialized_tables:
                        overrides[recursive_table] = self._public_view_name(
                            table_name=recursive_table,
                            schema_name=schema_name,
                        )
                self._table_source_overrides = overrides
                base_queries.append(self._lower_group_query(analysis, group))

            if base_source is None and not base_queries:
                base_query = self._empty_rowset_query_for_table(
                    analysis=analysis,
                    table_name=table_name,
                    output_columns=output_columns,
                )
            else:
                base_query = self._merge_table_views(
                    source_views=(base_source,) if base_source is not None else (),
                    source_queries=tuple(base_queries),
                    output_columns=output_columns,
                    key_columns=key_columns,
                )

            recursive_queries: list[SQL2SelectQuery] = []
            for group in recursive_groups:
                overrides = dict(stable_overrides)
                overrides.update(marker_by_table)
                self._table_source_overrides = overrides
                recursive_query = self._lower_group_query(analysis, group)
                projected = self._project_query_to_output_columns(
                    query=recursive_query,
                    output_columns=output_columns,
                    alias="_r0",
                )
                recursive_queries.append(projected)

            delta_variants: list[SQL2SelectQuery] = []
            for recursive_query in recursive_queries:
                delta_variants.extend(
                    self._build_semi_naive_delta_variants(
                        query=recursive_query,
                        marker_by_table=marker_by_table,
                        accum_name_by_table=accum_name_by_table,
                        delta_name_by_table=delta_name_by_table,
                    )
                )
            delta_query: SQL2Query
            if delta_variants:
                delta_query = self._merge_table_views(
                    source_views=(),
                    source_queries=tuple(delta_variants),
                    output_columns=output_columns,
                    key_columns=key_columns,
                )
            else:
                delta_query = self._empty_rowset_query_for_table(
                    analysis=analysis,
                    table_name=table_name,
                    output_columns=output_columns,
                )

            merge_query = self._merge_table_views(
                source_views=(accum_name_by_table[table_name], next_delta_name_by_table[table_name]),
                output_columns=output_columns,
                key_columns=key_columns,
            )
            final_query = self._merge_table_views(
                source_views=(accum_name_by_table[table_name],),
                output_columns=output_columns,
                key_columns=key_columns,
            )

            loop_tables.append(
                SQL2SemiNaiveTable(
                    table_name=self._public_view_name(table_name=table_name, schema_name=schema_name),
                    columns=output_columns,
                    base_query=base_query,
                    delta_query=delta_query,
                    merge_query=merge_query,
                    final_query=final_query,
                    accum_view_name=accum_name_by_table[table_name],
                    delta_view_name=delta_name_by_table[table_name],
                    next_delta_view_name=next_delta_name_by_table[table_name],
                )
            )
        return SQL2SemiNaiveLoop(tables=tuple(loop_tables)) if loop_tables else None

    def _build_semi_naive_delta_variants(
        self,
        *,
        query: SQL2SelectQuery,
        marker_by_table: dict[str, str],
        accum_name_by_table: dict[str, str],
        delta_name_by_table: dict[str, str],
    ) -> list[SQL2SelectQuery]:
        marker_to_table = {marker: table_name for table_name, marker in marker_by_table.items()}
        marker_names = set(marker_to_table)
        marker_ref_count = self._count_recursive_marker_refs(query, marker_names=marker_names)
        if marker_ref_count == 0:
            return [query]

        variants: list[SQL2SelectQuery] = []
        for delta_ref_ix in range(marker_ref_count):
            rewritten = self._rewrite_recursive_marker_refs(
                query=query,
                marker_to_table=marker_to_table,
                accum_name_by_table=accum_name_by_table,
                delta_name_by_table=delta_name_by_table,
                delta_ref_ix=delta_ref_ix,
            )
            variants.append(rewritten)
        return variants

    def _count_recursive_marker_refs(self, query: SQL2Query, *, marker_names: set[str]) -> int:
        def count_from_item(from_item: SQL2FromItem | None) -> int:
            if from_item is None:
                return 0
            source = from_item.source
            if isinstance(source, SQL2TableSource):
                return 1 if source.name in marker_names else 0
            if isinstance(source, SQL2SubquerySource):
                return count_query(source.query)
            return 0

        def count_expr(expr: SQL2Expr) -> int:
            if isinstance(expr, SQL2BinaryExpr):
                return count_expr(expr.left) + count_expr(expr.right)
            if isinstance(expr, SQL2FunctionExpr):
                return sum(count_expr(arg) for arg in expr.args)
            if isinstance(expr, SQL2WindowExpr):
                return (
                    sum(count_expr(arg) for arg in expr.args)
                    + sum(count_expr(arg) for arg in expr.partition_by)
                    + sum(count_expr(item.expr) for item in expr.order_by)
                )
            if isinstance(expr, SQL2CastExpr):
                return count_expr(expr.expr)
            if isinstance(expr, SQL2ExistsExpr):
                return count_query(expr.query)
            return 0

        def count_query(inner: SQL2Query) -> int:
            if isinstance(inner, SQL2UnionQuery):
                return sum(count_query(part) for part in inner.parts)
            if isinstance(inner, SQL2RecursiveQuery):
                count = 0
                if inner.base_query is not None:
                    count += count_query(inner.base_query)
                if inner.recursive_query is not None:
                    count += count_query(inner.recursive_query)
                if inner.final_query is not None:
                    count += count_query(inner.final_query)
                return count
            assert isinstance(inner, SQL2SelectQuery)
            count = count_from_item(inner.from_item)
            count += sum(count_from_item(join.source) for join in inner.joins)
            count += sum(count_expr(item.expr) for item in inner.select_items)
            count += sum(count_expr(predicate) for predicate in inner.where)
            count += sum(count_expr(expr) for expr in inner.group_by)
            count += sum(count_expr(predicate) for predicate in inner.having)
            return count

        return count_query(query)

    def _rewrite_recursive_marker_refs(
        self,
        *,
        query: SQL2SelectQuery,
        marker_to_table: dict[str, str],
        accum_name_by_table: dict[str, str],
        delta_name_by_table: dict[str, str],
        delta_ref_ix: int,
    ) -> SQL2SelectQuery:
        marker_names = set(marker_to_table)
        seen_ref_ix = 0

        def rewrite_from_item(from_item: SQL2FromItem | None) -> SQL2FromItem | None:
            nonlocal seen_ref_ix
            if from_item is None:
                return None
            source = from_item.source
            if isinstance(source, SQL2TableSource):
                if source.name in marker_names:
                    table_name = marker_to_table[source.name]
                    replacement = (
                        delta_name_by_table[table_name]
                        if seen_ref_ix == delta_ref_ix
                        else accum_name_by_table[table_name]
                    )
                    seen_ref_ix += 1
                    source = SQL2TableSource(name=replacement)
                return SQL2FromItem(source=source, alias=from_item.alias, lateral=from_item.lateral)
            if isinstance(source, SQL2SubquerySource):
                return SQL2FromItem(
                    source=SQL2SubquerySource(query=rewrite_query(source.query)),
                    alias=from_item.alias,
                    lateral=from_item.lateral,
                )
            return from_item

        def rewrite_expr(expr: SQL2Expr) -> SQL2Expr:
            if isinstance(expr, SQL2BinaryExpr):
                return SQL2BinaryExpr(op=expr.op, left=rewrite_expr(expr.left), right=rewrite_expr(expr.right))
            if isinstance(expr, SQL2FunctionExpr):
                return SQL2FunctionExpr(
                    name=expr.name,
                    args=tuple(rewrite_expr(arg) for arg in expr.args),
                    distinct=expr.distinct,
                )
            if isinstance(expr, SQL2WindowExpr):
                return SQL2WindowExpr(
                    name=expr.name,
                    args=tuple(rewrite_expr(arg) for arg in expr.args),
                    partition_by=tuple(rewrite_expr(arg) for arg in expr.partition_by),
                    order_by=tuple(
                        SQL2OrderByExpr(
                            expr=rewrite_expr(item.expr),
                            descending=item.descending,
                        )
                        for item in expr.order_by
                    ),
                    distinct=expr.distinct,
                )
            if isinstance(expr, SQL2CastExpr):
                return SQL2CastExpr(expr=rewrite_expr(expr.expr), target_type=expr.target_type)
            if isinstance(expr, SQL2ExistsExpr):
                return SQL2ExistsExpr(query=rewrite_query(expr.query), negated=expr.negated)
            return expr

        def rewrite_query(inner: SQL2Query) -> SQL2Query:
            if isinstance(inner, SQL2UnionQuery):
                return SQL2UnionQuery(parts=tuple(rewrite_query(part) for part in inner.parts), distinct=inner.distinct)
            if isinstance(inner, SQL2RecursiveQuery):
                return SQL2RecursiveQuery(
                    cte_name=inner.cte_name,
                    column_names=inner.column_names,
                    base_query=rewrite_query(inner.base_query) if inner.base_query is not None else None,
                    recursive_query=rewrite_query(inner.recursive_query) if inner.recursive_query is not None else None,
                    final_query=cast(SQL2SelectQuery, rewrite_query(inner.final_query)) if inner.final_query is not None else None,
                    distinct=inner.distinct,
                )
            assert isinstance(inner, SQL2SelectQuery)
            return SQL2SelectQuery(
                select_items=tuple(SQL2SelectItem(expr=rewrite_expr(item.expr), alias=item.alias) for item in inner.select_items),
                from_item=rewrite_from_item(inner.from_item),
                joins=tuple(
                    SQL2Join(
                        source=cast(SQL2FromItem, rewrite_from_item(join.source)),
                        join_type=join.join_type,
                        on=tuple(rewrite_expr(predicate) for predicate in join.on),
                    )
                    for join in inner.joins
                ),
                where=tuple(rewrite_expr(predicate) for predicate in inner.where),
                group_by=tuple(rewrite_expr(expr) for expr in inner.group_by),
                having=tuple(rewrite_expr(predicate) for predicate in inner.having),
                distinct=inner.distinct,
            )

        rewritten = rewrite_query(query)
        assert isinstance(rewritten, SQL2SelectQuery)
        return rewritten

    def _recursive_marker_name(self, *, stratum_id: int, table_name: str) -> str:
        safe = self._safe_table_token(table_name)
        return f"sql2_rec_marker_s{stratum_id}_{safe}"

    def _recursive_temp_name(self, *, stratum_id: int, table_name: str, kind: str) -> str:
        safe = self._safe_table_token(table_name)
        return f"sql2_rec_s{stratum_id}_{safe}_{kind}"

    def _empty_rowset_query(
        self,
        output_columns: tuple[str, ...],
        *,
        cast_types: dict[str, str] | None = None,
    ) -> SQL2SelectQuery:
        select_items = []
        for column_name in output_columns:
            expr: SQL2Expr = SQL2NullExpr()
            target_type = cast_types.get(column_name) if cast_types is not None else None
            if target_type is not None:
                expr = SQL2CastExpr(expr=expr, target_type=target_type)
            select_items.append(SQL2SelectItem(expr=expr, alias=column_name))
        return SQL2SelectQuery(
            select_items=tuple(select_items),
            where=(SQL2BinaryExpr(op="=", left=SQL2LiteralExpr(value=1), right=SQL2LiteralExpr(value=0)),),
        )

    def _empty_rowset_query_for_table(
        self,
        *,
        analysis: SQL2Analysis,
        table_name: str,
        output_columns: tuple[str, ...],
    ) -> SQL2SelectQuery:
        return self._empty_rowset_query(
            output_columns,
            cast_types=self._empty_rowset_cast_types_for_table(
                analysis=analysis,
                table_name=table_name,
                output_columns=output_columns,
            ),
        )

    def _empty_rowset_cast_types_for_table(
        self,
        *,
        analysis: SQL2Analysis,
        table_name: str,
        output_columns: tuple[str, ...],
    ) -> dict[str, str]:
        table_shape = next((shape for shape in analysis.table_shape.tables if shape.table_name == table_name), None)
        if table_shape is None:
            return {column_name: "VARCHAR" for column_name in output_columns if column_name == "_id"}

        relation_by_id = {
            relation.id: relation
            for relation in [*analysis.model_analysis.model.types, *analysis.model_analysis.model.relations]
        }
        column_shape_by_name = {column.column_name: column for column in table_shape.columns}
        cast_types: dict[str, str] = {}
        for column_name in output_columns:
            column_shape = column_shape_by_name.get(column_name)
            if column_shape is None:
                if column_name == "_id":
                    cast_types[column_name] = "VARCHAR"
                continue
            if column_shape.is_id:
                cast_types[column_name] = "VARCHAR"
                continue
            relation = relation_by_id.get(column_shape.source_relation_id)
            if relation is None:
                continue
            source_type = None
            if column_shape.source_field_index is not None and 0 <= column_shape.source_field_index < len(relation.fields):
                source_type = relation.fields[column_shape.source_field_index].type
            sql_type = self._sql_type_for_type(source_type)
            if sql_type is not None:
                cast_types[column_name] = sql_type
        return cast_types

    def _project_query_to_output_columns(
        self,
        *,
        query: SQL2Query,
        output_columns: tuple[str, ...],
        alias: str,
    ) -> SQL2SelectQuery:
        if isinstance(query, SQL2SelectQuery):
            select_aliases = tuple(item.alias for item in query.select_items)
            if select_aliases == output_columns:
                return query
            select_alias_set = {item.alias for item in query.select_items if item.alias is not None}
        else:
            select_alias_set = set(output_columns)
        select_items = tuple(
            SQL2SelectItem(
                expr=(
                    SQL2ColumnExpr(table_alias=alias, column_name=column_name)
                    if column_name in select_alias_set
                    else SQL2NullExpr()
                ),
                alias=column_name,
            )
            for column_name in output_columns
        )
        return SQL2SelectQuery(
            select_items=select_items,
            from_item=SQL2FromItem(source=SQL2SubquerySource(query=query), alias=alias),
        )

    def _merge_select_queries(
        self,
        *,
        source_queries: tuple[SQL2SelectQuery, ...],
        output_columns: tuple[str, ...],
        key_columns: set[str],
        output_column_sql_types: dict[str, str] | None = None,
    ) -> SQL2Query:
        if not source_queries:
            return SQL2SelectQuery(select_items=(SQL2SelectItem(SQL2LiteralExpr(1), alias="empty"),))
        if len(source_queries) == 1:
            return source_queries[0]
        if not output_columns or not key_columns:
            return SQL2UnionQuery(parts=source_queries, distinct=True)

        union_query = SQL2UnionQuery(parts=source_queries, distinct=False)
        from_item = SQL2FromItem(source=SQL2SubquerySource(query=union_query), alias="_u")
        group_by: list[SQL2Expr] = []
        select_items: list[SQL2SelectItem] = []
        for column_name in output_columns:
            col_expr = SQL2ColumnExpr(table_alias="_u", column_name=column_name)
            if column_name in key_columns:
                group_by.append(col_expr)
                select_items.append(SQL2SelectItem(expr=col_expr, alias=column_name))
            else:
                merged_expr: SQL2Expr = SQL2FunctionExpr(name="MAX", args=(col_expr,))
                target_sql_type = output_column_sql_types.get(column_name) if output_column_sql_types is not None else None
                if target_sql_type is not None:
                    inferred_type = self._expr_sql_type(merged_expr)
                    if inferred_type is None or not self._same_sql_type(inferred_type, target_sql_type):
                        merged_expr = SQL2CastExpr(expr=merged_expr, target_type=target_sql_type)
                select_items.append(SQL2SelectItem(expr=merged_expr, alias=column_name))
        return SQL2SelectQuery(
            select_items=tuple(select_items),
            from_item=from_item,
            group_by=tuple(group_by),
        )

    def _infer_output_column_sql_types(
        self,
        *,
        source_queries: tuple[SQL2SelectQuery, ...],
        output_columns: tuple[str, ...],
    ) -> dict[str, str]:
        inferred: dict[str, str] = {}
        ambiguous: set[str] = set()
        for query in source_queries:
            select_expr_by_alias = {
                item.alias: item.expr
                for item in query.select_items
                if item.alias is not None
            }
            for column_name in output_columns:
                if column_name in ambiguous:
                    continue
                expr = select_expr_by_alias.get(column_name)
                if expr is None or self._expr_is_nullish(expr):
                    continue
                sql_type = self._expr_sql_type(expr)
                if sql_type is None:
                    continue
                previous = inferred.get(column_name)
                if previous is None:
                    inferred[column_name] = sql_type
                    continue
                if not self._same_sql_type(previous, sql_type):
                    inferred.pop(column_name, None)
                    ambiguous.add(column_name)
        return inferred

    def _lower_channel_query(
        self,
        analysis: SQL2Analysis,
        channel,
        *,
        projection_var_ids: tuple[int, ...] = (),
    ) -> SQL2SelectQuery:
        return self._lower_channel_query_with_projection_meta(
            analysis,
            channel,
            projection_var_ids=projection_var_ids,
        ).query

    def _lower_channel_query_with_projection_meta(
        self,
        analysis: SQL2Analysis,
        channel,
        *,
        projection_var_ids: tuple[int, ...] = (),
    ) -> _LoweredChannelQuery:
        update_task = channel.update_task
        body_rule = channel.body_rule

        ctx = _RuleCtx()
        ctx.optional_projection_var_ids_by_task_id = dict(channel.optional_projection_var_ids_by_task_id)
        ctx.branch_projection_var_ids_by_task_id = dict(channel.branch_projection_var_ids_by_task_id)
        ctx.union_mode_by_task_id = dict(channel.union_mode_by_task_id)
        ctx.future_var_ids_by_task_id = dict(channel.future_var_ids_by_task_id)
        self._lower_task_into_ctx(analysis, body_rule, ctx)

        table_shape = analysis.table_shape.table_for_relation_id(channel.target_relation_id)
        if table_shape is None:
            output_columns = self._dedupe_column_names(tuple(channel.output_columns))
        else:
            output_columns = self._dedupe_column_names(tuple(column.column_name for column in table_shape.columns))
        select_items = list(
            SQL2SelectItem(
                expr=self._column_expr_for_update(
                    analysis=analysis,
                    update=update_task,
                    target_column=column_name,
                    ctx=ctx,
                ),
                alias=column_name,
            )
            for column_name in output_columns
        )
        resolved_projection_var_ids: list[int] = []
        if projection_var_ids:
            selected_aliases = {item.alias for item in select_items if item.alias is not None}
            for var_id in projection_var_ids:
                alias = f"v_{var_id}"
                if alias in selected_aliases:
                    continue
                resolved = ctx.var_map.get(var_id)
                if resolved is None:
                    continue
                select_items.append(SQL2SelectItem(expr=resolved, alias=alias))
                selected_aliases.add(alias)
                resolved_projection_var_ids.append(var_id)

        from_item = ctx.from_item
        return self._LoweredChannelQuery(
            query=SQL2SelectQuery(
                select_items=tuple(select_items),
                from_item=from_item,
                joins=tuple(ctx.joins),
                where=tuple(ctx.where),
            ),
            resolved_projection_var_ids=tuple(resolved_projection_var_ids),
        )

    def _dedupe_column_names(self, columns: tuple[str, ...]) -> tuple[str, ...]:
        return tuple(dict.fromkeys(columns))

    def _lower_task_into_ctx(
        self,
        analysis: SQL2Analysis,
        task: Task,
        ctx: _RuleCtx,
        *,
        future_var_ids: set[int] | None = None,
    ) -> None:
        if isinstance(task, Lookup):
            self._lower_lookup_into_ctx(analysis, task, ctx)
            return
        if isinstance(task, Union):
            self._lower_union_into_ctx(analysis, task, ctx)
            return
        if isinstance(task, Not):
            subquery = self._compile_exists_subquery(analysis, task.task, ctx)
            exists_expr = SQL2ExistsExpr(query=subquery, negated=True)
            ctx.where.append(exists_expr)
            ctx.used_aliases.update(self._aliases_in_expr(exists_expr))
            return
        if isinstance(task, Exists):
            subquery = self._compile_exists_subquery(analysis, task.task, ctx)
            exists_expr = SQL2ExistsExpr(query=subquery, negated=False)
            ctx.where.append(exists_expr)
            ctx.used_aliases.update(self._aliases_in_expr(exists_expr))
            return
        if isinstance(task, Aggregate):
            self._lower_aggregate_into_ctx(
                analysis,
                task,
                ctx,
                future_var_ids=future_var_ids,
            )
            return
        if isinstance(task, Logical):
            if task.optional:
                self._lower_optional_logical_into_ctx(analysis, task, ctx)
                return
            body = tuple(task.body)
            for child in body:
                child_future_var_ids = set(ctx.future_var_ids_by_task_id.get(child.id, ()))
                if future_var_ids is not None:
                    child_future_var_ids.update(future_var_ids)
                if isinstance(child, Lookup):
                    non_key_var_ids = self._lookup_non_key_var_ids(analysis=analysis, lookup=child)
                    require_non_null_outputs = bool(non_key_var_ids) and non_key_var_ids.isdisjoint(
                        child_future_var_ids
                    )
                    self._lower_lookup_into_ctx(
                        analysis,
                        child,
                        ctx,
                        require_non_null_outputs=require_non_null_outputs,
                    )
                    continue
                self._lower_task_into_ctx(
                    analysis,
                    child,
                    ctx,
                    future_var_ids=child_future_var_ids,
                )
            return
        if isinstance(task, Construct):
            self._bind_or_predicate(
                output_value=task.id_var,
                expr=self._construct_id_expr(task.values, ctx.var_map),
                ctx=ctx,
            )
            return
        # Ignore unsupported no-op task categories for now in the phase-F scaffold.

    def _lookup_non_key_var_ids(self, *, analysis: SQL2Analysis, lookup: Lookup) -> set[int]:
        table_shape = analysis.table_shape.table_for_relation_id(lookup.relation.id)
        key_columns = set(table_shape.key_column_names) if table_shape is not None else set()
        out: set[int] = set()
        for ix, arg in enumerate(lookup.args):
            if ix >= len(lookup.relation.fields):
                continue
            if not isinstance(arg, Var):
                continue
            field = lookup.relation.fields[ix]
            column_name = self._lookup_column_name(
                analysis,
                lookup.relation,
                field_index=ix,
                fallback_name=field.name,
            )
            if column_name in key_columns:
                continue
            out.add(arg.id)
        return out

    def _lower_optional_logical_into_ctx(self, analysis: SQL2Analysis, logical: Logical, ctx: _RuleCtx) -> None:
        if ctx.from_item is None:
            for child in logical.body:
                self._lower_task_into_ctx(analysis, child, ctx)
            return

        base_var_map = dict(ctx.var_map)
        sub_ctx = _RuleCtx(
            var_map=dict(base_var_map),
            lookup_alias_by_signature=dict(ctx.lookup_alias_by_signature),
            alias_table_name_by_alias=dict(ctx.alias_table_name_by_alias),
            optional_projection_var_ids_by_task_id=dict(ctx.optional_projection_var_ids_by_task_id),
            branch_projection_var_ids_by_task_id=dict(ctx.branch_projection_var_ids_by_task_id),
            union_mode_by_task_id=dict(ctx.union_mode_by_task_id),
            future_var_ids_by_task_id=dict(ctx.future_var_ids_by_task_id),
            bound_union_output_var_ids_by_branch_signature=dict(ctx.bound_union_output_var_ids_by_branch_signature),
            seed_union_scope_from_bound_aliases=ctx.seed_union_scope_from_bound_aliases,
            used_aliases=self._child_ctx_used_aliases(ctx),
        )
        for child in logical.body:
            self._lower_task_into_ctx(analysis, child, sub_ctx)

        if ctx.from_item is not None:
            outer_aliases = self._ctx_source_aliases(ctx)
            required_outer_aliases = self._required_outer_aliases_for_ctx(sub_ctx=sub_ctx, outer_aliases=outer_aliases)
            if required_outer_aliases:
                self._attach_required_outer_scope_to_subctx(
                    outer_ctx=ctx,
                    sub_ctx=sub_ctx,
                    required_outer_aliases=required_outer_aliases,
                )

        if logical.id not in ctx.optional_projection_var_ids_by_task_id:
            raise ValueError(
                f"Missing normalize optional projection plan for logical task id {logical.id}"
            )
        produced_var_ids = list(ctx.optional_projection_var_ids_by_task_id[logical.id])
        if not produced_var_ids:
            return

        select_items = tuple(
            SQL2SelectItem(expr=sub_ctx.var_map.get(var_id, SQL2NullExpr()), alias=f"v_{var_id}")
            for var_id in produced_var_ids
        )
        from_item = sub_ctx.from_item
        if from_item is None:
            seed_alias = sub_ctx.new_alias("seed")
            from_item = SQL2FromItem(
                source=SQL2SubquerySource(
                    query=SQL2SelectQuery(select_items=(SQL2SelectItem(SQL2LiteralExpr(1), alias="seed"),))
                ),
                alias=seed_alias,
            )
        optional_query = SQL2SelectQuery(
            select_items=select_items,
            from_item=from_item,
            joins=tuple(sub_ctx.joins),
            where=tuple(sub_ctx.where),
        )

        optional_alias = ctx.new_alias("opt")
        outer_aliases = self._ctx_source_aliases(ctx)
        use_lateral = self._query_references_aliases(optional_query, outer_aliases)
        ctx.joins.append(
            SQL2Join(
                source=SQL2FromItem(
                    source=SQL2SubquerySource(query=optional_query),
                    alias=optional_alias,
                    lateral=use_lateral,
                ),
                join_type="LEFT",
                on=(),
            )
        )
        for var_id in produced_var_ids:
            ctx.var_map[var_id] = SQL2ColumnExpr(table_alias=optional_alias, column_name=f"v_{var_id}")

    def _lower_union_into_ctx(self, analysis: SQL2Analysis, union_task: Union, ctx: _RuleCtx) -> None:
        if union_task.id not in ctx.union_mode_by_task_id:
            raise ValueError(
                f"Missing normalize union mode plan for union task id {union_task.id}"
            )
        union_mode = ctx.union_mode_by_task_id[union_task.id]
        if ctx.seed_union_scope_from_bound_aliases:
            self._seed_ctx_sources_from_bound_aliases(ctx)
        branch_signature = self._union_branch_signature(union_task)
        if (
            union_mode == "filter"
            and self._is_redundant_filter_union(
                branch_signature=branch_signature,
                ctx=ctx,
            )
        ):
            return
        branch_contexts: list[tuple[Task, _RuleCtx]] = []
        for branch in union_task.tasks:
            branch_ctx = _RuleCtx(
                var_map=dict(ctx.var_map),
                lookup_alias_by_signature=dict(ctx.lookup_alias_by_signature),
                alias_table_name_by_alias=dict(ctx.alias_table_name_by_alias),
                optional_projection_var_ids_by_task_id=dict(ctx.optional_projection_var_ids_by_task_id),
                branch_projection_var_ids_by_task_id=dict(ctx.branch_projection_var_ids_by_task_id),
                union_mode_by_task_id=dict(ctx.union_mode_by_task_id),
                future_var_ids_by_task_id=dict(ctx.future_var_ids_by_task_id),
                bound_union_output_var_ids_by_branch_signature=dict(ctx.bound_union_output_var_ids_by_branch_signature),
                seed_union_scope_from_bound_aliases=ctx.seed_union_scope_from_bound_aliases,
                used_aliases=self._child_ctx_used_aliases(ctx),
            )
            self._lower_task_into_ctx(analysis, branch, branch_ctx)
            branch_contexts.append((branch, branch_ctx))

        if not branch_contexts:
            return

        if union_mode == "filter":
            self._append_union_exists_disjunction(branch_contexts=branch_contexts, ctx=ctx)
            return
        if union_mode not in {"bind_required", "bind_optional"}:
            raise ValueError(
                f"Unsupported union mode '{union_mode}' for union task id {union_task.id}"
            )

        explicit_output_var_ids = tuple(
            sorted(
                value.id
                for value in union_task.outputs
                if isinstance(value, Var)
            )
        )
        if explicit_output_var_ids:
            ctx.bound_union_output_var_ids_by_branch_signature[branch_signature] = explicit_output_var_ids

        if union_task.id not in ctx.branch_projection_var_ids_by_task_id:
            raise ValueError(
                f"Missing normalize branch projection plan for union task id {union_task.id}"
            )
        planned_projection_var_ids = tuple(ctx.branch_projection_var_ids_by_task_id[union_task.id])
        required_projection_var_ids = {
            var_id
            for var_id in planned_projection_var_ids
            if var_id not in ctx.var_map
        }
        all_var_ids: set[int] = set(ctx.var_map)
        all_var_ids.update(planned_projection_var_ids)
        all_var_ids.update(explicit_output_var_ids)
        ordered_var_ids = tuple(sorted(all_var_ids))
        if not ordered_var_ids:
            self._append_union_exists_disjunction(branch_contexts=branch_contexts, ctx=ctx)
            return
        branch_queries: list[SQL2SelectQuery] = []
        outer_aliases = self._ctx_source_aliases(ctx)
        for branch_ix, (_branch_task, branch_ctx) in enumerate(branch_contexts):
            if ctx.from_item is not None:
                required_outer_aliases = self._required_outer_aliases_for_ctx(
                    sub_ctx=branch_ctx,
                    outer_aliases=outer_aliases,
                )
                if required_outer_aliases:
                    self._attach_required_outer_scope_to_subctx(
                        outer_ctx=ctx,
                        sub_ctx=branch_ctx,
                        required_outer_aliases=required_outer_aliases,
                    )
            for output_var_id in explicit_output_var_ids:
                if output_var_id not in branch_ctx.var_map:
                    raise ValueError(
                        "Union branch missing output binding for output var id "
                        f"{output_var_id} at branch index {branch_ix}"
                    )
            for projection_var_id in required_projection_var_ids:
                if projection_var_id not in branch_ctx.var_map:
                    raise ValueError(
                        "Union branch missing projection binding for carried var id "
                        f"{projection_var_id} at branch index {branch_ix}"
                    )
            from_item = branch_ctx.from_item
            if from_item is None:
                seed_alias = branch_ctx.new_alias("seed")
                from_item = SQL2FromItem(
                    source=SQL2SubquerySource(
                        query=SQL2SelectQuery(select_items=(SQL2SelectItem(SQL2LiteralExpr(1), alias="seed"),))
                    ),
                    alias=seed_alias,
                )
            select_items = tuple(
                SQL2SelectItem(
                    expr=branch_ctx.var_map.get(var_id, SQL2NullExpr()),
                    alias=f"v_{var_id}",
                )
                for var_id in ordered_var_ids
            )
            branch_queries.append(
                SQL2SelectQuery(
                    select_items=select_items,
                    from_item=from_item,
                    joins=tuple(branch_ctx.joins),
                    where=tuple(branch_ctx.where),
                )
            )

        union_query = SQL2UnionQuery(parts=tuple(branch_queries), distinct=True)
        use_lateral = ctx.from_item is not None and self._query_references_aliases(union_query, outer_aliases)
        union_alias = ctx.new_alias("union")
        union_from = SQL2FromItem(
            source=SQL2SubquerySource(query=union_query),
            alias=union_alias,
            lateral=use_lateral,
        )

        prior_var_map = dict(ctx.var_map)
        if ctx.from_item is None:
            ctx.from_item = SQL2FromItem(
                source=union_from.source,
                alias=union_alias,
                lateral=False,
            )
        else:
            shared_var_ids = sorted(set(ctx.var_map).intersection(all_var_ids))
            on_predicates = tuple(
                SQL2BinaryExpr(
                    op="=",
                    left=ctx.var_map[var_id],
                    right=SQL2ColumnExpr(table_alias=union_alias, column_name=f"v_{var_id}"),
                )
                for var_id in shared_var_ids
            )
            join_type = "INNER" if union_mode == "bind_required" else "LEFT"
            ctx.joins.append(SQL2Join(source=union_from, join_type=join_type, on=on_predicates))

        available_aliases = self._ctx_source_aliases(ctx)
        for var_id in ordered_var_ids:
            prior_expr = prior_var_map.get(var_id)
            if prior_expr is not None:
                prior_aliases = self._aliases_in_expr(prior_expr)
                if prior_aliases.issubset(available_aliases):
                    ctx.var_map[var_id] = prior_expr
                    continue
            ctx.var_map[var_id] = SQL2ColumnExpr(table_alias=union_alias, column_name=f"v_{var_id}")

    def _seed_ctx_sources_from_bound_aliases(self, ctx: _RuleCtx) -> None:
        if ctx.from_item is not None:
            return
        required_aliases = sorted(self._aliases_in_var_map(ctx.var_map))
        table_aliases = [alias for alias in required_aliases if alias in ctx.alias_table_name_by_alias]
        if not table_aliases:
            return

        first_alias = table_aliases[0]
        first_table_name = ctx.alias_table_name_by_alias[first_alias]
        ctx.from_item = SQL2FromItem(
            source=SQL2TableSource(name=first_table_name),
            alias=first_alias,
        )
        for alias in table_aliases[1:]:
            table_name = ctx.alias_table_name_by_alias[alias]
            ctx.joins.append(
                SQL2Join(
                    source=SQL2FromItem(
                        source=SQL2TableSource(name=table_name),
                        alias=alias,
                    ),
                    join_type="INNER",
                    on=(),
                )
            )

    def _is_redundant_filter_union(
        self,
        *,
        branch_signature: tuple[tuple[object, ...], ...],
        ctx: _RuleCtx,
    ) -> bool:
        required_output_var_ids = ctx.bound_union_output_var_ids_by_branch_signature.get(branch_signature)
        if not required_output_var_ids:
            return False
        return all(var_id in ctx.var_map for var_id in required_output_var_ids)

    def _union_branch_signature(self, union_task: Union) -> tuple[tuple[object, ...], ...]:
        return tuple(
            self._task_semantic_signature(branch, drop_construct=True)
            for branch in union_task.tasks
        )

    def _task_semantic_signature(self, task: Task, *, drop_construct: bool) -> tuple[object, ...]:
        if isinstance(task, Logical):
            children = tuple(
                self._task_semantic_signature(child, drop_construct=drop_construct)
                for child in task.body
                if not (drop_construct and isinstance(child, Construct))
            )
            return ("Logical", task.optional, children)
        if isinstance(task, Lookup):
            return (
                "Lookup",
                task.relation.id,
                tuple(self._value_semantic_signature(arg) for arg in task.args),
            )
        if isinstance(task, Construct):
            return (
                "Construct",
                tuple(self._value_semantic_signature(value) for value in task.values),
            )
        if isinstance(task, Union):
            return (
                "Union",
                tuple(self._task_semantic_signature(child, drop_construct=drop_construct) for child in task.tasks),
            )
        if isinstance(task, Exists):
            return ("Exists", self._task_semantic_signature(task.task, drop_construct=drop_construct))
        if isinstance(task, Not):
            return ("Not", self._task_semantic_signature(task.task, drop_construct=drop_construct))
        if isinstance(task, Aggregate):
            return ("Aggregate", task.aggregation.name, self._task_semantic_signature(task.body, drop_construct=drop_construct))
        return (type(task).__name__, task.id)

    def _value_semantic_signature(self, value: Value) -> tuple[object, ...]:
        if isinstance(value, Var):
            return ("Var", value.id)
        if isinstance(value, Literal):
            return ("Literal", value.value)
        return (type(value).__name__, repr(value))

    def _required_outer_aliases_for_ctx(self, *, sub_ctx: _RuleCtx, outer_aliases: set[str]) -> set[str]:
        if not outer_aliases:
            return set()
        required_aliases: set[str] = set()
        for expr in sub_ctx.var_map.values():
            required_aliases.update(self._aliases_in_expr(expr))
        for predicate in sub_ctx.where:
            required_aliases.update(self._aliases_in_expr(predicate))
        for join in sub_ctx.joins:
            for predicate in join.on:
                required_aliases.update(self._aliases_in_expr(predicate))
            if isinstance(join.source.source, SQL2SubquerySource):
                required_aliases.update(self._aliases_in_query(join.source.source.query))
        if sub_ctx.from_item is not None and isinstance(sub_ctx.from_item.source, SQL2SubquerySource):
            required_aliases.update(self._aliases_in_query(sub_ctx.from_item.source.query))
        local_aliases = self._ctx_source_aliases(sub_ctx)
        return required_aliases.intersection(outer_aliases).difference(local_aliases)

    def _attach_required_outer_scope_to_subctx(
        self,
        *,
        outer_ctx: _RuleCtx,
        sub_ctx: _RuleCtx,
        required_outer_aliases: set[str],
    ) -> None:
        if not required_outer_aliases:
            return
        outer_aliases = self._ctx_source_aliases(outer_ctx)
        local_aliases = self._ctx_source_aliases(sub_ctx)

        def attach_source(source_item: SQL2FromItem, *, join_type: str = "INNER", on: tuple[SQL2Expr, ...] = ()) -> None:
            nonlocal local_aliases
            if source_item.alias in local_aliases:
                return
            cloned = SQL2FromItem(source=source_item.source, alias=source_item.alias, lateral=False)
            if sub_ctx.from_item is None:
                sub_ctx.from_item = cloned
                if on:
                    sub_ctx.where.extend(on)
            else:
                sub_ctx.joins.append(SQL2Join(source=cloned, join_type=join_type, on=on))
            local_aliases.add(source_item.alias)

        if outer_ctx.from_item is not None and outer_ctx.from_item.alias in required_outer_aliases:
            attach_source(outer_ctx.from_item)
        for join in outer_ctx.joins:
            if join.source.alias not in required_outer_aliases:
                continue
            allowed_aliases = set(local_aliases)
            allowed_aliases.add(join.source.alias)
            filtered_on = tuple(
                predicate
                for predicate in join.on
                if self._aliases_in_expr(predicate).issubset(allowed_aliases)
            )
            attach_source(join.source, join_type=join.join_type, on=filtered_on)

        inherited_predicates = [
            predicate
            for predicate in outer_ctx.where
            if self._aliases_in_expr(predicate).intersection(outer_aliases).issubset(required_outer_aliases)
        ]
        for predicate in inherited_predicates:
            if predicate not in sub_ctx.where:
                sub_ctx.where.insert(0, predicate)

    def _append_union_exists_disjunction(self, *, branch_contexts: list[tuple[Task, _RuleCtx]], ctx: _RuleCtx) -> None:
        disjunction: SQL2Expr | None = None
        for branch_task, branch_ctx in branch_contexts:
            referenced_var_ids = task_var_ids(branch_task).intersection(ctx.var_map)
            correlation_predicates: tuple[SQL2Expr, ...] = tuple(
                SQL2BinaryExpr(op="=", left=outer_expr, right=inner_expr)
                for var_id, outer_expr in sorted(ctx.var_map.items())
                if var_id in referenced_var_ids
                if (inner_expr := branch_ctx.var_map.get(var_id)) is not None and inner_expr != outer_expr
            )
            exists_query = SQL2SelectQuery(
                select_items=(SQL2SelectItem(expr=SQL2LiteralExpr(1), alias="exists_flag"),),
                from_item=branch_ctx.from_item,
                joins=tuple(branch_ctx.joins),
                where=tuple(branch_ctx.where) + correlation_predicates,
            )
            exists_expr: SQL2Expr = SQL2ExistsExpr(query=exists_query, negated=False)
            if disjunction is None:
                disjunction = exists_expr
            else:
                disjunction = SQL2BinaryExpr(op="OR", left=disjunction, right=exists_expr)
        if disjunction is not None:
            ctx.where.append(disjunction)

    def _lower_lookup_into_ctx(
        self,
        analysis: SQL2Analysis,
        lookup: Lookup,
        ctx: _RuleCtx,
        *,
        require_non_null_outputs: bool = False,
    ) -> None:
        relation = lookup.relation
        if relation.fields and all(field.input for field in relation.fields):
            if self._is_builtin_lookup(analysis=analysis, lookup=lookup):
                self._lower_builtin_lookup(lookup=lookup, ctx=ctx)
            return

        if self._is_builtin_lookup(analysis=analysis, lookup=lookup):
            self._lower_builtin_lookup(lookup=lookup, ctx=ctx)
            return

        table_name = self._table_name_for_relation(analysis, relation)
        table_shape = analysis.table_shape.table_for_relation_id(relation.id)
        key_columns = set(table_shape.key_column_names) if table_shape is not None else set()
        unresolved_var_columns: list[tuple[int, int, str]] = []
        predicate_specs: list[tuple[int, str, SQL2Expr]] = []
        for ix, (rel_field, arg) in enumerate(zip(relation.fields, lookup.args)):
            column_name = self._lookup_column_name(analysis, relation, field_index=ix, fallback_name=rel_field.name)
            resolved = self._resolve_value(arg, ctx.var_map)
            if resolved is not None:
                predicate_specs.append((ix, column_name, resolved))
            elif isinstance(arg, Var):
                unresolved_var_columns.append((arg.id, ix, column_name))

        signature_predicate_specs = tuple((column_name, expr) for _ix, column_name, expr in predicate_specs)

        signature = self._lookup_reuse_signature(
            analysis=analysis,
            relation=relation,
            table_name=table_name,
            predicate_specs=signature_predicate_specs,
        )
        key_reuse_alias = self._lookup_alias_from_key_column_bindings(
            analysis=analysis,
            relation=relation,
            table_name=table_name,
            predicate_specs=signature_predicate_specs,
            ctx=ctx,
        )
        if key_reuse_alias is not None:
            for var_id, field_index, column_name in unresolved_var_columns:
                ctx.var_map[var_id] = self._lookup_field_expr(
                    analysis=analysis,
                    relation=relation,
                    field_index=field_index,
                    column_name=column_name,
                    table_alias=key_reuse_alias,
                )
            if require_non_null_outputs:
                for _var_id, field_index, column_name in unresolved_var_columns:
                    if column_name in key_columns:
                        continue
                    ctx.where.append(
                        SQL2BinaryExpr(
                            op="IS NOT",
                            left=self._lookup_field_expr(
                                analysis=analysis,
                                relation=relation,
                                field_index=field_index,
                                column_name=column_name,
                                table_alias=key_reuse_alias,
                            ),
                            right=SQL2NullExpr(),
                        )
                    )
            if signature is not None:
                ctx.lookup_alias_by_signature[signature] = key_reuse_alias
            return
        if signature is not None:
            reused_alias = ctx.lookup_alias_by_signature.get(signature)
            if reused_alias is not None:
                for var_id, field_index, column_name in unresolved_var_columns:
                    ctx.var_map[var_id] = self._lookup_field_expr(
                        analysis=analysis,
                        relation=relation,
                        field_index=field_index,
                        column_name=column_name,
                        table_alias=reused_alias,
                    )
                if require_non_null_outputs:
                    for _var_id, field_index, column_name in unresolved_var_columns:
                        if column_name in key_columns:
                            continue
                        ctx.where.append(
                            SQL2BinaryExpr(
                                op="IS NOT",
                                left=self._lookup_field_expr(
                                    analysis=analysis,
                                    relation=relation,
                                    field_index=field_index,
                                    column_name=column_name,
                                    table_alias=reused_alias,
                                ),
                                right=SQL2NullExpr(),
                            )
                        )
                return

        alias = ctx.new_alias(relation.name or "rel")
        inline_data_query = self._inline_data_query_for_table_name(
            analysis=analysis,
            table_name=table_name,
        )
        if inline_data_query is not None:
            source = SQL2SubquerySource(query=inline_data_query)
        else:
            source = SQL2TableSource(name=table_name)
        from_item = SQL2FromItem(source=source, alias=alias)
        predicates = [
            SQL2BinaryExpr(
                op="=",
                left=self._lookup_field_expr(
                    analysis=analysis,
                    relation=relation,
                    field_index=field_index,
                    column_name=column_name,
                    table_alias=alias,
                ),
                right=resolved,
            )
            for field_index, column_name, resolved in predicate_specs
        ]
        if require_non_null_outputs:
            for _var_id, field_index, column_name in unresolved_var_columns:
                if column_name in key_columns:
                    continue
                predicates.append(
                    SQL2BinaryExpr(
                        op="IS NOT",
                        left=self._lookup_field_expr(
                            analysis=analysis,
                            relation=relation,
                            field_index=field_index,
                            column_name=column_name,
                            table_alias=alias,
                        ),
                        right=SQL2NullExpr(),
                    )
                )
        for var_id, field_index, column_name in unresolved_var_columns:
            ctx.var_map[var_id] = self._lookup_field_expr(
                analysis=analysis,
                relation=relation,
                field_index=field_index,
                column_name=column_name,
                table_alias=alias,
            )
        if ctx.from_item is None:
            ctx.from_item = from_item
            ctx.where.extend(predicates)
        else:
            ctx.joins.append(SQL2Join(source=from_item, join_type="INNER", on=tuple(predicates)))
        ctx.alias_table_name_by_alias[alias] = table_name
        if signature is not None:
            ctx.lookup_alias_by_signature[signature] = alias

    def _lookup_reuse_signature(
        self,
        *,
        analysis: SQL2Analysis,
        relation: Relation,
        table_name: str,
        predicate_specs: tuple[tuple[str, SQL2Expr], ...],
    ) -> tuple[str, tuple[tuple[str, SQL2Expr], ...]] | None:
        table_shape = analysis.table_shape.table_for_relation_id(relation.id)
        if table_shape is None:
            return None
        key_columns = tuple(table_shape.key_column_names)
        if not key_columns:
            return None
        bound_columns = {column_name for column_name, _ in predicate_specs}
        if not all(column_name in bound_columns for column_name in key_columns):
            return None
        return (table_name, predicate_specs)

    def _lookup_alias_from_key_column_bindings(
        self,
        *,
        analysis: SQL2Analysis,
        relation: Relation,
        table_name: str,
        predicate_specs: tuple[tuple[str, SQL2Expr], ...],
        ctx: _RuleCtx,
    ) -> str | None:
        table_shape = analysis.table_shape.table_for_relation_id(relation.id)
        if table_shape is None:
            return None
        key_columns = tuple(table_shape.key_column_names)
        if not key_columns:
            return None
        key_column_set = set(key_columns)
        if not predicate_specs:
            return None

        key_bindings: dict[str, SQL2Expr] = {
            column_name: expr
            for column_name, expr in predicate_specs
            if column_name in key_column_set
        }
        if not all(column_name in key_bindings for column_name in key_columns):
            return None

        for candidate_alias, candidate_table_name in ctx.alias_table_name_by_alias.items():
            if candidate_table_name != table_name:
                continue
            # Key bindings must match exactly what this relation would read from
            # the candidate alias (including synthetic external `_id` expressions).
            key_match = True
            for column_name in key_columns:
                expected_expr = self._lookup_expr_for_column_binding(
                    analysis=analysis,
                    relation=relation,
                    column_name=column_name,
                    table_alias=candidate_alias,
                )
                if expected_expr is None or key_bindings[column_name] != expected_expr:
                    key_match = False
                    break
            if not key_match:
                continue

            # Allow non-key predicates only when they are tautological bindings
            # against the same alias/column expression.
            non_key_match = True
            for column_name, expr in predicate_specs:
                if column_name in key_column_set:
                    continue
                expected_expr = self._lookup_expr_for_column_binding(
                    analysis=analysis,
                    relation=relation,
                    column_name=column_name,
                    table_alias=candidate_alias,
                )
                if expected_expr is None or expr != expected_expr:
                    non_key_match = False
                    break
            if non_key_match:
                return candidate_alias
        return None

    def _is_builtin_lookup(self, *, analysis: SQL2Analysis, lookup: Lookup) -> bool:
        relation = lookup.relation
        forced_builtin_names = {
            "=",
            "!=",
            "<",
            "<=",
            ">",
            ">=",
            "+",
            "-",
            "*",
            "/",
            "%",
            "cast",
            "concat",
            "join",
            "range",
            "split",
        }
        if relation.name in forced_builtin_names:
            return True
        if not relation.fields or not any(field.input for field in relation.fields):
            return False
        return analysis.table_shape.table_for_relation_id(relation.id) is None

    def _lower_builtin_lookup(self, *, lookup: Lookup, ctx: _RuleCtx) -> None:
        relation = lookup.relation
        args = list(lookup.args)
        input_positions = [ix for ix, field in enumerate(relation.fields) if field.input]
        output_positions = [ix for ix, field in enumerate(relation.fields) if not field.input]

        if relation.name in {"range", "split"}:
            self._lower_builtin_table_lookup(relation=relation, args=args, output_positions=output_positions, ctx=ctx)
            return

        if relation.name == "=" and len(args) == 2:
            self._bind_or_predicate(output_value=args[0], expr=self._resolve_value(args[1], ctx.var_map), ctx=ctx)
            self._bind_or_predicate(output_value=args[1], expr=self._resolve_value(args[0], ctx.var_map), ctx=ctx)
            left = self._resolve_or_literal(args[0], ctx.var_map)
            right = self._resolve_or_literal(args[1], ctx.var_map)
            if left is not None and right is not None:
                ctx.where.append(SQL2BinaryExpr(op="=", left=left, right=right))
            return

        if relation.name in {"!=", "<", "<=", ">", ">="} and len(args) == 2:
            left = self._resolve_or_literal(args[0], ctx.var_map)
            right = self._resolve_or_literal(args[1], ctx.var_map)
            if left is not None and right is not None:
                ctx.where.append(SQL2BinaryExpr(op=relation.name, left=left, right=right))
            return

        if relation.name in {"+", "-", "*", "/", "%"} and len(args) == 3:
            left = self._resolve_or_literal(args[0], ctx.var_map) or SQL2NullExpr()
            right = self._resolve_or_literal(args[1], ctx.var_map) or SQL2NullExpr()
            if relation.name == "*":
                if isinstance(left, SQL2LiteralExpr) and isinstance(left.value, int):
                    left = SQL2CastExpr(expr=left, target_type="DECIMAL(38,0)")
                if isinstance(right, SQL2LiteralExpr) and isinstance(right.value, int):
                    right = SQL2CastExpr(expr=right, target_type="DECIMAL(38,0)")
            output_expr: SQL2Expr = SQL2BinaryExpr(op=relation.name, left=left, right=right)
            output_expr = self._coerce_builtin_output_expr(output_value=args[2], expr=output_expr)
            self._bind_or_predicate(
                output_value=args[2],
                expr=output_expr,
                ctx=ctx,
            )
            return

        if relation.name == "cast" and output_positions:
            out_pos = self._choose_builtin_output_position(args=args, output_positions=output_positions, ctx=ctx)
            out_value = args[out_pos]
            cast_value_pos = 1 if len(args) > 1 else 0
            cast_value_expr = self._resolve_or_literal(args[cast_value_pos], ctx.var_map)
            cast_type_sql = self._sql_type_for_value(out_value)
            if cast_value_expr is not None and cast_type_sql is not None:
                self._bind_or_predicate(
                    output_value=out_value,
                    expr=SQL2CastExpr(expr=cast_value_expr, target_type=cast_type_sql),
                    ctx=ctx,
                )
            elif cast_value_expr is not None:
                # Entity/meta casts are type-level constraints with identity
                # runtime representation in SQL backends.
                self._bind_or_predicate(
                    output_value=out_value,
                    expr=cast_value_expr,
                    ctx=ctx,
                )
            return

        if relation.name == "concat" and output_positions:
            out_pos = self._choose_builtin_output_position(args=args, output_positions=output_positions, ctx=ctx)
            in_exprs = tuple(
                self._resolve_or_literal(arg, ctx.var_map) or SQL2NullExpr()
                for ix, arg in enumerate(args)
                if ix != out_pos
            )
            self._bind_or_predicate(
                output_value=args[out_pos],
                expr=SQL2FunctionExpr(name="CONCAT", args=in_exprs),
                ctx=ctx,
            )
            return

        if relation.name == "join" and output_positions and len(args) >= 2:
            out_pos = self._choose_builtin_output_position(args=args, output_positions=output_positions, ctx=ctx)
            parts_arg = args[0]
            sep_expr = self._resolve_or_literal(args[1], ctx.var_map) or SQL2LiteralExpr("")
            if isinstance(parts_arg, (tuple, list)):
                piece_exprs: list[SQL2Expr] = []
                for part in parts_arg:
                    part_expr = self._resolve_or_literal(part, ctx.var_map) or SQL2NullExpr()
                    piece_exprs.append(
                        SQL2FunctionExpr(
                            name="COALESCE",
                            args=(SQL2CastExpr(expr=part_expr, target_type="VARCHAR"), SQL2LiteralExpr("")),
                        )
                    )
                if not piece_exprs:
                    join_expr: SQL2Expr = SQL2LiteralExpr("")
                else:
                    join_expr = piece_exprs[0]
                    for piece in piece_exprs[1:]:
                        join_expr = SQL2BinaryExpr(
                            op="||",
                            left=SQL2BinaryExpr(op="||", left=join_expr, right=sep_expr),
                            right=piece,
                        )
                join_expr = self._coerce_builtin_output_expr(output_value=args[out_pos], expr=join_expr)
                self._bind_or_predicate(output_value=args[out_pos], expr=join_expr, ctx=ctx)
                return

        if output_positions:
            out_pos = self._choose_builtin_output_position(args=args, output_positions=output_positions, ctx=ctx)
            in_exprs = tuple(
                self._resolve_or_literal(arg, ctx.var_map) or SQL2NullExpr()
                for ix, arg in enumerate(args)
                if ix != out_pos
            )
            output_expr: SQL2Expr
            if relation.name in {"parse_number", "parse_decimal"} and len(in_exprs) == 1:
                target_type = self._sql_type_for_value(args[out_pos])
                decimal_shape = self._decimal_precision_scale(target_type)
                if decimal_shape is not None:
                    precision, scale = decimal_shape
                    output_expr = SQL2FunctionExpr(
                        name=relation.name.upper(),
                        args=(
                            in_exprs[0],
                            SQL2LiteralExpr(precision),
                            SQL2LiteralExpr(scale),
                        ),
                    )
                else:
                    output_expr = SQL2FunctionExpr(name=relation.name.upper(), args=in_exprs)
            else:
                output_expr = SQL2FunctionExpr(name=relation.name.upper(), args=in_exprs)
            output_expr = self._coerce_builtin_output_expr(
                output_value=args[out_pos],
                expr=output_expr,
            )
            self._bind_or_predicate(
                output_value=args[out_pos],
                expr=output_expr,
                ctx=ctx,
            )
            return

        if input_positions:
            in_exprs = tuple(
                self._resolve_or_literal(args[ix], ctx.var_map) or SQL2NullExpr()
                for ix in input_positions
            )
        else:
            in_exprs = tuple(
                self._resolve_or_literal(arg, ctx.var_map) or SQL2NullExpr()
                for arg in args
            )
        ctx.where.append(SQL2FunctionExpr(name=relation.name.upper(), args=in_exprs))

    def _choose_builtin_output_position(
        self,
        *,
        args: list[Value],
        output_positions: list[int],
        ctx: _RuleCtx,
    ) -> int:
        for out_pos in output_positions:
            if out_pos >= len(args):
                continue
            out_value = args[out_pos]
            if isinstance(out_value, Var) and self._resolve_value(out_value, ctx.var_map) is None:
                return out_pos
        for out_pos in output_positions:
            if out_pos < len(args):
                return out_pos
        return output_positions[0]

    def _lower_builtin_table_lookup(
        self,
        *,
        relation: Relation,
        args: list[Value],
        output_positions: list[int],
        ctx: _RuleCtx,
    ) -> None:
        input_positions = [ix for ix, field in enumerate(relation.fields) if field.input]
        input_exprs = tuple(
            self._resolve_or_literal(args[ix], ctx.var_map) or SQL2NullExpr()
            for ix in input_positions
            if ix < len(args)
        )

        table_query: SQL2SelectQuery | None = None
        if relation.name == "range" and len(input_exprs) == 3:
            range_expr = SQL2FunctionExpr(
                name="RANGE",
                args=(
                    SQL2CastExpr(expr=input_exprs[0], target_type="BIGINT"),
                    SQL2CastExpr(expr=input_exprs[1], target_type="BIGINT"),
                    SQL2CastExpr(expr=input_exprs[2], target_type="BIGINT"),
                ),
            )
            table_query = SQL2SelectQuery(
                select_items=(
                    SQL2SelectItem(
                        expr=SQL2FunctionExpr(name="UNNEST", args=(range_expr,)),
                        alias=relation.fields[output_positions[0]].name if output_positions else "value",
                    ),
                ),
            )
        elif relation.name == "split" and len(input_exprs) == 2:
            split_list = SQL2FunctionExpr(name="STRING_SPLIT", args=input_exprs)
            idx_name = relation.fields[output_positions[0]].name if output_positions else "index"
            result_name = relation.fields[output_positions[1]].name if len(output_positions) > 1 else "result"
            idx_alias = "split_idx"
            idx_query = SQL2SelectQuery(
                select_items=(
                    SQL2SelectItem(
                        expr=SQL2FunctionExpr(
                            name="UNNEST",
                            args=(
                                SQL2FunctionExpr(
                                    name="RANGE",
                                    args=(
                                        SQL2CastExpr(expr=SQL2LiteralExpr(0), target_type="BIGINT"),
                                        SQL2CastExpr(
                                            expr=SQL2FunctionExpr(name="ARRAY_LENGTH", args=(split_list,)),
                                            target_type="BIGINT",
                                        ),
                                        SQL2CastExpr(expr=SQL2LiteralExpr(1), target_type="BIGINT"),
                                    ),
                                ),
                            ),
                        ),
                        alias=idx_name,
                    ),
                ),
            )
            table_query = SQL2SelectQuery(
                select_items=(
                    SQL2SelectItem(expr=SQL2ColumnExpr(table_alias=idx_alias, column_name=idx_name), alias=idx_name),
                    SQL2SelectItem(
                        expr=SQL2FunctionExpr(
                            name="SPLIT_PART",
                            args=(
                                input_exprs[0],
                                input_exprs[1],
                                SQL2ColumnExpr(table_alias=idx_alias, column_name=idx_name),
                            ),
                        ),
                        alias=result_name,
                    ),
                ),
                from_item=SQL2FromItem(source=SQL2SubquerySource(query=idx_query), alias=idx_alias),
            )
        if table_query is None:
            return

        alias = ctx.new_alias(relation.name or "tablefn")
        from_item = SQL2FromItem(
            source=SQL2SubquerySource(query=table_query),
            alias=alias,
            lateral=ctx.from_item is not None,
        )
        predicates: list[SQL2Expr] = []
        for out_pos in output_positions:
            if out_pos >= len(args):
                continue
            out_arg = args[out_pos]
            column_name = relation.fields[out_pos].name
            column_expr = SQL2ColumnExpr(table_alias=alias, column_name=column_name)
            resolved = self._resolve_value(out_arg, ctx.var_map)
            if resolved is None and isinstance(out_arg, Var):
                self._bind_or_predicate(output_value=out_arg, expr=column_expr, ctx=ctx)
                continue
            if resolved is not None:
                predicates.append(SQL2BinaryExpr(op="=", left=column_expr, right=resolved))

        if ctx.from_item is None:
            ctx.from_item = SQL2FromItem(source=from_item.source, alias=from_item.alias, lateral=False)
            ctx.where.extend(predicates)
        else:
            ctx.joins.append(SQL2Join(source=from_item, join_type="INNER", on=tuple(predicates)))

    def _bind_or_predicate(self, *, output_value: Value, expr: SQL2Expr | None, ctx: _RuleCtx) -> None:
        if expr is None:
            return
        if isinstance(output_value, Var):
            existing = ctx.var_map.get(output_value.id)
            if existing is None:
                ctx.var_map[output_value.id] = expr
            else:
                if existing == expr:
                    return
                ctx.where.append(SQL2BinaryExpr(op="=", left=existing, right=expr))
            return
        literal_expr = self._resolve_value(output_value, ctx.var_map)
        if literal_expr is not None:
            if literal_expr == expr:
                return
            ctx.where.append(SQL2BinaryExpr(op="=", left=literal_expr, right=expr))

    def _coerce_builtin_output_expr(self, *, output_value: Value, expr: SQL2Expr) -> SQL2Expr:
        target_type = self._sql_type_for_value(output_value)
        if target_type is None:
            return expr
        inferred_type = self._expr_sql_type(expr)
        if inferred_type is not None and self._same_sql_type(inferred_type, target_type):
            return expr
        if isinstance(expr, SQL2CastExpr) and self._same_sql_type(expr.target_type, target_type):
            return expr
        return SQL2CastExpr(expr=expr, target_type=target_type)

    def _resolve_or_literal(self, value: Value, var_map: dict[int, SQL2Expr]) -> SQL2Expr | None:
        resolved = self._resolve_value(value, var_map)
        if resolved is not None:
            return resolved
        if isinstance(value, Var):
            return None
        return SQL2NullExpr()

    def _sql_type_for_value(self, value: Value) -> str | None:
        value_type = value.type if isinstance(value, Var) else getattr(value, "type", None)
        if value_type is None and isinstance(value, Relation):
            value_type = value
        return self._sql_type_for_type(value_type)

    def _decimal_precision_scale(self, sql_type: str | None) -> tuple[int, int] | None:
        if sql_type is None:
            return None
        match = re.fullmatch(r"DECIMAL\(\s*(\d+)\s*,\s*(\d+)\s*\)", sql_type.upper())
        if match is None:
            return None
        return int(match.group(1)), int(match.group(2))

    def _normalize_sql_type(self, sql_type: str) -> str:
        return normalize_sql_type(sql_type)

    def _same_sql_type(self, left: str, right: str) -> bool:
        return same_sql_type(left, right)

    def _expr_sql_type(self, expr: SQL2Expr) -> str | None:
        if isinstance(expr, SQL2CastExpr):
            return self._normalize_sql_type(expr.target_type)
        if isinstance(expr, SQL2LiteralExpr):
            value = expr.value
            if value is None:
                return None
            if isinstance(value, bool):
                return "BOOLEAN"
            if isinstance(value, int):
                return None
            if isinstance(value, float):
                return None
            if isinstance(value, str):
                return "VARCHAR"
            if isinstance(value, dt.datetime):
                return "TIMESTAMP"
            if isinstance(value, dt.date):
                return "DATE"
            return None
        if isinstance(expr, SQL2FunctionExpr):
            arg_types = tuple(self._expr_sql_type(arg) for arg in expr.args)
            return infer_function_return_sql_type(expr.name, arg_types=arg_types)
        if isinstance(expr, SQL2WindowExpr):
            return infer_function_return_sql_type(expr.name)
        if isinstance(expr, SQL2BinaryExpr):
            op = expr.op.upper()
            if op in {"=", "!=", "<", "<=", ">", ">=", "IS", "IS NOT"}:
                return "BOOLEAN"
            if op == "||":
                return "VARCHAR"
            if op in {"+", "-", "*", "/", "%"}:
                left_type = self._expr_sql_type(expr.left)
                right_type = self._expr_sql_type(expr.right)
                if left_type == "DOUBLE" or right_type == "DOUBLE":
                    return "DOUBLE"
            return None
        return None

    def _sql_type_for_type(self, value_type: object) -> str | None:
        def _collect_type_names(t: object) -> set[str]:
            names: set[str] = set()
            stack: list[object] = [t]
            while stack:
                current = stack.pop()
                if current is None:
                    continue
                name = getattr(current, "name", "")
                if isinstance(name, str) and name:
                    lowered = name.lower()
                    if lowered not in names:
                        names.add(lowered)
                for super_type in getattr(current, "super_types", ()) or ():
                    stack.append(super_type)
            return names

        def _find_number_super_type(t: object) -> NumberType | None:
            seen: set[int] = set()
            stack: list[object] = [t]
            while stack:
                current = stack.pop()
                if current is None:
                    continue
                current_id = id(current)
                if current_id in seen:
                    continue
                seen.add(current_id)
                if isinstance(current, NumberType):
                    return current
                for super_type in getattr(current, "super_types", ()) or ():
                    stack.append(super_type)
            return None

        if isinstance(value_type, NumberType):
            return f"DECIMAL({value_type.precision},{value_type.scale})"
        if isinstance(value_type, TypeNode) and is_entity_type(value_type):
            return "VARCHAR"
        type_names = _collect_type_names(value_type) if value_type is not None else set()
        int_type_names = {
            "int",
            "integer",
            "int8",
            "int16",
            "int32",
            "int64",
            "int128",
            "bigint",
            "smallint",
            "tinyint",
            "uint8",
            "uint16",
            "uint32",
            "uint64",
        }
        if type_names.intersection(int_type_names):
            return "DECIMAL(38,0)"
        if any(("string" in name) or ("uuid" in name) for name in type_names):
            return "VARCHAR"
        if type_names.intersection({"boolean", "bool"}):
            return "BOOLEAN"
        if type_names.intersection({"float", "double"}):
            return "DOUBLE"
        number_super_type = _find_number_super_type(value_type)
        if number_super_type is not None:
            return f"DECIMAL({number_super_type.precision},{number_super_type.scale})"
        if "date" in type_names:
            return "DATE"
        if "timestamp" in type_names or any("datetime" in name for name in type_names):
            return "TIMESTAMP"
        return None

    def _lower_aggregate_into_ctx(
        self,
        analysis: SQL2Analysis,
        aggregate: Aggregate,
        ctx: _RuleCtx,
        *,
        future_var_ids: set[int] | None = None,
    ) -> None:
        if self._is_rank_aggregate(aggregate):
            self._lower_rank_aggregate_in_current_scope(analysis=analysis, aggregate=aggregate, ctx=ctx)
            return

        sub_var_map = dict(ctx.var_map)

        sub_ctx = _RuleCtx(
            var_map=sub_var_map,
            lookup_alias_by_signature=dict(ctx.lookup_alias_by_signature),
            alias_table_name_by_alias=dict(ctx.alias_table_name_by_alias),
            optional_projection_var_ids_by_task_id=dict(ctx.optional_projection_var_ids_by_task_id),
            branch_projection_var_ids_by_task_id=dict(ctx.branch_projection_var_ids_by_task_id),
            union_mode_by_task_id=dict(ctx.union_mode_by_task_id),
            future_var_ids_by_task_id=dict(ctx.future_var_ids_by_task_id),
            bound_union_output_var_ids_by_branch_signature=dict(ctx.bound_union_output_var_ids_by_branch_signature),
            seed_union_scope_from_bound_aliases=self._aggregate_needs_union_scope_seed(aggregate=aggregate, ctx=ctx),
            used_aliases=self._child_ctx_used_aliases(ctx),
        )
        if sub_ctx.from_item is None and ctx.from_item is None:
            self._seed_ctx_sources_from_bound_aliases(sub_ctx)
        if isinstance(aggregate.body, Logical):
            for child in aggregate.body.body:
                self._lower_task_into_ctx(analysis, child, sub_ctx)
        else:
            self._lower_task_into_ctx(analysis, aggregate.body, sub_ctx)
        if sub_ctx.from_item is None and ctx.from_item is not None:
            # Aggregate expressions are evaluated over the current logical scope.
            # If the aggregate body contributes only scalar predicates/expressions
            # (and no new source), inherit outer scope sources so lowering remains
            # non-correlated and backend-compatible.
            sub_ctx.from_item = SQL2FromItem(
                source=ctx.from_item.source,
                alias=ctx.from_item.alias,
                lateral=False,
            )
            sub_ctx.joins = [
                SQL2Join(
                    source=SQL2FromItem(
                        source=join.source.source,
                        alias=join.source.alias,
                        lateral=False,
                    ),
                    join_type=join.join_type,
                    on=join.on,
                )
                for join in ctx.joins
            ]
            sub_ctx.where = [*ctx.where, *sub_ctx.where]
        sub_ctx.where.extend(self._count_projection_non_null_predicates(aggregate=aggregate, var_map=sub_ctx.var_map))
        if (
            ctx.from_item is not None
            and (
                not aggregate.projection
                or not self._projection_dedupe_required(aggregate=aggregate, sub_ctx=sub_ctx)
            )
            and self._aggregate_inline_from_item_compatible(
                outer_from_item=ctx.from_item,
                aggregate_from_item=sub_ctx.from_item,
                outer_joins=tuple(ctx.joins),
                aggregate_joins=tuple(sub_ctx.joins),
            )
        ):
            extra_where = tuple(
                predicate
                for predicate in sub_ctx.where
                if predicate not in ctx.where
            )
            # Safe inline fast path: keep this restricted to same-scope aggregates
            # without extra filters. Filtered/reshaped aggregates stay on the
            # non-inline path to avoid cross-aggregate context leakage.
            if not extra_where:
                carry_var_ids = self._inline_carry_var_ids(
                    aggregate=aggregate,
                    ctx=ctx,
                    future_var_ids=future_var_ids or set(),
                )
                self._lower_inline_aggregate_over_ctx(
                    aggregate,
                    ctx,
                    value_var_map=sub_ctx.var_map,
                    extra_where=extra_where,
                    carry_var_ids=carry_var_ids,
                )
                return
        # NOTE:
        # The inline aggregate path mutates `ctx` in-place, which can leak filter
        # context across sibling aggregates in the same logical scope. That leads
        # to incorrect semantics for expressions like:
        #   sum(x).where(pred) / sum(x)
        # where the second aggregate must see the unfiltered scope.
        # Keep aggregate lowering on the non-inline subquery path until normalize
        # provides an explicit safe aggregate batching/inline artifact.

        where_predicates = tuple(sub_ctx.where)
        group_expr_overrides: dict[int, SQL2Expr] | None = None
        decorrelated = self._attempt_grouped_aggregate_decorrelation(
            aggregate=aggregate,
            outer_ctx=ctx,
            sub_ctx=sub_ctx,
        )
        if decorrelated is not None:
            group_expr_overrides, where_predicates = decorrelated

        subquery, group_alias_by_var_id, agg_alias_name = self._build_grouped_aggregate_subquery(
            aggregate=aggregate,
            sub_ctx=sub_ctx,
            where_predicates=where_predicates,
            group_expr_overrides=group_expr_overrides,
        )
        outer_aliases = self._ctx_source_aliases(ctx)
        use_lateral = self._query_references_aliases(subquery, outer_aliases)
        if use_lateral:
            where_predicates = self._attach_required_outer_scope_to_aggregate_subctx(
                aggregate=aggregate,
                outer_ctx=ctx,
                sub_ctx=sub_ctx,
                where_predicates=where_predicates,
                group_expr_overrides=group_expr_overrides,
            )
            subquery, group_alias_by_var_id, agg_alias_name = self._build_grouped_aggregate_subquery(
                aggregate=aggregate,
                sub_ctx=sub_ctx,
                where_predicates=where_predicates,
                group_expr_overrides=group_expr_overrides,
            )
            use_lateral = self._query_references_aliases(subquery, outer_aliases)
        if use_lateral and group_expr_overrides is not None:
            subquery, group_alias_by_var_id, agg_alias_name = self._build_grouped_aggregate_subquery(
                aggregate=aggregate,
                sub_ctx=sub_ctx,
                where_predicates=tuple(sub_ctx.where),
                group_expr_overrides=None,
            )
            use_lateral = self._query_references_aliases(subquery, outer_aliases)

        join_alias = self._attach_lateral_subquery(
            outer_ctx=ctx,
            query=subquery,
            join_alias_by_var_id=group_alias_by_var_id,
            alias_prefix="agg",
            lateral=use_lateral,
        )
        output_var_id = aggregate.args[-1].id if aggregate.args and isinstance(aggregate.args[-1], Var) else None
        self._bind_subquery_outputs(
            ctx=ctx,
            join_alias=join_alias,
            key_alias_by_var_id=group_alias_by_var_id,
            output_var_id=output_var_id,
            output_alias_name=agg_alias_name,
        )

    def _aggregate_needs_union_scope_seed(self, *, aggregate: Aggregate, ctx: _RuleCtx) -> bool:
        if ctx.from_item is None:
            return False
        return any(isinstance(value, Var) for value in aggregate.group)

    def _attach_required_outer_scope_to_aggregate_subctx(
        self,
        *,
        aggregate: Aggregate,
        outer_ctx: _RuleCtx,
        sub_ctx: _RuleCtx,
        where_predicates: tuple[SQL2Expr, ...],
        group_expr_overrides: dict[int, SQL2Expr] | None,
    ) -> tuple[SQL2Expr, ...]:
        outer_aliases = self._ctx_source_aliases(outer_ctx)
        if not outer_aliases:
            return where_predicates

        required_aliases: set[str] = set()
        for value in aggregate.group:
            if not isinstance(value, Var):
                continue
            expr = (
                group_expr_overrides.get(value.id)
                if group_expr_overrides is not None
                else self._resolve_value(value, sub_ctx.var_map)
            )
            if expr is not None:
                required_aliases.update(self._aliases_in_expr(expr))
        required_aliases.update(self._aliases_in_expr(self._aggregate_expr(aggregate, sub_ctx.var_map)))
        for predicate in where_predicates:
            required_aliases.update(self._aliases_in_expr(predicate))

        outer_required_aliases = required_aliases.intersection(outer_aliases)
        if not outer_required_aliases:
            return where_predicates

        local_aliases = self._ctx_source_aliases(sub_ctx)

        def attach_source(source_item: SQL2FromItem, *, join_type: str = "INNER", on: tuple[SQL2Expr, ...] = ()) -> None:
            nonlocal local_aliases
            if source_item.alias in local_aliases:
                return
            cloned = SQL2FromItem(source=source_item.source, alias=source_item.alias, lateral=False)
            if sub_ctx.from_item is None:
                sub_ctx.from_item = cloned
                if on:
                    sub_ctx.where.extend(on)
            else:
                sub_ctx.joins.append(SQL2Join(source=cloned, join_type=join_type, on=on))
            local_aliases.add(source_item.alias)

        if outer_ctx.from_item is not None and outer_ctx.from_item.alias in outer_required_aliases:
            attach_source(outer_ctx.from_item)
        for join in outer_ctx.joins:
            if join.source.alias not in outer_required_aliases:
                continue
            allowed_aliases = set(local_aliases)
            allowed_aliases.add(join.source.alias)
            filtered_on = tuple(
                predicate
                for predicate in join.on
                if self._aliases_in_expr(predicate).issubset(allowed_aliases)
            )
            attach_source(join.source, join_type=join.join_type, on=filtered_on)

        keep_aliases = set(local_aliases)
        keep_aliases.update(outer_required_aliases)
        inherited_predicates = [
            predicate
            for predicate in outer_ctx.where
            if self._aliases_in_expr(predicate).intersection(outer_aliases).issubset(outer_required_aliases)
        ]
        if not inherited_predicates:
            return where_predicates
        return tuple([*inherited_predicates, *where_predicates])

    def _build_grouped_aggregate_subquery(
        self,
        *,
        aggregate: Aggregate,
        sub_ctx: _RuleCtx,
        where_predicates: tuple[SQL2Expr, ...],
        group_expr_overrides: dict[int, SQL2Expr] | None,
    ) -> tuple[SQL2SelectQuery, dict[int, str], str]:
        eval_var_map = sub_ctx.var_map
        if group_expr_overrides:
            eval_var_map = dict(sub_ctx.var_map)
            eval_var_map.update(group_expr_overrides)

        group_alias_by_var_id: dict[int, str] = {}
        select_items: list[SQL2SelectItem] = []
        group_by: list[SQL2Expr] = []

        for value in aggregate.group:
            if not isinstance(value, Var):
                continue
            expr = (
                group_expr_overrides.get(value.id)
                if group_expr_overrides is not None
                else self._resolve_value(value, eval_var_map)
            )
            if expr is None:
                continue
            alias_name = f"g_{value.id}"
            group_alias_by_var_id[value.id] = alias_name
            select_items.append(SQL2SelectItem(expr=expr, alias=alias_name))
            group_by.append(expr)

        agg_alias_name = f"agg_{aggregate.id}"
        fn = aggregate.aggregation.name.strip().lower()
        uses_projection_dedupe = (
            bool(aggregate.projection)
            and fn != "count"
            and self._projection_dedupe_required(aggregate=aggregate, sub_ctx=sub_ctx)
        )
        if uses_projection_dedupe:
            source_alias = "agg_src"
            source_select_items: list[SQL2SelectItem] = [
                SQL2SelectItem(expr=item.expr, alias=item.alias)
                for item in select_items
                if item.alias is not None
            ]
            arg_aliases: list[str] = []
            for arg_ix, value in enumerate(aggregate.args[:-1]):
                resolved = self._resolve_value(value, eval_var_map)
                if resolved is None:
                    continue
                alias_name = f"a_{arg_ix}"
                arg_aliases.append(alias_name)
                source_select_items.append(SQL2SelectItem(expr=resolved, alias=alias_name))
            for proj_ix, value in enumerate(aggregate.projection):
                resolved = self._resolve_value(value, eval_var_map)
                if resolved is None:
                    continue
                source_select_items.append(SQL2SelectItem(expr=resolved, alias=f"p_{proj_ix}"))
            source_query = SQL2SelectQuery(
                select_items=tuple(source_select_items),
                from_item=sub_ctx.from_item,
                joins=tuple(sub_ctx.joins),
                where=where_predicates,
                distinct=True,
            )
            fn_name = {
                "count": "COUNT",
                "sum": "SUM",
                "min": "MIN",
                "max": "MAX",
                "avg": "AVG",
            }.get(fn, aggregate.aggregation.name.upper())
            arg_exprs: list[SQL2Expr] = [
                SQL2ColumnExpr(table_alias=source_alias, column_name=alias_name)
                for alias_name in arg_aliases
            ]
            if fn == "count" and not arg_exprs:
                aggregate_expr = SQL2FunctionExpr(name="COUNT", args=())
            else:
                if not arg_exprs:
                    arg_exprs = [SQL2NullExpr()]
                aggregate_expr = SQL2FunctionExpr(name=fn_name, args=tuple(arg_exprs))
            aggregate_expr = self._cast_for_output_type(aggregate=aggregate, expr=aggregate_expr)
            outer_select_items: list[SQL2SelectItem] = []
            outer_group_by: list[SQL2Expr] = []
            for var_id, alias_name in group_alias_by_var_id.items():
                col = SQL2ColumnExpr(table_alias=source_alias, column_name=alias_name)
                outer_select_items.append(SQL2SelectItem(expr=col, alias=alias_name))
                outer_group_by.append(col)
            outer_select_items.append(SQL2SelectItem(expr=aggregate_expr, alias=agg_alias_name))
            having: tuple[SQL2Expr, ...] = tuple()
            if not outer_group_by:
                having = (
                    SQL2BinaryExpr(
                        op=">",
                        left=SQL2FunctionExpr(name="COUNT", args=(SQL2LiteralExpr(1),)),
                        right=SQL2LiteralExpr(0),
                    ),
                )
            return (
                SQL2SelectQuery(
                    select_items=tuple(outer_select_items),
                    from_item=SQL2FromItem(source=SQL2SubquerySource(query=source_query), alias=source_alias),
                    group_by=tuple(outer_group_by),
                    having=having,
                ),
                group_alias_by_var_id,
                agg_alias_name,
            )

        aggregate_expr = self._aggregate_expr(aggregate, eval_var_map)
        select_items.append(SQL2SelectItem(expr=aggregate_expr, alias=agg_alias_name))
        having: tuple[SQL2Expr, ...] = tuple()
        if not group_by:
            having = (
                SQL2BinaryExpr(
                    op=">",
                    left=SQL2FunctionExpr(name="COUNT", args=(SQL2LiteralExpr(1),)),
                    right=SQL2LiteralExpr(0),
                ),
            )
        return (
            SQL2SelectQuery(
                select_items=tuple(select_items),
                from_item=sub_ctx.from_item,
                joins=tuple(sub_ctx.joins),
                where=where_predicates,
                group_by=tuple(group_by),
                having=having,
            ),
            group_alias_by_var_id,
            agg_alias_name,
        )

    def _projection_dedupe_trivially_safe(self, *, aggregate: Aggregate, sub_ctx: _RuleCtx) -> bool:
        if not aggregate.projection:
            return True
        if sub_ctx.from_item is None or sub_ctx.joins:
            return False
        base_alias = sub_ctx.from_item.alias
        for value in aggregate.projection:
            expr = self._resolve_value(value, sub_ctx.var_map)
            if not isinstance(expr, SQL2ColumnExpr):
                return False
            if expr.table_alias != base_alias:
                return False
        return True

    def _projection_dedupe_required(self, *, aggregate: Aggregate, sub_ctx: _RuleCtx) -> bool:
        if not aggregate.projection:
            return False
        if self._projection_dedupe_trivially_safe(aggregate=aggregate, sub_ctx=sub_ctx):
            return False
        if sub_ctx.from_item is None:
            return False

        source_aliases = self._ctx_source_aliases(sub_ctx)
        projection_aliases: set[str] = set()
        for value in aggregate.projection:
            expr = self._resolve_value(value, sub_ctx.var_map)
            if not isinstance(expr, SQL2ColumnExpr):
                return True
            projection_aliases.add(expr.table_alias)
        if not projection_aliases:
            return False
        if source_aliases.issubset(projection_aliases):
            return False

        projection_table_names = {
            sub_ctx.alias_table_name_by_alias.get(alias)
            for alias in projection_aliases
        }
        extra_aliases = source_aliases.difference(projection_aliases)
        for alias in extra_aliases:
            table_name = sub_ctx.alias_table_name_by_alias.get(alias)
            if table_name is None:
                return True
            if table_name in projection_table_names:
                return True
        return False

    def _attempt_grouped_aggregate_decorrelation(
        self,
        *,
        aggregate: Aggregate,
        outer_ctx: _RuleCtx,
        sub_ctx: _RuleCtx,
    ) -> tuple[dict[int, SQL2Expr], tuple[SQL2Expr, ...]] | None:
        group_var_ids = [value.id for value in aggregate.group if isinstance(value, Var)]
        if not group_var_ids or outer_ctx.from_item is None:
            return None

        outer_aliases = self._ctx_source_aliases(outer_ctx)
        if not outer_aliases:
            return None

        outer_expr_by_var_id = {
            var_id: outer_ctx.var_map.get(var_id)
            for var_id in group_var_ids
        }
        if not any(expr is not None for expr in outer_expr_by_var_id.values()):
            return None

        group_expr_overrides: dict[int, SQL2Expr] = {}
        consumed_where_indexes: set[int] = set()

        for ix, predicate in enumerate(sub_ctx.where):
            if not isinstance(predicate, SQL2BinaryExpr) or predicate.op != "=":
                continue
            matched_var_id: int | None = None
            matched_expr: SQL2Expr | None = None
            for var_id, outer_expr in outer_expr_by_var_id.items():
                if outer_expr is None:
                    continue
                if (
                    predicate.left == outer_expr
                    and not self._expr_references_aliases(predicate.right, outer_aliases)
                ):
                    matched_var_id = var_id
                    matched_expr = predicate.right
                    break
                if (
                    predicate.right == outer_expr
                    and not self._expr_references_aliases(predicate.left, outer_aliases)
                ):
                    matched_var_id = var_id
                    matched_expr = predicate.left
                    break
            if matched_var_id is None or matched_expr is None:
                continue
            existing = group_expr_overrides.get(matched_var_id)
            if existing is not None and existing != matched_expr:
                return None
            group_expr_overrides[matched_var_id] = matched_expr
            consumed_where_indexes.add(ix)

        if not group_expr_overrides:
            return None

        for var_id in group_var_ids:
            resolved_expr = group_expr_overrides.get(var_id, sub_ctx.var_map.get(var_id))
            if resolved_expr is None:
                return None
            if self._expr_references_aliases(resolved_expr, outer_aliases):
                return None
            group_expr_overrides[var_id] = resolved_expr

        filtered_where = tuple(
            predicate
            for ix, predicate in enumerate(sub_ctx.where)
            if ix not in consumed_where_indexes
        )
        return group_expr_overrides, filtered_where

    def _attach_lateral_subquery(
        self,
        *,
        outer_ctx: _RuleCtx,
        query: SQL2SelectQuery,
        join_alias_by_var_id: dict[int, str],
        alias_prefix: str,
        lateral: bool = True,
    ) -> str:
        join_alias = outer_ctx.new_alias(alias_prefix)
        join_from = SQL2FromItem(
            source=SQL2SubquerySource(query=query),
            alias=join_alias,
            lateral=lateral and outer_ctx.from_item is not None,
        )
        on_predicates: list[SQL2Expr] = []
        for var_id, alias_name in join_alias_by_var_id.items():
            outer_expr = outer_ctx.var_map.get(var_id)
            if outer_expr is None:
                continue
            inner_expr = SQL2ColumnExpr(table_alias=join_alias, column_name=alias_name)
            on_predicates.append(SQL2BinaryExpr(op="=", left=inner_expr, right=outer_expr))

        if outer_ctx.from_item is None:
            outer_ctx.from_item = SQL2FromItem(
                source=join_from.source,
                alias=join_alias,
                lateral=False,
            )
            if on_predicates:
                outer_ctx.where.extend(on_predicates)
        else:
            outer_ctx.joins.append(SQL2Join(source=join_from, join_type="INNER", on=tuple(on_predicates)))
        return join_alias

    def _aggregate_inline_from_item_compatible(
        self,
        *,
        outer_from_item: SQL2FromItem,
        aggregate_from_item: SQL2FromItem | None,
        outer_joins: tuple[SQL2Join, ...],
        aggregate_joins: tuple[SQL2Join, ...],
    ) -> bool:
        if aggregate_from_item is None:
            return True
        return aggregate_from_item == outer_from_item and aggregate_joins == outer_joins

    def _bind_subquery_outputs(
        self,
        *,
        ctx: _RuleCtx,
        join_alias: str,
        key_alias_by_var_id: dict[int, str],
        output_var_id: int | None,
        output_alias_name: str,
    ) -> None:
        if output_var_id is not None:
            ctx.var_map[output_var_id] = SQL2ColumnExpr(table_alias=join_alias, column_name=output_alias_name)
        for var_id, alias_name in key_alias_by_var_id.items():
            if var_id in ctx.var_map:
                continue
            ctx.var_map[var_id] = SQL2ColumnExpr(table_alias=join_alias, column_name=alias_name)

    def _inline_carry_var_ids(
        self,
        *,
        aggregate: Aggregate,
        ctx: _RuleCtx,
        future_var_ids: set[int],
    ) -> tuple[int, ...]:
        group_var_ids = {
            value.id
            for value in aggregate.group
            if isinstance(value, Var)
        }
        output_var_id = aggregate.args[-1].id if aggregate.args and isinstance(aggregate.args[-1], Var) else None
        preserved_var_ids = set(group_var_ids)
        if output_var_id is not None:
            preserved_var_ids.add(output_var_id)

        carry_ids: list[int] = []
        for var_id in future_var_ids:
            if var_id in preserved_var_ids:
                continue
            expr = ctx.var_map.get(var_id)
            if expr is None:
                continue
            if self._expr_has_column_ref(expr):
                carry_ids.append(var_id)
        return tuple(sorted(set(carry_ids)))

    def _lower_inline_aggregate_over_ctx(
        self,
        aggregate: Aggregate,
        ctx: _RuleCtx,
        *,
        value_var_map: dict[int, SQL2Expr] | None = None,
        extra_where: tuple[SQL2Expr, ...] = (),
        carry_var_ids: tuple[int, ...] = (),
    ) -> None:
        prior_var_map = dict(ctx.var_map)
        eval_var_map = value_var_map if value_var_map is not None else ctx.var_map
        count_non_null_predicates = self._count_projection_non_null_predicates(
            aggregate=aggregate,
            var_map=eval_var_map,
        )
        group_alias_by_var_id: dict[int, str] = {}
        select_items: list[SQL2SelectItem] = []
        group_by: list[SQL2Expr] = []
        for value in aggregate.group:
            if not isinstance(value, Var):
                continue
            expr = self._resolve_value(value, eval_var_map)
            if expr is None:
                continue
            alias_name = f"g_{value.id}"
            group_alias_by_var_id[value.id] = alias_name
            select_items.append(SQL2SelectItem(expr=expr, alias=alias_name))
            group_by.append(expr)
        partition_by = tuple(group_by)
        grouped_column_refs = self._column_refs_in_exprs(tuple(group_by))

        carry_alias_by_var_id: dict[int, str] = {}
        requires_window = False
        for var_id in carry_var_ids:
            if var_id in group_alias_by_var_id:
                continue
            expr = prior_var_map.get(var_id)
            if expr is None:
                continue
            if not self._expr_has_column_ref(expr):
                continue
            column_refs = self._column_refs_in_expr(expr)
            if column_refs and not column_refs.issubset(grouped_column_refs):
                requires_window = True
            alias_name = f"c_{var_id}"
            carry_alias_by_var_id[var_id] = alias_name
            select_items.append(SQL2SelectItem(expr=expr, alias=alias_name))
            if requires_window:
                group_by.append(expr)

        agg_alias_name = f"agg_{aggregate.id}"
        if requires_window:
            aggregate_expr = self._aggregate_window_expr(
                aggregate=aggregate,
                var_map=eval_var_map,
                partition_by=partition_by,
            )
            select_items.append(SQL2SelectItem(expr=aggregate_expr, alias=agg_alias_name))
            subquery = SQL2SelectQuery(
                select_items=tuple(select_items),
                from_item=ctx.from_item,
                joins=tuple(ctx.joins),
                where=tuple([*ctx.where, *extra_where, *count_non_null_predicates]),
                distinct=True,
            )
        else:
            aggregate_expr = self._aggregate_expr(aggregate, eval_var_map)
            select_items.append(SQL2SelectItem(expr=aggregate_expr, alias=agg_alias_name))
            having: tuple[SQL2Expr, ...] = tuple()
            if not group_by:
                having = (
                    SQL2BinaryExpr(
                        op=">",
                        left=SQL2FunctionExpr(name="COUNT", args=(SQL2LiteralExpr(1),)),
                        right=SQL2LiteralExpr(0),
                    ),
                )
            subquery = SQL2SelectQuery(
                select_items=tuple(select_items),
                from_item=ctx.from_item,
                joins=tuple(ctx.joins),
                where=tuple([*ctx.where, *extra_where, *count_non_null_predicates]),
                group_by=tuple(group_by),
                having=having,
            )

        join_alias = ctx.new_alias("agg")
        ctx.from_item = SQL2FromItem(source=SQL2SubquerySource(query=subquery), alias=join_alias)
        ctx.joins = []
        ctx.where = []

        next_var_map: dict[int, SQL2Expr] = {}
        for var_id, alias_name in group_alias_by_var_id.items():
            next_var_map[var_id] = SQL2ColumnExpr(table_alias=join_alias, column_name=alias_name)
        for var_id, alias_name in carry_alias_by_var_id.items():
            next_var_map[var_id] = SQL2ColumnExpr(table_alias=join_alias, column_name=alias_name)
        if aggregate.args and isinstance(aggregate.args[-1], Var):
            out_var = aggregate.args[-1]
            next_var_map[out_var.id] = SQL2ColumnExpr(table_alias=join_alias, column_name=agg_alias_name)
        for var_id, expr in prior_var_map.items():
            if var_id in next_var_map:
                continue
            if not self._expr_has_column_ref(expr):
                next_var_map[var_id] = expr
        ctx.var_map = next_var_map

    def _is_rank_aggregate(self, aggregate: Aggregate) -> bool:
        return aggregate.aggregation.name.strip().lower() == "rank"

    def _lower_rank_aggregate_in_current_scope(
        self,
        *,
        analysis: SQL2Analysis,
        aggregate: Aggregate,
        ctx: _RuleCtx,
    ) -> None:
        source_ctx = _RuleCtx(
            from_item=ctx.from_item,
            joins=list(ctx.joins),
            where=list(ctx.where),
            var_map=dict(ctx.var_map),
            lookup_alias_by_signature=dict(ctx.lookup_alias_by_signature),
            alias_table_name_by_alias=dict(ctx.alias_table_name_by_alias),
            optional_projection_var_ids_by_task_id=dict(ctx.optional_projection_var_ids_by_task_id),
            branch_projection_var_ids_by_task_id=dict(ctx.branch_projection_var_ids_by_task_id),
            union_mode_by_task_id=dict(ctx.union_mode_by_task_id),
            future_var_ids_by_task_id=dict(ctx.future_var_ids_by_task_id),
            bound_union_output_var_ids_by_branch_signature=dict(ctx.bound_union_output_var_ids_by_branch_signature),
            seed_union_scope_from_bound_aliases=ctx.seed_union_scope_from_bound_aliases,
            used_aliases=self._child_ctx_used_aliases(ctx),
            alias_counter=ctx.alias_counter,
        )
        if isinstance(aggregate.body, Logical):
            for child in aggregate.body.body:
                self._lower_task_into_ctx(analysis, child, source_ctx)
        else:
            self._lower_task_into_ctx(analysis, aggregate.body, source_ctx)
        self._lower_rank_aggregate_into_ctx(
            aggregate=aggregate,
            outer_ctx=ctx,
            source_ctx=source_ctx,
        )
        ctx.alias_counter = max(ctx.alias_counter, source_ctx.alias_counter)

    def _lower_rank_aggregate_into_ctx(
        self,
        *,
        aggregate: Aggregate,
        outer_ctx: _RuleCtx,
        source_ctx: _RuleCtx,
    ) -> None:
        ranked_query, join_alias_by_var_id, rank_alias_name = self._build_ranked_query(
            aggregate=aggregate,
            source_ctx=source_ctx,
        )
        join_alias = self._attach_lateral_subquery(
            outer_ctx=outer_ctx,
            query=ranked_query,
            join_alias_by_var_id=join_alias_by_var_id,
            alias_prefix="agg_rank",
        )
        output_var_id = aggregate.args[-1].id if aggregate.args and isinstance(aggregate.args[-1], Var) else None
        self._bind_subquery_outputs(
            ctx=outer_ctx,
            join_alias=join_alias,
            key_alias_by_var_id=join_alias_by_var_id,
            output_var_id=output_var_id,
            output_alias_name=rank_alias_name,
        )

    def _build_ranked_query(
        self,
        *,
        aggregate: Aggregate,
        source_ctx: _RuleCtx,
    ) -> tuple[SQL2SelectQuery, dict[int, str], str]:
        shape_vars: list[Var] = []
        for value in [*aggregate.group, *aggregate.projection, *aggregate.args[:-1]]:
            for var in self._value_vars_in_order(value):
                if var not in shape_vars:
                    shape_vars.append(var)

        join_vars: list[Var] = []
        for value in [*aggregate.group, *aggregate.projection]:
            for var in self._value_vars_in_order(value):
                if var not in join_vars:
                    join_vars.append(var)

        shape_alias_by_var_id: dict[int, str] = {}
        shape_select_items: list[SQL2SelectItem] = []
        for var in shape_vars:
            expr = self._resolve_value(var, source_ctx.var_map)
            if expr is None:
                continue
            alias_name = f"s_{var.id}"
            shape_alias_by_var_id[var.id] = alias_name
            shape_select_items.append(SQL2SelectItem(expr=expr, alias=alias_name))

        if shape_select_items:
            shape_query = SQL2SelectQuery(
                select_items=tuple(shape_select_items),
                from_item=source_ctx.from_item,
                joins=tuple(source_ctx.joins),
                where=tuple(source_ctx.where),
                distinct=True,
            )
        else:
            shape_query = SQL2SelectQuery(
                select_items=(SQL2SelectItem(expr=SQL2LiteralExpr(1), alias="s_0"),),
                from_item=source_ctx.from_item,
                joins=tuple(source_ctx.joins),
                where=tuple(source_ctx.where),
            )

        partition_by = tuple(
            SQL2ColumnExpr(table_alias="_shape", column_name=shape_alias_by_var_id[var.id])
            for var in aggregate.group
            if isinstance(var, Var) and var.id in shape_alias_by_var_id
        )

        order_values = self._as_value_sequence(aggregate.args[0]) if aggregate.args else []
        direction_values = self._as_value_sequence(aggregate.args[1]) if len(aggregate.args) > 1 else []
        order_by: list[SQL2OrderByExpr] = []
        for ix, value in enumerate(order_values):
            if isinstance(value, Var) and value.id in shape_alias_by_var_id:
                expr = SQL2ColumnExpr(table_alias="_shape", column_name=shape_alias_by_var_id[value.id])
            elif isinstance(value, Literal):
                expr = SQL2LiteralExpr(value=value.value)
            elif value is None:
                expr = SQL2LiteralExpr(value=None)
            else:
                expr = SQL2NullExpr()
            ascending = True
            if ix < len(direction_values):
                parsed = self._as_literal_bool(
                    direction_values[ix],
                    source_var_map=source_ctx.var_map,
                )
                if parsed is not None:
                    ascending = parsed
            order_by.append(SQL2OrderByExpr(expr=expr, descending=not ascending))

        if not order_by:
            order_by = [
                SQL2OrderByExpr(expr=SQL2ColumnExpr(table_alias="_shape", column_name=alias_name))
                for alias_name in shape_alias_by_var_id.values()
            ]
        if not order_by:
            order_by = [SQL2OrderByExpr(expr=SQL2LiteralExpr(1))]

        rank_alias_name = f"agg_{aggregate.id}"
        rank_expr: SQL2Expr = SQL2WindowExpr(
            name="ROW_NUMBER",
            args=(),
            partition_by=partition_by,
            order_by=tuple(order_by),
        )
        rank_expr = self._cast_for_output_type(aggregate=aggregate, expr=rank_expr)

        select_items: list[SQL2SelectItem] = []
        join_alias_by_var_id: dict[int, str] = {}
        for var in join_vars:
            shape_alias = shape_alias_by_var_id.get(var.id)
            if shape_alias is None:
                continue
            alias_name = f"k_{var.id}"
            join_alias_by_var_id[var.id] = alias_name
            select_items.append(
                SQL2SelectItem(
                    expr=SQL2ColumnExpr(table_alias="_shape", column_name=shape_alias),
                    alias=alias_name,
                )
            )
        select_items.append(SQL2SelectItem(expr=rank_expr, alias=rank_alias_name))

        ranked_query = SQL2SelectQuery(
            select_items=tuple(select_items),
            from_item=SQL2FromItem(
                source=SQL2SubquerySource(query=shape_query),
                alias="_shape",
            ),
        )
        return ranked_query, join_alias_by_var_id, rank_alias_name

    def _as_value_sequence(self, value: Value) -> list[Value]:
        if isinstance(value, tuple):
            return list(value)
        return [value]

    def _as_literal_bool(
        self,
        value: Value,
        *,
        source_var_map: dict[int, SQL2Expr],
    ) -> bool | None:
        if isinstance(value, bool):
            return value
        if isinstance(value, Literal) and isinstance(value.value, bool):
            return value.value
        resolved = self._resolve_value(value, source_var_map)
        if isinstance(resolved, SQL2LiteralExpr) and isinstance(resolved.value, bool):
            return resolved.value
        return None

    def _value_vars_in_order(self, value: Value) -> list[Var]:
        out: list[Var] = []
        if isinstance(value, Var):
            out.append(value)
        elif isinstance(value, tuple):
            for item in value:
                out.extend(self._value_vars_in_order(item))
        return out

    def _expr_has_column_ref(self, expr: SQL2Expr) -> bool:
        if isinstance(expr, SQL2ColumnExpr):
            return True
        if isinstance(expr, SQL2BinaryExpr):
            return self._expr_has_column_ref(expr.left) or self._expr_has_column_ref(expr.right)
        if isinstance(expr, SQL2FunctionExpr):
            return any(self._expr_has_column_ref(arg) for arg in expr.args)
        if isinstance(expr, SQL2WindowExpr):
            return (
                any(self._expr_has_column_ref(arg) for arg in expr.args)
                or any(self._expr_has_column_ref(arg) for arg in expr.partition_by)
                or any(self._expr_has_column_ref(item.expr) for item in expr.order_by)
            )
        if isinstance(expr, SQL2CastExpr):
            return self._expr_has_column_ref(expr.expr)
        if isinstance(expr, SQL2ExistsExpr):
            return True
        return False

    def _expr_references_aliases(self, expr: SQL2Expr, aliases: set[str]) -> bool:
        if not aliases:
            return False
        if isinstance(expr, SQL2ColumnExpr):
            return expr.table_alias in aliases
        if isinstance(expr, SQL2BinaryExpr):
            return self._expr_references_aliases(expr.left, aliases) or self._expr_references_aliases(
                expr.right,
                aliases,
            )
        if isinstance(expr, SQL2FunctionExpr):
            return any(self._expr_references_aliases(arg, aliases) for arg in expr.args)
        if isinstance(expr, SQL2WindowExpr):
            return (
                any(self._expr_references_aliases(arg, aliases) for arg in expr.args)
                or any(self._expr_references_aliases(arg, aliases) for arg in expr.partition_by)
                or any(self._expr_references_aliases(item.expr, aliases) for item in expr.order_by)
            )
        if isinstance(expr, SQL2CastExpr):
            return self._expr_references_aliases(expr.expr, aliases)
        if isinstance(expr, SQL2ExistsExpr):
            return self._query_references_aliases(expr.query, aliases)
        return False

    def _ctx_source_aliases(self, ctx: _RuleCtx) -> set[str]:
        aliases: set[str] = set()
        if ctx.from_item is not None:
            aliases.add(ctx.from_item.alias)
        for join in ctx.joins:
            aliases.add(join.source.alias)
        return aliases

    def _child_ctx_used_aliases(self, ctx: _RuleCtx) -> set[str]:
        aliases = set(ctx.used_aliases)
        aliases.update(self._ctx_source_aliases(ctx))
        if ctx.from_item is not None and isinstance(ctx.from_item.source, SQL2SubquerySource):
            aliases.update(self._aliases_in_query(ctx.from_item.source.query))
        for predicate in ctx.where:
            aliases.update(self._aliases_in_expr(predicate))
        for join in ctx.joins:
            for predicate in join.on:
                aliases.update(self._aliases_in_expr(predicate))
            if isinstance(join.source.source, SQL2SubquerySource):
                aliases.update(self._aliases_in_query(join.source.source.query))
        return aliases

    def _query_references_aliases(self, query: SQL2Query, aliases: set[str]) -> bool:
        if not aliases:
            return False
        if isinstance(query, SQL2UnionQuery):
            return any(self._query_references_aliases(part, aliases) for part in query.parts)
        if isinstance(query, SQL2RecursiveQuery):
            return any(
                inner is not None and self._query_references_aliases(inner, aliases)
                for inner in (query.base_query, query.recursive_query, query.final_query)
            )
        assert isinstance(query, SQL2SelectQuery)

        local_aliases: set[str] = set()
        if query.from_item is not None:
            local_aliases.add(query.from_item.alias)
        for join in query.joins:
            local_aliases.add(join.source.alias)
        scoped_aliases = aliases - local_aliases
        if not scoped_aliases:
            return False

        def from_item_refs(from_item: SQL2FromItem | None) -> bool:
            if from_item is None:
                return False
            source = from_item.source
            if isinstance(source, SQL2SubquerySource):
                return self._query_references_aliases(source.query, scoped_aliases)
            return False

        if from_item_refs(query.from_item):
            return True
        for join in query.joins:
            if from_item_refs(join.source):
                return True
            if any(self._expr_references_aliases(predicate, scoped_aliases) for predicate in join.on):
                return True
        if any(self._expr_references_aliases(item.expr, scoped_aliases) for item in query.select_items):
            return True
        if any(self._expr_references_aliases(predicate, scoped_aliases) for predicate in query.where):
            return True
        if any(self._expr_references_aliases(expr, scoped_aliases) for expr in query.group_by):
            return True
        if any(self._expr_references_aliases(predicate, scoped_aliases) for predicate in query.having):
            return True
        return False

    def _column_refs_in_exprs(self, exprs: tuple[SQL2Expr, ...]) -> set[tuple[str, str]]:
        refs: set[tuple[str, str]] = set()
        for expr in exprs:
            refs.update(self._column_refs_in_expr(expr))
        return refs

    def _column_refs_in_expr(self, expr: SQL2Expr) -> set[tuple[str, str]]:
        if isinstance(expr, SQL2ColumnExpr):
            return {(expr.table_alias, expr.column_name)}
        if isinstance(expr, SQL2BinaryExpr):
            return self._column_refs_in_expr(expr.left) | self._column_refs_in_expr(expr.right)
        if isinstance(expr, SQL2FunctionExpr):
            refs: set[tuple[str, str]] = set()
            for arg in expr.args:
                refs.update(self._column_refs_in_expr(arg))
            return refs
        if isinstance(expr, SQL2WindowExpr):
            refs: set[tuple[str, str]] = set()
            for arg in expr.args:
                refs.update(self._column_refs_in_expr(arg))
            for part in expr.partition_by:
                refs.update(self._column_refs_in_expr(part))
            for item in expr.order_by:
                refs.update(self._column_refs_in_expr(item.expr))
            return refs
        if isinstance(expr, SQL2CastExpr):
            return self._column_refs_in_expr(expr.expr)
        if isinstance(expr, SQL2ExistsExpr):
            return set()
        return set()

    def _aliases_in_var_map(self, var_map: dict[int, SQL2Expr]) -> set[str]:
        aliases: set[str] = set()
        for expr in var_map.values():
            aliases.update(self._aliases_in_expr(expr))
        return aliases

    def _aliases_in_expr(self, expr: SQL2Expr) -> set[str]:
        if isinstance(expr, SQL2ColumnExpr):
            return {expr.table_alias}
        if isinstance(expr, SQL2BinaryExpr):
            return self._aliases_in_expr(expr.left) | self._aliases_in_expr(expr.right)
        if isinstance(expr, SQL2FunctionExpr):
            aliases: set[str] = set()
            for arg in expr.args:
                aliases.update(self._aliases_in_expr(arg))
            return aliases
        if isinstance(expr, SQL2WindowExpr):
            aliases: set[str] = set()
            for arg in expr.args:
                aliases.update(self._aliases_in_expr(arg))
            for part in expr.partition_by:
                aliases.update(self._aliases_in_expr(part))
            for item in expr.order_by:
                aliases.update(self._aliases_in_expr(item.expr))
            return aliases
        if isinstance(expr, SQL2CastExpr):
            return self._aliases_in_expr(expr.expr)
        if isinstance(expr, SQL2ExistsExpr):
            return self._aliases_in_query(expr.query)
        return set()

    def _aliases_in_query(self, query: SQL2Query) -> set[str]:
        if isinstance(query, SQL2UnionQuery):
            aliases: set[str] = set()
            for part in query.parts:
                aliases.update(self._aliases_in_query(part))
            return aliases
        if not isinstance(query, SQL2SelectQuery):
            return set()

        aliases: set[str] = set()
        for item in query.select_items:
            aliases.update(self._aliases_in_expr(item.expr))
        for predicate in query.where:
            aliases.update(self._aliases_in_expr(predicate))
        for expr in query.group_by:
            aliases.update(self._aliases_in_expr(expr))
        for predicate in query.having:
            aliases.update(self._aliases_in_expr(predicate))
        if query.from_item is not None and isinstance(query.from_item.source, SQL2SubquerySource):
            aliases.update(self._aliases_in_query(query.from_item.source.query))
        for join in query.joins:
            for predicate in join.on:
                aliases.update(self._aliases_in_expr(predicate))
            if isinstance(join.source.source, SQL2SubquerySource):
                aliases.update(self._aliases_in_query(join.source.source.query))
        return aliases

    def _compile_exists_subquery(
        self,
        analysis: SQL2Analysis,
        task: Task,
        outer_ctx: _RuleCtx,
    ) -> SQL2SelectQuery:
        sub_ctx = _RuleCtx(
            var_map=dict(outer_ctx.var_map),
            lookup_alias_by_signature={},
            alias_table_name_by_alias=dict(outer_ctx.alias_table_name_by_alias),
            optional_projection_var_ids_by_task_id=dict(outer_ctx.optional_projection_var_ids_by_task_id),
            branch_projection_var_ids_by_task_id=dict(outer_ctx.branch_projection_var_ids_by_task_id),
            union_mode_by_task_id=dict(outer_ctx.union_mode_by_task_id),
            future_var_ids_by_task_id=dict(outer_ctx.future_var_ids_by_task_id),
            bound_union_output_var_ids_by_branch_signature=dict(outer_ctx.bound_union_output_var_ids_by_branch_signature),
            seed_union_scope_from_bound_aliases=outer_ctx.seed_union_scope_from_bound_aliases,
            used_aliases=self._child_ctx_used_aliases(outer_ctx),
        )
        self._lower_task_into_ctx(analysis, task, sub_ctx)
        outer_ctx.used_aliases.update(sub_ctx.used_aliases)
        outer_ctx.alias_counter = max(outer_ctx.alias_counter, sub_ctx.alias_counter)
        return SQL2SelectQuery(
            select_items=(SQL2SelectItem(SQL2LiteralExpr(1), alias="exists_flag"),),
            from_item=sub_ctx.from_item,
            joins=tuple(sub_ctx.joins),
            where=tuple(sub_ctx.where),
        )

    def _aggregate_expr(self, aggregate: Aggregate, var_map: dict[int, SQL2Expr]) -> SQL2Expr:
        fn = aggregate.aggregation.name.strip().lower()
        fn_name = {
            "count": "COUNT",
            "sum": "SUM",
            "min": "MIN",
            "max": "MAX",
            "avg": "AVG",
        }.get(fn, aggregate.aggregation.name.upper())

        projection_exprs = [
            resolved
            for value in aggregate.projection
            if (resolved := self._resolve_value(value, var_map)) is not None
        ]
        if fn == "count" and projection_exprs:
            if len(projection_exprs) == 1:
                aggregate_expr = SQL2FunctionExpr(name="COUNT", args=(projection_exprs[0],), distinct=True)
            else:
                aggregate_expr = SQL2FunctionExpr(
                    name="COUNT",
                    args=(SQL2FunctionExpr(name="ROW", args=tuple(projection_exprs)),),
                    distinct=True,
                )
            return self._cast_for_output_type(aggregate=aggregate, expr=aggregate_expr)

        arg_exprs: list[SQL2Expr] = []
        for value in aggregate.args[:-1]:
            resolved = self._resolve_value(value, var_map)
            if resolved is not None:
                arg_exprs.append(resolved)
        if fn == "count" and not arg_exprs:
            aggregate_expr = SQL2FunctionExpr(name="COUNT", args=())
            return self._cast_for_output_type(aggregate=aggregate, expr=aggregate_expr)
        if not arg_exprs:
            arg_exprs = [SQL2NullExpr()]
        aggregate_expr = SQL2FunctionExpr(name=fn_name, args=tuple(arg_exprs))
        return self._cast_for_output_type(aggregate=aggregate, expr=aggregate_expr)

    def _aggregate_window_expr(
        self,
        *,
        aggregate: Aggregate,
        var_map: dict[int, SQL2Expr],
        partition_by: tuple[SQL2Expr, ...],
    ) -> SQL2Expr:
        fn = aggregate.aggregation.name.strip().lower()
        fn_name = {
            "count": "COUNT",
            "sum": "SUM",
            "min": "MIN",
            "max": "MAX",
            "avg": "AVG",
        }.get(fn, aggregate.aggregation.name.upper())

        distinct = False
        args: tuple[SQL2Expr, ...]
        projection_exprs = [
            resolved
            for value in aggregate.projection
            if (resolved := self._resolve_value(value, var_map)) is not None
        ]
        if fn == "count" and projection_exprs:
            distinct = True
            if len(projection_exprs) == 1:
                args = (projection_exprs[0],)
            else:
                args = (SQL2FunctionExpr(name="ROW", args=tuple(projection_exprs)),)
        else:
            arg_exprs: list[SQL2Expr] = []
            for value in aggregate.args[:-1]:
                resolved = self._resolve_value(value, var_map)
                if resolved is not None:
                    arg_exprs.append(resolved)
            args = tuple(arg_exprs)

        window_expr = SQL2WindowExpr(
            name=fn_name,
            args=args,
            partition_by=partition_by,
            order_by=(),
            distinct=distinct,
        )
        return self._cast_for_output_type(aggregate=aggregate, expr=window_expr)

    def _count_projection_non_null_predicates(
        self,
        *,
        aggregate: Aggregate,
        var_map: dict[int, SQL2Expr],
    ) -> list[SQL2Expr]:
        if aggregate.aggregation.name.strip().lower() != "count":
            return []
        predicates: list[SQL2Expr] = []
        for value in aggregate.projection:
            resolved = self._resolve_value(value, var_map)
            if resolved is None:
                continue
            # COUNT(expr) and COUNT(DISTINCT expr) already ignore NULL values.
            # For direct `_id` references this explicit non-null predicate is
            # redundant and can prevent projection-source coalescing.
            if isinstance(resolved, SQL2ColumnExpr) and resolved.column_name == "_id":
                continue
            predicates.append(SQL2BinaryExpr(op="IS NOT", left=resolved, right=SQL2NullExpr()))
        return predicates

    def _cast_for_output_type(self, *, aggregate: Aggregate, expr: SQL2Expr) -> SQL2Expr:
        output_var = aggregate.args[-1] if aggregate.args and isinstance(aggregate.args[-1], Var) else None
        output_type = output_var.type if output_var is not None else None
        if isinstance(output_type, NumberType):
            target_precision = output_type.precision
            target_scale = output_type.scale
            aggregate_name = aggregate.aggregation.name.strip().lower()
            if aggregate_name in {"sum", "avg"}:
                # TEMPORARY: widen aggregate decimal precision in SQL2 lowering to
                # avoid runtime overflow until typer owns aggregate result widths.
                # Remove this once typer emits widened aggregate output types.
                target_precision = max(target_precision, 38)
            target_type = f"DECIMAL({target_precision},{target_scale})"
            inferred_type = self._expr_sql_type(expr)
            if inferred_type is not None and self._same_sql_type(inferred_type, target_type):
                return expr
            if isinstance(expr, SQL2CastExpr) and self._same_sql_type(expr.target_type, target_type):
                return expr
            return SQL2CastExpr(expr=expr, target_type=target_type)
        return expr

    def _construct_id_expr(self, values: tuple[Value, ...], var_map: dict[int, SQL2Expr]) -> SQL2Expr:
        parts = tuple(
            resolved
            for value in values
            if (resolved := self._resolve_value(value, var_map)) is not None
        )
        if not parts:
            concat_expr = SQL2LiteralExpr(value="")
        elif len(parts) == 1:
            concat_expr = parts[0]
        else:
            concat_expr = SQL2FunctionExpr(name="CONCAT", args=parts)
        return SQL2FunctionExpr(name="MD5", args=(concat_expr,))

    def _column_expr_for_update(
        self,
        *,
        analysis: SQL2Analysis,
        update: Update,
        target_column: str,
        ctx: _RuleCtx,
    ) -> SQL2Expr:
        def cast_for_field(ix: int, expr: SQL2Expr) -> SQL2Expr:
            if ix < 0 or ix >= len(update.relation.fields):
                return expr
            sql_type = self._sql_type_for_type(update.relation.fields[ix].type)
            if sql_type is None:
                return expr
            inferred_type = self._expr_sql_type(expr)
            if inferred_type is not None and self._same_sql_type(inferred_type, sql_type):
                return expr
            if isinstance(expr, SQL2CastExpr) and self._same_sql_type(expr.target_type, sql_type):
                return expr
            return SQL2CastExpr(expr=expr, target_type=sql_type)

        property_column = self._property_column_for_relation(analysis, update.relation.id)
        if property_column is not None:
            if target_column == "_id" and update.args:
                resolved = self._resolve_value(update.args[0], ctx.var_map)
                expr = resolved if resolved is not None else SQL2NullExpr()
                return cast_for_field(0, expr)
            if target_column == property_column and update.args:
                resolved = self._resolve_value(update.args[-1], ctx.var_map)
                expr = resolved if resolved is not None else SQL2NullExpr()
                return cast_for_field(len(update.args) - 1, expr)
            return SQL2NullExpr()

        for ix, rel_field in enumerate(update.relation.fields):
            column_name = "_id" if ix == 0 and target_column == "_id" else rel_field.name
            if column_name != target_column:
                continue
            if ix >= len(update.args):
                return cast_for_field(ix, SQL2NullExpr())
            resolved = self._resolve_value(update.args[ix], ctx.var_map)
            expr = resolved if resolved is not None else SQL2NullExpr()
            return cast_for_field(ix, expr)
        return SQL2NullExpr()

    def _resolve_value(self, value: Value, var_map: dict[int, SQL2Expr]) -> SQL2Expr | None:
        if isinstance(value, Var):
            return var_map.get(value.id)
        if isinstance(value, Literal):
            return SQL2LiteralExpr(value=value.value)
        if value is None:
            return SQL2LiteralExpr(value=None)
        return None

    def _table_name_for_relation(self, analysis: SQL2Analysis, relation: Relation) -> str:
        table_info = analysis.model_analysis.get_table_info(relation)
        table_shape = analysis.table_shape.table_for_relation_id(relation.id)
        table_name = analysis.table_shape.table_name_by_relation_id.get(relation.id)
        if table_name is None and table_info is not None:
            table_name = table_info.name
        if table_name is None:
            for super_type in getattr(relation, "super_types", ()):
                super_table_name = analysis.table_shape.table_name_by_relation_id.get(super_type.id)
                if super_table_name is not None:
                    table_name = super_table_name
                    break
        if table_name is None:
            table_name = relation.name
        override = self._table_source_overrides.get(table_name)
        if override is not None:
            return override
        physical_name = self._physical_table_name_by_logical.get(table_name, table_name)
        table_kind = table_info.kind if table_info is not None else (table_shape.table_kind if table_shape is not None else None)
        if self._source_schema_name and table_kind != TableKind.EXTERNAL:
            return f"{self._source_schema_name}.{physical_name}"
        return physical_name

    def _property_column_for_relation(self, analysis: SQL2Analysis, relation_id: int) -> str | None:
        return analysis.table_shape.property_column_by_relation_id.get(relation_id)

    def _lookup_column_name(
        self,
        analysis: SQL2Analysis,
        relation: Relation,
        *,
        field_index: int,
        fallback_name: str,
    ) -> str:
        property_column = analysis.table_shape.property_column_by_relation_id.get(relation.id)
        table_shape = analysis.table_shape.table_for_relation_id(relation.id)
        if property_column is not None:
            if field_index == 0:
                if table_shape is not None and table_shape.key_column_names:
                    return table_shape.key_column_names[0]
                return "_id"
            return property_column
        if table_shape is None:
            return fallback_name
        if relation.id == table_shape.owner_relation_id and field_index == 0:
            if table_shape.table_kind != TableKind.RELATIONSHIP:
                return "_id"
            if table_shape.key_column_names:
                return table_shape.key_column_names[0]
        for column in table_shape.columns:
            if column.source_relation_id == relation.id and column.source_field_index == field_index:
                return column.column_name
        if field_index == 0 and table_shape.key_column_names:
            return table_shape.key_column_names[0]
        return fallback_name

    def _lookup_field_expr(
        self,
        *,
        analysis: SQL2Analysis,
        relation: Relation,
        field_index: int,
        column_name: str,
        table_alias: str,
    ) -> SQL2Expr:
        table_shape = analysis.table_shape.table_for_relation_id(relation.id)
        if (
            table_shape is not None
            and table_shape.table_kind == TableKind.EXTERNAL
            and field_index == 0
            and column_name == "_id"
        ):
            has_physical_id_column = any(
                (not column.is_id) and column.column_name == "_id"
                for column in table_shape.columns
            )
            if not has_physical_id_column:
                return self._external_table_row_identity_expr(table_shape=table_shape, table_alias=table_alias)
        return SQL2ColumnExpr(table_alias=table_alias, column_name=column_name)

    def _lookup_expr_for_column_binding(
        self,
        *,
        analysis: SQL2Analysis,
        relation: Relation,
        column_name: str,
        table_alias: str,
    ) -> SQL2Expr | None:
        for field_index, rel_field in enumerate(relation.fields):
            resolved_column_name = self._lookup_column_name(
                analysis,
                relation,
                field_index=field_index,
                fallback_name=rel_field.name,
            )
            if resolved_column_name != column_name:
                continue
            return self._lookup_field_expr(
                analysis=analysis,
                relation=relation,
                field_index=field_index,
                column_name=column_name,
                table_alias=table_alias,
            )
        return None

    def _external_table_row_identity_expr(self, *, table_shape, table_alias: str) -> SQL2Expr:
        if self._prefer_native_row_hash_identity:
            value_columns = [
                SQL2ColumnExpr(table_alias=table_alias, column_name=column.column_name)
                for column in table_shape.columns
                if not column.is_id
            ]
            primary_hash_args: tuple[SQL2Expr, ...]
            secondary_hash_args: tuple[SQL2Expr, ...]
            if value_columns:
                primary_hash_args = tuple(value_columns)
                secondary_hash_args = (
                    SQL2LiteralExpr("__sql2_rowhash_salt__"),
                    *tuple(reversed(value_columns)),
                )
            else:
                primary_hash_args = (SQL2LiteralExpr(0),)
                secondary_hash_args = (SQL2LiteralExpr("__sql2_rowhash_empty__"), SQL2LiteralExpr(0))
            primary_hash = SQL2FunctionExpr(name="HASH", args=primary_hash_args)
            secondary_hash = SQL2FunctionExpr(name="HASH", args=secondary_hash_args)
            return SQL2FunctionExpr(
                name="MD5",
                args=(
                    SQL2FunctionExpr(
                        name="CONCAT",
                        args=(
                            SQL2LiteralExpr(table_shape.table_name),
                            SQL2LiteralExpr("|"),
                            SQL2CastExpr(expr=primary_hash, target_type="VARCHAR"),
                            SQL2LiteralExpr("|"),
                            SQL2CastExpr(expr=secondary_hash, target_type="VARCHAR"),
                        ),
                    ),
                ),
            )

        concat_args: list[SQL2Expr] = [SQL2LiteralExpr(table_shape.table_name)]
        for column in table_shape.columns:
            if column.is_id:
                continue
            concat_args.append(SQL2LiteralExpr("|"))
            concat_args.append(
                SQL2FunctionExpr(
                    name="COALESCE",
                    args=(
                        SQL2CastExpr(
                            expr=SQL2ColumnExpr(table_alias=table_alias, column_name=column.column_name),
                            target_type="VARCHAR",
                        ),
                        SQL2LiteralExpr("__NULL__"),
                    ),
                )
            )
        if len(concat_args) == 1:
            concat_args.extend((SQL2LiteralExpr("|"), SQL2LiteralExpr("0")))
        return SQL2FunctionExpr(
            name="MD5",
            args=(SQL2FunctionExpr(name="CONCAT", args=tuple(concat_args)),),
        )

    def _state_view_name_for_group(
        self,
        *,
        table_name: str,
        stratum_id: int,
        ordinal: int,
        schema_name: str | None,
    ) -> str:
        safe_table = self._safe_table_token(table_name)
        base = f"sql2_s{stratum_id}_{safe_table}_state_{ordinal}"
        if schema_name:
            return f"{schema_name}.{base}"
        return base

    def _deferred_seed_view_name(
        self,
        *,
        table_name: str,
        stratum_id: int,
        schema_name: str | None,
    ) -> str:
        safe_table = self._safe_table_token(table_name)
        base = f"sql2_seed_s{stratum_id}_{safe_table}"
        if schema_name:
            return f"{schema_name}.{base}"
        return base

    def _initial_seed_view_name(
        self,
        *,
        table_name: str,
        stratum_id: int,
        schema_name: str | None,
    ) -> str:
        safe_table = self._safe_table_token(table_name)
        base = f"sql2_seed_init_s{stratum_id}_{safe_table}"
        if schema_name:
            return f"{schema_name}.{base}"
        return base

    def _public_view_name(self, *, table_name: str, schema_name: str | None) -> str:
        physical_name = self._physical_table_name_by_logical.get(table_name, table_name)
        if schema_name:
            return f"{schema_name}.{physical_name}"
        return physical_name

    def _build_physical_table_name_map(self, analysis: SQL2Analysis) -> dict[str, str]:
        logical_names: set[str] = set()
        logical_names.update(table.table_name for table in analysis.table_shape.tables)
        for groups in analysis.normalized.groups_by_stratum.values():
            for group in groups:
                logical_names.add(group.table_name)

        by_casefold: dict[str, list[str]] = {}
        for name in sorted(logical_names):
            by_casefold.setdefault(name.casefold(), []).append(name)

        mapping: dict[str, str] = {}
        for _folded, names in by_casefold.items():
            if len(names) == 1:
                mapping[names[0]] = names[0]
                continue
            for ix, name in enumerate(names, start=1):
                mapping[name] = f"{name}__{ix}"
        return mapping

    def _safe_table_token(self, table_name: str) -> str:
        physical_name = self._physical_table_name_by_logical.get(table_name, table_name)
        return re.sub(r"[^A-Za-z0-9_]+", "_", physical_name).strip("_") or "table"

    def _inherited_empty_columns(self, *, analysis: SQL2Analysis, relation: Relation) -> tuple[str, ...]:
        pending = list(getattr(relation, "super_types", ()) or ())
        seen: set[int] = set()
        while pending:
            super_type = pending.pop(0)
            if super_type.id in seen:
                continue
            seen.add(super_type.id)
            table_shape = analysis.table_shape.table_for_relation_id(super_type.id)
            if table_shape is not None:
                columns = tuple(column.column_name for column in table_shape.columns)
                if columns:
                    return columns
            pending.extend(getattr(super_type, "super_types", ()) or ())
        return ("_id",)
