Skip to content

Commit 896eb5e

Browse files
committed
In the planner, delete joinaliasvars lists after we're done with them.
Although joinaliasvars lists coming out of the parser are quite simple, those lists can contain arbitrarily complex expressions after subquery pullup. We do not perform expression preprocessing on them, meaning that expressions in those lists will not meet the expectations of later phases of the planner (for example, that they do not contain SubLinks). This had been thought pretty harmless, since we don't intentionally touch those lists in later phases --- but Andreas Seltenreich found a case in which adjust_appendrel_attrs() could recurse into a joinaliasvars list and then die on its assertion that it never sees a SubLink. We considered a couple of localized fixes to prevent that specific case from looking at the joinaliasvars lists, but really this seems like a generic hazard for all expression processing in the planner. Therefore, probably the best answer is to delete the joinaliasvars lists from the parsetree at the end of expression preprocessing, so that there are no reachable expressions that haven't been through preprocessing. The case Andreas found seems to be harmless in non-Assert builds, and so far there are no field reports suggesting that there are user-visible effects in other cases. I considered back-patching this anyway, but it turns out that Andreas' test doesn't fail at all in 9.4-9.6, because in those versions adjust_appendrel_attrs contains code (added in commit 842faa7 and removed again in commit 215b43c) to process SubLinks rather than complain about them. Barring discovery of another path by which unprocessed joinaliasvars lists can cause trouble, the most prudent compromise seems to be to patch this into v10 but not further. Patch by me, with thanks to Amit Langote for initial investigation and review. Discussion: https://postgr.es/m/87r2tvt9f1.fsf@ansel.ydns.eu
1 parent a32c092 commit 896eb5e

File tree

3 files changed

+59
-5
lines changed

3 files changed

+59
-5
lines changed

src/backend/optimizer/plan/planner.c

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -776,6 +776,27 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
776776
}
777777
}
778778

779+
/*
780+
* Now that we are done preprocessing expressions, and in particular done
781+
* flattening join alias variables, get rid of the joinaliasvars lists.
782+
* They no longer match what expressions in the rest of the tree look
783+
* like, because we have not preprocessed expressions in those lists (and
784+
* do not want to; for example, expanding a SubLink there would result in
785+
* a useless unreferenced subplan). Leaving them in place simply creates
786+
* a hazard for later scans of the tree. We could try to prevent that by
787+
* using QTW_IGNORE_JOINALIASES in every tree scan done after this point,
788+
* but that doesn't sound very reliable.
789+
*/
790+
if (root->hasJoinRTEs)
791+
{
792+
foreach(l, parse->rtable)
793+
{
794+
RangeTblEntry *rte = lfirst_node(RangeTblEntry, l);
795+
796+
rte->joinaliasvars = NIL;
797+
}
798+
}
799+
779800
/*
780801
* In some cases we may want to transfer a HAVING clause into WHERE. We
781802
* cannot do so if the HAVING clause contains aggregates (obviously) or
@@ -902,11 +923,12 @@ preprocess_expression(PlannerInfo *root, Node *expr, int kind)
902923

903924
/*
904925
* If the query has any join RTEs, replace join alias variables with
905-
* base-relation variables. We must do this before sublink processing,
906-
* else sublinks expanded out from join aliases would not get processed.
907-
* We can skip it in non-lateral RTE functions, VALUES lists, and
908-
* TABLESAMPLE clauses, however, since they can't contain any Vars of the
909-
* current query level.
926+
* base-relation variables. We must do this first, since any expressions
927+
* we may extract from the joinaliasvars lists have not been preprocessed.
928+
* For example, if we did this after sublink processing, sublinks expanded
929+
* out from join aliases would not get processed. But we can skip this in
930+
* non-lateral RTE functions, VALUES lists, and TABLESAMPLE clauses, since
931+
* they can't contain any Vars of the current query level.
910932
*/
911933
if (root->hasJoinRTEs &&
912934
!(kind == EXPRKIND_RTFUNC ||

src/test/regress/expected/subselect.out

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -678,6 +678,20 @@ with q as (select max(f1) from int4_tbl group by f1 order by f1)
678678
(2147483647)
679679
(5 rows)
680680

681+
--
682+
-- Test case for sublinks pulled up into joinaliasvars lists in an
683+
-- inherited update/delete query
684+
--
685+
begin; -- this shouldn't delete anything, but be safe
686+
delete from road
687+
where exists (
688+
select 1
689+
from
690+
int4_tbl cross join
691+
( select f1, array(select q1 from int8_tbl) as arr
692+
from text_tbl ) ss
693+
where road.name = ss.f1 );
694+
rollback;
681695
--
682696
-- Test case for sublinks pushed down into subselects via join alias expansion
683697
--

src/test/regress/sql/subselect.sql

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,24 @@ select q from (select max(f1) from int4_tbl group by f1 order by f1) q;
376376
with q as (select max(f1) from int4_tbl group by f1 order by f1)
377377
select q from q;
378378

379+
--
380+
-- Test case for sublinks pulled up into joinaliasvars lists in an
381+
-- inherited update/delete query
382+
--
383+
384+
begin; -- this shouldn't delete anything, but be safe
385+
386+
delete from road
387+
where exists (
388+
select 1
389+
from
390+
int4_tbl cross join
391+
( select f1, array(select q1 from int8_tbl) as arr
392+
from text_tbl ) ss
393+
where road.name = ss.f1 );
394+
395+
rollback;
396+
379397
--
380398
-- Test case for sublinks pushed down into subselects via join alias expansion
381399
--

0 commit comments

Comments
 (0)