Commit 57c9dff9 authored by Tom Lane's avatar Tom Lane

Fix subquery pullup to wrap a PlaceHolderVar around the entire RowExpr

that's generated for a whole-row Var referencing the subquery, when the
subquery is in the nullable side of an outer join.  The previous coding
instead put PlaceHolderVars around the elements of the RowExpr.  The effect
was that when the outer join made the subquery outputs go to null, the
whole-row Var produced ROW(NULL,NULL,...) rather than just NULL.  There
are arguments afoot about whether those things ought to be semantically
indistinguishable, but for the moment they are not entirely so, and the
planner needs to take care that its machinations preserve the difference.
Per bug #5025.

Making this feasible required refactoring ResolveNew() to allow more caller
control over what is substituted for a Var.  I chose to make ResolveNew()
a wrapper around a new general-purpose function replace_rte_variables().
I also fixed the ancient bogosity that ResolveNew might fail to set
a query's hasSubLinks field after inserting a SubLink in it.  Although
all current callers make sure that happens anyway, we've had bugs of that
sort before, and it seemed like a good time to install a proper solution.

Back-patch to 8.4.  The problem can be demonstrated clear back to 8.0,
but the fix would be too invasive in earlier branches; not to mention
that people may be depending on the subtly-incorrect behavior.  The
8.4 series is new enough that fixing this probably won't cause complaints,
but it might in older branches.  Also, 8.4 shows the incorrect behavior
in more cases than older branches do, because it is able to flatten
subqueries in more cases.
parent 040f28b4
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/path/allpaths.c,v 1.184 2009/07/06 18:26:30 tgl Exp $ * $PostgreSQL: pgsql/src/backend/optimizer/path/allpaths.c,v 1.185 2009/09/02 17:52:24 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -1254,7 +1254,8 @@ subquery_push_qual(Query *subquery, RangeTblEntry *rte, Index rti, Node *qual) ...@@ -1254,7 +1254,8 @@ subquery_push_qual(Query *subquery, RangeTblEntry *rte, Index rti, Node *qual)
*/ */
qual = ResolveNew(qual, rti, 0, rte, qual = ResolveNew(qual, rti, 0, rte,
subquery->targetList, subquery->targetList,
CMD_SELECT, 0); CMD_SELECT, 0,
&subquery->hasSubLinks);
/* /*
* Now attach the qual to the proper place: normally WHERE, but if the * Now attach the qual to the proper place: normally WHERE, but if the
......
This diff is collapsed.
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/prep/prepunion.c,v 1.173 2009/08/13 16:53:09 tgl Exp $ * $PostgreSQL: pgsql/src/backend/optimizer/prep/prepunion.c,v 1.174 2009/09/02 17:52:24 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -1477,8 +1477,8 @@ translate_col_privs(const Bitmapset *parent_privs, ...@@ -1477,8 +1477,8 @@ translate_col_privs(const Bitmapset *parent_privs,
* Note: this is only applied after conversion of sublinks to subplans, * Note: this is only applied after conversion of sublinks to subplans,
* so we don't need to cope with recursion into sub-queries. * so we don't need to cope with recursion into sub-queries.
* *
* Note: this is not hugely different from what ResolveNew() does; maybe * Note: this is not hugely different from what pullup_replace_vars() does;
* we should try to fold the two routines together. * maybe we should try to fold the two routines together.
*/ */
Node * Node *
adjust_appendrel_attrs(Node *node, AppendRelInfo *appinfo) adjust_appendrel_attrs(Node *node, AppendRelInfo *appinfo)
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 1994, Regents of the University of California
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/rewrite/rewriteHandler.c,v 1.186 2009/06/11 14:49:01 momjian Exp $ * $PostgreSQL: pgsql/src/backend/rewrite/rewriteHandler.c,v 1.187 2009/09/02 17:52:24 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -463,7 +463,8 @@ rewriteRuleAction(Query *parsetree, ...@@ -463,7 +463,8 @@ rewriteRuleAction(Query *parsetree,
sub_action->rtable), sub_action->rtable),
parsetree->targetList, parsetree->targetList,
event, event,
current_varno); current_varno,
NULL);
if (sub_action_ptr) if (sub_action_ptr)
*sub_action_ptr = sub_action; *sub_action_ptr = sub_action;
else else
...@@ -493,7 +494,8 @@ rewriteRuleAction(Query *parsetree, ...@@ -493,7 +494,8 @@ rewriteRuleAction(Query *parsetree,
parsetree->rtable), parsetree->rtable),
rule_action->returningList, rule_action->returningList,
CMD_SELECT, CMD_SELECT,
0); 0,
&rule_action->hasSubLinks);
/* /*
* There could have been some SubLinks in parsetree's returningList, * There could have been some SubLinks in parsetree's returningList,
...@@ -1510,7 +1512,8 @@ CopyAndAddInvertedQual(Query *parsetree, ...@@ -1510,7 +1512,8 @@ CopyAndAddInvertedQual(Query *parsetree,
rt_fetch(rt_index, parsetree->rtable), rt_fetch(rt_index, parsetree->rtable),
parsetree->targetList, parsetree->targetList,
event, event,
rt_index); rt_index,
&parsetree->hasSubLinks);
/* And attach the fixed qual */ /* And attach the fixed qual */
AddInvertedQual(parsetree, new_qual); AddInvertedQual(parsetree, new_qual);
......
This diff is collapsed.
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 1994, Regents of the University of California
* *
* $PostgreSQL: pgsql/src/include/rewrite/rewriteManip.h,v 1.50 2009/06/11 14:49:12 momjian Exp $ * $PostgreSQL: pgsql/src/include/rewrite/rewriteManip.h,v 1.51 2009/09/02 17:52:24 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -17,6 +17,21 @@ ...@@ -17,6 +17,21 @@
#include "nodes/parsenodes.h" #include "nodes/parsenodes.h"
typedef struct replace_rte_variables_context replace_rte_variables_context;
typedef Node * (*replace_rte_variables_callback) (Var *var,
replace_rte_variables_context *context);
struct replace_rte_variables_context
{
replace_rte_variables_callback callback; /* callback function */
void *callback_arg; /* context data for callback function */
int target_varno; /* RTE index to search for */
int sublevels_up; /* (current) nesting depth */
bool inserted_sublink; /* have we inserted a SubLink? */
};
extern void OffsetVarNodes(Node *node, int offset, int sublevels_up); extern void OffsetVarNodes(Node *node, int offset, int sublevels_up);
extern void ChangeVarNodes(Node *node, int old_varno, int new_varno, extern void ChangeVarNodes(Node *node, int old_varno, int new_varno,
int sublevels_up); int sublevels_up);
...@@ -42,8 +57,17 @@ extern bool checkExprHasAggs(Node *node); ...@@ -42,8 +57,17 @@ extern bool checkExprHasAggs(Node *node);
extern bool checkExprHasWindowFuncs(Node *node); extern bool checkExprHasWindowFuncs(Node *node);
extern bool checkExprHasSubLink(Node *node); extern bool checkExprHasSubLink(Node *node);
extern Node *replace_rte_variables(Node *node,
int target_varno, int sublevels_up,
replace_rte_variables_callback callback,
void *callback_arg,
bool *outer_hasSubLinks);
extern Node *replace_rte_variables_mutator(Node *node,
replace_rte_variables_context *context);
extern Node *ResolveNew(Node *node, int target_varno, int sublevels_up, extern Node *ResolveNew(Node *node, int target_varno, int sublevels_up,
RangeTblEntry *target_rte, RangeTblEntry *target_rte,
List *targetlist, int event, int update_varno); List *targetlist, int event, int update_varno,
bool *outer_hasSubLinks);
#endif /* REWRITEMANIP_H */ #endif /* REWRITEMANIP_H */
...@@ -2394,3 +2394,52 @@ select * from a left join b on i = x and i = y and x = i; ...@@ -2394,3 +2394,52 @@ select * from a left join b on i = x and i = y and x = i;
(0 rows) (0 rows)
rollback; rollback;
--
-- test NULL behavior of whole-row Vars, per bug #5025
--
select t1.q2, count(t2.*)
from int8_tbl t1 left join int8_tbl t2 on (t1.q2 = t2.q1)
group by t1.q2 order by 1;
q2 | count
-------------------+-------
-4567890123456789 | 0
123 | 2
456 | 0
4567890123456789 | 6
(4 rows)
select t1.q2, count(t2.*)
from int8_tbl t1 left join (select * from int8_tbl) t2 on (t1.q2 = t2.q1)
group by t1.q2 order by 1;
q2 | count
-------------------+-------
-4567890123456789 | 0
123 | 2
456 | 0
4567890123456789 | 6
(4 rows)
select t1.q2, count(t2.*)
from int8_tbl t1 left join (select * from int8_tbl offset 0) t2 on (t1.q2 = t2.q1)
group by t1.q2 order by 1;
q2 | count
-------------------+-------
-4567890123456789 | 0
123 | 2
456 | 0
4567890123456789 | 6
(4 rows)
select t1.q2, count(t2.*)
from int8_tbl t1 left join
(select q1, case when q2=1 then 1 else q2 end as q2 from int8_tbl) t2
on (t1.q2 = t2.q1)
group by t1.q2 order by 1;
q2 | count
-------------------+-------
-4567890123456789 | 0
123 | 2
456 | 0
4567890123456789 | 6
(4 rows)
...@@ -540,3 +540,24 @@ create temp table b (x integer, y integer); ...@@ -540,3 +540,24 @@ create temp table b (x integer, y integer);
select * from a left join b on i = x and i = y and x = i; select * from a left join b on i = x and i = y and x = i;
rollback; rollback;
--
-- test NULL behavior of whole-row Vars, per bug #5025
--
select t1.q2, count(t2.*)
from int8_tbl t1 left join int8_tbl t2 on (t1.q2 = t2.q1)
group by t1.q2 order by 1;
select t1.q2, count(t2.*)
from int8_tbl t1 left join (select * from int8_tbl) t2 on (t1.q2 = t2.q1)
group by t1.q2 order by 1;
select t1.q2, count(t2.*)
from int8_tbl t1 left join (select * from int8_tbl offset 0) t2 on (t1.q2 = t2.q1)
group by t1.q2 order by 1;
select t1.q2, count(t2.*)
from int8_tbl t1 left join
(select q1, case when q2=1 then 1 else q2 end as q2 from int8_tbl) t2
on (t1.q2 = t2.q1)
group by t1.q2 order by 1;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment