]> granicus.if.org Git - postgresql/commitdiff
Flatten join alias Vars before pulling up targetlist items from a subquery.
authorTom Lane <tgl@sss.pgh.pa.us>
Fri, 22 Nov 2013 19:37:31 +0000 (14:37 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Fri, 22 Nov 2013 19:37:31 +0000 (14:37 -0500)
pullup_replace_vars()'s decisions about whether a pulled-up replacement
expression needs to be wrapped in a PlaceHolderVar depend on the assumption
that what looks like a Var behaves like a Var.  However, if the Var is a
join alias reference, later flattening of join aliases might replace the
Var with something that's not a Var at all, and should have been wrapped.

To fix, do a forcible pass of flatten_join_alias_vars() on the subquery
targetlist before we start to copy items out of it.  We'll re-run that
processing on the pulled-up expressions later, but that's harmless.

Per report from Ken Tanzer; the added regression test case is based on his
example.  This bug has been there since the PlaceHolderVar mechanism was
invented, but has escaped detection because the circumstances that trigger
it are fairly narrow.  You need a flattenable query underneath an outer
join, which contains another flattenable query inside a join of its own,
with a dangerous expression (a constant or something else non-strict)
in that one's targetlist.

Having seen this, I'm wondering if it wouldn't be prudent to do all
alias-variable flattening earlier, perhaps even in the rewriter.
But that would probably not be a back-patchable change.

src/backend/optimizer/prep/prepjointree.c
src/backend/optimizer/util/var.c
src/test/regress/expected/join.out
src/test/regress/sql/join.sql

index 1750a5e3e1f0f2dd8091ae3f7f1414b307251f2f..a7a7574af641ac5d660629a9c2c634fc54782af4 100644 (file)
@@ -731,6 +731,18 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
                return jtnode;
        }
 
+       /*
+        * We must flatten any join alias Vars in the subquery's targetlist,
+        * because pulling up the subquery's subqueries might have changed their
+        * expansions into arbitrary expressions, which could affect
+        * pullup_replace_vars' decisions about whether PlaceHolderVar wrappers
+        * are needed for tlist entries.  (Likely it'd be better to do
+        * flatten_join_alias_vars on the whole query tree at some earlier stage,
+        * maybe even in the rewriter; but for now let's just fix this case here.)
+        */
+       subquery->targetList = (List *)
+               flatten_join_alias_vars(subroot, (Node *) subquery->targetList);
+
        /*
         * Adjust level-0 varnos in subquery so that we can append its rangetable
         * to upper query's.  We have to fix the subquery's append_rel_list as
index 96bf733dd4d129b749a06d8a11a30305dab3cce1..e5fa48d3644ae75e3bab4f48d457c3b0778b7ae5 100644 (file)
@@ -779,16 +779,14 @@ flatten_join_alias_vars_mutator(Node *node,
                                /* Ignore dropped columns */
                                if (newvar == NULL)
                                        continue;
+                               newvar = copyObject(newvar);
 
                                /*
                                 * If we are expanding an alias carried down from an upper
                                 * query, must adjust its varlevelsup fields.
                                 */
                                if (context->sublevels_up != 0)
-                               {
-                                       newvar = copyObject(newvar);
                                        IncrementVarSublevelsUp(newvar, context->sublevels_up, 0);
-                               }
                                /* Recurse in case join input is itself a join */
                                /* (also takes care of setting inserted_sublink if needed) */
                                newvar = flatten_join_alias_vars_mutator(newvar, context);
