Обсуждение: BUG #17768: Assert triggered on initsplan.c

Поиск
Список
Период
Сортировка

BUG #17768: Assert triggered on initsplan.c

От
PG Bug reporting form
Дата:
The following bug has been logged on the website:

Bug reference:      17768
Logged by:          Robins Tharakan
Email address:      tharakan@gmail.com
PostgreSQL version: 15.1
Operating system:   Ubuntu 20.04
Description:

This assert() is not easily reproducible, but thought may be of interest. 
Can provide full backtrace, if required.

Git: 253432f426@master
OS: Ubuntu 20.04

TRAP: failed Assert("j->jointype == JOIN_INNER"), File: "initsplan.c", Line:
1206, PID: 3285978

Backtrace / backtrace full excerpt / SQL below.

Backtrace
=========
Core was generated by `postgres: 253432f426@master@sqith: u73 postgres
127.0.0.1(35840) SELECT       '.
Program terminated with signal SIGABRT, Aborted.
#0  __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
#0  __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
#1  0x00007f1d76f78859 in __GI_abort () at abort.c:79
#2  0x0000562684d12e9a in ExceptionalCondition (
    conditionName=0x562684ec7d20 "j->jointype == JOIN_INNER",
    fileName=0x562684ec7b3a "initsplan.c", lineNumber=1206) at assert.c:66
#3  0x00005626849ead15 in deconstruct_distribute (root=0x56268751f078,
    jtitem=0x5626870ff6a8, postponed_qual_list=0x7ffc7e6196c0)
    at initsplan.c:1206
#4  0x00005626849e9b3d in deconstruct_jointree (root=0x56268751f078)
    at initsplan.c:783
#5  0x00005626849eeae4 in query_planner (root=0x56268751f078,
    qp_callback=0x5626849f53c9 <standard_qp_callback>,
    qp_extra=0x7ffc7e6198e0) at planmain.c:186
#6  0x00005626849f1610 in grouping_planner (root=0x56268751f078,
    tuple_fraction=0) at planner.c:1496
#7  0x00005626849f0cbf in subquery_planner (glob=0x5626870b6cf8,
    parse=0x5626870b6e08, parent_root=0x0, hasRecursion=false,
    tuple_fraction=0) at planner.c:1065
#8  0x00005626849ef277 in standard_planner (parse=0x5626870b6e08,
    query_string=0x562686a6d188 "select  \n  public.constant_refcursor() as
c0\nfrom \n  public.rtest_t4 as ref_0,\n  lateral (select  \n
pg_catalog.nummultirange() as c0\n      from>
#9  0x00007f1d73f6608b in pgss_planner (parse=0x5626870b6e08,


Backtrace full excerpt
=================
#2  0x0000562684d12e9a in ExceptionalCondition (
    conditionName=0x562684ec7d20 "j->jointype == JOIN_INNER",
    fileName=0x562684ec7b3a "initsplan.c", lineNumber=1206) at assert.c:66
No locals.
#3  0x00005626849ead15 in deconstruct_distribute (root=0x56268751f078,
    jtitem=0x5626870ff6a8, postponed_qual_list=0x7ffc7e6196c0)
    at initsplan.c:1206
        pq = 0x5626870ffaf8
        l__state = {l = 0x5626870ffb18, i = 0}
        ojscope = 0x0
        j = 0x5626870f5228
        new_postponed_quals = 0x0
        my_quals = 0x0
        sjinfo = 0x0
        postponed_oj_qual_list = 0x0
        l = 0x5626870ffb30
        jtnode = 0x5626870f5228
        __func__ = "deconstruct_distribute"
#4  0x00005626849e9b3d in deconstruct_jointree (root=0x56268751f078)
    at initsplan.c:783
        jtitem = 0x5626870ff6a8
        lc__state = {l = 0x5626870ff018, i = 6}
        result = 0x5626870ff2f8
        top_jdomain = 0x56268751f628
        item_list = 0x5626870ff018
        postponed_qual_list = 0x5626870ffb18
        lc = 0x5626870ffa48
#5  0x00005626849eeae4 in query_planner (root=0x56268751f078,
    qp_callback=0x5626849f53c9 <standard_qp_callback>,
    qp_extra=0x7ffc7e6198e0) at planmain.c:186
        parse = 0x5626870b6e08
        joinlist = 0x562684d985e0 <__libc_csu_init>
        final_rel = 0x7ffc7e619790
        __func__ = "query_planner"





SQL
===
select
  public.constant_refcursor() as c0
from
  public.rtest_t4 as ref_0,
  lateral (select
        pg_catalog.nummultirange() as c0
      from
        public.mic_inputs as sample_0 tablesample system (3.1)
            inner join pg_catalog.pg_subscription_rel as sample_1
tablesample bernoulli (8.3)
            on (ref_0.b is NULL)
          inner join sqithsch16.chr as sample_2 tablesample bernoulli
(5.2)
            right join public.num_result as ref_1
            on ((select s from public.lseg_tbl limit 1 offset 2)
                   <> (select lseg from public.tab_core_types limit 1 offset
1)
                  )
          on (true)
      where false) as subq_0
where false;

Thanks to SQLSmith for the find.

-
Robins Tharakan
Amazon Web Services


Re: BUG #17768: Assert triggered on initsplan.c

От
Richard Guo
Дата:

On Thu, Feb 2, 2023 at 3:34 PM PG Bug reporting form <noreply@postgresql.org> wrote:
The following bug has been logged on the website:

Bug reference:      17768
Logged by:          Robins Tharakan
Email address:      tharakan@gmail.com
PostgreSQL version: 15.1
Operating system:   Ubuntu 20.04
Description:       

This assert() is not easily reproducible, but thought may be of interest.
Can provide full backtrace, if required.

Git: 253432f426@master
OS: Ubuntu 20.04

TRAP: failed Assert("j->jointype == JOIN_INNER"), File: "initsplan.c", Line:
1206, PID: 3285978
 
Thanks for the report!  I can reproduce this issue with the following
query.

create table t1(a int);
create table t2(a int);
create table t3(a int);
create table t4(a int);
create table t5(a int);

select * from t1, lateral (select * from t2 inner join t3 on t1.a = 1 inner join (t4 right join t5 on true) on true) as sub;

There is something wrong about postponing quals.  We are supposed to
postpone quals only from children to their parent nodes.  But here in
deconstruct_distribute we scan all the jointree nodes in depth-first
traversal order and any quals postponed by left children may be checked
against right children.  If the right subtree is an outer join as in
this example, the assertion would be triggered.

Thanks
Richard

Re: BUG #17768: Assert triggered on initsplan.c

От
Richard Guo
Дата:

On Thu, Feb 2, 2023 at 5:13 PM Richard Guo <guofenglinux@gmail.com> wrote:

On Thu, Feb 2, 2023 at 3:34 PM PG Bug reporting form <noreply@postgresql.org> wrote:
The following bug has been logged on the website:

Bug reference:      17768
Logged by:          Robins Tharakan
Email address:      tharakan@gmail.com
PostgreSQL version: 15.1
Operating system:   Ubuntu 20.04
Description:       

This assert() is not easily reproducible, but thought may be of interest.
Can provide full backtrace, if required.

Git: 253432f426@master
OS: Ubuntu 20.04

TRAP: failed Assert("j->jointype == JOIN_INNER"), File: "initsplan.c", Line:
1206, PID: 3285978
 
Thanks for the report!  I can reproduce this issue with the following
query.

create table t1(a int);
create table t2(a int);
create table t3(a int);
create table t4(a int);
create table t5(a int);

select * from t1, lateral (select * from t2 inner join t3 on t1.a = 1 inner join (t4 right join t5 on true) on true) as sub;

There is something wrong about postponing quals.  We are supposed to
postpone quals only from children to their parent nodes.  But here in
deconstruct_distribute we scan all the jointree nodes in depth-first
traversal order and any quals postponed by left children may be checked
against right children.  If the right subtree is an outer join as in
this example, the assertion would be triggered.
 
To fix this issue, I'm considering that we can add two types of info in
JoinTreeItem for each jointree node, one is the jointree's children, and
one is the list of quals that need to be postponed at this join level.
Thus in deconstruct_distribute we can process the quals postponed by
children.

Attach a draft patch (with no tests, no comments, no formats yet) to
show what I'm thinking.

Thanks
Richard
Вложения

Re: BUG #17768: Assert triggered on initsplan.c

От
Tom Lane
Дата:
Richard Guo <guofenglinux@gmail.com> writes:
>> There is something wrong about postponing quals.  We are supposed to
>> postpone quals only from children to their parent nodes.  But here in
>> deconstruct_distribute we scan all the jointree nodes in depth-first
>> traversal order and any quals postponed by left children may be checked
>> against right children.  If the right subtree is an outer join as in
>> this example, the assertion would be triggered.

> To fix this issue, I'm considering that we can add two types of info in
> JoinTreeItem for each jointree node, one is the jointree's children, and
> one is the list of quals that need to be postponed at this join level.
> Thus in deconstruct_distribute we can process the quals postponed by
> children.

I've not looked closely at this, but ... I remember thinking while
revising deconstruct_jointree that the whole PostponedQual mechanism
was a wart we should try to get rid of.  I didn't touch it since
I saw no obvious bugs and the patch was too large already, but maybe
now is the time to try harder.  In any case I suspect you are right
that the core of this issue is that we're dealing with PostponedQuals
in the wrong order now.

            regards, tom lane



Re: BUG #17768: Assert triggered on initsplan.c

От
Tom Lane
Дата:
I wrote:
> I've not looked closely at this, but ... I remember thinking while
> revising deconstruct_jointree that the whole PostponedQual mechanism
> was a wart we should try to get rid of.  I didn't touch it since
> I saw no obvious bugs and the patch was too large already, but maybe
> now is the time to try harder.

Here's a draft patch (no test cases as yet) that does it like that.
I think this is morally equivalent to what you did, but perhaps a
bit neater and faster.

            regards, tom lane

diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index 35b2dc1034..d61b827f02 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -62,6 +62,8 @@ typedef struct JoinTreeItem
     /* Fields filled during deconstruct_recurse: */
     Node       *jtnode;            /* jointree node to examine */
     JoinDomain *jdomain;        /* join domain for its ON/WHERE clauses */
+    struct JoinTreeItem *jti_parent;    /* JoinTreeItem for this node's
+                                         * parent, or NULL if it's the top */
     Relids        qualscope;        /* base+OJ Relids syntactically included in
                                  * this jointree node */
     Relids        inner_join_rels;    /* base+OJ Relids syntactically included
@@ -74,26 +76,20 @@ typedef struct JoinTreeItem
     /* Fields filled during deconstruct_distribute: */
     SpecialJoinInfo *sjinfo;    /* if outer join, its SpecialJoinInfo */
     List       *oj_joinclauses; /* outer join quals not yet distributed */
+    List       *lateral_clauses;    /* quals postponed from children due to
+                                     * lateral references */
 } JoinTreeItem;

-/* Elements of the postponed_qual_list used during deconstruct_distribute */
-typedef struct PostponedQual
-{
-    Node       *qual;            /* a qual clause waiting to be processed */
-    Relids        relids;            /* the set of baserels it references */
-} PostponedQual;
-

 static void extract_lateral_references(PlannerInfo *root, RelOptInfo *brel,
                                        Index rtindex);
 static List *deconstruct_recurse(PlannerInfo *root, Node *jtnode,
                                  JoinDomain *parent_domain,
+                                 JoinTreeItem *parent_jtitem,
                                  List **item_list);
-static void deconstruct_distribute(PlannerInfo *root, JoinTreeItem *jtitem,
-                                   List **postponed_qual_list);
+static void deconstruct_distribute(PlannerInfo *root, JoinTreeItem *jtitem);
 static void process_security_barrier_quals(PlannerInfo *root,
-                                           int rti, Relids qualscope,
-                                           JoinDomain *jdomain);
+                                           int rti, JoinTreeItem *jtitem);
 static void mark_rels_nulled_by_join(PlannerInfo *root, Index ojrelid,
                                      Relids lower_rels);
 static SpecialJoinInfo *make_outerjoininfo(PlannerInfo *root,
@@ -107,7 +103,7 @@ static void deconstruct_distribute_oj_quals(PlannerInfo *root,
                                             List *jtitems,
                                             JoinTreeItem *jtitem);
 static void distribute_quals_to_rels(PlannerInfo *root, List *clauses,
-                                     JoinDomain *jdomain,
+                                     JoinTreeItem *jtitem,
                                      SpecialJoinInfo *sjinfo,
                                      Index security_level,
                                      Relids qualscope,
@@ -116,10 +112,9 @@ static void distribute_quals_to_rels(PlannerInfo *root, List *clauses,
                                      bool allow_equivalence,
                                      bool has_clone,
                                      bool is_clone,
-                                     List **postponed_qual_list,
                                      List **postponed_oj_qual_list);
 static void distribute_qual_to_rels(PlannerInfo *root, Node *clause,
-                                    JoinDomain *jdomain,
+                                    JoinTreeItem *jtitem,
                                     SpecialJoinInfo *sjinfo,
                                     Index security_level,
                                     Relids qualscope,
@@ -128,7 +123,6 @@ static void distribute_qual_to_rels(PlannerInfo *root, Node *clause,
                                     bool allow_equivalence,
                                     bool has_clone,
                                     bool is_clone,
-                                    List **postponed_qual_list,
                                     List **postponed_oj_qual_list);
 static bool check_redundant_nullability_qual(PlannerInfo *root, Node *clause);
 static void check_mergejoinable(RestrictInfo *restrictinfo);
@@ -742,7 +736,6 @@ deconstruct_jointree(PlannerInfo *root)
     List       *result;
     JoinDomain *top_jdomain;
     List       *item_list = NIL;
-    List       *postponed_qual_list = NIL;
     ListCell   *lc;

     /*
@@ -766,7 +759,7 @@ deconstruct_jointree(PlannerInfo *root)

     /* Perform the initial scan of the jointree */
     result = deconstruct_recurse(root, (Node *) root->parse->jointree,
-                                 top_jdomain,
+                                 top_jdomain, NULL,
                                  &item_list);

     /* Now we can form the value of all_query_rels, too */
@@ -780,16 +773,12 @@ deconstruct_jointree(PlannerInfo *root)
     {
         JoinTreeItem *jtitem = (JoinTreeItem *) lfirst(lc);

-        deconstruct_distribute(root, jtitem,
-                               &postponed_qual_list);
+        deconstruct_distribute(root, jtitem);
     }

-    /* Shouldn't be any leftover postponed quals */
-    Assert(postponed_qual_list == NIL);
-
     /*
-     * However, if there were any special joins then we may have some
-     * postponed LEFT JOIN clauses to deal with.
+     * If there were any special joins then we may have some postponed LEFT
+     * JOIN clauses to deal with.
      */
     if (root->join_info_list)
     {
@@ -814,7 +803,8 @@ deconstruct_jointree(PlannerInfo *root)
  *
  * jtnode is the jointree node to examine, and parent_domain is the
  * enclosing join domain.  (We must add all base+OJ relids appearing
- * here or below to parent_domain.)
+ * here or below to parent_domain.)  parent_jtitem is the JoinTreeItem
+ * for the parent jointree node, or NULL at the top of the recursion.
  *
  * item_list is an in/out parameter: we add a JoinTreeItem struct to
  * that list for each jointree node, in depth-first traversal order.
@@ -825,6 +815,7 @@ deconstruct_jointree(PlannerInfo *root)
 static List *
 deconstruct_recurse(PlannerInfo *root, Node *jtnode,
                     JoinDomain *parent_domain,
+                    JoinTreeItem *parent_jtitem,
                     List **item_list)
 {
     List       *joinlist;
@@ -835,6 +826,7 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode,
     /* Make the new JoinTreeItem, but don't add it to item_list yet */
     jtitem = palloc0_object(JoinTreeItem);
     jtitem->jtnode = jtnode;
+    jtitem->jti_parent = parent_jtitem;

     if (IsA(jtnode, RangeTblRef))
     {
@@ -880,6 +872,7 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode,

             sub_joinlist = deconstruct_recurse(root, lfirst(l),
                                                parent_domain,
+                                               jtitem,
                                                item_list);
             sub_item = (JoinTreeItem *) llast(*item_list);
             jtitem->qualscope = bms_add_members(jtitem->qualscope,
@@ -922,10 +915,12 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode,
                 /* Recurse */
                 leftjoinlist = deconstruct_recurse(root, j->larg,
                                                    parent_domain,
+                                                   jtitem,
                                                    item_list);
                 left_item = (JoinTreeItem *) llast(*item_list);
                 rightjoinlist = deconstruct_recurse(root, j->rarg,
                                                     parent_domain,
+                                                    jtitem,
                                                     item_list);
                 right_item = (JoinTreeItem *) llast(*item_list);
                 /* Compute qualscope etc */
@@ -947,10 +942,12 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode,
                 /* Recurse */
                 leftjoinlist = deconstruct_recurse(root, j->larg,
                                                    parent_domain,
+                                                   jtitem,
                                                    item_list);
                 left_item = (JoinTreeItem *) llast(*item_list);
                 rightjoinlist = deconstruct_recurse(root, j->rarg,
                                                     child_domain,
+                                                    jtitem,
                                                     item_list);
                 right_item = (JoinTreeItem *) llast(*item_list);
                 /* Compute join domain contents, qualscope etc */
@@ -984,10 +981,12 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode,
                 /* Recurse */
                 leftjoinlist = deconstruct_recurse(root, j->larg,
                                                    parent_domain,
+                                                   jtitem,
                                                    item_list);
                 left_item = (JoinTreeItem *) llast(*item_list);
                 rightjoinlist = deconstruct_recurse(root, j->rarg,
                                                     parent_domain,
+                                                    jtitem,
                                                     item_list);
                 right_item = (JoinTreeItem *) llast(*item_list);
                 /* Compute qualscope etc */
@@ -1013,6 +1012,7 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode,
                 root->join_domains = lappend(root->join_domains, child_domain);
                 leftjoinlist = deconstruct_recurse(root, j->larg,
                                                    child_domain,
+                                                   jtitem,
                                                    item_list);
                 left_item = (JoinTreeItem *) llast(*item_list);
                 fj_domain->jd_relids = bms_copy(child_domain->jd_relids);
@@ -1021,6 +1021,7 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode,
                 root->join_domains = lappend(root->join_domains, child_domain);
                 rightjoinlist = deconstruct_recurse(root, j->rarg,
                                                     child_domain,
+                                                    jtitem,
                                                     item_list);
                 right_item = (JoinTreeItem *) llast(*item_list);
                 /* Compute qualscope etc */
@@ -1108,20 +1109,9 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode,
  *
  * Distribute quals of the node to appropriate restriction and join lists.
  * In addition, entries will be added to root->join_info_list for outer joins.
- *
- * Inputs:
- *    jtitem is the JoinTreeItem to examine
- * Input/Outputs:
- *    *postponed_qual_list is a list of PostponedQual structs
- *
- * On entry, *postponed_qual_list contains any quals that had to be postponed
- * out of lower join levels (because they contain lateral references).
- * On exit, *postponed_qual_list contains quals that can't be processed yet
- * (because their lateral references are still unsatisfied).
  */
 static void
-deconstruct_distribute(PlannerInfo *root, JoinTreeItem *jtitem,
-                       List **postponed_qual_list)
+deconstruct_distribute(PlannerInfo *root, JoinTreeItem *jtitem)
 {
     Node       *jtnode = jtitem->jtnode;

@@ -1133,82 +1123,51 @@ deconstruct_distribute(PlannerInfo *root, JoinTreeItem *jtitem,
         if (root->qual_security_level > 0)
             process_security_barrier_quals(root,
                                            varno,
-                                           jtitem->qualscope,
-                                           jtitem->jdomain);
+                                           jtitem);
     }
     else if (IsA(jtnode, FromExpr))
     {
         FromExpr   *f = (FromExpr *) jtnode;
-        List       *new_postponed_quals = NIL;
-        ListCell   *l;

         /*
-         * Try to process any quals postponed by children.  If they need
-         * further postponement, add them to my output postponed_qual_list.
+         * Process any lateral-referencing quals that were postponed to this
+         * level by children.
          */
-        foreach(l, *postponed_qual_list)
-        {
-            PostponedQual *pq = (PostponedQual *) lfirst(l);
-
-            if (bms_is_subset(pq->relids, jtitem->qualscope))
-                distribute_qual_to_rels(root, pq->qual,
-                                        jtitem->jdomain,
-                                        NULL,
-                                        root->qual_security_level,
-                                        jtitem->qualscope, NULL, NULL,
-                                        true, false, false,
-                                        NULL, NULL);
-            else
-                new_postponed_quals = lappend(new_postponed_quals, pq);
-        }
-        *postponed_qual_list = new_postponed_quals;
+        distribute_quals_to_rels(root, jtitem->lateral_clauses,
+                                 jtitem,
+                                 NULL,
+                                 root->qual_security_level,
+                                 jtitem->qualscope, NULL, NULL,
+                                 true, false, false,
+                                 NULL);

         /*
          * Now process the top-level quals.
          */
         distribute_quals_to_rels(root, (List *) f->quals,
-                                 jtitem->jdomain,
+                                 jtitem,
                                  NULL,
                                  root->qual_security_level,
                                  jtitem->qualscope, NULL, NULL,
                                  true, false, false,
-                                 postponed_qual_list, NULL);
+                                 NULL);
     }
     else if (IsA(jtnode, JoinExpr))
     {
         JoinExpr   *j = (JoinExpr *) jtnode;
-        List       *new_postponed_quals = NIL;
         Relids        ojscope;
         List       *my_quals;
         SpecialJoinInfo *sjinfo;
         List      **postponed_oj_qual_list;
-        ListCell   *l;

         /*
-         * Try to process any quals postponed by children.  If they need
-         * further postponement, add them to my output postponed_qual_list.
-         * Quals that can be processed now must be included in my_quals, so
-         * that they'll be handled properly in make_outerjoininfo.
+         * Include lateral-referencing quals postponed from children in
+         * my_quals, so that they'll be handled properly in
+         * make_outerjoininfo.  (This is destructive to
+         * jtitem->lateral_clauses, but we won't use that again.)
          */
-        my_quals = NIL;
-        foreach(l, *postponed_qual_list)
-        {
-            PostponedQual *pq = (PostponedQual *) lfirst(l);
-
-            if (bms_is_subset(pq->relids, jtitem->qualscope))
-                my_quals = lappend(my_quals, pq->qual);
-            else
-            {
-                /*
-                 * We should not be postponing any quals past an outer join.
-                 * If this Assert fires, pull_up_subqueries() messed up.
-                 */
-                Assert(j->jointype == JOIN_INNER);
-                new_postponed_quals = lappend(new_postponed_quals, pq);
-            }
-        }
-        *postponed_qual_list = new_postponed_quals;
-        my_quals = list_concat(my_quals, (List *) j->quals);
+        my_quals = list_concat(jtitem->lateral_clauses,
+                               (List *) j->quals);

         /*
          * For an OJ, form the SpecialJoinInfo now, so that we can pass it to
@@ -1268,14 +1227,13 @@ deconstruct_distribute(PlannerInfo *root, JoinTreeItem *jtitem,

         /* Process the JOIN's qual clauses */
         distribute_quals_to_rels(root, my_quals,
-                                 jtitem->jdomain,
+                                 jtitem,
                                  sjinfo,
                                  root->qual_security_level,
                                  jtitem->qualscope,
                                  ojscope, jtitem->nonnullable_rels,
                                  true,    /* allow_equivalence */
                                  false, false,    /* not clones */
-                                 postponed_qual_list,
                                  postponed_oj_qual_list);

         /* And add the SpecialJoinInfo to join_info_list */
@@ -1304,8 +1262,7 @@ deconstruct_distribute(PlannerInfo *root, JoinTreeItem *jtitem,
  */
 static void
 process_security_barrier_quals(PlannerInfo *root,
-                               int rti, Relids qualscope,
-                               JoinDomain *jdomain)
+                               int rti, JoinTreeItem *jtitem)
 {
     RangeTblEntry *rte = root->simple_rte_array[rti];
     Index        security_level = 0;
@@ -1328,15 +1285,14 @@ process_security_barrier_quals(PlannerInfo *root,
          * pushed up to top of tree, which we don't want.
          */
         distribute_quals_to_rels(root, qualset,
-                                 jdomain,
+                                 jtitem,
                                  NULL,
                                  security_level,
-                                 qualscope,
-                                 qualscope,
+                                 jtitem->qualscope,
+                                 jtitem->qualscope,
                                  NULL,
                                  true,
                                  false, false,    /* not clones */
-                                 NULL,
                                  NULL);
         security_level++;
     }
@@ -2038,7 +1994,7 @@ deconstruct_distribute_oj_quals(PlannerInfo *root,
             is_clone = !has_clone;

             distribute_quals_to_rels(root, quals,
-                                     otherjtitem->jdomain,
+                                     otherjtitem,
                                      sjinfo,
                                      root->qual_security_level,
                                      this_qualscope,
@@ -2046,7 +2002,7 @@ deconstruct_distribute_oj_quals(PlannerInfo *root,
                                      allow_equivalence,
                                      has_clone,
                                      is_clone,
-                                     NULL, NULL);    /* no more postponement */
+                                     NULL); /* no more postponement */

             /*
              * Adjust qual nulling bits for next level up, if needed.  We
@@ -2067,14 +2023,14 @@ deconstruct_distribute_oj_quals(PlannerInfo *root,
     {
         /* No commutation possible, just process the postponed clauses */
         distribute_quals_to_rels(root, jtitem->oj_joinclauses,
-                                 jtitem->jdomain,
+                                 jtitem,
                                  sjinfo,
                                  root->qual_security_level,
                                  qualscope,
                                  ojscope, nonnullable_rels,
                                  true,    /* allow_equivalence */
                                  false, false,    /* not clones */
-                                 NULL, NULL);    /* no more postponement */
+                                 NULL); /* no more postponement */
     }
 }

@@ -2092,7 +2048,7 @@ deconstruct_distribute_oj_quals(PlannerInfo *root,
  */
 static void
 distribute_quals_to_rels(PlannerInfo *root, List *clauses,
-                         JoinDomain *jdomain,
+                         JoinTreeItem *jtitem,
                          SpecialJoinInfo *sjinfo,
                          Index security_level,
                          Relids qualscope,
@@ -2101,7 +2057,6 @@ distribute_quals_to_rels(PlannerInfo *root, List *clauses,
                          bool allow_equivalence,
                          bool has_clone,
                          bool is_clone,
-                         List **postponed_qual_list,
                          List **postponed_oj_qual_list)
 {
     ListCell   *lc;
@@ -2111,7 +2066,7 @@ distribute_quals_to_rels(PlannerInfo *root, List *clauses,
         Node       *clause = (Node *) lfirst(lc);

         distribute_qual_to_rels(root, clause,
-                                jdomain,
+                                jtitem,
                                 sjinfo,
                                 security_level,
                                 qualscope,
@@ -2120,7 +2075,6 @@ distribute_quals_to_rels(PlannerInfo *root, List *clauses,
                                 allow_equivalence,
                                 has_clone,
                                 is_clone,
-                                postponed_qual_list,
                                 postponed_oj_qual_list);
     }
 }
@@ -2134,12 +2088,12 @@ distribute_quals_to_rels(PlannerInfo *root, List *clauses,
  *      mergejoinable operator, enter its left- and right-side expressions into
  *      the query's EquivalenceClasses.
  *
- * In some cases, quals will be added to postponed_qual_list or
+ * In some cases, quals will be added to parent jtitems' lateral_clauses or
  * postponed_oj_qual_list instead of being processed right away.
  * These will be dealt with in later steps of deconstruct_jointree.
  *
  * 'clause': the qual clause to be distributed
- * 'jdomain': the join domain containing the clause
+ * 'jtitem': the JoinTreeItem for the containing jointree node
  * 'sjinfo': join's SpecialJoinInfo (NULL for an inner join or WHERE clause)
  * 'security_level': security_level to assign to the qual
  * 'qualscope': set of base+OJ rels the qual's syntactic scope covers
@@ -2153,9 +2107,6 @@ distribute_quals_to_rels(PlannerInfo *root, List *clauses,
  *        EquivalenceClass
  * 'has_clone': has_clone property to assign to the qual
  * 'is_clone': is_clone property to assign to the qual
- * 'postponed_qual_list': list of PostponedQual structs, which we can add
- *        this qual to if it turns out to belong to a higher join level.
- *        Can be NULL if caller knows postponement is impossible.
  * 'postponed_oj_qual_list': if not NULL, non-degenerate outer join clauses
  *        should be added to this list instead of being processed (list entries
  *        are just the bare clauses)
@@ -2170,7 +2121,7 @@ distribute_quals_to_rels(PlannerInfo *root, List *clauses,
  */
 static void
 distribute_qual_to_rels(PlannerInfo *root, Node *clause,
-                        JoinDomain *jdomain,
+                        JoinTreeItem *jtitem,
                         SpecialJoinInfo *sjinfo,
                         Index security_level,
                         Relids qualscope,
@@ -2179,7 +2130,6 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
                         bool allow_equivalence,
                         bool has_clone,
                         bool is_clone,
-                        List **postponed_qual_list,
                         List **postponed_oj_qual_list)
 {
     Relids        relids;
@@ -2202,19 +2152,32 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
      * level that includes every rel they reference.  Although we could make
      * pull_up_subqueries() place such quals correctly to begin with, it's
      * easier to handle it here.  When we find a clause that contains Vars
-     * outside its syntactic scope, we add it to the postponed-quals list, and
-     * process it once we've recursed back up to the appropriate join level.
+     * outside its syntactic scope, locate the nearest parent join level that
+     * includes all the required rels and add the clause to that level's
+     * lateral_clauses list.  We'll process it when we reach that join level.
      */
     if (!bms_is_subset(relids, qualscope))
     {
-        PostponedQual *pq = (PostponedQual *) palloc(sizeof(PostponedQual));
+        JoinTreeItem *pitem;

         Assert(root->hasLateralRTEs);    /* shouldn't happen otherwise */
         Assert(sjinfo == NULL); /* mustn't postpone past outer join */
-        pq->qual = clause;
-        pq->relids = relids;
-        *postponed_qual_list = lappend(*postponed_qual_list, pq);
-        return;
+        for (pitem = jtitem->jti_parent; pitem; pitem = pitem->jti_parent)
+        {
+            if (bms_is_subset(relids, pitem->qualscope))
+            {
+                pitem->lateral_clauses = lappend(pitem->lateral_clauses,
+                                                 clause);
+                return;
+            }
+
+            /*
+             * We should not be postponing any quals past an outer join.  If
+             * this Assert fires, pull_up_subqueries() messed up.
+             */
+            Assert(pitem->sjinfo == NULL);
+        }
+        elog(ERROR, "failed to postpone qual containing lateral reference");
     }

     /*
@@ -2262,7 +2225,7 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
         else
         {
             /* eval at join domain level */
-            relids = bms_copy(jdomain->jd_relids);
+            relids = bms_copy(jtitem->jdomain->jd_relids);
             /* mark as gating qual */
             pseudoconstant = true;
             /* tell createplan.c to check for gating quals */
@@ -2458,7 +2421,7 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
     {
         if (maybe_equivalence)
         {
-            if (process_equivalence(root, &restrictinfo, jdomain))
+            if (process_equivalence(root, &restrictinfo, jtitem->jdomain))
                 return;
             /* EC rejected it, so set left_ec/right_ec the hard way ... */
             if (restrictinfo->mergeopfamilies)    /* EC might have changed this */