def METHOD_NAME(
        stmt: irast.SelectStmt, *,
        ctx: context.CompilerContextLevel) -> pgast.BaseExpr:
    if ctx.singleton_mode:
        return dispatch.compile(stmt.result, ctx=ctx)
    parent_ctx = ctx
    with parent_ctx.substmt() as ctx:
        # Common setup.
        clauses.compile_dml_bindings(stmt, ctx=ctx)
        query = ctx.stmt
        # Process materialized sets
        clauses.compile_materialized_exprs(query, stmt, ctx=ctx)
        iterator_set = stmt.iterator_stmt
        last_iterator: Optional[irast.Set] = None
        if iterator_set:
            if irutils.contains_dml(stmt):
                # If we have iterators and we contain nested DML
                # statements, we need to hoist the iterators into CTEs and
                # then explicitly join them back into the query.
                iterator = dml.compile_iterator_cte(iterator_set, ctx=ctx)
                ctx.path_scope = ctx.path_scope.new_child()
                dml.merge_iterator(iterator, ctx.rel, ctx=ctx)
                ctx.enclosing_cte_iterator = iterator
                last_iterator = stmt.iterator_stmt
            else:
                # Process FOR clause.
                with ctx.new() as ictx:
                    clauses.setup_iterator_volatility(last_iterator, ctx=ictx)
                    iterator_rvar = clauses.compile_iterator_expr(
                        query, iterator_set, ctx=ictx)
                for aspect in {'identity', 'value'}:
                    pathctx.put_path_rvar(
                        query,
                        path_id=iterator_set.path_id,
                        rvar=iterator_rvar,
                        aspect=aspect,
                    )
                last_iterator = iterator_set
        # Process the result expression.
        with ctx.new() as ictx:
            clauses.setup_iterator_volatility(last_iterator, ctx=ictx)
            outvar = clauses.compile_output(stmt.result, ctx=ictx)
        with ctx.new() as ictx:
            # FILTER and ORDER BY need to have the base result as a
            # volatility ref.
            clauses.setup_iterator_volatility(stmt.result, ctx=ictx)
            # The FILTER clause.
            if stmt.where is not None:
                query.where_clause = astutils.extend_binop(
                    query.where_clause,
                    clauses.compile_filter_clause(
                        stmt.where, stmt.where_card, ctx=ictx))
            # The ORDER BY clause
            if stmt.orderby is not None:
                with ictx.new() as octx:
                    query.sort_clause = clauses.compile_orderby_clause(
                        stmt.orderby, ctx=octx)
        if outvar.nullable and query is ctx.toplevel_stmt:
            # A nullable var has bubbled up to the top,
            # filter out NULLs.
            valvar: pgast.BaseExpr = pathctx.get_path_value_var(
                query, stmt.result.path_id, env=ctx.env)
            if isinstance(valvar, pgast.TupleVar):
                valvar = pgast.ImplicitRowExpr(
                    args=[e.val for e in valvar.elements])
            query.where_clause = astutils.extend_binop(
                query.where_clause,
                pgast.NullTest(arg=valvar, negated=True)
            )
        # The OFFSET clause
        query.limit_offset = clauses.compile_limit_offset_clause(
            stmt.offset, ctx=ctx)
        # The LIMIT clause
        query.limit_count = clauses.compile_limit_offset_clause(
            stmt.limit, ctx=ctx)
    return query