diff options
Diffstat (limited to 'src/backend/optimizer')
-rw-r--r-- | src/backend/optimizer/geqo/geqo_eval.c | 11 | ||||
-rw-r--r-- | src/backend/optimizer/path/allpaths.c | 84 | ||||
-rw-r--r-- | src/backend/optimizer/path/costsize.c | 20 | ||||
-rw-r--r-- | src/backend/optimizer/path/joinpath.c | 30 | ||||
-rw-r--r-- | src/backend/optimizer/plan/createplan.c | 111 | ||||
-rw-r--r-- | src/backend/optimizer/plan/initsplan.c | 62 | ||||
-rw-r--r-- | src/backend/optimizer/plan/planner.c | 3 | ||||
-rw-r--r-- | src/backend/optimizer/plan/subselect.c | 1 | ||||
-rw-r--r-- | src/backend/optimizer/prep/prepjointree.c | 7 | ||||
-rw-r--r-- | src/backend/optimizer/util/pathnode.c | 26 | ||||
-rw-r--r-- | src/backend/optimizer/util/var.c | 165 |
11 files changed, 411 insertions, 109 deletions
diff --git a/src/backend/optimizer/geqo/geqo_eval.c b/src/backend/optimizer/geqo/geqo_eval.c index 1e352fd3b5e..57eb2c39a4f 100644 --- a/src/backend/optimizer/geqo/geqo_eval.c +++ b/src/backend/optimizer/geqo/geqo_eval.c @@ -56,6 +56,7 @@ geqo_eval(PlannerInfo *root, Gene *tour, int num_gene) MemoryContext mycontext; MemoryContext oldcxt; RelOptInfo *joinrel; + Path *best_path; Cost fitness; int savelength; struct HTAB *savehash; @@ -99,6 +100,14 @@ geqo_eval(PlannerInfo *root, Gene *tour, int num_gene) /* construct the best path for the given combination of relations */ joinrel = gimme_tree(root, tour, num_gene); + best_path = joinrel->cheapest_total_path; + + /* + * If no unparameterized path, use the cheapest parameterized path for + * costing purposes. XXX revisit this after LATERAL dust settles + */ + if (!best_path) + best_path = linitial(joinrel->cheapest_parameterized_paths); /* * compute fitness @@ -106,7 +115,7 @@ geqo_eval(PlannerInfo *root, Gene *tour, int num_gene) * XXX geqo does not currently support optimization for partial result * retrieval --- how to fix? */ - fitness = joinrel->cheapest_total_path->total_cost; + fitness = best_path->total_cost; /* * Restore join_rel_list to its former state, and put back original diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index f02954982a7..bfda05394d6 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -253,8 +253,9 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel, case RTE_SUBQUERY: /* - * Subqueries don't support parameterized paths, so just go - * ahead and build their paths immediately. + * Subqueries don't support making a choice between + * parameterized and unparameterized paths, so just go ahead + * and build their paths immediately. */ set_subquery_pathlist(root, rel, rti, rte); break; @@ -698,6 +699,10 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, if (IS_DUMMY_REL(childrel)) continue; + /* XXX need to figure out what to do for LATERAL */ + if (childrel->cheapest_total_path == NULL) + elog(ERROR, "LATERAL within an append relation is not supported yet"); + /* * Child is live, so add its cheapest access path to the Append path * we are constructing for the parent. @@ -906,6 +911,10 @@ generate_mergeappend_paths(PlannerInfo *root, RelOptInfo *rel, */ if (cheapest_startup == NULL || cheapest_total == NULL) { + /* XXX need to figure out what to do for LATERAL */ + if (childrel->cheapest_total_path == NULL) + elog(ERROR, "LATERAL within an append relation is not supported yet"); + cheapest_startup = cheapest_total = childrel->cheapest_total_path; Assert(cheapest_total != NULL); @@ -1012,8 +1021,13 @@ has_multiple_baserels(PlannerInfo *root) * set_subquery_pathlist * Build the (single) access path for a subquery RTE * - * There's no need for a separate set_subquery_size phase, since we don't - * support parameterized paths for subqueries. + * We don't currently support generating parameterized paths for subqueries + * by pushing join clauses down into them; it seems too expensive to re-plan + * the subquery multiple times to consider different alternatives. So the + * subquery will have exactly one path. (The path will be parameterized + * if the subquery contains LATERAL references, otherwise not.) Since there's + * no freedom of action here, there's no need for a separate set_subquery_size + * phase: we just make the path right away. */ static void set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel, @@ -1021,6 +1035,7 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel, { Query *parse = root->parse; Query *subquery = rte->subquery; + Relids required_outer; bool *differentTypes; double tuple_fraction; PlannerInfo *subroot; @@ -1033,6 +1048,20 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel, */ subquery = copyObject(subquery); + /* + * If it's a LATERAL subquery, it might contain some Vars of the current + * query level, requiring it to be treated as parameterized. + */ + if (rte->lateral) + { + required_outer = pull_varnos_of_level((Node *) subquery, 1); + /* Enforce convention that empty required_outer is exactly NULL */ + if (bms_is_empty(required_outer)) + required_outer = NULL; + } + else + required_outer = NULL; + /* We need a workspace for keeping track of set-op type coercions */ differentTypes = (bool *) palloc0((list_length(subquery->targetList) + 1) * sizeof(bool)); @@ -1051,10 +1080,9 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel, * pseudoconstant clauses; better to have the gating node above the * subquery. * - * Also, if the sub-query has "security_barrier" flag, it means the + * Also, if the sub-query has the "security_barrier" flag, it means the * sub-query originated from a view that must enforce row-level security. - * We must not push down quals in order to avoid information leaks, either - * via side-effects or error output. + * Then we must not push down quals that contain leaky functions. * * Non-pushed-down clauses will get evaluated as qpquals of the * SubqueryScan node. @@ -1134,7 +1162,7 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel, pathkeys = convert_subquery_pathkeys(root, rel, subroot->query_pathkeys); /* Generate appropriate path */ - add_path(rel, create_subqueryscan_path(root, rel, pathkeys, NULL)); + add_path(rel, create_subqueryscan_path(root, rel, pathkeys, required_outer)); /* Select cheapest path (pretty easy in this case...) */ set_cheapest(rel); @@ -1143,12 +1171,32 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel, /* * set_function_pathlist * Build the (single) access path for a function RTE + * + * As with subqueries, a function RTE's path might be parameterized due to + * LATERAL references, but that's inherent in the function expression and + * not a result of pushing down join quals. */ static void set_function_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) { + Relids required_outer; + + /* + * If it's a LATERAL function, it might contain some Vars of the current + * query level, requiring it to be treated as parameterized. + */ + if (rte->lateral) + { + required_outer = pull_varnos_of_level(rte->funcexpr, 0); + /* Enforce convention that empty required_outer is exactly NULL */ + if (bms_is_empty(required_outer)) + required_outer = NULL; + } + else + required_outer = NULL; + /* Generate appropriate path */ - add_path(rel, create_functionscan_path(root, rel)); + add_path(rel, create_functionscan_path(root, rel, required_outer)); /* Select cheapest path (pretty easy in this case...) */ set_cheapest(rel); @@ -1157,6 +1205,10 @@ set_function_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) /* * set_values_pathlist * Build the (single) access path for a VALUES RTE + * + * There can be no need for a parameterized path here. (Although the SQL + * spec does allow LATERAL (VALUES (x)), the parser will transform that + * into a subquery, so it doesn't end up here.) */ static void set_values_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) @@ -1988,10 +2040,16 @@ debug_print_rel(PlannerInfo *root, RelOptInfo *rel) printf("\tpath list:\n"); foreach(l, rel->pathlist) print_path(root, lfirst(l), 1); - printf("\n\tcheapest startup path:\n"); - print_path(root, rel->cheapest_startup_path, 1); - printf("\n\tcheapest total path:\n"); - print_path(root, rel->cheapest_total_path, 1); + if (rel->cheapest_startup_path) + { + printf("\n\tcheapest startup path:\n"); + print_path(root, rel->cheapest_startup_path, 1); + } + if (rel->cheapest_total_path) + { + printf("\n\tcheapest total path:\n"); + print_path(root, rel->cheapest_total_path, 1); + } printf("\n"); fflush(stdout); } diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c index 875c611ab50..d3f04eea4b1 100644 --- a/src/backend/optimizer/path/costsize.c +++ b/src/backend/optimizer/path/costsize.c @@ -989,12 +989,17 @@ cost_subqueryscan(Path *path, PlannerInfo *root, /* * cost_functionscan * Determines and returns the cost of scanning a function RTE. + * + * 'baserel' is the relation to be scanned + * 'param_info' is the ParamPathInfo if this is a parameterized path, else NULL */ void -cost_functionscan(Path *path, PlannerInfo *root, RelOptInfo *baserel) +cost_functionscan(Path *path, PlannerInfo *root, + RelOptInfo *baserel, ParamPathInfo *param_info) { Cost startup_cost = 0; Cost run_cost = 0; + QualCost qpqual_cost; Cost cpu_per_tuple; RangeTblEntry *rte; QualCost exprcost; @@ -1004,8 +1009,11 @@ cost_functionscan(Path *path, PlannerInfo *root, RelOptInfo *baserel) rte = planner_rt_fetch(baserel->relid, root); Assert(rte->rtekind == RTE_FUNCTION); - /* functionscans are never parameterized */ - path->rows = baserel->rows; + /* Mark the path with the correct row estimate */ + if (param_info) + path->rows = param_info->ppi_rows; + else + path->rows = baserel->rows; /* * Estimate costs of executing the function expression. @@ -1025,8 +1033,10 @@ cost_functionscan(Path *path, PlannerInfo *root, RelOptInfo *baserel) startup_cost += exprcost.startup + exprcost.per_tuple; /* Add scanning CPU costs */ - startup_cost += baserel->baserestrictcost.startup; - cpu_per_tuple = cpu_tuple_cost + baserel->baserestrictcost.per_tuple; + get_restriction_qual_cost(root, baserel, param_info, &qpqual_cost); + + startup_cost += qpqual_cost.startup; + cpu_per_tuple = cpu_tuple_cost + qpqual_cost.per_tuple; run_cost += cpu_per_tuple * baserel->tuples; path->startup_cost = startup_cost; diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c index 65f86194e15..fe0e4d7c201 100644 --- a/src/backend/optimizer/path/joinpath.c +++ b/src/backend/optimizer/path/joinpath.c @@ -491,12 +491,18 @@ sort_inner_and_outer(PlannerInfo *root, * explosion of mergejoin paths of dubious value. This interacts with * decisions elsewhere that also discriminate against mergejoins with * parameterized inputs; see comments in src/backend/optimizer/README. - * - * If unique-ification is requested, do it and then handle as a plain - * inner join. */ outer_path = outerrel->cheapest_total_path; inner_path = innerrel->cheapest_total_path; + + /* Punt if either rel has only parameterized paths */ + if (!outer_path || !inner_path) + return; + + /* + * If unique-ification is requested, do it and then handle as a plain + * inner join. + */ if (jointype == JOIN_UNIQUE_OUTER) { outer_path = (Path *) create_unique_path(root, outerrel, @@ -696,6 +702,10 @@ match_unsorted_outer(PlannerInfo *root, */ if (save_jointype == JOIN_UNIQUE_INNER) { + /* XXX for the moment, don't crash on LATERAL --- rethink this */ + if (inner_cheapest_total == NULL) + return; + inner_cheapest_total = (Path *) create_unique_path(root, innerrel, inner_cheapest_total, sjinfo); Assert(inner_cheapest_total); @@ -707,7 +717,7 @@ match_unsorted_outer(PlannerInfo *root, * enable_material is off or the path in question materializes its * output anyway. */ - if (enable_material && + if (enable_material && inner_cheapest_total != NULL && !ExecMaterializesOutput(inner_cheapest_total->pathtype)) matpath = (Path *) create_material_path(innerrel, inner_cheapest_total); @@ -735,6 +745,8 @@ match_unsorted_outer(PlannerInfo *root, * If we need to unique-ify the outer path, it's pointless to consider * any but the cheapest outer. (XXX we don't consider parameterized * outers, nor inners, for unique-ified cases. Should we?) + * + * XXX does nothing for LATERAL, rethink */ if (save_jointype == JOIN_UNIQUE_OUTER) { @@ -814,6 +826,10 @@ match_unsorted_outer(PlannerInfo *root, if (save_jointype == JOIN_UNIQUE_OUTER) continue; + /* Can't do anything else if inner has no unparameterized paths */ + if (!inner_cheapest_total) + continue; + /* Look for useful mergeclauses (if any) */ mergeclauses = find_mergeclauses_for_pathkeys(root, outerpath->pathkeys, @@ -1092,6 +1108,12 @@ hash_inner_and_outer(PlannerInfo *root, Path *cheapest_total_outer = outerrel->cheapest_total_path; Path *cheapest_total_inner = innerrel->cheapest_total_path; + /* Punt if either rel has only parameterized paths */ + if (!cheapest_startup_outer || + !cheapest_total_outer || + !cheapest_total_inner) + return; + /* Unique-ify if need be; we ignore parameterized possibilities */ if (jointype == JOIN_UNIQUE_OUTER) { diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 414406bb8a1..6bb821fb385 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -84,6 +84,7 @@ static HashJoin *create_hashjoin_plan(PlannerInfo *root, HashPath *best_path, Plan *outer_plan, Plan *inner_plan); static Node *replace_nestloop_params(PlannerInfo *root, Node *expr); static Node *replace_nestloop_params_mutator(Node *node, PlannerInfo *root); +static void identify_nestloop_extparams(PlannerInfo *root, Plan *subplan); static List *fix_indexqual_references(PlannerInfo *root, IndexPath *index_path); static List *fix_indexorderby_references(PlannerInfo *root, IndexPath *index_path); static Node *fix_indexqual_operand(Node *node, IndexOptInfo *index, int indexcol); @@ -1640,6 +1641,7 @@ create_subqueryscan_plan(PlannerInfo *root, Path *best_path, { scan_clauses = (List *) replace_nestloop_params(root, (Node *) scan_clauses); + identify_nestloop_extparams(root, best_path->parent->subplan); } scan_plan = make_subqueryscan(tlist, @@ -1664,11 +1666,13 @@ create_functionscan_plan(PlannerInfo *root, Path *best_path, FunctionScan *scan_plan; Index scan_relid = best_path->parent->relid; RangeTblEntry *rte; + Node *funcexpr; /* it should be a function base rel... */ Assert(scan_relid > 0); rte = planner_rt_fetch(scan_relid, root); Assert(rte->rtekind == RTE_FUNCTION); + funcexpr = rte->funcexpr; /* Sort clauses into best execution order */ scan_clauses = order_qual_clauses(root, scan_clauses); @@ -1676,8 +1680,17 @@ create_functionscan_plan(PlannerInfo *root, Path *best_path, /* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */ scan_clauses = extract_actual_clauses(scan_clauses, false); + /* Replace any outer-relation variables with nestloop params */ + if (best_path->param_info) + { + scan_clauses = (List *) + replace_nestloop_params(root, (Node *) scan_clauses); + /* The func expression itself could contain nestloop params, too */ + funcexpr = replace_nestloop_params(root, funcexpr); + } + scan_plan = make_functionscan(tlist, scan_clauses, scan_relid, - rte->funcexpr, + funcexpr, rte->eref->colnames, rte->funccoltypes, rte->funccoltypmods, @@ -2560,6 +2573,102 @@ replace_nestloop_params_mutator(Node *node, PlannerInfo *root) } /* + * identify_nestloop_extparams + * Identify extParams of a parameterized subquery that need to be fed + * from an outer nestloop. + * + * The subplan's references to the outer variables are already represented + * as PARAM_EXEC Params, so we need not modify the subplan here. What we + * do need to do is add entries to root->curOuterParams to signal the parent + * nestloop plan node that it must provide these values. + */ +static void +identify_nestloop_extparams(PlannerInfo *root, Plan *subplan) +{ + Bitmapset *tmpset; + int paramid; + + /* Examine each extParam of the subquery's plan */ + tmpset = bms_copy(subplan->extParam); + while ((paramid = bms_first_member(tmpset)) >= 0) + { + PlannerParamItem *pitem = list_nth(root->glob->paramlist, paramid); + + /* Ignore anything coming from an upper query level */ + if (pitem->abslevel != root->query_level) + continue; + + if (IsA(pitem->item, Var)) + { + Var *var = (Var *) pitem->item; + NestLoopParam *nlp; + ListCell *lc; + + /* If not from a nestloop outer rel, nothing to do */ + if (!bms_is_member(var->varno, root->curOuterRels)) + continue; + /* Is this param already listed in root->curOuterParams? */ + foreach(lc, root->curOuterParams) + { + nlp = (NestLoopParam *) lfirst(lc); + if (nlp->paramno == paramid) + { + Assert(equal(var, nlp->paramval)); + /* Present, so nothing to do */ + break; + } + } + if (lc == NULL) + { + /* No, so add it */ + nlp = makeNode(NestLoopParam); + nlp->paramno = paramid; + nlp->paramval = copyObject(var); + root->curOuterParams = lappend(root->curOuterParams, nlp); + } + } + else if (IsA(pitem->item, PlaceHolderVar)) + { + PlaceHolderVar *phv = (PlaceHolderVar *) pitem->item; + NestLoopParam *nlp; + ListCell *lc; + + /* + * If not from a nestloop outer rel, nothing to do. We use + * bms_overlap as a cheap/quick test to see if the PHV might be + * evaluated in the outer rels, and then grab its PlaceHolderInfo + * to tell for sure. + */ + if (!bms_overlap(phv->phrels, root->curOuterRels)) + continue; + if (!bms_is_subset(find_placeholder_info(root, phv, false)->ph_eval_at, + root->curOuterRels)) + continue; + /* Is this param already listed in root->curOuterParams? */ + foreach(lc, root->curOuterParams) + { + nlp = (NestLoopParam *) lfirst(lc); + if (nlp->paramno == paramid) + { + Assert(equal(phv, nlp->paramval)); + /* Present, so nothing to do */ + break; + } + } + if (lc == NULL) + { + /* No, so add it */ + nlp = makeNode(NestLoopParam); + nlp->paramno = paramid; + nlp->paramval = copyObject(phv); + root->curOuterParams = lappend(root->curOuterParams, nlp); + } + } + } + bms_free(tmpset); +} + +/* * fix_indexqual_references * Adjust indexqual clauses to the form the executor's indexqual * machinery needs. diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index 3c7fa632b8e..4481db5c341 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -204,6 +204,64 @@ add_vars_to_targetlist(PlannerInfo *root, List *vars, } } +/* + * extract_lateral_references + * If the specified RTE is a LATERAL subquery, extract all its references + * to Vars of the current query level, and make sure those Vars will be + * available for evaluation of the RTE. + * + * XXX this is rather duplicative of processing that has to happen elsewhere. + * Maybe it'd be a good idea to do this type of extraction further upstream + * and save the results? + */ +static void +extract_lateral_references(PlannerInfo *root, int rtindex) +{ + RangeTblEntry *rte = root->simple_rte_array[rtindex]; + List *vars; + List *newvars; + Relids where_needed; + ListCell *lc; + + /* No cross-references are possible if it's not LATERAL */ + if (!rte->lateral) + return; + + /* Fetch the appropriate variables */ + if (rte->rtekind == RTE_SUBQUERY) + vars = pull_vars_of_level((Node *) rte->subquery, 1); + else if (rte->rtekind == RTE_FUNCTION) + vars = pull_vars_of_level(rte->funcexpr, 0); + else + return; + + /* Copy each Var and adjust it to match our level */ + newvars = NIL; + foreach(lc, vars) + { + Var *var = (Var *) lfirst(lc); + + var = copyObject(var); + var->varlevelsup = 0; + newvars = lappend(newvars, var); + } + + /* + * We mark the Vars as being "needed" at the LATERAL RTE. This is a bit + * of a cheat: a more formal approach would be to mark each one as needed + * at the join of the LATERAL RTE with its source RTE. But it will work, + * and it's much less tedious than computing a separate where_needed for + * each Var. + */ + where_needed = bms_make_singleton(rtindex); + + /* Push the Vars into their source relations' targetlists */ + add_vars_to_targetlist(root, newvars, where_needed, false); + + list_free(newvars); + list_free(vars); +} + /***************************************************************************** * @@ -286,7 +344,9 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join, { int varno = ((RangeTblRef *) jtnode)->rtindex; - /* No quals to deal with, just return correct result */ + /* No quals to deal with, but do check for LATERAL subqueries */ + extract_lateral_references(root, varno); + /* Result qualscope is just the one Relid */ *qualscope = bms_make_singleton(varno); /* A single baserel does not create an inner join */ *inner_join_rels = NULL; diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 31fe5570723..26b5dbb559d 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -1817,7 +1817,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) * * Once grouping_planner() has applied a general tlist to the topmost * scan/join plan node, any tlist eval cost for added-on nodes should be - * accounted for as we create those nodes. Presently, of the node types we + * accounted for as we create those nodes. Presently, of the node types we * can add on later, only Agg, WindowAgg, and Group project new tlists (the * rest just copy their input tuples) --- so make_agg(), make_windowagg() and * make_group() are responsible for calling this function to account for their @@ -3257,6 +3257,7 @@ plan_cluster_use_sort(Oid tableOid, Oid indexOid) rte->rtekind = RTE_RELATION; rte->relid = tableOid; rte->relkind = RELKIND_RELATION; + rte->lateral = false; rte->inh = false; rte->inFromCl = true; query->rtable = list_make1(rte); diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c index 8ce6bee8561..863c943f2a0 100644 --- a/src/backend/optimizer/plan/subselect.c +++ b/src/backend/optimizer/plan/subselect.c @@ -1231,6 +1231,7 @@ convert_ANY_sublink_to_join(PlannerInfo *root, SubLink *sublink, rte = addRangeTableEntryForSubquery(NULL, subselect, makeAlias("ANY_subquery", NIL), + false, false); parse->rtable = lappend(parse->rtable, rte); rtindex = list_length(parse->rtable); diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index be1219eb3d1..06dbe845404 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -1176,6 +1176,13 @@ is_simple_subquery(Query *subquery) return false; /* + * Don't pull up a LATERAL subquery (hopefully, this is just a temporary + * implementation restriction). + */ + if (contain_vars_of_level((Node *) subquery, 1)) + return false; + + /* * Don't pull up a subquery that has any set-returning functions in its * targetlist. Otherwise we might well wind up inserting set-returning * functions into places where they mustn't go, such as quals of higher diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index 00052f5c846..11de5c70d80 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -193,7 +193,7 @@ compare_path_costs_fuzzily(Path *path1, Path *path2, double fuzz_factor) * and cheapest_total. The cheapest_parameterized_paths list collects paths * that are cheapest-total for their parameterization (i.e., there is no * cheaper path with the same or weaker parameterization). This list always - * includes the unparameterized cheapest-total path, too. + * includes the unparameterized cheapest-total path, too, if there is one. * * This is normally called only after we've finished constructing the path * list for the rel node. @@ -250,15 +250,18 @@ set_cheapest(RelOptInfo *parent_rel) cheapest_total_path = path; } - if (cheapest_total_path == NULL) + if (cheapest_total_path == NULL && !have_parameterized_paths) elog(ERROR, "could not devise a query plan for the given query"); parent_rel->cheapest_startup_path = cheapest_startup_path; parent_rel->cheapest_total_path = cheapest_total_path; parent_rel->cheapest_unique_path = NULL; /* computed only if needed */ - /* Seed the parameterized-paths list with the cheapest total */ - parent_rel->cheapest_parameterized_paths = list_make1(cheapest_total_path); + /* Seed the parameterized-paths list with the cheapest total, if any */ + if (cheapest_total_path) + parent_rel->cheapest_parameterized_paths = list_make1(cheapest_total_path); + else + parent_rel->cheapest_parameterized_paths = NIL; /* And, if there are any parameterized paths, add them in one at a time */ if (have_parameterized_paths) @@ -1131,6 +1134,13 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath, int numCols; ListCell *lc; + /* XXX temporary band-aid to not crash on LATERAL queries */ + if (subpath == NULL) + { + Assert(subpath == rel->cheapest_total_path); + return NULL; + } + /* Caller made a mistake if subpath isn't cheapest_total ... */ Assert(subpath == rel->cheapest_total_path); Assert(subpath->parent == rel); @@ -1657,16 +1667,18 @@ create_subqueryscan_path(PlannerInfo *root, RelOptInfo *rel, * returning the pathnode. */ Path * -create_functionscan_path(PlannerInfo *root, RelOptInfo *rel) +create_functionscan_path(PlannerInfo *root, RelOptInfo *rel, + Relids required_outer) { Path *pathnode = makeNode(Path); pathnode->pathtype = T_FunctionScan; pathnode->parent = rel; - pathnode->param_info = NULL; /* never parameterized at present */ + pathnode->param_info = get_baserel_parampathinfo(root, rel, + required_outer); pathnode->pathkeys = NIL; /* for now, assume unordered result */ - cost_functionscan(pathnode, root, rel); + cost_functionscan(pathnode, root, rel, pathnode->param_info); return pathnode; } diff --git a/src/backend/optimizer/util/var.c b/src/backend/optimizer/util/var.c index 9bc90c25313..81332ff1cd1 100644 --- a/src/backend/optimizer/util/var.c +++ b/src/backend/optimizer/util/var.c @@ -42,16 +42,15 @@ typedef struct typedef struct { - int var_location; + List *vars; int sublevels_up; -} locate_var_of_level_context; +} pull_vars_context; typedef struct { int var_location; - int relid; int sublevels_up; -} locate_var_of_relation_context; +} locate_var_of_level_context; typedef struct { @@ -77,12 +76,11 @@ typedef struct static bool pull_varnos_walker(Node *node, pull_varnos_context *context); static bool pull_varattnos_walker(Node *node, pull_varattnos_context *context); +static bool pull_vars_walker(Node *node, pull_vars_context *context); static bool contain_var_clause_walker(Node *node, void *context); static bool contain_vars_of_level_walker(Node *node, int *sublevels_up); static bool locate_var_of_level_walker(Node *node, locate_var_of_level_context *context); -static bool locate_var_of_relation_walker(Node *node, - locate_var_of_relation_context *context); static bool find_minimum_var_level_walker(Node *node, find_minimum_var_level_context *context); static bool pull_var_clause_walker(Node *node, @@ -122,6 +120,31 @@ pull_varnos(Node *node) return context.varnos; } +/* + * pull_varnos_of_level + * Create a set of all the distinct varnos present in a parsetree. + * Only Vars of the specified level are considered. + */ +Relids +pull_varnos_of_level(Node *node, int levelsup) +{ + pull_varnos_context context; + + context.varnos = NULL; + context.sublevels_up = levelsup; + + /* + * Must be prepared to start with a Query or a bare expression tree; if + * it's a Query, we don't want to increment sublevels_up. + */ + query_or_expression_tree_walker(node, + pull_varnos_walker, + (void *) &context, + 0); + + return context.varnos; +} + static bool pull_varnos_walker(Node *node, pull_varnos_context *context) { @@ -231,6 +254,66 @@ pull_varattnos_walker(Node *node, pull_varattnos_context *context) /* + * pull_vars_of_level + * Create a list of all Vars referencing the specified query level + * in the given parsetree. + * + * This is used on unplanned parsetrees, so we don't expect to see any + * PlaceHolderVars. + * + * Caution: the Vars are not copied, only linked into the list. + */ +List * +pull_vars_of_level(Node *node, int levelsup) +{ + pull_vars_context context; + + context.vars = NIL; + context.sublevels_up = levelsup; + + /* + * Must be prepared to start with a Query or a bare expression tree; if + * it's a Query, we don't want to increment sublevels_up. + */ + query_or_expression_tree_walker(node, + pull_vars_walker, + (void *) &context, + 0); + + return context.vars; +} + +static bool +pull_vars_walker(Node *node, pull_vars_context *context) +{ + if (node == NULL) + return false; + if (IsA(node, Var)) + { + Var *var = (Var *) node; + + if (var->varlevelsup == context->sublevels_up) + context->vars = lappend(context->vars, var); + return false; + } + Assert(!IsA(node, PlaceHolderVar)); + if (IsA(node, Query)) + { + /* Recurse into RTE subquery or not-yet-planned sublink subquery */ + bool result; + + context->sublevels_up++; + result = query_tree_walker((Query *) node, pull_vars_walker, + (void *) context, 0); + context->sublevels_up--; + return result; + } + return expression_tree_walker(node, pull_vars_walker, + (void *) context); +} + + +/* * contain_var_clause * Recursively scan a clause to discover whether it contains any Var nodes * (of the current query level). @@ -406,76 +489,6 @@ locate_var_of_level_walker(Node *node, /* - * locate_var_of_relation - * Find the parse location of any Var of the specified relation. - * - * Returns -1 if no such Var is in the querytree, or if they all have - * unknown parse location. - * - * Will recurse into sublinks. Also, may be invoked directly on a Query. - */ -int -locate_var_of_relation(Node *node, int relid, int levelsup) -{ - locate_var_of_relation_context context; - - context.var_location = -1; /* in case we find nothing */ - context.relid = relid; - context.sublevels_up = levelsup; - - (void) query_or_expression_tree_walker(node, - locate_var_of_relation_walker, - (void *) &context, - 0); - - return context.var_location; -} - -static bool -locate_var_of_relation_walker(Node *node, - locate_var_of_relation_context *context) -{ - if (node == NULL) - return false; - if (IsA(node, Var)) - { - Var *var = (Var *) node; - - if (var->varno == context->relid && - var->varlevelsup == context->sublevels_up && - var->location >= 0) - { - context->var_location = var->location; - return true; /* abort tree traversal and return true */ - } - return false; - } - if (IsA(node, CurrentOfExpr)) - { - /* 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 */ - bool result; - - context->sublevels_up++; - result = query_tree_walker((Query *) node, - locate_var_of_relation_walker, - (void *) context, - 0); - context->sublevels_up--; - return result; - } - return expression_tree_walker(node, - locate_var_of_relation_walker, - (void *) context); -} - - -/* * find_minimum_var_level * Recursively scan a clause to find the lowest variable level it * contains --- for example, zero is returned if there are any local |