@@ -451,56 +451,92 @@ impl Unparser<'_> {
451451 /// Whether the subtree carrying a row limit needs a `SELECT` of its own.
452452 ///
453453 /// The walk is top-down, so any predicate already on `select` came from a
454- /// node *above* the one being unparsed . `WHERE`, `HAVING` and `QUALIFY`
455- /// are all evaluated before `LIMIT`/`OFFSET`, while the plan says the
456- /// opposite: the limit runs first and the predicate filters what it
457- /// produced. Keeping both in one `SELECT` therefore states the reverse of
458- /// the plan, and can return rows the plan excludes.
454+ /// node visited earlier . `WHERE`, `HAVING` and `QUALIFY` are all evaluated
455+ /// before `LIMIT`/`OFFSET`, while a plan that puts a filter above a limit
456+ /// says the opposite: the limit runs first and the predicate filters what
457+ /// it produced. Keeping both in one `SELECT` therefore states the reverse
458+ /// of the plan, and can return rows the plan excludes.
459459 ///
460- /// A `WHERE` can be left behind in an enclosing query while the limited
461- /// subtree moves into a derived table. `HAVING` and `QUALIFY` cannot: they
462- /// reference an aggregate or window expression that only the `SELECT`
463- /// computing it can name. Refuse those rather than emit either the
464- /// reversed form or a predicate with nothing to bind to.
465- fn row_limit_needs_own_scope ( select : & SelectBuilder ) -> Result < bool > {
466- if select. has_grouped_predicate ( ) {
460+ /// A `WHERE` can stay in the enclosing query while the limited subtree
461+ /// moves into a derived table. `HAVING` and `QUALIFY` cannot: they name an
462+ /// aggregate or window expression that only the `SELECT` computing it can
463+ /// name. Refuse those rather than emit the reversed form — but only when
464+ /// the grouping they filter is in fact below this limit. Join inputs are
465+ /// walked with one shared `SelectBuilder`, so a predicate on it may have
466+ /// come from a sibling input rather than from an ancestor of this node.
467+ fn row_limit_needs_own_scope (
468+ plan : & LogicalPlan ,
469+ select : & SelectBuilder ,
470+ ) -> Result < bool > {
471+ if select. has_grouped_predicate ( )
472+ && ( find_agg_node_within_select ( plan, select. already_projected ( ) ) . is_some ( )
473+ || find_window_nodes_within_select (
474+ plan,
475+ None ,
476+ select. already_projected ( ) ,
477+ )
478+ . is_some ( ) )
479+ {
467480 return not_impl_err ! (
468481 "Unparsing a HAVING or QUALIFY predicate that is applied after a row limit is not supported"
469482 ) ;
470483 }
471484 Ok ( select. has_selection ( ) )
472485 }
473486
474- /// Unparses `plan` — a `Limit`, or a `Sort` carrying a `fetch` — as a
475- /// derived table, so the predicate already on the enclosing `SELECT`
476- /// applies to the limited rows rather than to the rows feeding the limit.
487+ /// Unparses a `Limit` as a derived table, so the `WHERE` already on the
488+ /// enclosing `SELECT` applies to the limited rows rather than to the rows
489+ /// feeding the limit.
490+ ///
491+ /// The derived table takes the name of the relation it reads, because the
492+ /// predicate staying outside is still qualified by that name, and its
493+ /// columns are listed explicitly: a wildcard would expand to every
494+ /// relation in the enclosing `FROM`, not to this one's contribution.
477495 ///
478- /// The derived table takes the name of the relation the subtree reads,
479- /// because the predicate staying outside is still qualified by it. A
480- /// subtree reading more than one relation has no such name to take: every
481- /// qualifier the predicate could carry would be gone from scope, so the
482- /// query is refused rather than rendered unbindable.
496+ /// Both of those need the derived table's output columns to be exactly the
497+ /// relation's own columns, under their own names — which is why only a
498+ /// scan, and the clauses that can wrap one without renaming anything, are
499+ /// accepted here. A projection may emit a column the derived query never
500+ /// names (an unaliased expression) or two columns that differ only by a
501+ /// qualifier SQL cannot carry across the boundary; a join, union or
502+ /// aggregate has no single name for the alias to take. Those are refused,
503+ /// which costs the pushdown but never the rows.
483504 fn derive_row_limited_scope (
484505 & self ,
485506 plan : & LogicalPlan ,
486507 select : & mut SelectBuilder ,
487508 relation : & mut RelationBuilder ,
488509 ) -> Result < ( ) > {
489- let Some ( table_ref) = Self :: sole_relation_of ( plan) else {
510+ let Some ( table_ref) = Self :: scanned_relation_of ( plan) else {
490511 return not_impl_err ! (
491- "Unparsing a filter applied after a row limit is not supported when the limited input reads more than one relation "
512+ "Unparsing a filter applied after a row limit is only supported when the limited input is a single table scan "
492513 ) ;
493514 } ;
494515
516+ // Only the last component survives as an alias, so a predicate spelled
517+ // with the full path would be left pointing at a name that is gone.
518+ if self . dialect . full_qualified_col ( ) && table_ref. to_vec ( ) . len ( ) > 1 {
519+ return not_impl_err ! (
520+ "Unparsing a filter applied after a row limit is not supported for a qualified table name on a dialect that spells columns in full"
521+ ) ;
522+ }
523+
524+ // A scan can project no columns at all, which every other empty
525+ // projection in this unparser renders as `SELECT 1`. There is no
526+ // column list to name a derived table's output with here, so refuse.
527+ // (Two columns of one name cannot arrive: `DFSchema` rejects a scan
528+ // with a duplicate qualified field.)
529+ let fields = plan. schema ( ) . fields ( ) ;
530+ if fields. is_empty ( ) {
531+ return not_impl_err ! (
532+ "Unparsing a filter applied after a row limit is not supported for an input projecting no columns"
533+ ) ;
534+ }
535+
495536 // The subtree moves into a statement of its own, so this `SELECT` no
496- // longer receives a projection from the nodes below. Name the derived
497- // table's columns explicitly, as the scan underneath would have: a
498- // wildcard here would expand to every relation in the enclosing `FROM`
499- // — every column of a join, not this side's contribution to it.
537+ // longer receives a projection from the nodes below it.
500538 if !select. already_projected ( ) {
501- let items = plan
502- . schema ( )
503- . fields ( )
539+ let items = fields
504540 . iter ( )
505541 . map ( |field| {
506542 self . select_item_to_sql ( & Expr :: Column ( Column :: new (
@@ -520,22 +556,24 @@ impl Unparser<'_> {
520556 )
521557 }
522558
523- /// The single relation a subtree reads from, if it reads exactly one.
559+ /// The relation a subtree scans, when the subtree is one scan under
560+ /// clauses that neither rename nor add columns and neither reorder nor
561+ /// combine rows from elsewhere.
524562 ///
525- /// Only the shapes that can sit between a limit and its source are walked
526- /// through; anything else (a join, a union, a set operation) has no single
527- /// name to answer with, and neither does a subtree reading two tables .
528- fn sole_relation_of ( plan : & LogicalPlan ) -> Option < TableReference > {
563+ /// A `Sort` is deliberately not walked through. It is the one such clause
564+ /// whose effect does not survive being wrapped: SQL does not carry a
565+ /// derived table's row order into the query selecting from it .
566+ fn scanned_relation_of ( plan : & LogicalPlan ) -> Option < TableReference > {
529567 match plan {
530568 LogicalPlan :: TableScan ( scan) => Some ( scan. table_name . clone ( ) ) ,
531- LogicalPlan :: SubqueryAlias ( alias) => Some ( alias. alias . clone ( ) ) ,
532- LogicalPlan :: Limit ( limit) => Self :: sole_relation_of ( limit. input . as_ref ( ) ) ,
533- LogicalPlan :: Filter ( filter) => Self :: sole_relation_of ( filter. input . as_ref ( ) ) ,
534- LogicalPlan :: Sort ( sort) => Self :: sole_relation_of ( sort. input . as_ref ( ) ) ,
535- LogicalPlan :: Projection ( projection) => {
536- Self :: sole_relation_of ( projection. input . as_ref ( ) )
569+ LogicalPlan :: SubqueryAlias ( alias) => {
570+ Self :: scanned_relation_of ( alias. input . as_ref ( ) )
571+ . map ( |_| alias. alias . clone ( ) )
572+ }
573+ LogicalPlan :: Limit ( limit) => Self :: scanned_relation_of ( limit. input . as_ref ( ) ) ,
574+ LogicalPlan :: Filter ( filter) => {
575+ Self :: scanned_relation_of ( filter. input . as_ref ( ) )
537576 }
538- LogicalPlan :: Distinct ( distinct) => Self :: sole_relation_of ( distinct. input ( ) ) ,
539577 _ => None ,
540578 }
541579 }
@@ -894,7 +932,7 @@ impl Unparser<'_> {
894932 ) ;
895933 }
896934 if ( limit. fetch . is_some ( ) || limit. skip . is_some ( ) )
897- && Self :: row_limit_needs_own_scope ( select) ?
935+ && Self :: row_limit_needs_own_scope ( plan , select) ?
898936 {
899937 return self . derive_row_limited_scope ( plan, select, relation) ;
900938 }
@@ -940,10 +978,16 @@ impl Unparser<'_> {
940978 }
941979 // A `Sort` carrying a `fetch` renders that fetch as this
942980 // query's `LIMIT`, so it reorders against a predicate above it
943- // exactly as a `Limit` node does. A sort without one does not:
944- // `ORDER BY` is evaluated after `WHERE` either way.
945- if sort. fetch . is_some ( ) && Self :: row_limit_needs_own_scope ( select) ? {
946- return self . derive_row_limited_scope ( plan, select, relation) ;
981+ // exactly as a `Limit` node does — but it cannot be moved into
982+ // a derived table the way a `Limit` can, because SQL does not
983+ // carry a derived table's row order out to the query selecting
984+ // from it. (A sort without a fetch reorders nothing: `ORDER BY`
985+ // is evaluated after `WHERE` either way.)
986+ if sort. fetch . is_some ( ) && Self :: row_limit_needs_own_scope ( plan, select) ?
987+ {
988+ return not_impl_err ! (
989+ "Unparsing a filter applied after a sort's fetch is not supported"
990+ ) ;
947991 }
948992
949993 let Some ( query_ref) = query else {
0 commit comments