From cffd89ca736e485309cd51ae056f837bd7e683ad Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Sat, 1 Jul 2006 18:38:33 +0000 Subject: [PATCH] Revise the planner's handling of "pseudoconstant" WHERE clauses, that is clauses containing no variables and no volatile functions. Such a clause can be used as a one-time qual in a gating Result plan node, to suppress plan execution entirely when it is false. Even when the clause is true, putting it in a gating node wins by avoiding repeated evaluation of the clause. In previous PG releases, query_planner() would do this for pseudoconstant clauses appearing at the top level of the jointree, but there was no ability to generate a gating Result deeper in the plan tree. To fix it, get rid of the special case in query_planner(), and instead process pseudoconstant clauses through the normal RestrictInfo qual distribution mechanism. When a pseudoconstant clause is found attached to a path node in create_plan(), pull it out and generate a gating Result at that point. This requires special-casing pseudoconstants in selectivity estimation and cost_qual_eval, but on the whole it's pretty clean. It probably even makes the planner a bit faster than before for the normal case of no pseudoconstants, since removing pull_constant_clauses saves one useless traversal of the qual tree. Per gripe from Phil Frost. --- src/backend/nodes/copyfuncs.c | 3 +- src/backend/nodes/outfuncs.c | 7 +- src/backend/optimizer/README | 2 +- src/backend/optimizer/path/allpaths.c | 10 +- src/backend/optimizer/path/clausesel.c | 34 +++- src/backend/optimizer/path/costsize.c | 31 +++- src/backend/optimizer/path/indxpath.c | 28 ++- src/backend/optimizer/plan/createplan.c | 214 ++++++++++++++-------- src/backend/optimizer/plan/initsplan.c | 71 ++++++- src/backend/optimizer/plan/planagg.c | 47 ++--- src/backend/optimizer/plan/planmain.c | 35 +--- src/backend/optimizer/plan/planner.c | 5 +- src/backend/optimizer/util/clauses.c | 43 +---- src/backend/optimizer/util/pathnode.c | 39 ++-- src/backend/optimizer/util/restrictinfo.c | 118 +++++++++--- src/include/nodes/relation.h | 50 ++--- src/include/optimizer/clauses.h | 3 +- src/include/optimizer/pathnode.h | 5 +- src/include/optimizer/planmain.h | 3 +- src/include/optimizer/restrictinfo.h | 10 +- 20 files changed, 464 insertions(+), 294 deletions(-) diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 2f2111458e..6cf580c136 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -15,7 +15,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.337 2006/06/27 03:43:19 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.338 2006/07/01 18:38:32 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1264,6 +1264,7 @@ _copyRestrictInfo(RestrictInfo *from) COPY_SCALAR_FIELD(is_pushed_down); COPY_SCALAR_FIELD(outerjoin_delayed); COPY_SCALAR_FIELD(can_join); + COPY_SCALAR_FIELD(pseudoconstant); COPY_BITMAPSET_FIELD(clause_relids); COPY_BITMAPSET_FIELD(required_relids); COPY_BITMAPSET_FIELD(left_relids); diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 25d3a11279..7444001acb 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.274 2006/04/30 18:30:39 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.275 2006/07/01 18:38:32 tgl Exp $ * * NOTES * Every node type that can appear in stored rules' parsetrees *must* @@ -1107,8 +1107,7 @@ _outResultPath(StringInfo str, ResultPath *node) _outPathInfo(str, (Path *) node); - WRITE_NODE_FIELD(subpath); - WRITE_NODE_FIELD(constantqual); + WRITE_NODE_FIELD(quals); } static void @@ -1185,6 +1184,7 @@ _outPlannerInfo(StringInfo str, PlannerInfo *node) WRITE_BOOL_FIELD(hasJoinRTEs); WRITE_BOOL_FIELD(hasOuterJoins); WRITE_BOOL_FIELD(hasHavingQual); + WRITE_BOOL_FIELD(hasPseudoConstantQuals); } static void @@ -1252,6 +1252,7 @@ _outRestrictInfo(StringInfo str, RestrictInfo *node) WRITE_BOOL_FIELD(is_pushed_down); WRITE_BOOL_FIELD(outerjoin_delayed); WRITE_BOOL_FIELD(can_join); + WRITE_BOOL_FIELD(pseudoconstant); WRITE_BITMAPSET_FIELD(clause_relids); WRITE_BITMAPSET_FIELD(required_relids); WRITE_BITMAPSET_FIELD(left_relids); diff --git a/src/backend/optimizer/README b/src/backend/optimizer/README index df9828b215..f0bb64d1a9 100644 --- a/src/backend/optimizer/README +++ b/src/backend/optimizer/README @@ -329,7 +329,7 @@ RelOptInfo - a relation or joined relations BitmapHeapPath - top of a bitmapped index scan TidPath - scan by CTID AppendPath - append multiple subpaths together - ResultPath - a Result plan node (used for variable-free tlist or qual) + ResultPath - a Result plan node (used for FROM-less SELECT) MaterialPath - a Material plan node UniquePath - remove duplicate rows NestPath - nested-loop joins diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index de82c3df8d..ad55360a85 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/path/allpaths.c,v 1.146 2006/05/02 04:34:18 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/path/allpaths.c,v 1.147 2006/07/01 18:38:32 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -446,7 +446,9 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel, * There are several cases where we cannot push down clauses. Restrictions * involving the subquery are checked by subquery_is_pushdown_safe(). * Restrictions on individual clauses are checked by - * qual_is_pushdown_safe(). + * qual_is_pushdown_safe(). Also, we don't want to push down + * pseudoconstant clauses; better to have the gating node above the + * subquery. * * Non-pushed-down clauses will get evaluated as qpquals of the * SubqueryScan node. @@ -466,7 +468,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel, RestrictInfo *rinfo = (RestrictInfo *) lfirst(l); Node *clause = (Node *) rinfo->clause; - if (qual_is_pushdown_safe(subquery, rti, clause, differentTypes)) + if (!rinfo->pseudoconstant && + qual_is_pushdown_safe(subquery, rti, clause, differentTypes)) { /* Push it down */ subquery_push_qual(subquery, rte, rti, clause); @@ -1066,7 +1069,6 @@ print_path(PlannerInfo *root, Path *path, int indent) break; case T_ResultPath: ptype = "Result"; - subpath = ((ResultPath *) path)->subpath; break; case T_MaterialPath: ptype = "Material"; diff --git a/src/backend/optimizer/path/clausesel.c b/src/backend/optimizer/path/clausesel.c index 3ff02902f7..f595a3f07f 100644 --- a/src/backend/optimizer/path/clausesel.c +++ b/src/backend/optimizer/path/clausesel.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/path/clausesel.c,v 1.79 2006/03/07 01:00:15 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/path/clausesel.c,v 1.80 2006/07/01 18:38:32 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -117,10 +117,18 @@ clauselist_selectivity(PlannerInfo *root, /* * Check for being passed a RestrictInfo. + * + * If it's a pseudoconstant RestrictInfo, then s2 is either 1.0 or + * 0.0; just use that rather than looking for range pairs. */ if (IsA(clause, RestrictInfo)) { rinfo = (RestrictInfo *) clause; + if (rinfo->pseudoconstant) + { + s1 = s1 * s2; + continue; + } clause = (Node *) rinfo->clause; } else @@ -422,6 +430,20 @@ clause_selectivity(PlannerInfo *root, { rinfo = (RestrictInfo *) clause; + /* + * If the clause is marked pseudoconstant, then it will be used as + * a gating qual and should not affect selectivity estimates; hence + * return 1.0. The only exception is that a constant FALSE may + * be taken as having selectivity 0.0, since it will surely mean + * no rows out of the plan. This case is simple enough that we + * need not bother caching the result. + */ + if (rinfo->pseudoconstant) + { + if (! IsA(rinfo->clause, Const)) + return s1; + } + /* * If possible, cache the result of the selectivity calculation for * the clause. We can cache if varRelid is zero or the clause @@ -509,7 +531,10 @@ clause_selectivity(PlannerInfo *root, else if (IsA(clause, Const)) { /* bool constant is pretty easy... */ - s1 = ((bool) ((Const *) clause)->constvalue) ? 1.0 : 0.0; + Const *con = (Const *) clause; + + s1 = con->constisnull ? 0.0 : + DatumGetBool(con->constvalue) ? 1.0 : 0.0; } else if (IsA(clause, Param)) { @@ -519,7 +544,10 @@ clause_selectivity(PlannerInfo *root, if (IsA(subst, Const)) { /* bool constant is pretty easy... */ - s1 = ((bool) ((Const *) subst)->constvalue) ? 1.0 : 0.0; + Const *con = (Const *) subst; + + s1 = con->constisnull ? 0.0 : + DatumGetBool(con->constvalue) ? 1.0 : 0.0; } else { diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c index fa8d06707b..eec165ab5d 100644 --- a/src/backend/optimizer/path/costsize.c +++ b/src/backend/optimizer/path/costsize.c @@ -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.158 2006/06/06 17:59:57 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/path/costsize.c,v 1.159 2006/07/01 18:38:32 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1604,20 +1604,29 @@ cost_qual_eval(QualCost *cost, List *quals) * routine's use, so that it's not necessary to evaluate the qual * clause's cost more than once. If the clause's cost hasn't been * computed yet, the field's startup value will contain -1. + * + * If the RestrictInfo is marked pseudoconstant, it will be tested + * only once, so treat its cost as all startup cost. */ if (qual && IsA(qual, RestrictInfo)) { - RestrictInfo *restrictinfo = (RestrictInfo *) qual; + RestrictInfo *rinfo = (RestrictInfo *) qual; - if (restrictinfo->eval_cost.startup < 0) + if (rinfo->eval_cost.startup < 0) { - restrictinfo->eval_cost.startup = 0; - restrictinfo->eval_cost.per_tuple = 0; - cost_qual_eval_walker((Node *) restrictinfo->clause, - &restrictinfo->eval_cost); + rinfo->eval_cost.startup = 0; + rinfo->eval_cost.per_tuple = 0; + cost_qual_eval_walker((Node *) rinfo->clause, + &rinfo->eval_cost); + if (rinfo->pseudoconstant) + { + /* count one execution during startup */ + rinfo->eval_cost.startup += rinfo->eval_cost.per_tuple; + rinfo->eval_cost.per_tuple = 0; + } } - cost->startup += restrictinfo->eval_cost.startup; - cost->per_tuple += restrictinfo->eval_cost.per_tuple; + cost->startup += rinfo->eval_cost.startup; + cost->per_tuple += rinfo->eval_cost.per_tuple; } else { @@ -1876,7 +1885,9 @@ set_joinrel_size_estimates(PlannerInfo *root, RelOptInfo *rel, * * If we are doing an outer join, take that into account: the output must * be at least as large as the non-nullable input. (Is there any chance - * of being even smarter?) + * of being even smarter?) (XXX this is not really right, because it + * assumes all the restriction clauses are join clauses; we should figure + * pushed-down clauses separately.) * * For JOIN_IN and variants, the Cartesian product is figured with respect * to a unique-ified input, and then we can clamp to the size of the other diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c index 75f2487757..6e6f4ac3a7 100644 --- a/src/backend/optimizer/path/indxpath.c +++ b/src/backend/optimizer/path/indxpath.c @@ -9,7 +9,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/path/indxpath.c,v 1.208 2006/06/07 17:08:07 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/path/indxpath.c,v 1.209 2006/07/01 18:38:32 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -998,6 +998,15 @@ match_clause_to_indexcol(IndexOptInfo *index, Oid expr_op; bool plain_op; + /* + * Never match pseudoconstants to indexes. (Normally this could not + * happen anyway, since a pseudoconstant clause couldn't contain a + * Var, but what if someone builds an expression index on a constant? + * It's not totally unreasonable to do so with a partial index, either.) + */ + if (rinfo->pseudoconstant) + return false; + /* First check for boolean-index cases. */ if (IsBooleanOpclass(opclass)) { @@ -2212,6 +2221,7 @@ expand_indexqual_conditions(IndexOptInfo *index, List *clausegroups) make_restrictinfo(boolqual, true, false, + false, NULL)); continue; } @@ -2577,7 +2587,7 @@ expand_indexqual_rowcompare(RestrictInfo *rinfo, matching_cols); rc->rargs = list_truncate((List *) copyObject(clause->rargs), matching_cols); - return make_restrictinfo((Expr *) rc, true, false, NULL); + return make_restrictinfo((Expr *) rc, true, false, false, NULL); } else { @@ -2586,7 +2596,7 @@ expand_indexqual_rowcompare(RestrictInfo *rinfo, opexpr = make_opclause(linitial_oid(new_ops), BOOLOID, false, copyObject(linitial(clause->largs)), copyObject(linitial(clause->rargs))); - return make_restrictinfo(opexpr, true, false, NULL); + return make_restrictinfo(opexpr, true, false, false, NULL); } } @@ -2678,7 +2688,7 @@ prefix_quals(Node *leftop, Oid opclass, elog(ERROR, "no = operator for opclass %u", opclass); expr = make_opclause(oproid, BOOLOID, false, (Expr *) leftop, (Expr *) prefix_const); - result = list_make1(make_restrictinfo(expr, true, false, NULL)); + result = list_make1(make_restrictinfo(expr, true, false, false, NULL)); return result; } @@ -2693,7 +2703,7 @@ prefix_quals(Node *leftop, Oid opclass, elog(ERROR, "no >= operator for opclass %u", opclass); expr = make_opclause(oproid, BOOLOID, false, (Expr *) leftop, (Expr *) prefix_const); - result = list_make1(make_restrictinfo(expr, true, false, NULL)); + result = list_make1(make_restrictinfo(expr, true, false, false, NULL)); /*------- * If we can create a string larger than the prefix, we can say @@ -2709,7 +2719,8 @@ prefix_quals(Node *leftop, Oid opclass, elog(ERROR, "no < operator for opclass %u", opclass); expr = make_opclause(oproid, BOOLOID, false, (Expr *) leftop, (Expr *) greaterstr); - result = lappend(result, make_restrictinfo(expr, true, false, NULL)); + result = lappend(result, + make_restrictinfo(expr, true, false, false, NULL)); } return result; @@ -2772,7 +2783,7 @@ network_prefix_quals(Node *leftop, Oid expr_op, Oid opclass, Datum rightop) (Expr *) leftop, (Expr *) makeConst(datatype, -1, opr1right, false, false)); - result = list_make1(make_restrictinfo(expr, true, false, NULL)); + result = list_make1(make_restrictinfo(expr, true, false, false, NULL)); /* create clause "key <= network_scan_last( rightop )" */ @@ -2787,7 +2798,8 @@ network_prefix_quals(Node *leftop, Oid expr_op, Oid opclass, Datum rightop) (Expr *) leftop, (Expr *) makeConst(datatype, -1, opr2right, false, false)); - result = lappend(result, make_restrictinfo(expr, true, false, NULL)); + result = lappend(result, + make_restrictinfo(expr, true, false, false, NULL)); return result; } diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 1cad6574f3..7a9bb08b89 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.211 2006/05/18 18:57:31 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/plan/createplan.c,v 1.212 2006/07/01 18:38:33 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -35,11 +35,12 @@ #include "utils/syscache.h" -static Scan *create_scan_plan(PlannerInfo *root, Path *best_path); +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 void disuse_physical_tlist(Plan *plan, Path *path); -static Join *create_join_plan(PlannerInfo *root, JoinPath *best_path); +static Plan *create_gating_plan(PlannerInfo *root, Plan *plan, List *quals); +static Plan *create_join_plan(PlannerInfo *root, JoinPath *best_path); static Plan *create_append_plan(PlannerInfo *root, AppendPath *best_path); static Result *create_result_plan(PlannerInfo *root, ResultPath *best_path); static Material *create_material_plan(PlannerInfo *root, MaterialPath *best_path); @@ -74,6 +75,7 @@ static void fix_indexqual_references(List *indexquals, IndexPath *index_path, static Node *fix_indexqual_operand(Node *node, IndexOptInfo *index, Oid *opclass); static List *get_switched_clauses(List *clauses, Relids outerrelids); +static List *order_qual_clauses(PlannerInfo *root, List *clauses); static void copy_path_costsize(Plan *dest, Path *src); static void copy_plan_costsize(Plan *dest, Plan *src); static SeqScan *make_seqscan(List *qptlist, List *qpqual, Index scanrelid); @@ -146,17 +148,17 @@ create_plan(PlannerInfo *root, Path *best_path) case T_TidScan: case T_SubqueryScan: case T_FunctionScan: - plan = (Plan *) create_scan_plan(root, best_path); + plan = create_scan_plan(root, best_path); break; case T_HashJoin: case T_MergeJoin: case T_NestLoop: - plan = (Plan *) create_join_plan(root, - (JoinPath *) best_path); + plan = create_join_plan(root, + (JoinPath *) best_path); break; case T_Append: - plan = (Plan *) create_append_plan(root, - (AppendPath *) best_path); + plan = create_append_plan(root, + (AppendPath *) best_path); break; case T_Result: plan = (Plan *) create_result_plan(root, @@ -167,8 +169,8 @@ create_plan(PlannerInfo *root, Path *best_path) (MaterialPath *) best_path); break; case T_Unique: - plan = (Plan *) create_unique_plan(root, - (UniquePath *) best_path); + plan = create_unique_plan(root, + (UniquePath *) best_path); break; default: elog(ERROR, "unrecognized node type: %d", @@ -183,16 +185,14 @@ create_plan(PlannerInfo *root, Path *best_path) /* * create_scan_plan * Create a scan plan for the parent relation of 'best_path'. - * - * Returns a Plan node. */ -static Scan * +static Plan * create_scan_plan(PlannerInfo *root, Path *best_path) { RelOptInfo *rel = best_path->parent; List *tlist; List *scan_clauses; - Scan *plan; + Plan *plan; /* * For table scans, rather than using the relation targetlist (which is @@ -213,22 +213,23 @@ create_scan_plan(PlannerInfo *root, Path *best_path) tlist = build_relation_tlist(rel); /* - * Extract the relevant restriction clauses from the parent relation; the - * executor must apply all these restrictions during the scan. + * Extract the relevant restriction clauses from the parent relation. + * The executor must apply all these restrictions during the scan, + * except for pseudoconstants which we'll take care of below. */ scan_clauses = rel->baserestrictinfo; switch (best_path->pathtype) { case T_SeqScan: - plan = (Scan *) create_seqscan_plan(root, + plan = (Plan *) create_seqscan_plan(root, best_path, tlist, scan_clauses); break; case T_IndexScan: - plan = (Scan *) create_indexscan_plan(root, + plan = (Plan *) create_indexscan_plan(root, (IndexPath *) best_path, tlist, scan_clauses, @@ -236,28 +237,28 @@ create_scan_plan(PlannerInfo *root, Path *best_path) break; case T_BitmapHeapScan: - plan = (Scan *) create_bitmap_scan_plan(root, + plan = (Plan *) create_bitmap_scan_plan(root, (BitmapHeapPath *) best_path, tlist, scan_clauses); break; case T_TidScan: - plan = (Scan *) create_tidscan_plan(root, + plan = (Plan *) create_tidscan_plan(root, (TidPath *) best_path, tlist, scan_clauses); break; case T_SubqueryScan: - plan = (Scan *) create_subqueryscan_plan(root, + plan = (Plan *) create_subqueryscan_plan(root, best_path, tlist, scan_clauses); break; case T_FunctionScan: - plan = (Scan *) create_functionscan_plan(root, + plan = (Plan *) create_functionscan_plan(root, best_path, tlist, scan_clauses); @@ -270,6 +271,14 @@ create_scan_plan(PlannerInfo *root, Path *best_path) break; } + /* + * If there are any pseudoconstant clauses attached to this node, + * insert a gating Result node that evaluates the pseudoconstants + * as one-time quals. + */ + if (root->hasPseudoConstantQuals) + plan = create_gating_plan(root, plan, scan_clauses); + return plan; } @@ -365,19 +374,54 @@ disuse_physical_tlist(Plan *plan, Path *path) } } +/* + * create_gating_plan + * Deal with pseudoconstant qual clauses + * + * If the node's quals list includes any pseudoconstant quals, put them + * into a gating Result node atop the already-built plan. Otherwise, + * return the plan as-is. + * + * Note that we don't change cost or size estimates when doing gating. + * The costs of qual eval were already folded into the plan's startup cost. + * Leaving the size alone amounts to assuming that the gating qual will + * succeed, which is the conservative estimate for planning upper queries. + * We certainly don't want to assume the output size is zero (unless the + * gating qual is actually constant FALSE, and that case is dealt with in + * clausesel.c). Interpolating between the two cases is silly, because + * it doesn't reflect what will really happen at runtime, and besides which + * in most cases we have only a very bad idea of the probability of the gating + * qual being true. + */ +static Plan * +create_gating_plan(PlannerInfo *root, Plan *plan, List *quals) +{ + List *pseudoconstants; + + /* Pull out any pseudoconstant quals from the RestrictInfo list */ + pseudoconstants = extract_actual_clauses(quals, true); + + if (!pseudoconstants) + return plan; + + pseudoconstants = order_qual_clauses(root, pseudoconstants); + + return (Plan *) make_result((List *) copyObject(plan->targetlist), + (Node *) pseudoconstants, + plan); +} + /* * create_join_plan * Create a join plan for 'best_path' and (recursively) plans for its * inner and outer paths. - * - * Returns a Plan node. */ -static Join * +static Plan * create_join_plan(PlannerInfo *root, JoinPath *best_path) { Plan *outer_plan; Plan *inner_plan; - Join *plan; + Plan *plan; outer_plan = create_plan(root, best_path->outerjoinpath); inner_plan = create_plan(root, best_path->innerjoinpath); @@ -385,19 +429,19 @@ create_join_plan(PlannerInfo *root, JoinPath *best_path) switch (best_path->path.pathtype) { case T_MergeJoin: - plan = (Join *) create_mergejoin_plan(root, + plan = (Plan *) create_mergejoin_plan(root, (MergePath *) best_path, outer_plan, inner_plan); break; case T_HashJoin: - plan = (Join *) create_hashjoin_plan(root, + plan = (Plan *) create_hashjoin_plan(root, (HashPath *) best_path, outer_plan, inner_plan); break; case T_NestLoop: - plan = (Join *) create_nestloop_plan(root, + plan = (Plan *) create_nestloop_plan(root, (NestPath *) best_path, outer_plan, inner_plan); @@ -409,6 +453,14 @@ create_join_plan(PlannerInfo *root, JoinPath *best_path) break; } + /* + * If there are any pseudoconstant clauses attached to this node, + * insert a gating Result node that evaluates the pseudoconstants + * as one-time quals. + */ + if (root->hasPseudoConstantQuals) + plan = create_gating_plan(root, plan, best_path->joinrestrictinfo); + #ifdef NOT_USED /* @@ -473,34 +525,24 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path) /* * create_result_plan - * Create a Result plan for 'best_path' and (recursively) plans - * for its subpaths. + * Create a Result plan for 'best_path'. + * This is only used for the case of a query with an empty jointree. * * Returns a Plan node. */ static Result * create_result_plan(PlannerInfo *root, ResultPath *best_path) { - Result *plan; List *tlist; - List *constclauses; - Plan *subplan; + List *quals; - if (best_path->path.parent) - tlist = build_relation_tlist(best_path->path.parent); - else - tlist = NIL; /* will be filled in later */ + /* The tlist will be installed later, since we have no RelOptInfo */ + Assert(best_path->path.parent == NULL); + tlist = NIL; - if (best_path->subpath) - subplan = create_plan(root, best_path->subpath); - else - subplan = NULL; + quals = order_qual_clauses(root, best_path->quals); - constclauses = order_qual_clauses(root, best_path->constantqual); - - plan = make_result(tlist, (Node *) constclauses, subplan); - - return plan; + return make_result(tlist, (Node *) quals, NULL); } /* @@ -716,8 +758,8 @@ create_seqscan_plan(PlannerInfo *root, Path *best_path, Assert(scan_relid > 0); Assert(best_path->parent->rtekind == RTE_RELATION); - /* Reduce RestrictInfo list to bare expressions */ - scan_clauses = get_actual_clauses(scan_clauses); + /* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */ + scan_clauses = extract_actual_clauses(scan_clauses, false); /* Sort clauses into best execution order */ scan_clauses = order_qual_clauses(root, scan_clauses); @@ -824,7 +866,8 @@ create_indexscan_plan(PlannerInfo *root, * plan so that they'll be properly rechecked by EvalPlanQual testing. * * While at it, we strip off the RestrictInfos to produce a list of plain - * expressions. + * expressions (this loop replaces extract_actual_clauses used in the + * other routines in this file). We have to ignore pseudoconstants. */ qpqual = NIL; foreach(l, scan_clauses) @@ -832,6 +875,8 @@ create_indexscan_plan(PlannerInfo *root, RestrictInfo *rinfo = (RestrictInfo *) lfirst(l); Assert(IsA(rinfo, RestrictInfo)); + if (rinfo->pseudoconstant) + continue; if (list_member_ptr(nonlossy_indexquals, rinfo)) continue; if (!contain_mutable_functions((Node *) rinfo->clause)) @@ -900,8 +945,8 @@ create_bitmap_scan_plan(PlannerInfo *root, bitmapqualplan = create_bitmap_subplan(root, best_path->bitmapqual, &bitmapqualorig, &indexquals); - /* Reduce RestrictInfo list to bare expressions */ - scan_clauses = get_actual_clauses(scan_clauses); + /* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */ + scan_clauses = extract_actual_clauses(scan_clauses, false); /* * If this is a innerjoin scan, the indexclauses will contain join clauses @@ -1183,8 +1228,8 @@ create_tidscan_plan(PlannerInfo *root, TidPath *best_path, Assert(scan_relid > 0); Assert(best_path->path.parent->rtekind == RTE_RELATION); - /* Reduce RestrictInfo list to bare expressions */ - scan_clauses = get_actual_clauses(scan_clauses); + /* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */ + scan_clauses = extract_actual_clauses(scan_clauses, false); /* * Remove any clauses that are TID quals. This is a bit tricky since @@ -1224,8 +1269,8 @@ create_subqueryscan_plan(PlannerInfo *root, Path *best_path, Assert(scan_relid > 0); Assert(best_path->parent->rtekind == RTE_SUBQUERY); - /* Reduce RestrictInfo list to bare expressions */ - scan_clauses = get_actual_clauses(scan_clauses); + /* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */ + scan_clauses = extract_actual_clauses(scan_clauses, false); /* Sort clauses into best execution order */ scan_clauses = order_qual_clauses(root, scan_clauses); @@ -1256,8 +1301,8 @@ create_functionscan_plan(PlannerInfo *root, Path *best_path, Assert(scan_relid > 0); Assert(best_path->parent->rtekind == RTE_FUNCTION); - /* Reduce RestrictInfo list to bare expressions */ - scan_clauses = get_actual_clauses(scan_clauses); + /* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */ + scan_clauses = extract_actual_clauses(scan_clauses, false); /* Sort clauses into best execution order */ scan_clauses = order_qual_clauses(root, scan_clauses); @@ -1348,15 +1393,16 @@ create_nestloop_plan(PlannerInfo *root, } /* Get the join qual clauses (in plain expression form) */ + /* Any pseudoconstant clauses are ignored here */ if (IS_OUTER_JOIN(best_path->jointype)) { - get_actual_join_clauses(joinrestrictclauses, - &joinclauses, &otherclauses); + extract_actual_join_clauses(joinrestrictclauses, + &joinclauses, &otherclauses); } else { /* We can treat all clauses alike for an inner join */ - joinclauses = get_actual_clauses(joinrestrictclauses); + joinclauses = extract_actual_clauses(joinrestrictclauses, false); otherclauses = NIL; } @@ -1389,15 +1435,17 @@ create_mergejoin_plan(PlannerInfo *root, MergeJoin *join_plan; /* Get the join qual clauses (in plain expression form) */ + /* Any pseudoconstant clauses are ignored here */ if (IS_OUTER_JOIN(best_path->jpath.jointype)) { - get_actual_join_clauses(best_path->jpath.joinrestrictinfo, - &joinclauses, &otherclauses); + extract_actual_join_clauses(best_path->jpath.joinrestrictinfo, + &joinclauses, &otherclauses); } else { /* We can treat all clauses alike for an inner join */ - joinclauses = get_actual_clauses(best_path->jpath.joinrestrictinfo); + joinclauses = extract_actual_clauses(best_path->jpath.joinrestrictinfo, + false); otherclauses = NIL; } @@ -1473,15 +1521,17 @@ create_hashjoin_plan(PlannerInfo *root, Hash *hash_plan; /* Get the join qual clauses (in plain expression form) */ + /* Any pseudoconstant clauses are ignored here */ if (IS_OUTER_JOIN(best_path->jpath.jointype)) { - get_actual_join_clauses(best_path->jpath.joinrestrictinfo, - &joinclauses, &otherclauses); + extract_actual_join_clauses(best_path->jpath.joinrestrictinfo, + &joinclauses, &otherclauses); } else { /* We can treat all clauses alike for an inner join */ - joinclauses = get_actual_clauses(best_path->jpath.joinrestrictinfo); + joinclauses = extract_actual_clauses(best_path->jpath.joinrestrictinfo, + false); otherclauses = NIL; } @@ -1831,7 +1881,7 @@ get_switched_clauses(List *clauses, Relids outerrelids) * For now, we just move any quals that contain SubPlan references (but not * InitPlan references) to the end of the list. */ -List * +static List * order_qual_clauses(PlannerInfo *root, List *clauses) { List *nosubplans; @@ -2880,6 +2930,15 @@ make_limit(Plan *lefttree, Node *limitOffset, Node *limitCount, return node; } +/* + * make_result + * Build a Result plan node + * + * If we have a subplan, assume that any evaluation costs for the gating qual + * were already factored into the subplan's startup cost, and just copy the + * subplan cost. If there's no subplan, we should include the qual eval + * cost. In either case, tlist eval cost is not to be included here. + */ Result * make_result(List *tlist, Node *resconstantqual, @@ -2895,17 +2954,16 @@ make_result(List *tlist, plan->startup_cost = 0; plan->total_cost = cpu_tuple_cost; plan->plan_rows = 1; /* wrong if we have a set-valued function? */ - plan->plan_width = 0; /* XXX try to be smarter? */ - } + plan->plan_width = 0; /* XXX is it worth being smarter? */ + if (resconstantqual) + { + QualCost qual_cost; - if (resconstantqual) - { - QualCost qual_cost; - - cost_qual_eval(&qual_cost, (List *) resconstantqual); - /* resconstantqual is evaluated once at startup */ - plan->startup_cost += qual_cost.startup + qual_cost.per_tuple; - plan->total_cost += qual_cost.startup + qual_cost.per_tuple; + cost_qual_eval(&qual_cost, (List *) resconstantqual); + /* resconstantqual is evaluated once at startup */ + plan->startup_cost += qual_cost.startup + qual_cost.per_tuple; + plan->total_cost += qual_cost.startup + qual_cost.per_tuple; + } } plan->targetlist = tlist; diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index 1c04a8784d..bf8e67d0dc 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.117 2006/03/14 22:48:19 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/plan/initsplan.c,v 1.118 2006/07/01 18:38:33 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -23,6 +23,7 @@ #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/planmain.h" +#include "optimizer/prep.h" #include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" #include "optimizer/var.h" @@ -72,6 +73,9 @@ static void check_hashjoinable(RestrictInfo *restrictinfo); * the base relations (ie, table, subquery, and function RTEs) * appearing in the jointree. * + * The initial invocation must pass root->parse->jointree as the value of + * jtnode. Internally, the function recurses through the jointree. + * * At the end of this process, there should be one baserel RelOptInfo for * every non-join RTE that is used in the query. Therefore, this routine * is the only place that should call build_simple_rel with reloptkind @@ -578,6 +582,7 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause, { Relids relids; bool outerjoin_delayed; + bool pseudoconstant = false; bool maybe_equijoin; bool maybe_outer_join; RestrictInfo *restrictinfo; @@ -599,16 +604,57 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause, elog(ERROR, "JOIN qualification may not refer to other relations"); /* - * If the clause is variable-free, we force it to be evaluated at its - * original syntactic level. Note that this should not happen for - * top-level clauses, because query_planner() special-cases them. But it - * will happen for variable-free JOIN/ON clauses. We don't have to be - * real smart about such a case, we just have to be correct. Also note - * that for an outer-join clause, we must force it to the OJ's semantic - * level, not the syntactic scope. + * If the clause is variable-free, our normal heuristic for pushing it + * down to just the mentioned rels doesn't work, because there are none. + * + * If the clause is an outer-join clause, we must force it to the OJ's + * semantic level to preserve semantics. + * + * Otherwise, when the clause contains volatile functions, we force it + * to be evaluated at its original syntactic level. This preserves the + * expected semantics. + * + * When the clause contains no volatile functions either, it is actually + * a pseudoconstant clause that will not change value during any one + * execution of the plan, and hence can be used as a one-time qual in + * a gating Result plan node. We put such a clause into the regular + * RestrictInfo lists for the moment, but eventually createplan.c will + * pull it out and make a gating Result node immediately above whatever + * plan node the pseudoconstant clause is assigned to. It's usually + * best to put a gating node as high in the plan tree as possible. + * If we are not below an outer join, we can actually push the + * pseudoconstant qual all the way to the top of the tree. If we are + * below an outer join, we leave the qual at its original syntactic level + * (we could push it up to just below the outer join, but that seems more + * complex than it's worth). */ if (bms_is_empty(relids)) - relids = ojscope ? ojscope : qualscope; + { + if (ojscope) + { + /* clause is attached to outer join, eval it there */ + relids = ojscope; + /* mustn't use as gating qual, so don't mark pseudoconstant */ + } + else + { + /* eval at original syntactic level */ + relids = qualscope; + if (!contain_volatile_functions(clause)) + { + /* mark as gating qual */ + pseudoconstant = true; + /* tell createplan.c to check for gating quals */ + root->hasPseudoConstantQuals = true; + /* if not below outer join, push it to top of tree */ + if (!below_outer_join) + { + relids = get_relids_in_jointree((Node *) root->parse->jointree); + is_pushed_down = true; + } + } + } + } /* * Check to see if clause application must be delayed by outer-join @@ -624,6 +670,7 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause, */ Assert(bms_equal(relids, qualscope)); Assert(!ojscope); + Assert(!pseudoconstant); /* Needn't feed it back for more deductions */ outerjoin_delayed = false; maybe_equijoin = false; @@ -647,6 +694,7 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause, Assert(ojscope); relids = ojscope; outerjoin_delayed = true; + Assert(!pseudoconstant); /* * We can't use such a clause to deduce equijoin (the left and right @@ -738,6 +786,7 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause, restrictinfo = make_restrictinfo((Expr *) clause, is_pushed_down, outerjoin_delayed, + pseudoconstant, relids); /* @@ -1179,6 +1228,8 @@ check_mergejoinable(RestrictInfo *restrictinfo) leftOp, rightOp; + if (restrictinfo->pseudoconstant) + return; if (!is_opclause(clause)) return; if (list_length(((OpExpr *) clause)->args) != 2) @@ -1212,6 +1263,8 @@ check_hashjoinable(RestrictInfo *restrictinfo) Expr *clause = restrictinfo->clause; Oid opno; + if (restrictinfo->pseudoconstant) + return; if (!is_opclause(clause)) return; if (list_length(((OpExpr *) clause)->args) != 2) diff --git a/src/backend/optimizer/plan/planagg.c b/src/backend/optimizer/plan/planagg.c index c567f8b6b7..7a61b3fb17 100644 --- a/src/backend/optimizer/plan/planagg.c +++ b/src/backend/optimizer/plan/planagg.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/plan/planagg.c,v 1.15 2006/06/06 17:59:57 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/plan/planagg.c,v 1.16 2006/07/01 18:38:33 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -46,8 +46,7 @@ static bool build_minmax_path(PlannerInfo *root, RelOptInfo *rel, MinMaxAggInfo *info); static ScanDirection match_agg_to_index_col(MinMaxAggInfo *info, IndexOptInfo *index, int indexcol); -static void make_agg_subplan(PlannerInfo *root, MinMaxAggInfo *info, - List *constant_quals); +static void make_agg_subplan(PlannerInfo *root, MinMaxAggInfo *info); static Node *replace_aggs_with_params_mutator(Node *node, List **context); static Oid fetch_agg_sort_op(Oid aggfnoid); @@ -81,7 +80,6 @@ optimize_minmax_aggregates(PlannerInfo *root, List *tlist, Path *best_path) Plan *plan; Node *hqual; QualCost tlist_cost; - List *constant_quals; /* Nothing to do if query has no aggregates */ if (!parse->hasAggs) @@ -164,27 +162,13 @@ optimize_minmax_aggregates(PlannerInfo *root, List *tlist, Path *best_path) return NULL; /* too expensive */ /* - * OK, we are going to generate an optimized plan. The first thing we - * need to do is look for any non-variable WHERE clauses that - * query_planner might have removed from the basic plan. (Normal WHERE - * clauses will be properly incorporated into the sub-plans by - * create_plan.) If there are any, they will be in a gating Result node - * atop the best_path. They have to be incorporated into a gating Result - * in each sub-plan in order to produce the semantically correct result. + * OK, we are going to generate an optimized plan. */ - if (IsA(best_path, ResultPath)) - { - constant_quals = ((ResultPath *) best_path)->constantqual; - /* no need to do this more than once: */ - constant_quals = order_qual_clauses(root, constant_quals); - } - else - constant_quals = NIL; /* Pass 3: generate subplans and output Param nodes */ foreach(l, aggs_list) { - make_agg_subplan(root, (MinMaxAggInfo *) lfirst(l), constant_quals); + make_agg_subplan(root, (MinMaxAggInfo *) lfirst(l)); } /* @@ -434,11 +418,12 @@ match_agg_to_index_col(MinMaxAggInfo *info, IndexOptInfo *index, int indexcol) * Construct a suitable plan for a converted aggregate query */ static void -make_agg_subplan(PlannerInfo *root, MinMaxAggInfo *info, List *constant_quals) +make_agg_subplan(PlannerInfo *root, MinMaxAggInfo *info) { PlannerInfo subroot; Query *subparse; Plan *plan; + Plan *iplan; TargetEntry *tle; SortClause *sortcl; NullTest *ntest; @@ -482,8 +467,7 @@ make_agg_subplan(PlannerInfo *root, MinMaxAggInfo *info, List *constant_quals) /* * Generate the plan for the subquery. We already have a Path for the * basic indexscan, but we have to convert it to a Plan and attach a LIMIT - * node above it. We might need a gating Result, too, to handle any - * non-variable qual clauses. + * node above it. * * Also we must add a "WHERE foo IS NOT NULL" restriction to the * indexscan, to be sure we don't return a NULL, which'd be contrary to @@ -491,21 +475,26 @@ make_agg_subplan(PlannerInfo *root, MinMaxAggInfo *info, List *constant_quals) * earlier, so that the selectivity of the restriction could be included * in our cost estimates. But that looks painful, and in most cases the * fraction of NULLs isn't high enough to change the decision. + * + * The NOT NULL qual has to go on the actual indexscan; create_plan + * might have stuck a gating Result atop that, if there were any + * pseudoconstant quals. */ plan = create_plan(&subroot, (Path *) info->path); plan->targetlist = copyObject(subparse->targetList); + if (IsA(plan, Result)) + iplan = plan->lefttree; + else + iplan = plan; + Assert(IsA(iplan, IndexScan)); + ntest = makeNode(NullTest); ntest->nulltesttype = IS_NOT_NULL; ntest->arg = copyObject(info->target); - plan->qual = lcons(ntest, plan->qual); - - if (constant_quals) - plan = (Plan *) make_result(copyObject(plan->targetlist), - copyObject(constant_quals), - plan); + iplan->qual = lcons(ntest, iplan->qual); plan = (Plan *) make_limit(plan, subparse->limitOffset, diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index bc1438a467..2ad786ce64 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.93 2006/03/05 15:58:29 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/plan/planmain.c,v 1.94 2006/07/01 18:38:33 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -82,7 +82,6 @@ query_planner(PlannerInfo *root, List *tlist, double tuple_fraction, double *num_groups) { Query *parse = root->parse; - List *constant_quals; List *joinlist; RelOptInfo *final_rel; Path *cheapestpath; @@ -99,26 +98,12 @@ query_planner(PlannerInfo *root, List *tlist, double tuple_fraction, */ if (parse->jointree->fromlist == NIL) { - *cheapest_path = (Path *) create_result_path(NULL, NULL, - (List *) parse->jointree->quals); + *cheapest_path = (Path *) + create_result_path((List *) parse->jointree->quals); *sorted_path = NULL; return; } - /* - * Pull out any non-variable WHERE clauses so these can be put in a - * toplevel "Result" node, where they will gate execution of the whole - * plan (the Result will not invoke its descendant plan unless the quals - * are true). Note that any *really* non-variable quals will have been - * optimized away by eval_const_expressions(). What we're mostly - * interested in here is quals that depend only on outer-level vars, - * although if the qual reduces to "WHERE FALSE" this path will also be - * taken. - */ - parse->jointree->quals = (Node *) - pull_constant_clauses((List *) parse->jointree->quals, - &constant_quals); - /* * Init planner lists to empty, and set up the array to hold RelOptInfos * for "simple" rels. @@ -324,20 +309,6 @@ query_planner(PlannerInfo *root, List *tlist, double tuple_fraction, } } - /* - * If we have constant quals, add a toplevel Result step to process them. - */ - if (constant_quals) - { - cheapestpath = (Path *) create_result_path(final_rel, - cheapestpath, - constant_quals); - if (sortedpath) - sortedpath = (Path *) create_result_path(final_rel, - sortedpath, - constant_quals); - } - *cheapest_path = cheapestpath; *sorted_path = sortedpath; } diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index da1cadcd0d..160310c56d 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.200 2006/06/28 20:04:38 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.201 2006/07/01 18:38:33 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -270,6 +270,9 @@ subquery_planner(Query *parse, double tuple_fraction, */ root->hasHavingQual = (parse->havingQual != NULL); + /* Clear this flag; might get set in distribute_qual_to_rels */ + root->hasPseudoConstantQuals = false; + /* * Do expression preprocessing on targetlist and quals. */ diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 4da9f47dec..bde0966f4a 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.212 2006/06/16 18:42:22 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.213 2006/07/01 18:38:33 tgl Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -1052,14 +1052,13 @@ is_strict_saop(ScalarArrayOpExpr *expr, bool falseOK) /* * is_pseudo_constant_clause - * Detect whether a clause is "constant", ie, it contains no variables - * of the current query level and no uses of volatile functions. - * Such a clause is not necessarily a true constant: it can still contain + * Detect whether an expression is "pseudo constant", ie, it contains no + * variables of the current query level and no uses of volatile functions. + * Such an expr is not necessarily a true constant: it can still contain * Params and outer-level Vars, not to mention functions whose results - * may vary from one statement to the next. However, the clause's value + * may vary from one statement to the next. However, the expr's value * will be constant over any one scan of the current query, so it can be - * used as an indexscan key or (if a top-level qual) can be pushed up to - * become a gating qual. + * used as, eg, an indexscan key. */ bool is_pseudo_constant_clause(Node *clause) @@ -1079,7 +1078,7 @@ is_pseudo_constant_clause(Node *clause) /* * is_pseudo_constant_clause_relids * Same as above, except caller already has available the var membership - * of the clause; this lets us avoid the contain_var_clause() scan. + * of the expression; this lets us avoid the contain_var_clause() scan. */ bool is_pseudo_constant_clause_relids(Node *clause, Relids relids) @@ -1090,34 +1089,6 @@ is_pseudo_constant_clause_relids(Node *clause, Relids relids) return false; } -/* - * pull_constant_clauses - * Scan through a list of qualifications and separate "constant" quals - * from those that are not. - * - * Returns a list of the pseudo-constant clauses in constantQual and the - * remaining quals as the return value. - */ -List * -pull_constant_clauses(List *quals, List **constantQual) -{ - List *constqual = NIL, - *restqual = NIL; - ListCell *q; - - foreach(q, quals) - { - Node *qual = (Node *) lfirst(q); - - if (is_pseudo_constant_clause(qual)) - constqual = lappend(constqual, qual); - else - restqual = lappend(restqual, qual); - } - *constantQual = constqual; - return restqual; -} - /***************************************************************************** * Tests on clauses of queries diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index f827007414..c0363d2405 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/util/pathnode.c,v 1.128 2006/06/06 17:59:57 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/util/pathnode.c,v 1.129 2006/07/01 18:38:33 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -667,36 +667,29 @@ create_append_path(RelOptInfo *rel, List *subpaths) /* * create_result_path - * Creates a path corresponding to a Result plan, returning the - * pathnode. + * Creates a path representing a Result-and-nothing-else plan. + * This is only used for the case of a query with an empty jointree. */ ResultPath * -create_result_path(RelOptInfo *rel, Path *subpath, List *constantqual) +create_result_path(List *quals) { ResultPath *pathnode = makeNode(ResultPath); pathnode->path.pathtype = T_Result; - pathnode->path.parent = rel; /* may be NULL */ - - if (subpath) - pathnode->path.pathkeys = subpath->pathkeys; - else - pathnode->path.pathkeys = NIL; - - pathnode->subpath = subpath; - pathnode->constantqual = constantqual; + pathnode->path.parent = NULL; + pathnode->path.pathkeys = NIL; + pathnode->quals = quals; /* Ideally should define cost_result(), but I'm too lazy */ - if (subpath) - { - pathnode->path.startup_cost = subpath->startup_cost; - pathnode->path.total_cost = subpath->total_cost; - } - else - { - pathnode->path.startup_cost = 0; - pathnode->path.total_cost = cpu_tuple_cost; - } + pathnode->path.startup_cost = 0; + pathnode->path.total_cost = cpu_tuple_cost; + /* + * In theory we should include the qual eval cost as well, but + * at present that doesn't accomplish much except duplicate work that + * will be done again in make_result; since this is only used for + * degenerate cases, nothing interesting will be done with the path + * cost values... + */ return pathnode; } diff --git a/src/backend/optimizer/util/restrictinfo.c b/src/backend/optimizer/util/restrictinfo.c index 606f77bf90..ec43ee39f0 100644 --- a/src/backend/optimizer/util/restrictinfo.c +++ b/src/backend/optimizer/util/restrictinfo.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/util/restrictinfo.c,v 1.47 2006/04/07 17:05:39 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/util/restrictinfo.c,v 1.48 2006/07/01 18:38:33 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -26,10 +26,12 @@ static RestrictInfo *make_restrictinfo_internal(Expr *clause, Expr *orclause, bool is_pushed_down, bool outerjoin_delayed, + bool pseudoconstant, Relids required_relids); static Expr *make_sub_restrictinfos(Expr *clause, bool is_pushed_down, bool outerjoin_delayed, + bool pseudoconstant, Relids required_relids); static RestrictInfo *join_clause_is_redundant(PlannerInfo *root, RestrictInfo *rinfo, @@ -42,9 +44,10 @@ static RestrictInfo *join_clause_is_redundant(PlannerInfo *root, * * Build a RestrictInfo node containing the given subexpression. * - * The is_pushed_down and outerjoin_delayed flags must be supplied by the - * caller. required_relids can be NULL, in which case it defaults to the - * actual clause contents (i.e., clause_relids). + * The is_pushed_down, outerjoin_delayed, and pseudoconstant flags for the + * RestrictInfo must be supplied by the caller. required_relids can be NULL, + * in which case it defaults to the actual clause contents (i.e., + * clause_relids). * * We initialize fields that depend only on the given subexpression, leaving * others that depend on context (or may never be needed at all) to be filled @@ -54,6 +57,7 @@ RestrictInfo * make_restrictinfo(Expr *clause, bool is_pushed_down, bool outerjoin_delayed, + bool pseudoconstant, Relids required_relids) { /* @@ -64,13 +68,17 @@ make_restrictinfo(Expr *clause, return (RestrictInfo *) make_sub_restrictinfos(clause, is_pushed_down, outerjoin_delayed, + pseudoconstant, required_relids); /* Shouldn't be an AND clause, else AND/OR flattening messed up */ Assert(!and_clause((Node *) clause)); - return make_restrictinfo_internal(clause, NULL, - is_pushed_down, outerjoin_delayed, + return make_restrictinfo_internal(clause, + NULL, + is_pushed_down, + outerjoin_delayed, + pseudoconstant, required_relids); } @@ -85,7 +93,8 @@ make_restrictinfo(Expr *clause, * RestrictInfos. * * The caller must pass is_pushed_down, but we assume outerjoin_delayed - * is false (no such qual should ever get into a bitmapqual). + * and pseudoconstant are false (no such qual should ever get into a + * bitmapqual). * * If include_predicates is true, we add any partial index predicates to * the explicit index quals. When this is not true, we return a condition @@ -214,6 +223,7 @@ make_restrictinfo_from_bitmapqual(Path *bitmapqual, make_orclause(withris), is_pushed_down, false, + false, NULL)); } } @@ -239,6 +249,7 @@ make_restrictinfo_from_bitmapqual(Path *bitmapqual, make_restrictinfo(pred, is_pushed_down, false, + false, NULL)); } } @@ -258,8 +269,11 @@ make_restrictinfo_from_bitmapqual(Path *bitmapqual, * Common code for the main entry points and the recursive cases. */ static RestrictInfo * -make_restrictinfo_internal(Expr *clause, Expr *orclause, - bool is_pushed_down, bool outerjoin_delayed, +make_restrictinfo_internal(Expr *clause, + Expr *orclause, + bool is_pushed_down, + bool outerjoin_delayed, + bool pseudoconstant, Relids required_relids) { RestrictInfo *restrictinfo = makeNode(RestrictInfo); @@ -268,6 +282,7 @@ make_restrictinfo_internal(Expr *clause, Expr *orclause, restrictinfo->orclause = orclause; restrictinfo->is_pushed_down = is_pushed_down; restrictinfo->outerjoin_delayed = outerjoin_delayed; + restrictinfo->pseudoconstant = pseudoconstant; restrictinfo->can_join = false; /* may get set below */ /* @@ -292,7 +307,11 @@ make_restrictinfo_internal(Expr *clause, Expr *orclause, !bms_is_empty(restrictinfo->right_relids) && !bms_overlap(restrictinfo->left_relids, restrictinfo->right_relids)) + { restrictinfo->can_join = true; + /* pseudoconstant should certainly not be true */ + Assert(!restrictinfo->pseudoconstant); + } } else { @@ -346,13 +365,18 @@ make_restrictinfo_internal(Expr *clause, Expr *orclause, * implicit-AND lists at top level of RestrictInfo lists. Only ORs and * simple clauses are valid RestrictInfos. * + * The same is_pushed_down, outerjoin_delayed, and pseudoconstant flag + * values can be applied to all RestrictInfo nodes in the result. + * * The given required_relids are attached to our top-level output, * but any OR-clause constituents are allowed to default to just the * contained rels. */ static Expr * make_sub_restrictinfos(Expr *clause, - bool is_pushed_down, bool outerjoin_delayed, + bool is_pushed_down, + bool outerjoin_delayed, + bool pseudoconstant, Relids required_relids) { if (or_clause((Node *) clause)) @@ -365,11 +389,13 @@ make_sub_restrictinfos(Expr *clause, make_sub_restrictinfos(lfirst(temp), is_pushed_down, outerjoin_delayed, + pseudoconstant, NULL)); return (Expr *) make_restrictinfo_internal(clause, make_orclause(orlist), is_pushed_down, outerjoin_delayed, + pseudoconstant, required_relids); } else if (and_clause((Node *) clause)) @@ -382,6 +408,7 @@ make_sub_restrictinfos(Expr *clause, make_sub_restrictinfos(lfirst(temp), is_pushed_down, outerjoin_delayed, + pseudoconstant, required_relids)); return make_andclause(andlist); } @@ -390,6 +417,7 @@ make_sub_restrictinfos(Expr *clause, NULL, is_pushed_down, outerjoin_delayed, + pseudoconstant, required_relids); } @@ -411,47 +439,91 @@ restriction_is_or_clause(RestrictInfo *restrictinfo) * get_actual_clauses * * Returns a list containing the bare clauses from 'restrictinfo_list'. + * + * This is only to be used in cases where none of the RestrictInfos can + * be pseudoconstant clauses (for instance, it's OK on indexqual lists). */ List * get_actual_clauses(List *restrictinfo_list) { List *result = NIL; - ListCell *temp; + ListCell *l; - foreach(temp, restrictinfo_list) + foreach(l, restrictinfo_list) { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(temp); + RestrictInfo *rinfo = (RestrictInfo *) lfirst(l); Assert(IsA(rinfo, RestrictInfo)); + Assert(!rinfo->pseudoconstant); + result = lappend(result, rinfo->clause); } return result; } /* - * get_actual_join_clauses + * extract_actual_clauses * - * Extract clauses from 'restrictinfo_list', separating those that + * Extract bare clauses from 'restrictinfo_list', returning either the + * regular ones or the pseudoconstant ones per 'pseudoconstant'. + */ +List * +extract_actual_clauses(List *restrictinfo_list, + bool pseudoconstant) +{ + List *result = NIL; + ListCell *l; + + foreach(l, restrictinfo_list) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(l); + + Assert(IsA(rinfo, RestrictInfo)); + + if (rinfo->pseudoconstant == pseudoconstant) + result = lappend(result, rinfo->clause); + } + return result; +} + +/* + * extract_actual_join_clauses + * + * Extract bare clauses from 'restrictinfo_list', separating those that * syntactically match the join level from those that were pushed down. + * Pseudoconstant clauses are excluded from the results. + * + * This is only used at outer joins, since for plain joins we don't care + * about pushed-down-ness. */ void -get_actual_join_clauses(List *restrictinfo_list, - List **joinquals, List **otherquals) +extract_actual_join_clauses(List *restrictinfo_list, + List **joinquals, + List **otherquals) { - ListCell *temp; + ListCell *l; *joinquals = NIL; *otherquals = NIL; - foreach(temp, restrictinfo_list) + foreach(l, restrictinfo_list) { - RestrictInfo *clause = (RestrictInfo *) lfirst(temp); + RestrictInfo *rinfo = (RestrictInfo *) lfirst(l); - if (clause->is_pushed_down) - *otherquals = lappend(*otherquals, clause->clause); + Assert(IsA(rinfo, RestrictInfo)); + + if (rinfo->is_pushed_down) + { + if (!rinfo->pseudoconstant) + *otherquals = lappend(*otherquals, rinfo->clause); + } else - *joinquals = lappend(*joinquals, clause->clause); + { + /* joinquals shouldn't have been marked pseudoconstant */ + Assert(!rinfo->pseudoconstant); + *joinquals = lappend(*joinquals, rinfo->clause); + } } } diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 1f971118f5..d7a93f0f65 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/relation.h,v 1.125 2006/06/06 17:59:58 tgl Exp $ + * $PostgreSQL: pgsql/src/include/nodes/relation.h,v 1.126 2006/07/01 18:38:33 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -114,6 +114,8 @@ typedef struct PlannerInfo bool hasJoinRTEs; /* true if any RTEs are RTE_JOIN kind */ bool hasOuterJoins; /* true if any RTEs are outer joins */ bool hasHavingQual; /* true if havingQual was non-null */ + bool hasPseudoConstantQuals; /* true if any RestrictInfo has + * pseudoconstant = true */ } PlannerInfo; @@ -524,25 +526,16 @@ typedef struct AppendPath } AppendPath; /* - * ResultPath represents use of a Result plan node. There are several - * applications for this: - * * To compute a variable-free targetlist (a "SELECT expressions" query). - * In this case subpath and path.parent will both be NULL. constantqual - * might or might not be empty ("SELECT expressions WHERE something"). - * * To gate execution of a subplan with a one-time (variable-free) qual - * condition. path.parent is copied from the subpath. - * * To substitute for a scan plan when we have proven that no rows in - * a table will satisfy the query. subpath is NULL but path.parent - * references the not-to-be-scanned relation, and constantqual is - * a constant FALSE. + * ResultPath represents use of a Result plan node to compute a variable-free + * targetlist with no underlying tables (a "SELECT expressions" query). + * The query could have a WHERE clause, too, represented by "quals". * - * Note that constantqual is a list of bare clauses, not RestrictInfos. + * Note that quals is a list of bare clauses, not RestrictInfos. */ typedef struct ResultPath { Path path; - Path *subpath; - List *constantqual; + List *quals; } ResultPath; /* @@ -732,6 +725,22 @@ typedef struct HashPath * OR/AND structure. This is a convenience for OR indexscan processing: * indexquals taken from either the top level or an OR subclause will have * associated RestrictInfo nodes. + * + * The can_join flag is set true if the clause looks potentially useful as + * a merge or hash join clause, that is if it is a binary opclause with + * nonoverlapping sets of relids referenced in the left and right sides. + * (Whether the operator is actually merge or hash joinable isn't checked, + * however.) + * + * The pseudoconstant flag is set true if the clause contains no Vars of + * the current query level and no volatile functions. Such a clause can be + * pulled out and used as a one-time qual in a gating Result node. We keep + * pseudoconstant clauses in the same lists as other RestrictInfos so that + * the regular clause-pushing machinery can assign them to the correct join + * level, but they need to be treated specially for cost and selectivity + * estimates. Note that a pseudoconstant clause can never be an indexqual + * or merge or hash join clause, so it's of no interest to large parts of + * the planner. */ typedef struct RestrictInfo @@ -744,14 +753,9 @@ typedef struct RestrictInfo bool outerjoin_delayed; /* TRUE if delayed by outer join */ - /* - * This flag is set true if the clause looks potentially useful as a merge - * or hash join clause, that is if it is a binary opclause with - * nonoverlapping sets of relids referenced in the left and right sides. - * (Whether the operator is actually merge or hash joinable isn't checked, - * however.) - */ - bool can_join; + bool can_join; /* see comment above */ + + bool pseudoconstant; /* see comment above */ /* The set of relids (varnos) actually referenced in the clause: */ Relids clause_relids; diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h index d384d2fa16..b7c960e77a 100644 --- a/src/include/optimizer/clauses.h +++ b/src/include/optimizer/clauses.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/optimizer/clauses.h,v 1.83 2006/03/05 15:58:57 momjian Exp $ + * $PostgreSQL: pgsql/src/include/optimizer/clauses.h,v 1.84 2006/07/01 18:38:33 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -61,7 +61,6 @@ extern Relids find_nonnullable_rels(Node *clause); extern bool is_pseudo_constant_clause(Node *clause); extern bool is_pseudo_constant_clause_relids(Node *clause, Relids relids); -extern List *pull_constant_clauses(List *quals, List **constantQual); extern bool has_distinct_clause(Query *query); extern bool has_distinct_on_clause(Query *query); diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index f728489be5..3e7f510b28 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/optimizer/pathnode.h,v 1.68 2006/06/06 17:59:58 tgl Exp $ + * $PostgreSQL: pgsql/src/include/optimizer/pathnode.h,v 1.69 2006/07/01 18:38:33 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -47,8 +47,7 @@ extern BitmapOrPath *create_bitmap_or_path(PlannerInfo *root, extern TidPath *create_tidscan_path(PlannerInfo *root, RelOptInfo *rel, List *tidquals); extern AppendPath *create_append_path(RelOptInfo *rel, List *subpaths); -extern ResultPath *create_result_path(RelOptInfo *rel, Path *subpath, - List *constantqual); +extern ResultPath *create_result_path(List *quals); extern MaterialPath *create_material_path(RelOptInfo *rel, Path *subpath); extern UniquePath *create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath); diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h index 16948801c5..30b89d0974 100644 --- a/src/include/optimizer/planmain.h +++ b/src/include/optimizer/planmain.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/optimizer/planmain.h,v 1.92 2006/03/05 15:58:57 momjian Exp $ + * $PostgreSQL: pgsql/src/include/optimizer/planmain.h,v 1.93 2006/07/01 18:38:33 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -42,7 +42,6 @@ extern Sort *make_sort_from_sortclauses(PlannerInfo *root, List *sortcls, Plan *lefttree); extern Sort *make_sort_from_groupcols(PlannerInfo *root, List *groupcls, AttrNumber *grpColIdx, Plan *lefttree); -extern List *order_qual_clauses(PlannerInfo *root, List *clauses); extern Agg *make_agg(PlannerInfo *root, List *tlist, List *qual, AggStrategy aggstrategy, int numGroupCols, AttrNumber *grpColIdx, diff --git a/src/include/optimizer/restrictinfo.h b/src/include/optimizer/restrictinfo.h index 17978ba666..86add79160 100644 --- a/src/include/optimizer/restrictinfo.h +++ b/src/include/optimizer/restrictinfo.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/optimizer/restrictinfo.h,v 1.36 2006/03/05 15:58:57 momjian Exp $ + * $PostgreSQL: pgsql/src/include/optimizer/restrictinfo.h,v 1.37 2006/07/01 18:38:33 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -20,14 +20,18 @@ extern RestrictInfo *make_restrictinfo(Expr *clause, bool is_pushed_down, bool outerjoin_delayed, + bool pseudoconstant, Relids required_relids); extern List *make_restrictinfo_from_bitmapqual(Path *bitmapqual, bool is_pushed_down, bool include_predicates); extern bool restriction_is_or_clause(RestrictInfo *restrictinfo); extern List *get_actual_clauses(List *restrictinfo_list); -extern void get_actual_join_clauses(List *restrictinfo_list, - List **joinquals, List **otherquals); +extern List *extract_actual_clauses(List *restrictinfo_list, + bool pseudoconstant); +extern void extract_actual_join_clauses(List *restrictinfo_list, + List **joinquals, + List **otherquals); extern List *remove_redundant_join_clauses(PlannerInfo *root, List *restrictinfo_list, bool isouterjoin);