1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-27 12:41:57 +03:00

Add a concept of "placeholder" variables to the planner. These are variables

that represent some expression that we desire to compute below the top level
of the plan, and then let that value "bubble up" as though it were a plain
Var (ie, a column value).

The immediate application is to allow sub-selects to be flattened even when
they are below an outer join and have non-nullable output expressions.
Formerly we couldn't flatten because such an expression wouldn't properly
go to NULL when evaluated above the outer join.  Now, we wrap it in a
PlaceHolderVar and arrange for the actual evaluation to occur below the outer
join.  When the resulting Var bubbles up through the join, it will be set to
NULL if necessary, yielding the correct results.  This fixes a planner
limitation that's existed since 7.1.

In future we might want to use this mechanism to re-introduce some form of
Hellerstein's "expensive functions" optimization, ie place the evaluation of
an expensive function at the most suitable point in the plan tree.
This commit is contained in:
Tom Lane
2008-10-21 20:42:53 +00:00
parent 831abae506
commit e6ae3b5dbf
28 changed files with 1135 additions and 226 deletions

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/path/allpaths.c,v 1.174 2008/10/04 21:56:53 tgl Exp $
* $PostgreSQL: pgsql/src/backend/optimizer/path/allpaths.c,v 1.175 2008/10/21 20:42:52 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -423,6 +423,10 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
Var *parentvar = (Var *) lfirst(parentvars);
Var *childvar = (Var *) lfirst(childvars);
/*
* Accumulate per-column estimates too. Whole-row Vars and
* PlaceHolderVars can be ignored here.
*/
if (IsA(parentvar, Var) &&
IsA(childvar, Var))
{
@ -1105,12 +1109,25 @@ qual_is_pushdown_safe(Query *subquery, Index rti, Node *qual,
* Examine all Vars used in clause; since it's a restriction clause, all
* such Vars must refer to subselect output columns.
*/
vars = pull_var_clause(qual, false);
vars = pull_var_clause(qual, true);
foreach(vl, vars)
{
Var *var = (Var *) lfirst(vl);
TargetEntry *tle;
/*
* XXX Punt if we find any PlaceHolderVars in the restriction clause.
* It's not clear whether a PHV could safely be pushed down, and even
* less clear whether such a situation could arise in any cases of
* practical interest anyway. So for the moment, just refuse to push
* down.
*/
if (!IsA(var, Var))
{
safe = false;
break;
}
Assert(var->varno == rti);
/* Check point 2 */

View File

@ -54,7 +54,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/path/costsize.c,v 1.199 2008/10/17 20:27:24 tgl Exp $
* $PostgreSQL: pgsql/src/backend/optimizer/path/costsize.c,v 1.200 2008/10/21 20:42:52 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -69,6 +69,7 @@
#include "optimizer/clauses.h"
#include "optimizer/cost.h"
#include "optimizer/pathnode.h"
#include "optimizer/placeholder.h"
#include "optimizer/planmain.h"
#include "parser/parsetree.h"
#include "utils/lsyscache.h"
@ -2610,55 +2611,66 @@ set_rel_width(PlannerInfo *root, RelOptInfo *rel)
{
Oid reloid = planner_rt_fetch(rel->relid, root)->relid;
int32 tuple_width = 0;
ListCell *tllist;
ListCell *lc;
foreach(tllist, rel->reltargetlist)
foreach(lc, rel->reltargetlist)
{
Var *var = (Var *) lfirst(tllist);
int ndx;
int32 item_width;
Node *node = (Node *) lfirst(lc);
/* For now, punt on whole-row child Vars */
if (!IsA(var, Var))
if (IsA(node, Var))
{
tuple_width += 32; /* arbitrary */
continue;
}
Var *var = (Var *) node;
int ndx;
int32 item_width;
Assert(var->varno == rel->relid);
Assert(var->varattno >= rel->min_attr);
Assert(var->varattno <= rel->max_attr);
Assert(var->varno == rel->relid);
Assert(var->varattno >= rel->min_attr);
Assert(var->varattno <= rel->max_attr);
ndx = var->varattno - rel->min_attr;
ndx = var->varattno - rel->min_attr;
/*
* The width probably hasn't been cached yet, but may as well check
*/
if (rel->attr_widths[ndx] > 0)
{
tuple_width += rel->attr_widths[ndx];
continue;
}
if (reloid != InvalidOid)
{
item_width = get_attavgwidth(reloid, var->varattno);
if (item_width > 0)
/*
* The width probably hasn't been cached yet, but may as well check
*/
if (rel->attr_widths[ndx] > 0)
{
rel->attr_widths[ndx] = item_width;
tuple_width += item_width;
tuple_width += rel->attr_widths[ndx];
continue;
}
}
/*
* Not a plain relation, or can't find statistics for it. Estimate
* using just the type info.
*/
item_width = get_typavgwidth(var->vartype, var->vartypmod);
Assert(item_width > 0);
rel->attr_widths[ndx] = item_width;
tuple_width += item_width;
/* Try to get column width from statistics */
if (reloid != InvalidOid)
{
item_width = get_attavgwidth(reloid, var->varattno);
if (item_width > 0)
{
rel->attr_widths[ndx] = item_width;
tuple_width += item_width;
continue;
}
}
/*
* Not a plain relation, or can't find statistics for it. Estimate
* using just the type info.
*/
item_width = get_typavgwidth(var->vartype, var->vartypmod);
Assert(item_width > 0);
rel->attr_widths[ndx] = item_width;
tuple_width += item_width;
}
else if (IsA(node, PlaceHolderVar))
{
PlaceHolderVar *phv = (PlaceHolderVar *) node;
PlaceHolderInfo *phinfo = find_placeholder_info(root, phv);
tuple_width += phinfo->ph_width;
}
else
{
/* For now, punt on whole-row child Vars */
tuple_width += 32; /* arbitrary */
}
}
Assert(tuple_width >= 0);
rel->width = tuple_width;

View File

@ -10,7 +10,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/path/equivclass.c,v 1.12 2008/08/25 22:42:33 tgl Exp $
* $PostgreSQL: pgsql/src/backend/optimizer/path/equivclass.c,v 1.13 2008/10/21 20:42:52 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -687,7 +687,7 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
foreach(lc, ec->ec_members)
{
EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc);
List *vars = pull_var_clause((Node *) cur_em->em_expr, false);
List *vars = pull_var_clause((Node *) cur_em->em_expr, true);
add_vars_to_targetlist(root, vars, ec->ec_relids);
list_free(vars);

View File

@ -10,7 +10,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/plan/createplan.c,v 1.250 2008/10/07 19:27:04 tgl Exp $
* $PostgreSQL: pgsql/src/backend/optimizer/plan/createplan.c,v 1.251 2008/10/21 20:42:53 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -37,7 +37,7 @@
static Plan *create_scan_plan(PlannerInfo *root, Path *best_path);
static List *build_relation_tlist(RelOptInfo *rel);
static bool use_physical_tlist(RelOptInfo *rel);
static bool use_physical_tlist(PlannerInfo *root, RelOptInfo *rel);
static void disuse_physical_tlist(Plan *plan, Path *path);
static Plan *create_gating_plan(PlannerInfo *root, Plan *plan, List *quals);
static Plan *create_join_plan(PlannerInfo *root, JoinPath *best_path);
@ -212,7 +212,7 @@ create_scan_plan(PlannerInfo *root, Path *best_path)
* planner.c may replace the tlist we generate here, forcing projection to
* occur.)
*/
if (use_physical_tlist(rel))
if (use_physical_tlist(root, rel))
{
tlist = build_physical_tlist(root, rel);
/* if fail because of dropped cols, use regular method */
@ -325,9 +325,9 @@ build_relation_tlist(RelOptInfo *rel)
foreach(v, rel->reltargetlist)
{
/* Do we really need to copy here? Not sure */
Var *var = (Var *) copyObject(lfirst(v));
Node *node = (Node *) copyObject(lfirst(v));
tlist = lappend(tlist, makeTargetEntry((Expr *) var,
tlist = lappend(tlist, makeTargetEntry((Expr *) node,
resno,
NULL,
false));
@ -342,9 +342,10 @@ build_relation_tlist(RelOptInfo *rel)
* rather than only those Vars actually referenced.
*/
static bool
use_physical_tlist(RelOptInfo *rel)
use_physical_tlist(PlannerInfo *root, RelOptInfo *rel)
{
int i;
ListCell *lc;
/*
* We can do this for real relation scans, subquery scans, function scans,
@ -365,9 +366,9 @@ use_physical_tlist(RelOptInfo *rel)
return false;
/*
* Can't do it if any system columns or whole-row Vars are requested,
* either. (This could possibly be fixed but would take some fragile
* assumptions in setrefs.c, I think.)
* Can't do it if any system columns or whole-row Vars are requested.
* (This could possibly be fixed but would take some fragile assumptions
* in setrefs.c, I think.)
*/
for (i = rel->min_attr; i <= 0; i++)
{
@ -375,6 +376,19 @@ use_physical_tlist(RelOptInfo *rel)
return false;
}
/*
* Can't do it if the rel is required to emit any placeholder expressions,
* either.
*/
foreach(lc, root->placeholder_list)
{
PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(lc);
if (bms_nonempty_difference(phinfo->ph_needed, rel->relids) &&
bms_is_subset(phinfo->ph_eval_at, rel->relids))
return false;
}
return true;
}
@ -2925,7 +2939,7 @@ make_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys,
if (em->em_is_const || em->em_is_child)
continue;
sortexpr = em->em_expr;
exprvars = pull_var_clause((Node *) sortexpr, false);
exprvars = pull_var_clause((Node *) sortexpr, true);
foreach(k, exprvars)
{
if (!tlist_member_ignore_relabel(lfirst(k), tlist))

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/plan/initsplan.c,v 1.142 2008/08/17 01:19:59 tgl Exp $
* $PostgreSQL: pgsql/src/backend/optimizer/plan/initsplan.c,v 1.143 2008/10/21 20:42:53 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -21,6 +21,7 @@
#include "optimizer/joininfo.h"
#include "optimizer/pathnode.h"
#include "optimizer/paths.h"
#include "optimizer/placeholder.h"
#include "optimizer/planmain.h"
#include "optimizer/prep.h"
#include "optimizer/restrictinfo.h"
@ -131,7 +132,7 @@ add_base_rels_to_query(PlannerInfo *root, Node *jtnode)
void
build_base_rel_tlists(PlannerInfo *root, List *final_tlist)
{
List *tlist_vars = pull_var_clause((Node *) final_tlist, false);
List *tlist_vars = pull_var_clause((Node *) final_tlist, true);
if (tlist_vars != NIL)
{
@ -146,6 +147,10 @@ build_base_rel_tlists(PlannerInfo *root, List *final_tlist)
* relation's targetlist if not already present, and mark the variable
* as being needed for the indicated join (or for final output if
* where_needed includes "relation 0").
*
* The list may also contain PlaceHolderVars. These don't necessarily
* have a single owning relation; we keep their attr_needed info in
* root->placeholder_list instead.
*/
void
add_vars_to_targetlist(PlannerInfo *root, List *vars, Relids where_needed)
@ -156,20 +161,36 @@ add_vars_to_targetlist(PlannerInfo *root, List *vars, Relids where_needed)
foreach(temp, vars)
{
Var *var = (Var *) lfirst(temp);
RelOptInfo *rel = find_base_rel(root, var->varno);
int attrno = var->varattno;
Node *node = (Node *) lfirst(temp);
Assert(attrno >= rel->min_attr && attrno <= rel->max_attr);
attrno -= rel->min_attr;
if (bms_is_empty(rel->attr_needed[attrno]))
if (IsA(node, Var))
{
/* Variable not yet requested, so add to reltargetlist */
/* XXX is copyObject necessary here? */
rel->reltargetlist = lappend(rel->reltargetlist, copyObject(var));
Var *var = (Var *) node;
RelOptInfo *rel = find_base_rel(root, var->varno);
int attno = var->varattno;
Assert(attno >= rel->min_attr && attno <= rel->max_attr);
attno -= rel->min_attr;
if (rel->attr_needed[attno] == NULL)
{
/* Variable not yet requested, so add to reltargetlist */
/* XXX is copyObject necessary here? */
rel->reltargetlist = lappend(rel->reltargetlist,
copyObject(var));
}
rel->attr_needed[attno] = bms_add_members(rel->attr_needed[attno],
where_needed);
}
rel->attr_needed[attrno] = bms_add_members(rel->attr_needed[attrno],
where_needed);
else if (IsA(node, PlaceHolderVar))
{
PlaceHolderVar *phv = (PlaceHolderVar *) node;
PlaceHolderInfo *phinfo = find_placeholder_info(root, phv);
phinfo->ph_needed = bms_add_members(phinfo->ph_needed,
where_needed);
}
else
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
}
}
@ -934,7 +955,7 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
*/
if (bms_membership(relids) == BMS_MULTIPLE)
{
List *vars = pull_var_clause(clause, false);
List *vars = pull_var_clause(clause, true);
add_vars_to_targetlist(root, vars, relids);
list_free(vars);

View File

@ -14,7 +14,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/plan/planmain.c,v 1.110 2008/08/14 18:47:59 tgl Exp $
* $PostgreSQL: pgsql/src/backend/optimizer/plan/planmain.c,v 1.111 2008/10/21 20:42:53 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -23,6 +23,7 @@
#include "optimizer/cost.h"
#include "optimizer/pathnode.h"
#include "optimizer/paths.h"
#include "optimizer/placeholder.h"
#include "optimizer/planmain.h"
#include "optimizer/tlist.h"
#include "utils/selfuncs.h"
@ -132,7 +133,7 @@ query_planner(PlannerInfo *root, List *tlist,
* for "simple" rels.
*
* NOTE: append_rel_list was set up by subquery_planner, so do not touch
* here; eq_classes may contain data already, too.
* here; ditto placeholder_list; eq_classes may contain data already, too.
*/
root->simple_rel_array_size = list_length(parse->rtable) + 1;
root->simple_rel_array = (RelOptInfo **)
@ -204,12 +205,6 @@ query_planner(PlannerInfo *root, List *tlist,
* added to appropriate lists belonging to the mentioned relations. We
* also build EquivalenceClasses for provably equivalent expressions, and
* form a target joinlist for make_one_rel() to work from.
*
* Note: all subplan nodes will have "flat" (var-only) tlists. This
* implies that all expression evaluations are done at the root of the
* plan tree. Once upon a time there was code to try to push expensive
* function calls down to lower plan nodes, but that's dead code and has
* been for a long time...
*/
build_base_rel_tlists(root, tlist);
@ -240,6 +235,13 @@ query_planner(PlannerInfo *root, List *tlist,
root->distinct_pathkeys = canonicalize_pathkeys(root, root->distinct_pathkeys);
root->sort_pathkeys = canonicalize_pathkeys(root, root->sort_pathkeys);
/*
* Examine any "placeholder" expressions generated during subquery pullup.
* Make sure that we know what level to evaluate them at, and that the
* Vars they need are marked as needed.
*/
fix_placeholder_eval_levels(root);
/*
* Ready to do the primary planning.
*/

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.244 2008/10/04 21:56:53 tgl Exp $
* $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.245 2008/10/21 20:42:53 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -55,7 +55,7 @@ planner_hook_type planner_hook = NULL;
#define EXPRKIND_RTFUNC 2
#define EXPRKIND_VALUES 3
#define EXPRKIND_LIMIT 4
#define EXPRKIND_APPINFO 5
#define EXPRKIND_AUXINFO 5
static Node *preprocess_expression(PlannerInfo *root, Node *expr, int kind);
@ -141,6 +141,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
glob->finalrtable = NIL;
glob->relationOids = NIL;
glob->invalItems = NIL;
glob->lastPHId = 0;
glob->transientPlan = false;
/* Determine what fraction of the plan is likely to be scanned */
@ -273,6 +274,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
root->cte_plan_ids = NIL;
root->eq_classes = NIL;
root->append_rel_list = NIL;
root->placeholder_list = NIL;
root->hasRecursion = hasRecursion;
if (hasRecursion)
@ -378,7 +380,10 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
root->append_rel_list = (List *)
preprocess_expression(root, (Node *) root->append_rel_list,
EXPRKIND_APPINFO);
EXPRKIND_AUXINFO);
root->placeholder_list = (List *)
preprocess_expression(root, (Node *) root->placeholder_list,
EXPRKIND_AUXINFO);
/* Also need to preprocess expressions for function and values RTEs */
foreach(l, parse->rtable)
@ -656,7 +661,12 @@ inheritance_planner(PlannerInfo *root)
subroot.parse = (Query *)
adjust_appendrel_attrs((Node *) parse,
appinfo);
subroot.returningLists = NIL;
subroot.init_plans = NIL;
/* We needn't modify the child's append_rel_list */
subroot.placeholder_list = (List *)
adjust_appendrel_attrs((Node *) root->placeholder_list,
appinfo);
/* There shouldn't be any OJ info to translate, as yet */
Assert(subroot.join_info_list == NIL);
@ -2046,7 +2056,7 @@ make_subplanTargetList(PlannerInfo *root,
* Vars; they will be replaced by Params later on).
*/
sub_tlist = flatten_tlist(tlist);
extravars = pull_var_clause(parse->havingQual, false);
extravars = pull_var_clause(parse->havingQual, true);
sub_tlist = add_to_flat_tlist(sub_tlist, extravars);
list_free(extravars);
*need_tlist_eval = false; /* only eval if not flat tlist */

View File

@ -9,7 +9,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/plan/setrefs.c,v 1.145 2008/10/04 21:56:53 tgl Exp $
* $PostgreSQL: pgsql/src/backend/optimizer/plan/setrefs.c,v 1.146 2008/10/21 20:42:53 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -38,7 +38,8 @@ typedef struct
{
List *tlist; /* underlying target list */
int num_vars; /* number of plain Var tlist entries */
bool has_non_vars; /* are there non-plain-Var entries? */
bool has_ph_vars; /* are there PlaceHolderVar entries? */
bool has_non_vars; /* are there other entries? */
/* array of num_vars entries: */
tlist_vinfo vars[1]; /* VARIABLE LENGTH ARRAY */
} indexed_tlist; /* VARIABLE LENGTH STRUCT */
@ -741,15 +742,16 @@ fix_scan_expr(PlannerGlobal *glob, Node *node, int rtoffset)
context.glob = glob;
context.rtoffset = rtoffset;
if (rtoffset != 0)
if (rtoffset != 0 || glob->lastPHId != 0)
{
return fix_scan_expr_mutator(node, &context);
}
else
{
/*
* If rtoffset == 0, we don't need to change any Vars, which makes
* it OK to just scribble on the input node tree instead of copying
* If rtoffset == 0, we don't need to change any Vars, and if there
* are no placeholders anywhere we won't need to remove them. Then
* it's OK to just scribble on the input node tree instead of copying
* (since the only change, filling in any unset opfuncid fields,
* is harmless). This saves just enough cycles to be noticeable on
* trivial queries.
@ -790,6 +792,13 @@ fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context)
cexpr->cvarno += context->rtoffset;
return (Node *) cexpr;
}
if (IsA(node, PlaceHolderVar))
{
/* At scan level, we should always just evaluate the contained expr */
PlaceHolderVar *phv = (PlaceHolderVar *) node;
return fix_scan_expr_mutator((Node *) phv->phexpr, context);
}
fix_expr_common(context->glob, node);
return expression_tree_mutator(node, fix_scan_expr_mutator,
(void *) context);
@ -800,6 +809,7 @@ fix_scan_expr_walker(Node *node, fix_scan_expr_context *context)
{
if (node == NULL)
return false;
Assert(!IsA(node, PlaceHolderVar));
fix_expr_common(context->glob, node);
return expression_tree_walker(node, fix_scan_expr_walker,
(void *) context);
@ -1199,6 +1209,7 @@ build_tlist_index(List *tlist)
list_length(tlist) * sizeof(tlist_vinfo));
itlist->tlist = tlist;
itlist->has_ph_vars = false;
itlist->has_non_vars = false;
/* Find the Vars and fill in the index array */
@ -1216,6 +1227,8 @@ build_tlist_index(List *tlist)
vinfo->resno = tle->resno;
vinfo++;
}
else if (tle->expr && IsA(tle->expr, PlaceHolderVar))
itlist->has_ph_vars = true;
else
itlist->has_non_vars = true;
}
@ -1229,7 +1242,9 @@ build_tlist_index(List *tlist)
* build_tlist_index_other_vars --- build a restricted tlist index
*
* This is like build_tlist_index, but we only index tlist entries that
* are Vars and belong to some rel other than the one specified.
* are Vars belonging to some rel other than the one specified. We will set
* has_ph_vars (allowing PlaceHolderVars to be matched), but not has_non_vars
* (so nothing other than Vars and PlaceHolderVars can be matched).
*/
static indexed_tlist *
build_tlist_index_other_vars(List *tlist, Index ignore_rel)
@ -1244,6 +1259,7 @@ build_tlist_index_other_vars(List *tlist, Index ignore_rel)
list_length(tlist) * sizeof(tlist_vinfo));
itlist->tlist = tlist;
itlist->has_ph_vars = false;
itlist->has_non_vars = false;
/* Find the desired Vars and fill in the index array */
@ -1264,6 +1280,8 @@ build_tlist_index_other_vars(List *tlist, Index ignore_rel)
vinfo++;
}
}
else if (tle->expr && IsA(tle->expr, PlaceHolderVar))
itlist->has_ph_vars = true;
}
itlist->num_vars = (vinfo - itlist->vars);
@ -1314,7 +1332,8 @@ search_indexed_tlist_for_var(Var *var, indexed_tlist *itlist,
* If a match is found, return a Var constructed to reference the tlist item.
* If no match, return NULL.
*
* NOTE: it is a waste of time to call this if !itlist->has_non_vars
* NOTE: it is a waste of time to call this unless itlist->has_ph_vars or
* itlist->has_non_vars
*/
static Var *
search_indexed_tlist_for_non_var(Node *node,
@ -1429,6 +1448,31 @@ fix_join_expr_mutator(Node *node, fix_join_expr_context *context)
/* No referent found for Var */
elog(ERROR, "variable not found in subplan target lists");
}
if (IsA(node, PlaceHolderVar))
{
PlaceHolderVar *phv = (PlaceHolderVar *) node;
/* See if the PlaceHolderVar has bubbled up from a lower plan node */
if (context->outer_itlist->has_ph_vars)
{
newvar = search_indexed_tlist_for_non_var((Node *) phv,
context->outer_itlist,
OUTER);
if (newvar)
return (Node *) newvar;
}
if (context->inner_itlist && context->inner_itlist->has_ph_vars)
{
newvar = search_indexed_tlist_for_non_var((Node *) phv,
context->inner_itlist,
INNER);
if (newvar)
return (Node *) newvar;
}
/* If not supplied by input plans, evaluate the contained expr */
return fix_join_expr_mutator((Node *) phv->phexpr, context);
}
/* Try matching more complex expressions too, if tlists have any */
if (context->outer_itlist->has_non_vars)
{
@ -1512,6 +1556,22 @@ fix_upper_expr_mutator(Node *node, fix_upper_expr_context *context)
elog(ERROR, "variable not found in subplan target list");
return (Node *) newvar;
}
if (IsA(node, PlaceHolderVar))
{
PlaceHolderVar *phv = (PlaceHolderVar *) node;
/* See if the PlaceHolderVar has bubbled up from a lower plan node */
if (context->subplan_itlist->has_ph_vars)
{
newvar = search_indexed_tlist_for_non_var((Node *) phv,
context->subplan_itlist,
OUTER);
if (newvar)
return (Node *) newvar;
}
/* If not supplied by input plan, evaluate the contained expr */
return fix_upper_expr_mutator((Node *) phv->phexpr, context);
}
/* Try matching more complex expressions too, if tlist has any */
if (context->subplan_itlist->has_non_vars)
{
@ -1564,6 +1624,13 @@ set_returning_clause_references(PlannerGlobal *glob,
* top plan's targetlist for Vars of non-result relations, and use
* fix_join_expr to convert RETURNING Vars into references to those tlist
* entries, while leaving result-rel Vars as-is.
*
* PlaceHolderVars will also be sought in the targetlist, but no
* more-complex expressions will be. Note that it is not possible for
* a PlaceHolderVar to refer to the result relation, since the result
* is never below an outer join. If that case could happen, we'd have
* to be prepared to pick apart the PlaceHolderVar and evaluate its
* contained expression instead.
*/
itlist = build_tlist_index_other_vars(topplan->targetlist, resultRelation);
@ -1721,6 +1788,7 @@ extract_query_dependencies_walker(Node *node, PlannerGlobal *context)
{
if (node == NULL)
return false;
Assert(!IsA(node, PlaceHolderVar));
/* Extract function dependencies and check for regclass Consts */
fix_expr_common(context, node);
if (IsA(node, Query))

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/plan/subselect.c,v 1.141 2008/10/04 21:56:53 tgl Exp $
* $PostgreSQL: pgsql/src/backend/optimizer/plan/subselect.c,v 1.142 2008/10/21 20:42:53 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -1040,6 +1040,10 @@ convert_ANY_sublink_to_join(PlannerInfo *root, SubLink *sublink,
/*
* And finally, build the FlattenedSubLink node.
*
* Note: at this point left_varnos may well contain join relids, since
* the testexpr hasn't been run through flatten_join_alias_vars. This
* will get fixed when flatten_join_alias_vars is run.
*/
fslink = makeNode(FlattenedSubLink);
fslink->jointype = JOIN_SEMI;
@ -1189,6 +1193,9 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink,
/*
* And finally, build the FlattenedSubLink node.
*
* Note: at this point left_varnos and subselect_varnos may well contain
* join relids. This will get fixed when flatten_join_alias_vars is run.
*/
fslink = makeNode(FlattenedSubLink);
fslink->jointype = under_not ? JOIN_ANTI : JOIN_SEMI;

View File

@ -16,7 +16,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/prep/prepjointree.c,v 1.56 2008/10/09 19:27:40 tgl Exp $
* $PostgreSQL: pgsql/src/backend/optimizer/prep/prepjointree.c,v 1.57 2008/10/21 20:42:53 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -25,6 +25,7 @@
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
#include "optimizer/placeholder.h"
#include "optimizer/prep.h"
#include "optimizer/subselect.h"
#include "optimizer/tlist.h"
@ -60,7 +61,8 @@ static bool is_simple_subquery(Query *subquery);
static bool is_simple_union_all(Query *subquery);
static bool is_simple_union_all_recurse(Node *setOp, Query *setOpQuery,
List *colTypes);
static bool has_nullable_targetlist(Query *subquery);
static List *insert_targetlist_placeholders(PlannerInfo *root, List *tlist,
int varno, bool wrap_non_vars);
static bool is_safe_append_member(Query *subquery);
static void resolvenew_in_jointree(Node *jtnode, int varno,
RangeTblEntry *rte, List *subtlist);
@ -71,8 +73,8 @@ static void reduce_outer_joins_pass2(Node *jtnode,
Relids nonnullable_rels,
List *nonnullable_vars,
List *forced_null_vars);
static void fix_flattened_sublink_relids(Node *node,
int varno, Relids subrelids);
static void substitute_multiple_relids(Node *node,
int varno, Relids subrelids);
static void fix_append_rel_relids(List *append_rel_list, int varno,
Relids subrelids);
static Node *find_jointree_node_for_rel(Node *jtnode, int relid);
@ -406,11 +408,13 @@ inline_set_returning_functions(PlannerInfo *root)
* converted into "append relations".
*
* below_outer_join is true if this jointree node is within the nullable
* side of an outer join. This restricts what we can do.
* side of an outer join. This forces use of the PlaceHolderVar mechanism
* for non-nullable targetlist items.
*
* append_rel_member is true if we are looking at a member subquery of
* an append relation. This puts some different restrictions on what
* we can do.
* an append relation. This forces use of the PlaceHolderVar mechanism
* for all non-Var targetlist items, and puts some additional restrictions
* on what can be pulled up.
*
* A tricky aspect of this code is that if we pull up a subquery we have
* to replace Vars that reference the subquery's outputs throughout the
@ -434,24 +438,13 @@ pull_up_subqueries(PlannerInfo *root, Node *jtnode,
/*
* Is this a subquery RTE, and if so, is the subquery simple enough to
* pull up? (If not, do nothing at this node.)
*
* If we are inside an outer join, only pull up subqueries whose
* targetlists are nullable --- otherwise substituting their tlist
* entries for upper Var references would do the wrong thing (the
* results wouldn't become NULL when they're supposed to).
*
* XXX This could be improved by generating pseudo-variables for such
* expressions; we'd have to figure out how to get the pseudo-
* variables evaluated at the right place in the modified plan tree.
* Fix it someday.
* pull up?
*
* If we are looking at an append-relation member, we can't pull it up
* unless is_safe_append_member says so.
*/
if (rte->rtekind == RTE_SUBQUERY &&
is_simple_subquery(rte->subquery) &&
(!below_outer_join || has_nullable_targetlist(rte->subquery)) &&
(!append_rel_member || is_safe_append_member(rte->subquery)))
return pull_up_simple_subquery(root, jtnode, rte,
below_outer_join,
@ -459,12 +452,9 @@ pull_up_subqueries(PlannerInfo *root, Node *jtnode,
/*
* Alternatively, is it a simple UNION ALL subquery? If so, flatten
* into an "append relation". We can do this regardless of
* nullability considerations since this transformation does not
* result in propagating non-Var expressions into upper levels of the
* query.
* into an "append relation".
*
* It's also safe to do this regardless of whether this query is
* It's safe to do this regardless of whether this query is
* itself an appendrel member. (If you're thinking we should try to
* flatten the two levels of appendrel together, you're right; but we
* handle that in set_append_rel_pathlist, not here.)
@ -472,6 +462,8 @@ pull_up_subqueries(PlannerInfo *root, Node *jtnode,
if (rte->rtekind == RTE_SUBQUERY &&
is_simple_union_all(rte->subquery))
return pull_up_simple_union_all(root, jtnode, rte);
/* Otherwise, do nothing at this node. */
}
else if (IsA(jtnode, FromExpr))
{
@ -573,6 +565,7 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
subroot->cte_plan_ids = NIL;
subroot->eq_classes = NIL;
subroot->append_rel_list = NIL;
subroot->placeholder_list = NIL;
subroot->hasRecursion = false;
subroot->wt_param_id = -1;
subroot->non_recursive_plan = NULL;
@ -614,7 +607,6 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
* pull_up_subqueries.
*/
if (is_simple_subquery(subquery) &&
(!below_outer_join || has_nullable_targetlist(subquery)) &&
(!append_rel_member || is_safe_append_member(subquery)))
{
/* good to go */
@ -635,11 +627,12 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
/*
* 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 well.
* and placeholder_list as well.
*/
rtoffset = list_length(parse->rtable);
OffsetVarNodes((Node *) subquery, rtoffset, 0);
OffsetVarNodes((Node *) subroot->append_rel_list, rtoffset, 0);
OffsetVarNodes((Node *) subroot->placeholder_list, rtoffset, 0);
/*
* Upper-level vars in subquery are now one level closer to their parent
@ -647,6 +640,20 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
*/
IncrementVarSublevelsUp((Node *) subquery, -1, 1);
IncrementVarSublevelsUp((Node *) subroot->append_rel_list, -1, 1);
IncrementVarSublevelsUp((Node *) subroot->placeholder_list, -1, 1);
/*
* The subquery's targetlist items are now in the appropriate form to
* insert into the top query, but if we are under an outer join then
* non-nullable items have to be turned into PlaceHolderVars. If we
* are dealing with an appendrel member then anything that's not a
* simple Var has to be turned into a PlaceHolderVar.
*/
if (below_outer_join || append_rel_member)
subtlist = insert_targetlist_placeholders(root, subquery->targetList,
varno, append_rel_member);
else
subtlist = subquery->targetList;
/*
* Replace all of the top query's references to the subquery's outputs
@ -654,7 +661,6 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
* replace any of the jointree structure. (This'd be a lot cleaner if we
* could use query_tree_mutator.)
*/
subtlist = subquery->targetList;
parse->targetList = (List *)
ResolveNew((Node *) parse->targetList,
varno, 0, rte,
@ -700,29 +706,41 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
parse->rowMarks = list_concat(parse->rowMarks, subquery->rowMarks);
/*
* We also have to fix the relid sets of any FlattenedSubLink nodes in
* the parent query. (This could perhaps be done by ResolveNew, but it
* would clutter that routine's API unreasonably.)
* We also have to fix the relid sets of any FlattenedSubLink,
* PlaceHolderVar, and PlaceHolderInfo nodes in the parent query.
* (This could perhaps be done by ResolveNew, but it would clutter that
* routine's API unreasonably.) Note in particular that any placeholder
* nodes just created by insert_targetlist_placeholders() wiil be adjusted.
*
* Likewise, relids appearing in AppendRelInfo nodes have to be fixed (but
* we took care of their translated_vars lists above). We already checked
* that this won't require introducing multiple subrelids into the
* single-slot AppendRelInfo structs.
*/
if (parse->hasSubLinks || root->append_rel_list)
if (parse->hasSubLinks || root->placeholder_list || root->append_rel_list)
{
Relids subrelids;
subrelids = get_relids_in_jointree((Node *) subquery->jointree, false);
fix_flattened_sublink_relids((Node *) parse, varno, subrelids);
fix_append_rel_relids(root->append_rel_list, varno, subrelids);
substitute_multiple_relids((Node *) parse,
varno, subrelids);
substitute_multiple_relids((Node *) root->placeholder_list,
varno, subrelids);
fix_append_rel_relids(root->append_rel_list,
varno, subrelids);
}
/*
* And now add subquery's AppendRelInfos to our list.
* And now add subquery's AppendRelInfos and PlaceHolderInfos to our lists.
* Note that any placeholders pulled up from the subquery will appear
* after any we just created; this preserves the property that placeholders
* can only refer to other placeholders that appear later in the list
* (needed by fix_placeholder_eval_levels).
*/
root->append_rel_list = list_concat(root->append_rel_list,
subroot->append_rel_list);
root->placeholder_list = list_concat(root->placeholder_list,
subroot->placeholder_list);
/*
* We don't have to do the equivalent bookkeeping for outer-join info,
@ -950,7 +968,9 @@ is_simple_subquery(Query *subquery)
* Don't pull up a subquery that has any volatile functions in its
* targetlist. Otherwise we might introduce multiple evaluations of these
* functions, if they get copied to multiple places in the upper query,
* leading to surprising results.
* leading to surprising results. (Note: the PlaceHolderVar mechanism
* doesn't quite guarantee single evaluation; else we could pull up anyway
* and just wrap such items in PlaceHolderVars ...)
*/
if (contain_volatile_functions((Node *) subquery->targetList))
return false;
@ -959,8 +979,12 @@ is_simple_subquery(Query *subquery)
* Hack: don't try to pull up a subquery with an empty jointree.
* query_planner() will correctly generate a Result plan for a jointree
* that's totally empty, but I don't think the right things happen if an
* empty FromExpr appears lower down in a jointree. Not worth working hard
* on this, just to collapse SubqueryScan/Result into Result...
* empty FromExpr appears lower down in a jointree. It would pose a
* problem for the PlaceHolderVar mechanism too, since we'd have no
* way to identify where to evaluate a PHV coming out of the subquery.
* Not worth working hard on this, just to collapse SubqueryScan/Result
* into Result; especially since the SubqueryScan can often be optimized
* away by setrefs.c anyway.
*/
if (subquery->jointree->fromlist == NIL)
return false;
@ -1043,40 +1067,59 @@ is_simple_union_all_recurse(Node *setOp, Query *setOpQuery, List *colTypes)
}
/*
* has_nullable_targetlist
* Check a subquery in the range table to see if all the non-junk
* targetlist items are simple variables or strict functions of simple
* variables (and, hence, will correctly go to NULL when examined above
* the point of an outer join).
* insert_targetlist_placeholders
* Insert PlaceHolderVar nodes into any non-junk targetlist items that are
* not simple variables or strict functions of simple variables (and hence
* might not correctly go to NULL when examined above the point of an outer
* join). We assume we can modify the tlist items in-place.
*
* NOTE: it would be correct (and useful) to ignore output columns that aren't
* actually referenced by the enclosing query ... but we do not have that
* information available at this point.
* varno is the upper-query relid of the subquery; this is used as the
* syntactic location of the PlaceHolderVars.
* If wrap_non_vars is true then *only* simple Var references escape being
* wrapped with PlaceHolderVars.
*/
static bool
has_nullable_targetlist(Query *subquery)
static List *
insert_targetlist_placeholders(PlannerInfo *root, List *tlist,
int varno, bool wrap_non_vars)
{
ListCell *l;
ListCell *lc;
foreach(l, subquery->targetList)
foreach(lc, tlist)
{
TargetEntry *tle = (TargetEntry *) lfirst(l);
TargetEntry *tle = (TargetEntry *) lfirst(lc);
/* ignore resjunk columns */
if (tle->resjunk)
continue;
/* Must contain a Var of current level */
if (!contain_vars_of_level((Node *) tle->expr, 0))
return false;
/*
* Simple Vars always escape being wrapped. This is common enough
* to deserve a fast path even if we aren't doing wrap_non_vars.
*/
if (tle->expr && IsA(tle->expr, Var) &&
((Var *) tle->expr)->varlevelsup == 0)
continue;
/* Must not contain any non-strict constructs */
if (contain_nonstrict_functions((Node *) tle->expr))
return false;
if (!wrap_non_vars)
{
/*
* If it contains a Var of current level, and does not contain
* any non-strict constructs, then it's certainly nullable and we
* don't need to insert a PlaceHolderVar. (Note: in future maybe
* we should insert PlaceHolderVars anyway, when a tlist item is
* expensive to evaluate?
*/
if (contain_vars_of_level((Node *) tle->expr, 0) &&
!contain_nonstrict_functions((Node *) tle->expr))
continue;
}
/* This one's OK, keep scanning */
/* Else wrap it in a PlaceHolderVar */
tle->expr = (Expr *) make_placeholder_expr(root,
tle->expr,
bms_make_singleton(varno));
}
return true;
return tlist;
}
/*
@ -1088,7 +1131,6 @@ static bool
is_safe_append_member(Query *subquery)
{
FromExpr *jtnode;
ListCell *l;
/*
* It's only safe to pull up the child if its jointree contains exactly
@ -1113,24 +1155,6 @@ is_safe_append_member(Query *subquery)
if (!IsA(jtnode, RangeTblRef))
return false;
/*
* XXX For the moment we also have to insist that the subquery's tlist
* includes only simple Vars. This is pretty annoying, but fixing it
* seems to require nontrivial changes --- mainly because joinrel tlists
* are presently assumed to contain only Vars. Perhaps a pseudo-variable
* mechanism similar to the one speculated about in pull_up_subqueries'
* comments would help? FIXME someday.
*/
foreach(l, subquery->targetList)
{
TargetEntry *tle = (TargetEntry *) lfirst(l);
if (tle->resjunk)
continue;
if (!(tle->expr && IsA(tle->expr, Var)))
return false;
}
return true;
}
@ -1579,28 +1603,29 @@ reduce_outer_joins_pass2(Node *jtnode,
}
/*
* fix_flattened_sublink_relids - adjust FlattenedSubLink nodes after
* pulling up a subquery
* substitute_multiple_relids - adjust node relid sets after pulling up
* a subquery
*
* Find any FlattenedSubLink nodes in the given tree that reference the
* pulled-up relid, and change them to reference the replacement relid(s).
* We do not need to recurse into subqueries, since no subquery of the
* current top query could contain such a reference.
* Find any FlattenedSubLink, PlaceHolderVar, or PlaceHolderInfo nodes in the
* given tree that reference the pulled-up relid, and change them to reference
* the replacement relid(s). We do not need to recurse into subqueries, since
* no subquery of the current top query could (yet) contain such a reference.
*
* NOTE: although this has the form of a walker, we cheat and modify the
* nodes in-place. This should be OK since the tree was copied by ResolveNew
* earlier.
* earlier. Avoid scribbling on the original values of the bitmapsets, though,
* because expression_tree_mutator doesn't copy those.
*/
typedef struct
{
int varno;
Relids subrelids;
} fix_flattened_sublink_relids_context;
} substitute_multiple_relids_context;
static bool
fix_flattened_sublink_relids_walker(Node *node,
fix_flattened_sublink_relids_context *context)
substitute_multiple_relids_walker(Node *node,
substitute_multiple_relids_context *context)
{
if (node == NULL)
return false;
@ -1610,28 +1635,61 @@ fix_flattened_sublink_relids_walker(Node *node,
if (bms_is_member(context->varno, fslink->lefthand))
{
fslink->lefthand = bms_union(fslink->lefthand,
context->subrelids);
fslink->lefthand = bms_del_member(fslink->lefthand,
context->varno);
fslink->lefthand = bms_add_members(fslink->lefthand,
context->subrelids);
}
if (bms_is_member(context->varno, fslink->righthand))
{
fslink->righthand = bms_union(fslink->righthand,
context->subrelids);
fslink->righthand = bms_del_member(fslink->righthand,
context->varno);
fslink->righthand = bms_add_members(fslink->righthand,
context->subrelids);
}
/* fall through to examine children */
}
return expression_tree_walker(node, fix_flattened_sublink_relids_walker,
if (IsA(node, PlaceHolderVar))
{
PlaceHolderVar *phv = (PlaceHolderVar *) node;
if (bms_is_member(context->varno, phv->phrels))
{
phv->phrels = bms_union(phv->phrels,
context->subrelids);
phv->phrels = bms_del_member(phv->phrels,
context->varno);
}
/* fall through to examine children */
}
if (IsA(node, PlaceHolderInfo))
{
PlaceHolderInfo *phinfo = (PlaceHolderInfo *) node;
if (bms_is_member(context->varno, phinfo->ph_eval_at))
{
phinfo->ph_eval_at = bms_union(phinfo->ph_eval_at,
context->subrelids);
phinfo->ph_eval_at = bms_del_member(phinfo->ph_eval_at,
context->varno);
}
if (bms_is_member(context->varno, phinfo->ph_needed))
{
phinfo->ph_needed = bms_union(phinfo->ph_needed,
context->subrelids);
phinfo->ph_needed = bms_del_member(phinfo->ph_needed,
context->varno);
}
/* fall through to examine children */
}
return expression_tree_walker(node, substitute_multiple_relids_walker,
(void *) context);
}
static void
fix_flattened_sublink_relids(Node *node, int varno, Relids subrelids)
substitute_multiple_relids(Node *node, int varno, Relids subrelids)
{
fix_flattened_sublink_relids_context context;
substitute_multiple_relids_context context;
context.varno = varno;
context.subrelids = subrelids;
@ -1640,7 +1698,7 @@ fix_flattened_sublink_relids(Node *node, int varno, Relids subrelids)
* Must be prepared to start with a Query or a bare expression tree.
*/
query_or_expression_tree_walker(node,
fix_flattened_sublink_relids_walker,
substitute_multiple_relids_walker,
(void *) &context,
0);
}

View File

@ -16,7 +16,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/prep/preptlist.c,v 1.91 2008/08/28 23:09:46 tgl Exp $
* $PostgreSQL: pgsql/src/backend/optimizer/prep/preptlist.c,v 1.92 2008/10/21 20:42:53 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -168,13 +168,14 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
List *vars;
ListCell *l;
vars = pull_var_clause((Node *) parse->returningList, false);
vars = pull_var_clause((Node *) parse->returningList, true);
foreach(l, vars)
{
Var *var = (Var *) lfirst(l);
TargetEntry *tle;
if (var->varno == result_relation)
if (IsA(var, Var) &&
var->varno == result_relation)
continue; /* don't need it */
if (tlist_member((Node *) var, tlist))

View File

@ -22,7 +22,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/prep/prepunion.c,v 1.158 2008/10/07 19:27:04 tgl Exp $
* $PostgreSQL: pgsql/src/backend/optimizer/prep/prepunion.c,v 1.159 2008/10/21 20:42:53 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -1584,7 +1584,39 @@ adjust_appendrel_attrs_mutator(Node *node, AppendRelInfo *context)
context->child_relid);
return (Node *) fslink;
}
/* Shouldn't need to handle SpecialJoinInfo or AppendRelInfo here */
if (IsA(node, PlaceHolderVar))
{
/* Copy the PlaceHolderVar node with correct mutation of subnodes */
PlaceHolderVar *phv;
phv = (PlaceHolderVar *) expression_tree_mutator(node,
adjust_appendrel_attrs_mutator,
(void *) context);
/* now fix PlaceHolderVar's relid sets */
if (phv->phlevelsup == 0)
phv->phrels = adjust_relid_set(phv->phrels,
context->parent_relid,
context->child_relid);
return (Node *) phv;
}
if (IsA(node, PlaceHolderInfo))
{
/* Copy the PlaceHolderInfo node with correct mutation of subnodes */
PlaceHolderInfo *phinfo;
phinfo = (PlaceHolderInfo *) expression_tree_mutator(node,
adjust_appendrel_attrs_mutator,
(void *) context);
/* now fix PlaceHolderInfo's relid sets */
phinfo->ph_eval_at = adjust_relid_set(phinfo->ph_eval_at,
context->parent_relid,
context->child_relid);
phinfo->ph_needed = adjust_relid_set(phinfo->ph_needed,
context->parent_relid,
context->child_relid);
return (Node *) phinfo;
}
/* Shouldn't need to handle other planner auxiliary nodes here */
Assert(!IsA(node, SpecialJoinInfo));
Assert(!IsA(node, AppendRelInfo));

View File

@ -4,7 +4,7 @@
# Makefile for optimizer/util
#
# IDENTIFICATION
# $PostgreSQL: pgsql/src/backend/optimizer/util/Makefile,v 1.18 2008/02/19 10:30:07 petere Exp $
# $PostgreSQL: pgsql/src/backend/optimizer/util/Makefile,v 1.19 2008/10/21 20:42:53 tgl Exp $
#
#-------------------------------------------------------------------------
@ -12,7 +12,7 @@ subdir = src/backend/optimizer/util
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
OBJS = clauses.o joininfo.o pathnode.o plancat.o predtest.o \
OBJS = clauses.o joininfo.o pathnode.o placeholder.o plancat.o predtest.o \
relnode.o restrictinfo.o tlist.o var.o
include $(top_srcdir)/src/backend/common.mk

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.269 2008/10/09 19:27:40 tgl Exp $
* $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.270 2008/10/21 20:42:53 tgl Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
@ -1188,6 +1188,12 @@ find_nonnullable_rels_walker(Node *node, bool top_level)
result = find_nonnullable_rels_walker((Node *) expr->quals,
top_level);
}
else if (IsA(node, PlaceHolderVar))
{
PlaceHolderVar *phv = (PlaceHolderVar *) node;
result = find_nonnullable_rels_walker((Node *) phv->phexpr, top_level);
}
return result;
}
@ -1393,6 +1399,12 @@ find_nonnullable_vars_walker(Node *node, bool top_level)
result = find_nonnullable_vars_walker((Node *) expr->quals,
top_level);
}
else if (IsA(node, PlaceHolderVar))
{
PlaceHolderVar *phv = (PlaceHolderVar *) node;
result = find_nonnullable_vars_walker((Node *) phv->phexpr, top_level);
}
return result;
}
@ -1921,6 +1933,7 @@ eval_const_expressions(PlannerInfo *root, Node *node)
* constant. This effectively means that we plan using the first supplied
* value of the Param.
* 2. Fold stable, as well as immutable, functions to constants.
* 3. Reduce PlaceHolderVar nodes to their contained expressions.
*--------------------
*/
Node *
@ -2823,6 +2836,20 @@ eval_const_expressions_mutator(Node *node,
newfslink->quals = quals;
return (Node *) newfslink;
}
if (IsA(node, PlaceHolderVar) && context->estimate)
{
/*
* In estimation mode, just strip the PlaceHolderVar node altogether;
* this amounts to estimating that the contained value won't be forced
* to null by an outer join. In regular mode we just use the default
* behavior (ie, simplify the expression but leave the PlaceHolderVar
* node intact).
*/
PlaceHolderVar *phv = (PlaceHolderVar *) node;
return eval_const_expressions_mutator((Node *) phv->phexpr,
context);
}
/*
* For any node type not handled above, we recurse using

View File

@ -0,0 +1,226 @@
/*-------------------------------------------------------------------------
*
* placeholder.c
* PlaceHolderVar and PlaceHolderInfo manipulation routines
*
*
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/util/placeholder.c,v 1.1 2008/10/21 20:42:53 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/pathnode.h"
#include "optimizer/placeholder.h"
#include "optimizer/planmain.h"
#include "optimizer/var.h"
#include "utils/lsyscache.h"
/*
* make_placeholder_expr
* Make a PlaceHolderVar (and corresponding PlaceHolderInfo)
* for the given expression.
*
* phrels is the syntactic location (as a set of baserels) to attribute
* to the expression.
*/
PlaceHolderVar *
make_placeholder_expr(PlannerInfo *root, Expr *expr, Relids phrels)
{
PlaceHolderVar *phv = makeNode(PlaceHolderVar);
PlaceHolderInfo *phinfo = makeNode(PlaceHolderInfo);
phv->phexpr = expr;
phv->phrels = phrels;
phv->phid = ++(root->glob->lastPHId);
phv->phlevelsup = 0;
phinfo->phid = phv->phid;
phinfo->ph_var = copyObject(phv);
phinfo->ph_eval_at = pull_varnos((Node *) phv);
/* ph_eval_at may change later, see fix_placeholder_eval_levels */
phinfo->ph_needed = NULL; /* initially it's unused */
/* for the moment, estimate width using just the datatype info */
phinfo->ph_width = get_typavgwidth(exprType((Node *) expr),
exprTypmod((Node *) expr));
root->placeholder_list = lappend(root->placeholder_list, phinfo);
return phv;
}
/*
* find_placeholder_info
* Fetch the PlaceHolderInfo for the given PHV; error if not found
*/
PlaceHolderInfo *
find_placeholder_info(PlannerInfo *root, PlaceHolderVar *phv)
{
ListCell *lc;
/* if this ever isn't true, we'd need to be able to look in parent lists */
Assert(phv->phlevelsup == 0);
foreach(lc, root->placeholder_list)
{
PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(lc);
if (phinfo->phid == phv->phid)
return phinfo;
}
elog(ERROR, "could not find PlaceHolderInfo with id %u", phv->phid);
return NULL; /* keep compiler quiet */
}
/*
* fix_placeholder_eval_levels
* Adjust the target evaluation levels for placeholders
*
* The initial eval_at level set by make_placeholder_expr was the set of
* rels used in the placeholder's expression (or the whole subselect if
* the expr is variable-free). If the subselect contains any outer joins
* that can null any of those rels, we must delay evaluation to above those
* joins.
*
* In future we might want to put additional policy/heuristics here to
* try to determine an optimal evaluation level. The current rules will
* result in evaluation at the lowest possible level.
*/
void
fix_placeholder_eval_levels(PlannerInfo *root)
{
ListCell *lc1;
foreach(lc1, root->placeholder_list)
{
PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(lc1);
Relids syn_level = phinfo->ph_var->phrels;
Relids eval_at = phinfo->ph_eval_at;
BMS_Membership eval_membership;
bool found_some;
ListCell *lc2;
/*
* Ignore unreferenced placeholders. Note: if a placeholder is
* referenced only by some other placeholder's expr, we will do
* the right things because the referencing placeholder must appear
* earlier in the list.
*/
if (bms_is_empty(phinfo->ph_needed))
continue;
/*
* Check for delays due to lower outer joins. This is the same logic
* as in check_outerjoin_delay in initsplan.c, except that we don't
* want to modify the delay_upper_joins flags; that was all handled
* already during distribute_qual_to_rels.
*/
do
{
found_some = false;
foreach(lc2, root->join_info_list)
{
SpecialJoinInfo *sjinfo = (SpecialJoinInfo *) lfirst(lc2);
/* disregard joins not within the expr's sub-select */
if (!bms_is_subset(sjinfo->syn_lefthand, syn_level) ||
!bms_is_subset(sjinfo->syn_righthand, syn_level))
continue;
/* do we reference any nullable rels of this OJ? */
if (bms_overlap(eval_at, sjinfo->min_righthand) ||
(sjinfo->jointype == JOIN_FULL &&
bms_overlap(eval_at, sjinfo->min_lefthand)))
{
/* yes; have we included all its rels in eval_at? */
if (!bms_is_subset(sjinfo->min_lefthand, eval_at) ||
!bms_is_subset(sjinfo->min_righthand, eval_at))
{
/* no, so add them in */
eval_at = bms_add_members(eval_at,
sjinfo->min_lefthand);
eval_at = bms_add_members(eval_at,
sjinfo->min_righthand);
/* we'll need another iteration */
found_some = true;
}
}
}
} while (found_some);
phinfo->ph_eval_at = eval_at;
/*
* Now that we know where to evaluate the placeholder, make sure that
* any vars or placeholders it uses will be available at that join
* level. (Note that this has to be done within this loop to make
* sure we don't skip over such placeholders when we get to them.)
*/
eval_membership = bms_membership(eval_at);
if (eval_membership == BMS_MULTIPLE)
{
List *vars = pull_var_clause((Node *) phinfo->ph_var->phexpr,
true);
add_vars_to_targetlist(root, vars, eval_at);
list_free(vars);
}
/*
* Also, if the placeholder can be computed at a base rel and is
* needed above it, add it to that rel's targetlist. (This is
* essentially the same logic as in add_placeholders_to_joinrel, but
* we can't do that part until joinrels are formed.)
*/
if (eval_membership == BMS_SINGLETON)
{
int varno = bms_singleton_member(eval_at);
RelOptInfo *rel = find_base_rel(root, varno);
if (bms_nonempty_difference(phinfo->ph_needed, rel->relids))
rel->reltargetlist = lappend(rel->reltargetlist,
copyObject(phinfo->ph_var));
}
}
}
/*
* add_placeholders_to_joinrel
* Add any required PlaceHolderVars to a join rel's targetlist.
*
* A join rel should emit a PlaceHolderVar if (a) the PHV is needed above
* this join level and (b) the PHV can be computed at or below this level.
* At this time we do not need to distinguish whether the PHV will be
* computed here or copied up from below.
*/
void
add_placeholders_to_joinrel(PlannerInfo *root, RelOptInfo *joinrel)
{
Relids relids = joinrel->relids;
ListCell *lc;
foreach(lc, root->placeholder_list)
{
PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(lc);
/* Is it still needed above this joinrel? */
if (bms_nonempty_difference(phinfo->ph_needed, relids))
{
/* Is it computable here? */
if (bms_is_subset(phinfo->ph_eval_at, relids))
{
/* Yup, add it to the output */
joinrel->reltargetlist = lappend(joinrel->reltargetlist,
phinfo->ph_var);
joinrel->width += phinfo->ph_width;
}
}
}
}

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/util/relnode.c,v 1.91 2008/10/04 21:56:53 tgl Exp $
* $PostgreSQL: pgsql/src/backend/optimizer/util/relnode.c,v 1.92 2008/10/21 20:42:53 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -17,6 +17,7 @@
#include "optimizer/cost.h"
#include "optimizer/pathnode.h"
#include "optimizer/paths.h"
#include "optimizer/placeholder.h"
#include "optimizer/plancat.h"
#include "optimizer/restrictinfo.h"
#include "parser/parsetree.h"
@ -354,6 +355,7 @@ build_join_rel(PlannerInfo *root,
*/
build_joinrel_tlist(root, joinrel, outer_rel);
build_joinrel_tlist(root, joinrel, inner_rel);
add_placeholders_to_joinrel(root, joinrel);
/*
* Construct restrict and join clause lists for the new joinrel. (The
@ -403,7 +405,8 @@ build_join_rel(PlannerInfo *root,
/*
* build_joinrel_tlist
* Builds a join relation's target list.
* Builds a join relation's target list from an input relation.
* (This is invoked twice to handle the two input relations.)
*
* The join's targetlist includes all Vars of its member relations that
* will still be needed above the join. This subroutine adds all such
@ -421,16 +424,23 @@ build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
foreach(vars, input_rel->reltargetlist)
{
Var *origvar = (Var *) lfirst(vars);
Node *origvar = (Node *) lfirst(vars);
Var *var;
RelOptInfo *baserel;
int ndx;
/*
* Ignore PlaceHolderVars in the input tlists; we'll make our
* own decisions about whether to copy them.
*/
if (IsA(origvar, PlaceHolderVar))
continue;
/*
* We can't run into any child RowExprs here, but we could find a
* whole-row Var with a ConvertRowtypeExpr atop it.
*/
var = origvar;
var = (Var *) origvar;
while (!IsA(var, Var))
{
if (IsA(var, ConvertRowtypeExpr))

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/util/tlist.c,v 1.82 2008/08/25 22:42:33 tgl Exp $
* $PostgreSQL: pgsql/src/backend/optimizer/util/tlist.c,v 1.83 2008/10/21 20:42:53 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -91,7 +91,7 @@ tlist_member_ignore_relabel(Node *node, List *targetlist)
List *
flatten_tlist(List *tlist)
{
List *vlist = pull_var_clause((Node *) tlist, false);
List *vlist = pull_var_clause((Node *) tlist, true);
List *new_tlist;
new_tlist = add_to_flat_tlist(NIL, vlist);
@ -104,7 +104,7 @@ flatten_tlist(List *tlist)
* Add more vars to a flattened tlist (if they're not already in it)
*
* 'tlist' is the flattened tlist
* 'vars' is a list of var nodes
* 'vars' is a list of Var and/or PlaceHolderVar nodes
*
* Returns the extended tlist.
*/
@ -116,9 +116,9 @@ add_to_flat_tlist(List *tlist, List *vars)
foreach(v, vars)
{
Var *var = (Var *) lfirst(v);
Node *var = (Node *) lfirst(v);
if (!tlist_member((Node *) var, tlist))
if (!tlist_member(var, tlist))
{
TargetEntry *tle;

View File

@ -3,12 +3,18 @@
* var.c
* Var node manipulation routines
*
* Note: for most purposes, PlaceHolderVar is considered a Var too,
* even if its contained expression is variable-free. Also, CurrentOfExpr
* is treated as a Var for purposes of determining whether an expression
* contains variables.
*
*
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/util/var.c,v 1.80 2008/10/06 17:39:26 tgl Exp $
* $PostgreSQL: pgsql/src/backend/optimizer/util/var.c,v 1.81 2008/10/21 20:42:53 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -50,7 +56,7 @@ typedef struct
typedef struct
{
List *varlist;
bool includeUpperVars;
bool includePlaceHolderVars;
} pull_var_clause_context;
typedef struct
@ -128,6 +134,26 @@ pull_varnos_walker(Node *node, pull_varnos_context *context)
context->varnos = bms_add_member(context->varnos, cexpr->cvarno);
return false;
}
if (IsA(node, PlaceHolderVar))
{
/*
* Normally, we can just take the varnos in the contained expression.
* But if it is variable-free, use the PHV's syntactic relids.
*/
PlaceHolderVar *phv = (PlaceHolderVar *) node;
pull_varnos_context subcontext;
subcontext.varnos = NULL;
subcontext.sublevels_up = context->sublevels_up;
(void) pull_varnos_walker((Node *) phv->phexpr, &subcontext);
if (bms_is_empty(subcontext.varnos) &&
phv->phlevelsup == context->sublevels_up)
context->varnos = bms_add_members(context->varnos, phv->phrels);
else
context->varnos = bms_join(context->varnos, subcontext.varnos);
return false;
}
if (IsA(node, Query))
{
/* Recurse into RTE subquery or not-yet-planned sublink subquery */
@ -215,6 +241,12 @@ contain_var_clause_walker(Node *node, void *context)
}
if (IsA(node, CurrentOfExpr))
return true;
if (IsA(node, PlaceHolderVar))
{
if (((PlaceHolderVar *) node)->phlevelsup == 0)
return true; /* abort the tree traversal and return true */
/* else fall through to check the contained expr */
}
return expression_tree_walker(node, contain_var_clause_walker, context);
}
@ -256,6 +288,12 @@ contain_vars_of_level_walker(Node *node, int *sublevels_up)
return true;
return false;
}
if (IsA(node, PlaceHolderVar))
{
if (((PlaceHolderVar *) node)->phlevelsup == *sublevels_up)
return true; /* abort the tree traversal and return true */
/* else fall through to check the contained expr */
}
if (IsA(node, Query))
{
/* Recurse into subselects */
@ -329,6 +367,7 @@ locate_var_of_level_walker(Node *node,
/* since CurrentOfExpr doesn't carry location, nothing we can do */
return false;
}
/* No extra code needed for PlaceHolderVar; just look in contained expr */
if (IsA(node, Query))
{
/* Recurse into subselects */
@ -398,6 +437,7 @@ locate_var_of_relation_walker(Node *node,
/* since CurrentOfExpr doesn't carry location, nothing we can do */
return false;
}
/* No extra code needed for PlaceHolderVar; just look in contained expr */
if (IsA(node, Query))
{
/* Recurse into subselects */
@ -527,6 +567,30 @@ find_minimum_var_level_walker(Node *node,
}
}
}
/* Likewise, make sure PlaceHolderVar is treated correctly */
if (IsA(node, PlaceHolderVar))
{
int phlevelsup = ((PlaceHolderVar *) node)->phlevelsup;
/* convert levelsup to frame of reference of original query */
phlevelsup -= context->sublevels_up;
/* ignore local vars of subqueries */
if (phlevelsup >= 0)
{
if (context->min_varlevel < 0 ||
context->min_varlevel > phlevelsup)
{
context->min_varlevel = phlevelsup;
/*
* As soon as we find a local variable, we can abort the tree
* traversal, since min_varlevel is then certainly 0.
*/
if (phlevelsup == 0)
return true;
}
}
}
if (IsA(node, Query))
{
/* Recurse into subselects */
@ -548,25 +612,30 @@ find_minimum_var_level_walker(Node *node,
/*
* pull_var_clause
* Recursively pulls all var nodes from an expression clause.
* Recursively pulls all Var nodes from an expression clause.
*
* Upper-level vars (with varlevelsup > 0) are included only
* if includeUpperVars is true. Most callers probably want
* to ignore upper-level vars.
* PlaceHolderVars are included too, if includePlaceHolderVars is true.
* If it isn't true, an error is thrown if any are found.
* Note that Vars within a PHV's expression are *not* included.
*
* Returns list of varnodes found. Note the varnodes themselves are not
* CurrentOfExpr nodes are *not* included.
*
* Upper-level vars (with varlevelsup > 0) are not included.
* (These probably represent errors too, but we don't complain.)
*
* Returns list of nodes found. Note the nodes themselves are not
* copied, only referenced.
*
* Does not examine subqueries, therefore must only be used after reduction
* of sublinks to subplans!
*/
List *
pull_var_clause(Node *node, bool includeUpperVars)
pull_var_clause(Node *node, bool includePlaceHolderVars)
{
pull_var_clause_context context;
context.varlist = NIL;
context.includeUpperVars = includeUpperVars;
context.includePlaceHolderVars = includePlaceHolderVars;
pull_var_clause_walker(node, &context);
return context.varlist;
@ -579,10 +648,19 @@ pull_var_clause_walker(Node *node, pull_var_clause_context *context)
return false;
if (IsA(node, Var))
{
if (((Var *) node)->varlevelsup == 0 || context->includeUpperVars)
if (((Var *) node)->varlevelsup == 0)
context->varlist = lappend(context->varlist, node);
return false;
}
if (IsA(node, PlaceHolderVar))
{
if (!context->includePlaceHolderVars)
elog(ERROR, "PlaceHolderVar found where not expected");
if (((PlaceHolderVar *) node)->phlevelsup == 0)
context->varlist = lappend(context->varlist, node);
/* we do NOT descend into the contained expression */
return false;
}
return expression_tree_walker(node, pull_var_clause_walker,
(void *) context);
}
@ -597,6 +675,9 @@ pull_var_clause_walker(Node *node, pull_var_clause_context *context)
* is necessary since we will not scan the JOIN as a base relation, which
* is the only way that the executor can directly handle whole-row Vars.
*
* This also adjusts relid sets found in some expression node types to
* substitute the contained base rels for any join relid.
*
* NOTE: this is used on not-yet-planned expressions. We do not expect it
* to be applied directly to a Query node.
*/
@ -703,6 +784,40 @@ flatten_join_alias_vars_mutator(Node *node,
}
return (Node *) fslink;
}
if (IsA(node, PlaceHolderVar))
{
/* Copy the PlaceHolderVar node with correct mutation of subnodes */
PlaceHolderVar *phv;
phv = (PlaceHolderVar *) expression_tree_mutator(node,
flatten_join_alias_vars_mutator,
(void *) context);
/* now fix PlaceHolderVar's relid sets */
if (phv->phlevelsup == context->sublevels_up)
{
phv->phrels = alias_relid_set(context->root,
phv->phrels);
}
return (Node *) phv;
}
if (IsA(node, PlaceHolderInfo))
{
/* Copy the PlaceHolderInfo node with correct mutation of subnodes */
PlaceHolderInfo *phinfo;
phinfo = (PlaceHolderInfo *) expression_tree_mutator(node,
flatten_join_alias_vars_mutator,
(void *) context);
/* now fix PlaceHolderInfo's relid sets */
if (context->sublevels_up == 0)
{
phinfo->ph_eval_at = alias_relid_set(context->root,
phinfo->ph_eval_at);
phinfo->ph_needed = alias_relid_set(context->root,
phinfo->ph_needed);
}
return (Node *) phinfo;
}
if (IsA(node, Query))
{