@@ -808,16 +806,14 @@ flatten_join_alias_vars_mutator(Node *node,
                Assert(var->varattno > 0);
                newvar = (Node *) list_nth(rte->joinaliasvars, var->varattno - 1);
                Assert(newvar != NULL);
+               newvar = copyObject(newvar);
 
                /*
                 * If we are expanding an alias carried down from an upper query, must
                 * adjust its varlevelsup fields.
                 */
                if (context->sublevels_up != 0)
-               {
-                       newvar = copyObject(newvar);
                        IncrementVarSublevelsUp(newvar, context->sublevels_up, 0);
-               }
 
                /* Recurse in case join input is itself a join */
                newvar = flatten_join_alias_vars_mutator(newvar, context);
index 34ead49ca626b1b64a1047d361ebf1b4377f5928..4b5909f1567e63169d4cdf26a62b286234d021af 100644 (file)
@@ -2710,6 +2710,58 @@ select f1, unique2, case when unique2 is null then f1 else 0 end
   0 |       0 |    0
 (1 row)
 
+--
+-- check handling of join aliases when flattening multiple levels of subquery
+--
+explain (verbose, costs off)
+select foo1.join_key as foo1_id, foo3.join_key AS foo3_id, bug_field from
+  (values (0),(1)) foo1(join_key)
+left join
+  (select join_key, bug_field from
+    (select ss1.join_key, ss1.bug_field from
+      (select f1 as join_key, 666 as bug_field from int4_tbl i1) ss1
+    ) foo2
+   left join
+    (select unique2 as join_key from tenk1 i2) ss2
+   using (join_key)
+  ) foo3
+using (join_key);
+                                                                                                    QUERY PLAN                                                                                                     
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop Left Join
+   Output: "*VALUES*".column1, i1.f1, (666)
+   Join Filter: ("*VALUES*".column1 = i1.f1)
+   ->  Values Scan on "*VALUES*"
+         Output: "*VALUES*".column1
+   ->  Materialize
+         Output: i1.f1, (666)
+         ->  Nested Loop Left Join
+               Output: i1.f1, 666
+               ->  Seq Scan on public.int4_tbl i1
+                     Output: i1.f1
+               ->  Index Scan using tenk1_unique2 on public.tenk1 i2
+                     Output: i2.unique1, i2.unique2, i2.two, i2.four, i2.ten, i2.twenty, i2.hundred, i2.thousand, i2.twothousand, i2.fivethous, i2.tenthous, i2.odd, i2.even, i2.stringu1, i2.stringu2, i2.string4
+                     Index Cond: (i1.f1 = i2.unique2)
+(14 rows)
+
+select foo1.join_key as foo1_id, foo3.join_key AS foo3_id, bug_field from
+  (values (0),(1)) foo1(join_key)
+left join
+  (select join_key, bug_field from
+    (select ss1.join_key, ss1.bug_field from
+      (select f1 as join_key, 666 as bug_field from int4_tbl i1) ss1
+    ) foo2
+   left join
+    (select unique2 as join_key from tenk1 i2) ss2
+   using (join_key)
+  ) foo3
+using (join_key);
+ foo1_id | foo3_id | bug_field 
+---------+---------+-----------
+       0 |       0 |       666
+       1 |         |          
+(2 rows)
+
 --
 -- test ability to push constants through outer join clauses
 --
index 17fcc28f213b3d74fcf90f5acca68fa377f09c9c..a4cc67a4cd62b5c0d881584f10d7c155b795f2fa 100644 (file)
@@ -711,6 +711,37 @@ select f1, unique2, case when unique2 is null then f1 else 0 end
   from int4_tbl a left join tenk1 b on f1 = unique2
   where (case when unique2 is null then f1 else 0 end) = 0;
 
+--
+-- check handling of join aliases when flattening multiple levels of subquery
+--
+
+explain (verbose, costs off)
+select foo1.join_key as foo1_id, foo3.join_key AS foo3_id, bug_field from
+  (values (0),(1)) foo1(join_key)
+left join
+  (select join_key, bug_field from
+    (select ss1.join_key, ss1.bug_field from
+      (select f1 as join_key, 666 as bug_field from int4_tbl i1) ss1
+    ) foo2
+   left join
+    (select unique2 as join_key from tenk1 i2) ss2
+   using (join_key)
+  ) foo3
+using (join_key);
+
+select foo1.join_key as foo1_id, foo3.join_key AS foo3_id, bug_field from
+  (values (0),(1)) foo1(join_key)
+left join
+  (select join_key, bug_field from
+    (select ss1.join_key, ss1.bug_field from
+      (select f1 as join_key, 666 as bug_field from int4_tbl i1) ss1
+    ) foo2
+   left join
+    (select unique2 as join_key from tenk1 i2) ss2
+   using (join_key)
+  ) foo3
+using (join_key);
+
 --
 -- test ability to push constants through outer join clauses
 --