diff options
| author | Tom Lane <tgl@sss.pgh.pa.us> | 2008-10-21 20:42:53 +0000 |
|---|---|---|
| committer | Tom Lane <tgl@sss.pgh.pa.us> | 2008-10-21 20:42:53 +0000 |
| commit | e6ae3b5dbf2c07bceb737c5a0ff199b1156051d1 (patch) | |
| tree | b8616c6b889741b53c282574195fc41317778dfd /src/backend/optimizer/plan | |
| parent | 831abae5061b98b7f203a24af7badeaaeb15808f (diff) | |
| download | postgresql-e6ae3b5dbf2c07bceb737c5a0ff199b1156051d1.tar.gz | |
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.
Diffstat (limited to 'src/backend/optimizer/plan')
| -rw-r--r-- | src/backend/optimizer/plan/createplan.c | 34 | ||||
| -rw-r--r-- | src/backend/optimizer/plan/initsplan.c | 49 | ||||
| -rw-r--r-- | src/backend/optimizer/plan/planmain.c | 18 | ||||
| -rw-r--r-- | src/backend/optimizer/plan/planner.c | 18 | ||||
| -rw-r--r-- | src/backend/optimizer/plan/setrefs.c | 82 | ||||
| -rw-r--r-- | src/backend/optimizer/plan/subselect.c | 9 |
6 files changed, 166 insertions, 44 deletions
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index f812c93979..7bddf33e5c 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -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)) diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index 49c2cdf2f5..1208262b8e 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -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); + + if (IsA(node, Var)) + { + Var *var = (Var *) node; + RelOptInfo *rel = find_base_rel(root, var->varno); + int attno = var->varattno; - Assert(attrno >= rel->min_attr && attrno <= rel->max_attr); - attrno -= rel->min_attr; - if (bms_is_empty(rel->attr_needed[attrno])) + 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); + } + else if (IsA(node, PlaceHolderVar)) { - /* Variable not yet requested, so add to reltargetlist */ - /* XXX is copyObject necessary here? */ - rel->reltargetlist = lappend(rel->reltargetlist, copyObject(var)); + PlaceHolderVar *phv = (PlaceHolderVar *) node; + PlaceHolderInfo *phinfo = find_placeholder_info(root, phv); + + phinfo->ph_needed = bms_add_members(phinfo->ph_needed, + where_needed); } - rel->attr_needed[attrno] = bms_add_members(rel->attr_needed[attrno], - 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); diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index 7dcdaf250f..171724983d 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -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); @@ -241,6 +236,13 @@ query_planner(PlannerInfo *root, List *tlist, 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. */ final_rel = make_one_rel(root, joinlist); diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 0a704dc654..05de001a87 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -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 */ diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index 6d7ec283b3..9bec109f6f 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -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)) diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c index 00d84d5682..b80427f041 100644 --- a/src/backend/optimizer/plan/subselect.c +++ b/src/backend/optimizer/plan/subselect.c @@ -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; |
