diff --git a/doc/src/sgml/indexam.sgml b/doc/src/sgml/indexam.sgml index 07c816c38f279b057eab32d7f4b9eb06f7b193d4..28f2ae162753e105fffc24b922881eae39da60c5 100644 --- a/doc/src/sgml/indexam.sgml +++ b/doc/src/sgml/indexam.sgml @@ -289,7 +289,7 @@ amcanreturn (Relation indexRelation); void amcostestimate (PlannerInfo *root, IndexPath *path, - RelOptInfo *outer_rel, + double loop_count, Cost *indexStartupCost, Cost *indexTotalCost, Selectivity *indexSelectivity, @@ -928,7 +928,7 @@ amrestrpos (IndexScanDesc scan); void amcostestimate (PlannerInfo *root, IndexPath *path, - RelOptInfo *outer_rel, + double loop_count, Cost *indexStartupCost, Cost *indexTotalCost, Selectivity *indexSelectivity, @@ -958,16 +958,15 @@ amcostestimate (PlannerInfo *root, </varlistentry> <varlistentry> - <term><parameter>outer_rel</></term> + <term><parameter>loop_count</></term> <listitem> <para> - If the index is being considered for use in a join inner indexscan, - the planner's information about the outer side of the join. Otherwise - <symbol>NULL</>. When non-<symbol>NULL</>, some of the qual clauses - will be join clauses for joins - with this rel rather than being simple restriction clauses. Also, - the cost estimator should expect that the index scan will be repeated - for each row of the outer rel. + The number of repetitions of the index scan that should be factored + into the cost estimates. This will typically be greater than one when + considering a parameterized scan for use in the inside of a nestloop + join. Note that the cost estimates should still be for just one scan; + a larger <parameter>loop_count</> means that it may be appropriate + to allow for some caching effects across multiple scans. </para> </listitem> </varlistentry> @@ -1062,8 +1061,8 @@ amcostestimate (PlannerInfo *root, </para> <para> - In the join case, the returned numbers should be averages expected for - any one scan of the index. + When <parameter>loop_count</> is greater than one, the returned numbers + should be averages expected for any one scan of the index. </para> <procedure> @@ -1121,7 +1120,7 @@ cost_qual_eval(&index_qual_cost, path->indexquals, root); </programlisting> However, the above does not account for amortization of index reads - across repeated index scans in the join case. + across repeated index scans. </para> </step> diff --git a/src/backend/nodes/bitmapset.c b/src/backend/nodes/bitmapset.c index 3b5fb20964e7153a859bb639f15fd0be8bb8778b..4c904e03296051cf2c95fdcd3d4a029aea9dc142 100644 --- a/src/backend/nodes/bitmapset.c +++ b/src/backend/nodes/bitmapset.c @@ -335,6 +335,83 @@ bms_is_subset(const Bitmapset *a, const Bitmapset *b) return true; } +/* + * bms_subset_compare - compare A and B for equality/subset relationships + * + * This is more efficient than testing bms_is_subset in both directions. + */ +BMS_Comparison +bms_subset_compare(const Bitmapset *a, const Bitmapset *b) +{ + BMS_Comparison result; + int shortlen; + int longlen; + int i; + + /* Handle cases where either input is NULL */ + if (a == NULL) + { + if (b == NULL) + return BMS_EQUAL; + return bms_is_empty(b) ? BMS_EQUAL : BMS_SUBSET1; + } + if (b == NULL) + return bms_is_empty(a) ? BMS_EQUAL : BMS_SUBSET2; + /* Check common words */ + result = BMS_EQUAL; /* status so far */ + shortlen = Min(a->nwords, b->nwords); + for (i = 0; i < shortlen; i++) + { + bitmapword aword = a->words[i]; + bitmapword bword = b->words[i]; + + if ((aword & ~bword) != 0) + { + /* a is not a subset of b */ + if (result == BMS_SUBSET1) + return BMS_DIFFERENT; + result = BMS_SUBSET2; + } + if ((bword & ~aword) != 0) + { + /* b is not a subset of a */ + if (result == BMS_SUBSET2) + return BMS_DIFFERENT; + result = BMS_SUBSET1; + } + } + /* Check extra words */ + if (a->nwords > b->nwords) + { + longlen = a->nwords; + for (; i < longlen; i++) + { + if (a->words[i] != 0) + { + /* a is not a subset of b */ + if (result == BMS_SUBSET1) + return BMS_DIFFERENT; + result = BMS_SUBSET2; + } + } + } + else if (a->nwords < b->nwords) + { + longlen = b->nwords; + for (; i < longlen; i++) + { + if (b->words[i] != 0) + { + /* b is not a subset of a */ + if (result == BMS_SUBSET2) + return BMS_DIFFERENT; + result = BMS_SUBSET1; + } + } + } + return result; +} + /* * bms_is_member - is X a member of A? */ diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 8bc19478357e66d3fe8723da693672939c401ae2..829f6d4f7b59cebb0433d20c5a6c325aa5a2b178 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -1475,9 +1475,12 @@ _outPathInfo(StringInfo str, const Path *node) WRITE_ENUM_FIELD(pathtype, NodeTag); appendStringInfo(str, " :parent_relids "); _outBitmapset(str, node->parent->relids); + WRITE_FLOAT_FIELD(rows, "%.0f"); WRITE_FLOAT_FIELD(startup_cost, "%.2f"); WRITE_FLOAT_FIELD(total_cost, "%.2f"); WRITE_NODE_FIELD(pathkeys); + WRITE_BITMAPSET_FIELD(required_outer); + WRITE_NODE_FIELD(param_clauses); } /* @@ -1515,11 +1518,9 @@ _outIndexPath(StringInfo str, const IndexPath *node) WRITE_NODE_FIELD(indexqualcols); WRITE_NODE_FIELD(indexorderbys); WRITE_NODE_FIELD(indexorderbycols); - WRITE_BOOL_FIELD(isjoininner); WRITE_ENUM_FIELD(indexscandir, ScanDirection); WRITE_FLOAT_FIELD(indextotalcost, "%.2f"); WRITE_FLOAT_FIELD(indexselectivity, "%.4f"); - WRITE_FLOAT_FIELD(rows, "%.0f"); } static void @@ -1530,8 +1531,6 @@ _outBitmapHeapPath(StringInfo str, const BitmapHeapPath *node) _outPathInfo(str, (const Path *) node); WRITE_NODE_FIELD(bitmapqual); - WRITE_BOOL_FIELD(isjoininner); - WRITE_FLOAT_FIELD(rows, "%.0f"); } static void @@ -1628,7 +1627,6 @@ _outUniquePath(StringInfo str, const UniquePath *node) WRITE_ENUM_FIELD(umethod, UniquePathMethod); WRITE_NODE_FIELD(in_operators); WRITE_NODE_FIELD(uniq_exprs); - WRITE_FLOAT_FIELD(rows, "%.0f"); } static void @@ -1691,6 +1689,7 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node) WRITE_NODE_FIELD(parse); WRITE_NODE_FIELD(glob); WRITE_UINT_FIELD(query_level); + WRITE_BITMAPSET_FIELD(all_baserels); WRITE_NODE_FIELD(join_rel_list); WRITE_INT_FIELD(join_cur_level); WRITE_NODE_FIELD(init_plans); @@ -1738,6 +1737,7 @@ _outRelOptInfo(StringInfo str, const RelOptInfo *node) WRITE_NODE_FIELD(cheapest_startup_path); WRITE_NODE_FIELD(cheapest_total_path); WRITE_NODE_FIELD(cheapest_unique_path); + WRITE_NODE_FIELD(cheapest_parameterized_paths); WRITE_UINT_FIELD(relid); WRITE_UINT_FIELD(reltablespace); WRITE_ENUM_FIELD(rtekind, RTEKind); @@ -1752,8 +1752,6 @@ _outRelOptInfo(StringInfo str, const RelOptInfo *node) WRITE_NODE_FIELD(baserestrictinfo); WRITE_NODE_FIELD(joininfo); WRITE_BOOL_FIELD(has_eclass_joins); - WRITE_BITMAPSET_FIELD(index_outer_relids); - WRITE_NODE_FIELD(index_inner_paths); } static void @@ -1854,16 +1852,6 @@ _outRestrictInfo(StringInfo str, const RestrictInfo *node) WRITE_OID_FIELD(hashjoinoperator); } -static void -_outInnerIndexscanInfo(StringInfo str, const InnerIndexscanInfo *node) -{ - WRITE_NODE_TYPE("INNERINDEXSCANINFO"); - WRITE_BITMAPSET_FIELD(other_relids); - WRITE_BOOL_FIELD(isouterjoin); - WRITE_NODE_FIELD(cheapest_startup_innerpath); - WRITE_NODE_FIELD(cheapest_total_innerpath); -} - static void _outPlaceHolderVar(StringInfo str, const PlaceHolderVar *node) { @@ -3015,9 +3003,6 @@ _outNode(StringInfo str, const void *obj) case T_RestrictInfo: _outRestrictInfo(str, obj); break; - case T_InnerIndexscanInfo: - _outInnerIndexscanInfo(str, obj); - break; case T_PlaceHolderVar: _outPlaceHolderVar(str, obj); break; diff --git a/src/backend/optimizer/README b/src/backend/optimizer/README index aaa754cbb18e78823135bbc4a1acfbaa1f93c88f..ee450fb02e4e9e8942ff5a0d6b062c9ae5d18cba 100644 --- a/src/backend/optimizer/README +++ b/src/backend/optimizer/README @@ -78,10 +78,10 @@ ways. All the Paths made for a given relation are placed in its RelOptInfo.pathlist. (Actually, we discard Paths that are obviously inferior alternatives before they ever get into the pathlist --- what ends up in the pathlist is the cheapest way of generating each potentially -useful sort ordering of the relation.) Also create a RelOptInfo.joininfo -list including all the join clauses that involve this relation. For -example, the WHERE clause "tab1.col1 = tab2.col1" generates entries in -both tab1 and tab2's joininfo lists. +useful sort ordering and parameterization of the relation.) Also create a +RelOptInfo.joininfo list including all the join clauses that involve this +relation. For example, the WHERE clause "tab1.col1 = tab2.col1" generates +entries in both tab1 and tab2's joininfo lists. If we have only a single base relation in the query, we are done. Otherwise we have to figure out how to join the base relations into a @@ -173,12 +173,12 @@ for it or the cheapest path with the desired ordering (if that's cheaper than applying a sort to the cheapest other path). If the query contains one-sided outer joins (LEFT or RIGHT joins), or -IN or EXISTS WHERE clauses that were converted to joins, then some of -the possible join orders may be illegal. These are excluded by having -join_is_legal consult a side list of such "special" joins to see -whether a proposed join is illegal. (The same consultation allows it -to see which join style should be applied for a valid join, ie, -JOIN_INNER, JOIN_LEFT, etc.) +IN or EXISTS WHERE clauses that were converted to semijoins or antijoins, +then some of the possible join orders may be illegal. These are excluded +by having join_is_legal consult a side list of such "special" joins to see +whether a proposed join is illegal. (The same consultation allows it to +see which join style should be applied for a valid join, ie, JOIN_INNER, +JOIN_LEFT, etc.) Valid OUTER JOIN Optimizations @@ -526,12 +526,11 @@ multi-column index generates a list with one element per index column. are two possible sort orders and two possible PathKey lists it can generate.) -Note that a bitmap scan or multi-pass indexscan (OR clause scan) has NIL -pathkeys since we can say nothing about the overall order of its result. -Also, an indexscan on an unordered type of index generates NIL pathkeys. -However, we can always create a pathkey by doing an explicit sort. The -pathkeys for a Sort plan's output just represent the sort key fields and -the ordering operators used. +Note that a bitmap scan has NIL pathkeys since we can say nothing about +the overall order of its result. Also, an indexscan on an unordered type +of index generates NIL pathkeys. However, we can always create a pathkey +by doing an explicit sort. The pathkeys for a Sort plan's output just +represent the sort key fields and the ordering operators used. Things get more interesting when we consider joins. Suppose we do a mergejoin between A and B using the mergeclause A.X = B.Y. The output @@ -668,4 +667,102 @@ Currently this happens only for queries involving multiple window functions with different orderings, for which extra sorts are needed anyway. +Parameterized Paths +------------------- + +The naive way to join two relations using a clause like WHERE A.X = B.Y +is to generate a nestloop plan like this: + + NestLoop + Filter: A.X = B.Y + -> Seq Scan on A + -> Seq Scan on B + +We can make this better by using a merge or hash join, but it still +requires scanning all of both input relations. If A is very small and B is +very large, but there is an index on B.Y, it can be enormously better to do +something like this: + + NestLoop + -> Seq Scan on A + -> Index Scan using B_Y_IDX on B + Index Condition: B.Y = A.X + +Here, we are expecting that for each row scanned from A, the nestloop +plan node will pass down the current value of A.X into the scan of B. +That allows the indexscan to treat A.X as a constant for any one +invocation, and thereby use it as an index key. This is the only plan type +that can avoid fetching all of B, and for small numbers of rows coming from +A, that will dominate every other consideration. (As A gets larger, this +gets less attractive, and eventually a merge or hash join will win instead. +So we have to cost out all the alternatives to decide what to do.) + +It can be useful for the parameter value to be passed down through +intermediate layers of joins, for example: + + NestLoop + -> Seq Scan on A + Hash Join + Join Condition: B.Y = C.W + -> Seq Scan on B + -> Index Scan using C_Z_IDX on C + Index Condition: C.Z = A.X + +If all joins are plain inner joins then this is unnecessary, because +it's always possible to reorder the joins so that a parameter is used +immediately below the nestloop node that provides it. But in the +presence of outer joins, join reordering may not be possible, and then +this option can be critical. Before version 9.2, Postgres used ad-hoc +methods for planning and executing such queries, and those methods could +not handle passing parameters down through multiple join levels. + +To plan such queries, we now use a notion of a "parameterized path", +which is a path that makes use of a join clause to a relation that's not +scanned by the path. In the example just above, we would construct a +path representing the possibility of doing this: + + -> Index Scan using C_Z_IDX on C + Index Condition: C.Z = A.X + +This path will be marked as being parameterized by relation A. (Note that +this is only one of the possible access paths for C; we'd still have a +plain unparameterized seqscan, and perhaps other possibilities.) The +parameterization marker does not prevent joining the path to B, so one of +the paths generated for the joinrel {B C} will represent + + Hash Join + Join Condition: B.Y = C.W + -> Seq Scan on B + -> Index Scan using C_Z_IDX on C + Index Condition: C.Z = A.X + +This path is still marked as being parameterized by A. When we attempt to +join {B C} to A to form the complete join tree, such a path can only be +used as the inner side of a nestloop join: it will be ignored for other +possible join types. So we will form a join path representing the query +plan shown above, and it will compete in the usual way with paths built +from non-parameterized scans. + +To limit planning time, we have to avoid generating an unreasonably large +number of parameterized paths. We do this by only generating parameterized +relation scan paths for index scans, and then only for indexes for which +suitable join clauses are available. There are also heuristics in join +planning that try to limit the number of parameterized paths considered. + +In particular, there's been a deliberate policy decision to favor hash +joins over merge joins for parameterized join steps (those occurring below +a nestloop that provides parameters to the lower join's inputs). While we +do not ignore merge joins entirely, joinpath.c does not fully explore the +space of potential merge joins with parameterized inputs. Also, add_path +treats parameterized paths as having no pathkeys, so that they compete +only on cost and don't get preference for producing a special sort order. +This creates additional bias against merge joins, since we might discard +a path that could have been useful for performing a merge without an +explicit sort step. Since a parameterized path must ultimately be used +on the inside of a nestloop, where its sort order is uninteresting, these +choices do not affect any requirement for the final output order of a +query --- they only make it harder to use a merge join at a lower level. +The savings in planning work justifies that. + + -- bjm & tgl diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index b085553a1adbfee633ae6399cf6e188ec465368a..19bf7340ad454cda081c85d83272f66e6de43c4e 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -46,13 +46,28 @@ int geqo_threshold; join_search_hook_type join_search_hook = NULL; +static void set_base_rel_sizes(PlannerInfo *root); static void set_base_rel_pathlists(PlannerInfo *root); +static void set_rel_size(PlannerInfo *root, RelOptInfo *rel, + Index rti, RangeTblEntry *rte); static void set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); +static void set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, + RangeTblEntry *rte); static void set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); +static void set_foreign_size(PlannerInfo *root, RelOptInfo *rel, + RangeTblEntry *rte); +static void set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, + RangeTblEntry *rte); +static void set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, + Index rti, RangeTblEntry *rte); static void set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); +static void generate_mergeappend_paths(PlannerInfo *root, RelOptInfo *rel, + List *live_childrels, + List *all_child_pathkeys, + Relids required_outer); static List *accumulate_append_subpath(List *subpaths, Path *path); static void set_dummy_rel_pathlist(RelOptInfo *rel); static void set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel, @@ -65,8 +80,6 @@ static void set_cte_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static void set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); -static void set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, - RangeTblEntry *rte); static RelOptInfo *make_rel_from_joinlist(PlannerInfo *root, List *joinlist); static bool subquery_is_pushdown_safe(Query *subquery, Query *topquery, bool *differentTypes); @@ -91,10 +104,33 @@ RelOptInfo * make_one_rel(PlannerInfo *root, List *joinlist) { RelOptInfo *rel; + Index rti; + + /* + * Construct the all_baserels Relids set. + */ + root->all_baserels = NULL; + for (rti = 1; rti < root->simple_rel_array_size; rti++) + { + RelOptInfo *brel = root->simple_rel_array[rti]; + + /* there may be empty slots corresponding to non-baserel RTEs */ + if (brel == NULL) + continue; + + Assert(brel->relid == rti); /* sanity check on array */ + + /* ignore RTEs that are "other rels" */ + if (brel->reloptkind != RELOPT_BASEREL) + continue; + + root->all_baserels = bms_add_member(root->all_baserels, brel->relid); + } /* * Generate access paths for the base rels. */ + set_base_rel_sizes(root); set_base_rel_pathlists(root); /* @@ -105,33 +141,39 @@ make_one_rel(PlannerInfo *root, List *joinlist) /* * The result should join all and only the query's base rels. */ -#ifdef USE_ASSERT_CHECKING - { - int num_base_rels = 0; - Index rti; + Assert(bms_equal(rel->relids, root->all_baserels)); - for (rti = 1; rti < root->simple_rel_array_size; rti++) - { - RelOptInfo *brel = root->simple_rel_array[rti]; + return rel; +} - if (brel == NULL) - continue; +/* + * set_base_rel_sizes + * Set the size estimates (rows and widths) for each base-relation entry. + * + * We do this in a separate pass over the base rels so that rowcount + * estimates are available for parameterized path generation. + */ +static void +set_base_rel_sizes(PlannerInfo *root) +{ + Index rti; - Assert(brel->relid == rti); /* sanity check on array */ + for (rti = 1; rti < root->simple_rel_array_size; rti++) + { + RelOptInfo *rel = root->simple_rel_array[rti]; - /* ignore RTEs that are "other rels" */ - if (brel->reloptkind != RELOPT_BASEREL) - continue; + /* there may be empty slots corresponding to non-baserel RTEs */ + if (rel == NULL) + continue; - Assert(bms_is_member(rti, rel->relids)); - num_base_rels++; - } + Assert(rel->relid == rti); /* sanity check on array */ - Assert(bms_num_members(rel->relids) == num_base_rels); - } -#endif + /* ignore RTEs that are "other rels" */ + if (rel->reloptkind != RELOPT_BASEREL) + continue; - return rel; + set_rel_size(root, rel, rti, root->simple_rte_array[rti]); + } } /* @@ -164,11 +206,11 @@ set_base_rel_pathlists(PlannerInfo *root) } /* - * set_rel_pathlist - * Build access paths for a base relation + * set_rel_size + * Set size estimates for a base relation */ static void -set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, +set_rel_size(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte) { if (rel->reloptkind == RELOPT_BASEREL && @@ -179,13 +221,18 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, * so set up a single dummy path for it. Here we only check this for * regular baserels; if it's an otherrel, CE was already checked in * set_append_rel_pathlist(). + * + * In this case, we go ahead and set up the relation's path right away + * instead of leaving it for set_rel_pathlist to do. This is because + * we don't have a convention for marking a rel as dummy except by + * assigning a dummy path to it. */ set_dummy_rel_pathlist(rel); } else if (rte->inh) { /* It's an "append relation", process accordingly */ - set_append_rel_pathlist(root, rel, rti, rte); + set_append_rel_size(root, rel, rti, rte); } else { @@ -195,28 +242,32 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, if (rte->relkind == RELKIND_FOREIGN_TABLE) { /* Foreign table */ - set_foreign_pathlist(root, rel, rte); + set_foreign_size(root, rel, rte); } else { /* Plain relation */ - set_plain_rel_pathlist(root, rel, rte); + set_plain_rel_size(root, rel, rte); } break; case RTE_SUBQUERY: - /* Subquery --- generate a separate plan for it */ + /* + * Subqueries don't support parameterized paths, so just go + * ahead and build their paths immediately. + */ set_subquery_pathlist(root, rel, rti, rte); break; case RTE_FUNCTION: - /* RangeFunction --- generate a suitable path for it */ - set_function_pathlist(root, rel, rte); + set_function_size_estimates(root, rel); break; case RTE_VALUES: - /* Values list --- generate a suitable path for it */ - set_values_pathlist(root, rel, rte); + set_values_size_estimates(root, rel); break; case RTE_CTE: - /* CTE reference --- generate a suitable path for it */ + /* + * CTEs don't support parameterized paths, so just go ahead + * and build their paths immediately. + */ if (rte->self_reference) set_worktable_pathlist(root, rel, rte); else @@ -227,6 +278,60 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, break; } } +} + +/* + * set_rel_pathlist + * Build access paths for a base relation + */ +static void +set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, + Index rti, RangeTblEntry *rte) +{ + if (IS_DUMMY_REL(rel)) + { + /* We already proved the relation empty, so nothing more to do */ + } + else if (rte->inh) + { + /* It's an "append relation", process accordingly */ + set_append_rel_pathlist(root, rel, rti, rte); + } + else + { + switch (rel->rtekind) + { + case RTE_RELATION: + if (rte->relkind == RELKIND_FOREIGN_TABLE) + { + /* Foreign table */ + set_foreign_pathlist(root, rel, rte); + } + else + { + /* Plain relation */ + set_plain_rel_pathlist(root, rel, rte); + } + break; + case RTE_SUBQUERY: + /* Subquery --- fully handled during set_rel_size */ + break; + case RTE_FUNCTION: + /* RangeFunction */ + set_function_pathlist(root, rel, rte); + break; + case RTE_VALUES: + /* Values list */ + set_values_pathlist(root, rel, rte); + break; + case RTE_CTE: + /* CTE reference --- fully handled during set_rel_size */ + break; + default: + elog(ERROR, "unexpected rtekind: %d", (int) rel->rtekind); + break; + } + } #ifdef OPTIMIZER_DEBUG debug_print_rel(root, rel); @@ -234,11 +339,11 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, } /* - * set_plain_rel_pathlist - * Build access paths for a plain relation (no subquery, no inheritance) + * set_plain_rel_size + * Set size estimates for a plain relation (no subquery, no inheritance) */ static void -set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) +set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) { /* * Test any partial indexes of rel for applicability. We must do this @@ -259,15 +364,15 @@ set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) check_partial_indexes(root, rel); set_baserel_size_estimates(root, rel); } +} - /* - * Generate paths and add them to the rel's pathlist. - * - * Note: add_path() will discard any paths that are dominated by another - * available path, keeping only those paths that are superior along at - * least one dimension of cost or sortedness. - */ - +/* + * set_plain_rel_pathlist + * Build access paths for a plain relation (no subquery, no inheritance) + */ +static void +set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) +{ /* Consider sequential scan */ add_path(rel, create_seqscan_path(root, rel)); @@ -282,8 +387,33 @@ set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) } /* - * set_append_rel_pathlist - * Build access paths for an "append relation" + * set_foreign_size + * Set size estimates for a foreign table RTE + */ +static void +set_foreign_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) +{ + /* Mark rel with estimated output rows, width, etc */ + set_foreign_size_estimates(root, rel); +} + +/* + * set_foreign_pathlist + * Build the (single) access path for a foreign table RTE + */ +static void +set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) +{ + /* Generate appropriate path */ + add_path(rel, (Path *) create_foreignscan_path(root, rel)); + + /* Select cheapest path (pretty easy in this case...) */ + set_cheapest(rel); +} + +/* + * set_append_rel_size + * Set size estimates for an "append relation" * * The passed-in rel and RTE represent the entire append relation. The * relation's contents are computed by appending together the output of @@ -293,13 +423,10 @@ set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) * a good thing because their outputs are not the same size. */ static void -set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, - Index rti, RangeTblEntry *rte) +set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, + Index rti, RangeTblEntry *rte) { int parentRTindex = rti; - List *live_childrels = NIL; - List *subpaths = NIL; - List *all_child_pathkeys = NIL; double parent_rows; double parent_size; double *parent_attrsizes; @@ -325,10 +452,6 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, nattrs = rel->max_attr - rel->min_attr + 1; parent_attrsizes = (double *) palloc0(nattrs * sizeof(double)); - /* - * Generate access paths for each member relation, and pick the cheapest - * path for each one. - */ foreach(l, root->append_rel_list) { AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); @@ -337,7 +460,6 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RelOptInfo *childrel; List *childquals; Node *childqual; - ListCell *lcp; ListCell *parentvars; ListCell *childvars; @@ -394,8 +516,7 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, { /* * This child need not be scanned, so we can omit it from the - * appendrel. Mark it with a dummy cheapest-path though, in case - * best_appendrel_indexscan() looks at it later. + * appendrel. */ set_dummy_rel_pathlist(childrel); continue; @@ -438,65 +559,20 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, */ /* - * Compute the child's access paths. + * Compute the child's size. */ - set_rel_pathlist(root, childrel, childRTindex, childRTE); + set_rel_size(root, childrel, childRTindex, childRTE); /* * It is possible that constraint exclusion detected a contradiction * within a child subquery, even though we didn't prove one above. - * If what we got back was a dummy path, we can skip this child. + * If so, we can skip this child. */ - if (IS_DUMMY_PATH(childrel->cheapest_total_path)) + if (IS_DUMMY_REL(childrel)) continue; /* - * Child is live, so add its cheapest access path to the Append path - * we are constructing for the parent. - */ - subpaths = accumulate_append_subpath(subpaths, - childrel->cheapest_total_path); - - /* Remember which childrels are live, for MergeAppend logic below */ - live_childrels = lappend(live_childrels, childrel); - - /* - * Collect a list of all the available path orderings for all the - * children. We use this as a heuristic to indicate which sort - * orderings we should build MergeAppend paths for. - */ - foreach(lcp, childrel->pathlist) - { - Path *childpath = (Path *) lfirst(lcp); - List *childkeys = childpath->pathkeys; - ListCell *lpk; - bool found = false; - - /* Ignore unsorted paths */ - if (childkeys == NIL) - continue; - - /* Have we already seen this ordering? */ - foreach(lpk, all_child_pathkeys) - { - List *existing_pathkeys = (List *) lfirst(lpk); - - if (compare_pathkeys(existing_pathkeys, - childkeys) == PATHKEYS_EQUAL) - { - found = true; - break; - } - } - if (!found) - { - /* No, so add it to all_child_pathkeys */ - all_child_pathkeys = lappend(all_child_pathkeys, childkeys); - } - } - - /* - * Accumulate size information from each child. + * Accumulate size information from each live child. */ if (childrel->rows > 0) { @@ -560,24 +636,208 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, rel->tuples = parent_rows; pfree(parent_attrsizes); +} + +/* + * set_append_rel_pathlist + * Build access paths for an "append relation" + */ +static void +set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, + Index rti, RangeTblEntry *rte) +{ + int parentRTindex = rti; + List *live_childrels = NIL; + List *subpaths = NIL; + List *all_child_pathkeys = NIL; + List *all_child_outers = NIL; + ListCell *l; /* - * Next, build an unordered Append path for the rel. (Note: this is - * correct even if we have zero or one live subpath due to constraint - * exclusion.) + * Generate access paths for each member relation, and remember the + * cheapest path for each one. Also, identify all pathkeys (orderings) + * and parameterizations (required_outer sets) available for the member + * relations. + */ + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); + int childRTindex; + RangeTblEntry *childRTE; + RelOptInfo *childrel; + ListCell *lcp; + + /* append_rel_list contains all append rels; ignore others */ + if (appinfo->parent_relid != parentRTindex) + continue; + + /* Re-locate the child RTE and RelOptInfo */ + childRTindex = appinfo->child_relid; + childRTE = root->simple_rte_array[childRTindex]; + childrel = root->simple_rel_array[childRTindex]; + + /* + * Compute the child's access paths. + */ + set_rel_pathlist(root, childrel, childRTindex, childRTE); + + /* + * If child is dummy, ignore it. + */ + if (IS_DUMMY_REL(childrel)) + continue; + + /* + * Child is live, so add its cheapest access path to the Append path + * we are constructing for the parent. + */ + subpaths = accumulate_append_subpath(subpaths, + childrel->cheapest_total_path); + + /* Remember which childrels are live, for logic below */ + live_childrels = lappend(live_childrels, childrel); + + /* + * Collect lists of all the available path orderings and + * parameterizations for all the children. We use these as a + * heuristic to indicate which sort orderings and parameterizations we + * should build Append and MergeAppend paths for. + */ + foreach(lcp, childrel->pathlist) + { + Path *childpath = (Path *) lfirst(lcp); + List *childkeys = childpath->pathkeys; + Relids childouter = childpath->required_outer; + + /* Unsorted paths don't contribute to pathkey list */ + if (childkeys != NIL) + { + ListCell *lpk; + bool found = false; + + /* Have we already seen this ordering? */ + foreach(lpk, all_child_pathkeys) + { + List *existing_pathkeys = (List *) lfirst(lpk); + + if (compare_pathkeys(existing_pathkeys, + childkeys) == PATHKEYS_EQUAL) + { + found = true; + break; + } + } + if (!found) + { + /* No, so add it to all_child_pathkeys */ + all_child_pathkeys = lappend(all_child_pathkeys, + childkeys); + } + } + + /* Unparameterized paths don't contribute to param-set list */ + if (childouter) + { + ListCell *lco; + bool found = false; + + /* Have we already seen this param set? */ + foreach(lco, all_child_outers) + { + Relids existing_outers = (Relids) lfirst(lco); + + if (bms_equal(existing_outers, childouter)) + { + found = true; + break; + } + } + if (!found) + { + /* No, so add it to all_child_outers */ + all_child_outers = lappend(all_child_outers, + childouter); + } + } + } + } + + /* + * Next, build an unordered, unparameterized Append path for the rel. + * (Note: this is correct even if we have zero or one live subpath due to + * constraint exclusion.) */ add_path(rel, (Path *) create_append_path(rel, subpaths)); /* - * Next, build MergeAppend paths based on the collected list of child - * pathkeys. We consider both cheapest-startup and cheapest-total cases, - * ie, for each interesting ordering, collect all the cheapest startup - * subpaths and all the cheapest total paths, and build a MergeAppend path - * for each list. + * Build unparameterized MergeAppend paths based on the collected list of + * child pathkeys. + */ + generate_mergeappend_paths(root, rel, live_childrels, + all_child_pathkeys, NULL); + + /* + * Build Append and MergeAppend paths for each parameterization seen + * among the child rels. (This may look pretty expensive, but in most + * cases of practical interest, the child relations will tend to expose + * the same parameterizations and pathkeys, so that not that many cases + * actually get considered here.) */ - foreach(l, all_child_pathkeys) + foreach(l, all_child_outers) + { + Relids required_outer = (Relids) lfirst(l); + ListCell *lcr; + + /* Select the child paths for an Append with this parameterization */ + subpaths = NIL; + foreach(lcr, live_childrels) + { + RelOptInfo *childrel = (RelOptInfo *) lfirst(lcr); + Path *cheapest_total; + + cheapest_total = + get_cheapest_path_for_pathkeys(childrel->pathlist, + NIL, + required_outer, + TOTAL_COST); + Assert(cheapest_total != NULL); + + subpaths = accumulate_append_subpath(subpaths, cheapest_total); + } + add_path(rel, (Path *) create_append_path(rel, subpaths)); + + /* And build parameterized MergeAppend paths */ + generate_mergeappend_paths(root, rel, live_childrels, + all_child_pathkeys, required_outer); + } + + /* Select cheapest paths */ + set_cheapest(rel); +} + +/* + * generate_mergeappend_paths + * Generate MergeAppend paths for an append relation + * + * Generate a path for each ordering (pathkey list) appearing in + * all_child_pathkeys. If required_outer isn't NULL, accept paths having + * those relations as required outer relations. + * + * We consider both cheapest-startup and cheapest-total cases, ie, for each + * interesting ordering, collect all the cheapest startup subpaths and all the + * cheapest total paths, and build a MergeAppend path for each case. + */ +static void +generate_mergeappend_paths(PlannerInfo *root, RelOptInfo *rel, + List *live_childrels, + List *all_child_pathkeys, + Relids required_outer) +{ + ListCell *lcp; + + foreach(lcp, all_child_pathkeys) { - List *pathkeys = (List *) lfirst(l); + List *pathkeys = (List *) lfirst(lcp); List *startup_subpaths = NIL; List *total_subpaths = NIL; bool startup_neq_total = false; @@ -594,20 +854,32 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, cheapest_startup = get_cheapest_path_for_pathkeys(childrel->pathlist, pathkeys, + required_outer, STARTUP_COST); cheapest_total = get_cheapest_path_for_pathkeys(childrel->pathlist, pathkeys, + required_outer, TOTAL_COST); /* - * If we can't find any paths with the right order just add the - * cheapest-total path; we'll have to sort it. + * If we can't find any paths with the right order just use the + * cheapest-total path; we'll have to sort it later. We can + * use the cheapest path for the parameterization, though. */ - if (cheapest_startup == NULL) - cheapest_startup = childrel->cheapest_total_path; - if (cheapest_total == NULL) - cheapest_total = childrel->cheapest_total_path; + if (cheapest_startup == NULL || cheapest_total == NULL) + { + if (required_outer) + cheapest_startup = cheapest_total = + get_cheapest_path_for_pathkeys(childrel->pathlist, + NIL, + required_outer, + TOTAL_COST); + else + cheapest_startup = cheapest_total = + childrel->cheapest_total_path; + Assert(cheapest_total != NULL); + } /* * Notice whether we actually have different paths for the @@ -634,9 +906,6 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, total_subpaths, pathkeys)); } - - /* Select cheapest path */ - set_cheapest(rel); } /* @@ -667,7 +936,7 @@ accumulate_append_subpath(List *subpaths, Path *path) * Build a dummy path for a relation that's been excluded by constraints * * Rather than inventing a special "dummy" path type, we represent this as an - * AppendPath with no members (see also IS_DUMMY_PATH macro). + * AppendPath with no members (see also IS_DUMMY_PATH/IS_DUMMY_REL macros). */ static void set_dummy_rel_pathlist(RelOptInfo *rel) @@ -676,6 +945,9 @@ set_dummy_rel_pathlist(RelOptInfo *rel) rel->rows = 0; rel->width = 0; + /* Discard any pre-existing paths; no further need for them */ + rel->pathlist = NIL; + add_path(rel, (Path *) create_append_path(rel, NIL)); /* Select cheapest path (pretty easy in this case...) */ @@ -707,6 +979,9 @@ 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. */ static void set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel, @@ -847,9 +1122,6 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel, static void set_function_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) { - /* Mark rel with estimated output rows, width, etc */ - set_function_size_estimates(root, rel); - /* Generate appropriate path */ add_path(rel, create_functionscan_path(root, rel)); @@ -864,9 +1136,6 @@ set_function_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) static void set_values_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) { - /* Mark rel with estimated output rows, width, etc */ - set_values_size_estimates(root, rel); - /* Generate appropriate path */ add_path(rel, create_valuesscan_path(root, rel)); @@ -877,6 +1146,9 @@ set_values_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) /* * set_cte_pathlist * Build the (single) access path for a non-self-reference CTE RTE + * + * There's no need for a separate set_cte_size phase, since we don't + * support parameterized paths for CTEs. */ static void set_cte_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) @@ -935,6 +1207,9 @@ set_cte_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) /* * set_worktable_pathlist * Build the (single) access path for a self-reference CTE RTE + * + * There's no need for a separate set_worktable_size phase, since we don't + * support parameterized paths for CTEs. */ static void set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) @@ -973,23 +1248,6 @@ set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) set_cheapest(rel); } -/* - * set_foreign_pathlist - * Build the (single) access path for a foreign table RTE - */ -static void -set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) -{ - /* Mark rel with estimated output rows, width, etc */ - set_foreign_size_estimates(root, rel); - - /* Generate appropriate path */ - add_path(rel, (Path *) create_foreignscan_path(root, rel)); - - /* Select cheapest path (pretty easy in this case...) */ - set_cheapest(rel); -} - /* * make_rel_from_joinlist * Build access paths using a "joinlist" to guide the join path search. diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c index e1c070ea92c625ffa9593d26dbab369dcd566b55..885d8558c319fd283df351c2c8e062a449b72d3c 100644 --- a/src/backend/optimizer/path/costsize.c +++ b/src/backend/optimizer/path/costsize.c @@ -40,7 +40,7 @@ * path's result. A caller can estimate the cost of fetching a partial * result by interpolating between startup_cost and total_cost. In detail: * actual_cost = startup_cost + - * (total_cost - startup_cost) * tuples_to_fetch / path->parent->rows; + * (total_cost - startup_cost) * tuples_to_fetch / path->rows; * Note that a base relation's rows count (and, by extension, plan_rows for * plan nodes below the LIMIT node) are set without regard to any LIMIT, so * that this equation works properly. (Also, these routines guarantee not to @@ -48,11 +48,11 @@ * applied as a top-level plan node. * * For largely historical reasons, most of the routines in this module use - * the passed result Path only to store their startup_cost and total_cost - * results into. All the input data they need is passed as separate + * the passed result Path only to store their results (rows, startup_cost and + * total_cost) into. All the input data they need is passed as separate * parameters, even though much of it could be extracted from the Path. * An exception is made for the cost_XXXjoin() routines, which expect all - * the non-cost fields of the passed XXXPath to be filled in, and similarly + * the other fields of the passed XXXPath to be filled in, and similarly * cost_index() assumes the passed IndexPath is valid except for its output * values. * @@ -90,15 +90,6 @@ #define LOG2(x) (log(x) / 0.693147180559945) -/* - * Some Paths return less than the nominal number of rows of their parent - * relations; join nodes need to do this to get the correct input count: - */ -#define PATH_ROWS(path) \ - (IsA(path, UniquePath) ? \ - ((UniquePath *) (path))->rows : \ - (path)->parent->rows) - double seq_page_cost = DEFAULT_SEQ_PAGE_COST; double random_page_cost = DEFAULT_RANDOM_PAGE_COST; @@ -134,13 +125,17 @@ static MergeScanSelCache *cached_scansel(PlannerInfo *root, static void cost_rescan(PlannerInfo *root, Path *path, Cost *rescan_startup_cost, Cost *rescan_total_cost); static bool cost_qual_eval_walker(Node *node, cost_qual_eval_context *context); -static bool adjust_semi_join(PlannerInfo *root, JoinPath *path, - SpecialJoinInfo *sjinfo, - Selectivity *outer_match_frac, - Selectivity *match_count, - bool *indexed_join_quals); +static bool has_indexed_join_quals(NestPath *path, List *joinclauses); static double approx_tuple_count(PlannerInfo *root, JoinPath *path, List *quals); +static void set_joinpath_size_estimate(PlannerInfo *root, JoinPath *path, + SpecialJoinInfo *sjinfo, + List *restrictlist); +static double calc_joinrel_size_estimate(PlannerInfo *root, + double outer_rows, + double inner_rows, + SpecialJoinInfo *sjinfo, + List *restrictlist); static void set_rel_width(PlannerInfo *root, RelOptInfo *rel); static double relation_byte_size(double tuples, int width); static double page_size(double tuples, int width); @@ -184,6 +179,9 @@ cost_seqscan(Path *path, PlannerInfo *root, Assert(baserel->relid > 0); Assert(baserel->rtekind == RTE_RELATION); + /* For now, at least, seqscans are never parameterized */ + path->rows = baserel->rows; + if (!enable_seqscan) startup_cost += disable_cost; @@ -212,14 +210,12 @@ cost_seqscan(Path *path, PlannerInfo *root, * * 'path' describes the indexscan under consideration, and is complete * except for the fields to be set by this routine - * 'outer_rel' is the outer relation when we are considering using the index - * scan as the inside of a nestloop join (hence, some of the indexquals - * are join clauses, and we should expect repeated scans of the index); - * NULL for a plain index scan + * 'loop_count' is the number of repetitions of the indexscan to factor into + * estimates of caching behavior * - * In addition to startup_cost and total_cost, cost_index() sets the path's - * indextotalcost and indexselectivity fields. These values are needed if - * the IndexPath is used in a BitmapIndexScan. + * In addition to rows, startup_cost and total_cost, cost_index() sets the + * path's indextotalcost and indexselectivity fields. These values will be + * needed if the IndexPath is used in a BitmapIndexScan. * * NOTE: path->indexquals must contain only clauses usable as index * restrictions. Any additional quals evaluated as qpquals may reduce the @@ -227,7 +223,7 @@ cost_seqscan(Path *path, PlannerInfo *root, * we have to fetch from the table, so they don't reduce the scan cost. */ void -cost_index(IndexPath *path, PlannerInfo *root, RelOptInfo *outer_rel) +cost_index(IndexPath *path, PlannerInfo *root, double loop_count) { IndexOptInfo *index = path->indexinfo; RelOptInfo *baserel = index->rel; @@ -253,6 +249,47 @@ cost_index(IndexPath *path, PlannerInfo *root, RelOptInfo *outer_rel) Assert(baserel->relid > 0); Assert(baserel->rtekind == RTE_RELATION); + /* Estimate the number of rows returned by the indexscan */ + if (path->path.required_outer) + { + /* + * The estimate should be less than baserel->rows because of the + * additional selectivity of the join clauses. Since indexclauses may + * contain both restriction and join clauses, we have to do a set + * union to get the full set of clauses that must be considered to + * compute the correct selectivity. (Without the union operation, we + * might have some restriction clauses appearing twice, which'd + * mislead clauselist_selectivity into double-counting their + * selectivity. However, since RestrictInfo nodes aren't copied when + * linking them into different lists, it should be sufficient to use + * pointer comparison to remove duplicates.) + * + * Note that we force the clauses to be treated as non-join clauses + * during selectivity estimation. + */ + List *allclauses; + + allclauses = list_union_ptr(baserel->baserestrictinfo, + path->indexclauses); + path->path.rows = baserel->tuples * + clauselist_selectivity(root, + allclauses, + baserel->relid, /* do not use 0! */ + JOIN_INNER, + NULL); + if (path->path.rows > baserel->rows) + path->path.rows = baserel->rows; + path->path.rows = clamp_row_est(path->path.rows); + } + else + { + /* + * The number of rows is the same as the parent rel's estimate, since + * this isn't a parameterized path. + */ + path->path.rows = baserel->rows; + } + if (!enable_indexscan) startup_cost += disable_cost; /* we don't need to check enable_indexonlyscan; indxpath.c does that */ @@ -266,7 +303,7 @@ cost_index(IndexPath *path, PlannerInfo *root, RelOptInfo *outer_rel) OidFunctionCall7(index->amcostestimate, PointerGetDatum(root), PointerGetDatum(path), - PointerGetDatum(outer_rel), + Float8GetDatum(loop_count), PointerGetDatum(&indexStartupCost), PointerGetDatum(&indexTotalCost), PointerGetDatum(&indexSelectivity), @@ -319,7 +356,7 @@ cost_index(IndexPath *path, PlannerInfo *root, RelOptInfo *outer_rel) * that this query will fetch; but it's not clear how to do better. *---------- */ - if (outer_rel != NULL && outer_rel->rows > 1) + if (loop_count > 1) { /* * For repeated indexscans, the appropriate estimate for the @@ -329,9 +366,7 @@ cost_index(IndexPath *path, PlannerInfo *root, RelOptInfo *outer_rel) * pro-rate the costs for one scan. In this case we assume all the * fetches are random accesses. */ - double num_scans = outer_rel->rows; - - pages_fetched = index_pages_fetched(tuples_fetched * num_scans, + pages_fetched = index_pages_fetched(tuples_fetched * loop_count, baserel->pages, (double) index->pages, root); @@ -339,7 +374,7 @@ cost_index(IndexPath *path, PlannerInfo *root, RelOptInfo *outer_rel) if (indexonly) pages_fetched = ceil(pages_fetched * (1.0 - baserel->allvisfrac)); - max_IO_cost = (pages_fetched * spc_random_page_cost) / num_scans; + max_IO_cost = (pages_fetched * spc_random_page_cost) / loop_count; /* * In the perfectly correlated case, the number of pages touched by @@ -353,7 +388,7 @@ cost_index(IndexPath *path, PlannerInfo *root, RelOptInfo *outer_rel) */ pages_fetched = ceil(indexSelectivity * (double) baserel->pages); - pages_fetched = index_pages_fetched(pages_fetched * num_scans, + pages_fetched = index_pages_fetched(pages_fetched * loop_count, baserel->pages, (double) index->pages, root); @@ -361,7 +396,7 @@ cost_index(IndexPath *path, PlannerInfo *root, RelOptInfo *outer_rel) if (indexonly) pages_fetched = ceil(pages_fetched * (1.0 - baserel->allvisfrac)); - min_IO_cost = (pages_fetched * spc_random_page_cost) / num_scans; + min_IO_cost = (pages_fetched * spc_random_page_cost) / loop_count; } else { @@ -417,7 +452,7 @@ cost_index(IndexPath *path, PlannerInfo *root, RelOptInfo *outer_rel) startup_cost += baserel->baserestrictcost.startup; cpu_per_tuple = cpu_tuple_cost + baserel->baserestrictcost.per_tuple; - if (outer_rel == NULL) + if (path->path.required_outer == NULL) { QualCost index_qual_cost; @@ -578,17 +613,15 @@ get_indexpath_pages(Path *bitmapqual) * * 'baserel' is the relation to be scanned * 'bitmapqual' is a tree of IndexPaths, BitmapAndPaths, and BitmapOrPaths - * 'outer_rel' is the outer relation when we are considering using the bitmap - * scan as the inside of a nestloop join (hence, some of the indexquals - * are join clauses, and we should expect repeated scans of the table); - * NULL for a plain bitmap scan + * 'loop_count' is the number of repetitions of the indexscan to factor into + * estimates of caching behavior * - * Note: if this is a join inner path, the component IndexPaths in bitmapqual - * should have been costed accordingly. + * Note: the component IndexPaths in bitmapqual should have been costed + * using the same loop_count. */ void cost_bitmap_heap_scan(Path *path, PlannerInfo *root, RelOptInfo *baserel, - Path *bitmapqual, RelOptInfo *outer_rel) + Path *bitmapqual, double loop_count) { Cost startup_cost = 0; Cost run_cost = 0; @@ -607,6 +640,34 @@ cost_bitmap_heap_scan(Path *path, PlannerInfo *root, RelOptInfo *baserel, Assert(baserel->relid > 0); Assert(baserel->rtekind == RTE_RELATION); + /* Estimate the number of rows returned by the bitmap scan */ + if (path->required_outer) + { + /* + * The estimate should be less than baserel->rows because of the + * additional selectivity of the join clauses. We make use of the + * selectivity estimated for the bitmap to do this; this isn't really + * quite right since there may be restriction conditions not included + * in the bitmap ... + */ + Cost indexTotalCost; + Selectivity indexSelectivity; + + cost_bitmap_tree_node(bitmapqual, &indexTotalCost, &indexSelectivity); + path->rows = baserel->tuples * indexSelectivity; + if (path->rows > baserel->rows) + path->rows = baserel->rows; + path->rows = clamp_row_est(path->rows); + } + else + { + /* + * The number of rows is the same as the parent rel's estimate, since + * this isn't a parameterized path. + */ + path->rows = baserel->rows; + } + if (!enable_bitmapscan) startup_cost += disable_cost; @@ -630,7 +691,7 @@ cost_bitmap_heap_scan(Path *path, PlannerInfo *root, RelOptInfo *baserel, T = (baserel->pages > 1) ? (double) baserel->pages : 1.0; - if (outer_rel != NULL && outer_rel->rows > 1) + if (loop_count > 1) { /* * For repeated bitmap scans, scale up the number of tuples fetched in @@ -638,13 +699,11 @@ cost_bitmap_heap_scan(Path *path, PlannerInfo *root, RelOptInfo *baserel, * estimate the number of pages fetched by all the scans. Then * pro-rate for one scan. */ - double num_scans = outer_rel->rows; - - pages_fetched = index_pages_fetched(tuples_fetched * num_scans, + pages_fetched = index_pages_fetched(tuples_fetched * loop_count, baserel->pages, get_indexpath_pages(bitmapqual), root); - pages_fetched /= num_scans; + pages_fetched /= loop_count; } else { @@ -711,7 +770,7 @@ cost_bitmap_tree_node(Path *path, Cost *cost, Selectivity *selec) * scan doesn't look to be the same cost as an indexscan to retrieve a * single tuple. */ - *cost += 0.1 * cpu_operator_cost * ((IndexPath *) path)->rows; + *cost += 0.1 * cpu_operator_cost * path->rows; } else if (IsA(path, BitmapAndPath)) { @@ -737,7 +796,8 @@ cost_bitmap_tree_node(Path *path, Cost *cost, Selectivity *selec) * Note that this considers only the costs of index scanning and bitmap * creation, not the eventual heap access. In that sense the object isn't * truly a Path, but it has enough path-like properties (costs in particular) - * to warrant treating it as one. + * to warrant treating it as one. We don't bother to set the path rows field, + * however. */ void cost_bitmap_and_node(BitmapAndPath *path, PlannerInfo *root) @@ -772,6 +832,7 @@ cost_bitmap_and_node(BitmapAndPath *path, PlannerInfo *root) totalCost += 100.0 * cpu_operator_cost; } path->bitmapselectivity = selec; + path->path.rows = 0; /* per above, not used */ path->path.startup_cost = totalCost; path->path.total_cost = totalCost; } @@ -817,6 +878,7 @@ cost_bitmap_or_node(BitmapOrPath *path, PlannerInfo *root) totalCost += 100.0 * cpu_operator_cost; } path->bitmapselectivity = Min(selec, 1.0); + path->path.rows = 0; /* per above, not used */ path->path.startup_cost = totalCost; path->path.total_cost = totalCost; } @@ -842,6 +904,9 @@ cost_tidscan(Path *path, PlannerInfo *root, Assert(baserel->relid > 0); Assert(baserel->rtekind == RTE_RELATION); + /* For now, tidscans are never parameterized */ + path->rows = baserel->rows; + /* Count how many tuples we expect to retrieve */ ntuples = 0; foreach(l, tidquals) @@ -923,6 +988,9 @@ cost_subqueryscan(Path *path, RelOptInfo *baserel) Assert(baserel->relid > 0); Assert(baserel->rtekind == RTE_SUBQUERY); + /* subqueryscans are never parameterized */ + path->rows = baserel->rows; + /* * Cost of path is cost of evaluating the subplan, plus cost of evaluating * any restriction clauses that will be attached to the SubqueryScan node, @@ -957,6 +1025,9 @@ 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; + /* * Estimate costs of executing the function expression. * @@ -998,6 +1069,9 @@ cost_valuesscan(Path *path, PlannerInfo *root, RelOptInfo *baserel) Assert(baserel->relid > 0); Assert(baserel->rtekind == RTE_VALUES); + /* valuesscans are never parameterized */ + path->rows = baserel->rows; + /* * For now, estimate list evaluation cost at one operator eval per list * (probably pretty bogus, but is it worth being smarter?) @@ -1034,6 +1108,9 @@ cost_ctescan(Path *path, PlannerInfo *root, RelOptInfo *baserel) Assert(baserel->relid > 0); Assert(baserel->rtekind == RTE_CTE); + /* ctescans are never parameterized */ + path->rows = baserel->rows; + /* Charge one CPU tuple cost per row for tuplestore manipulation */ cpu_per_tuple = cpu_tuple_cost; @@ -1152,6 +1229,8 @@ cost_sort(Path *path, PlannerInfo *root, if (!enable_sort) startup_cost += disable_cost; + path->rows = tuples; + /* * We want to be sure the cost of a sort is never estimated as zero, even * if passed-in tuple count is zero. Besides, mustn't do log(0)... @@ -1320,6 +1399,8 @@ cost_material(Path *path, double nbytes = relation_byte_size(tuples, width); long work_mem_bytes = work_mem * 1024L; + path->rows = tuples; + /* * Whether spilling or not, charge 2x cpu_operator_cost per tuple to * reflect bookkeeping overhead. (This rate must be more than what @@ -1369,6 +1450,7 @@ cost_agg(Path *path, PlannerInfo *root, Cost input_startup_cost, Cost input_total_cost, double input_tuples) { + double output_tuples; Cost startup_cost; Cost total_cost; AggClauseCosts dummy_aggcosts; @@ -1411,6 +1493,7 @@ cost_agg(Path *path, PlannerInfo *root, startup_cost += aggcosts->finalCost; /* we aren't grouping */ total_cost = startup_cost + cpu_tuple_cost; + output_tuples = 1; } else if (aggstrategy == AGG_SORTED) { @@ -1423,6 +1506,7 @@ cost_agg(Path *path, PlannerInfo *root, total_cost += (cpu_operator_cost * numGroupCols) * input_tuples; total_cost += aggcosts->finalCost * numGroups; total_cost += cpu_tuple_cost * numGroups; + output_tuples = numGroups; } else { @@ -1434,8 +1518,10 @@ cost_agg(Path *path, PlannerInfo *root, total_cost = startup_cost; total_cost += aggcosts->finalCost * numGroups; total_cost += cpu_tuple_cost * numGroups; + output_tuples = numGroups; } + path->rows = output_tuples; path->startup_cost = startup_cost; path->total_cost = total_cost; } @@ -1498,6 +1584,7 @@ cost_windowagg(Path *path, PlannerInfo *root, total_cost += cpu_operator_cost * (numPartCols + numOrderCols) * input_tuples; total_cost += cpu_tuple_cost * input_tuples; + path->rows = input_tuples; path->startup_cost = startup_cost; path->total_cost = total_cost; } @@ -1528,84 +1615,49 @@ cost_group(Path *path, PlannerInfo *root, */ total_cost += cpu_operator_cost * input_tuples * numGroupCols; + path->rows = numGroups; path->startup_cost = startup_cost; path->total_cost = total_cost; } /* - * If a nestloop's inner path is an indexscan, be sure to use its estimated - * output row count, which may be lower than the restriction-clause-only row - * count of its parent. (We don't include this case in the PATH_ROWS macro - * because it applies *only* to a nestloop's inner relation.) We have to - * be prepared to recurse through Append or MergeAppend nodes in case of an - * appendrel. (It's not clear MergeAppend can be seen here, but we may as - * well handle it if so.) - */ -static double -nestloop_inner_path_rows(Path *path) -{ - double result; - - if (IsA(path, IndexPath)) - result = ((IndexPath *) path)->rows; - else if (IsA(path, BitmapHeapPath)) - result = ((BitmapHeapPath *) path)->rows; - else if (IsA(path, AppendPath)) - { - ListCell *l; - - result = 0; - foreach(l, ((AppendPath *) path)->subpaths) - { - result += nestloop_inner_path_rows((Path *) lfirst(l)); - } - } - else if (IsA(path, MergeAppendPath)) - { - ListCell *l; - - result = 0; - foreach(l, ((MergeAppendPath *) path)->subpaths) - { - result += nestloop_inner_path_rows((Path *) lfirst(l)); - } - } - else - result = PATH_ROWS(path); - - return result; -} - -/* - * cost_nestloop - * Determines and returns the cost of joining two relations using the - * nested loop algorithm. - * - * 'path' is already filled in except for the cost fields + * initial_cost_nestloop + * Preliminary estimate of the cost of a nestloop join path. + * + * This must quickly produce lower-bound estimates of the path's startup and + * total costs. If we are unable to eliminate the proposed path from + * consideration using the lower bounds, final_cost_nestloop will be called + * to obtain the final estimates. + * + * The exact division of labor between this function and final_cost_nestloop + * is private to them, and represents a tradeoff between speed of the initial + * estimate and getting a tight lower bound. We choose to not examine the + * join quals here, since that's by far the most expensive part of the + * calculations. The end result is that CPU-cost considerations must be + * left for the second phase. + * + * 'workspace' is to be filled with startup_cost, total_cost, and perhaps + * other data to be used by final_cost_nestloop + * 'jointype' is the type of join to be performed + * 'outer_path' is the outer input to the join + * 'inner_path' is the inner input to the join * 'sjinfo' is extra info about the join for selectivity estimation + * 'semifactors' contains valid data if jointype is SEMI or ANTI */ void -cost_nestloop(NestPath *path, PlannerInfo *root, SpecialJoinInfo *sjinfo) +initial_cost_nestloop(PlannerInfo *root, JoinCostWorkspace *workspace, + JoinType jointype, + Path *outer_path, Path *inner_path, + SpecialJoinInfo *sjinfo, + SemiAntiJoinFactors *semifactors) { - Path *outer_path = path->outerjoinpath; - Path *inner_path = path->innerjoinpath; Cost startup_cost = 0; Cost run_cost = 0; + double outer_path_rows = outer_path->rows; Cost inner_rescan_start_cost; Cost inner_rescan_total_cost; Cost inner_run_cost; Cost inner_rescan_run_cost; - Cost cpu_per_tuple; - QualCost restrict_qual_cost; - double outer_path_rows = PATH_ROWS(outer_path); - double inner_path_rows = nestloop_inner_path_rows(inner_path); - double ntuples; - Selectivity outer_match_frac; - Selectivity match_count; - bool indexed_join_quals; - - if (!enable_nestloop) - startup_cost += disable_cost; /* estimate costs to rescan the inner relation */ cost_rescan(root, inner_path, @@ -1628,10 +1680,7 @@ cost_nestloop(NestPath *path, PlannerInfo *root, SpecialJoinInfo *sjinfo) inner_run_cost = inner_path->total_cost - inner_path->startup_cost; inner_rescan_run_cost = inner_rescan_total_cost - inner_rescan_start_cost; - if (adjust_semi_join(root, path, sjinfo, - &outer_match_frac, - &match_count, - &indexed_join_quals)) + if (jointype == JOIN_SEMI || jointype == JOIN_ANTI) { double outer_matched_rows; Selectivity inner_scan_frac; @@ -1656,13 +1705,110 @@ cost_nestloop(NestPath *path, PlannerInfo *root, SpecialJoinInfo *sjinfo) */ run_cost += inner_run_cost; - outer_matched_rows = rint(outer_path_rows * outer_match_frac); - inner_scan_frac = 2.0 / (match_count + 1.0); + outer_matched_rows = rint(outer_path_rows * semifactors->outer_match_frac); + inner_scan_frac = 2.0 / (semifactors->match_count + 1.0); /* Add inner run cost for additional outer tuples having matches */ if (outer_matched_rows > 1) run_cost += (outer_matched_rows - 1) * inner_rescan_run_cost * inner_scan_frac; + /* + * The cost of processing unmatched rows varies depending on the + * details of the joinclauses, so we leave that part for later. + */ + + /* Save private data for final_cost_nestloop */ + workspace->outer_matched_rows = outer_matched_rows; + workspace->inner_scan_frac = inner_scan_frac; + } + else + { + /* Normal case; we'll scan whole input rel for each outer row */ + run_cost += inner_run_cost; + if (outer_path_rows > 1) + run_cost += (outer_path_rows - 1) * inner_rescan_run_cost; + } + + /* CPU costs left for later */ + + /* Public result fields */ + workspace->startup_cost = startup_cost; + workspace->total_cost = startup_cost + run_cost; + /* Save private data for final_cost_nestloop */ + workspace->run_cost = run_cost; + workspace->inner_rescan_run_cost = inner_rescan_run_cost; +} + +/* + * final_cost_nestloop + * Final estimate of the cost and result size of a nestloop join path. + * + * 'path' is already filled in except for the rows and cost fields + * 'workspace' is the result from initial_cost_nestloop + * 'sjinfo' is extra info about the join for selectivity estimation + * 'semifactors' contains valid data if path->jointype is SEMI or ANTI + */ +void +final_cost_nestloop(PlannerInfo *root, NestPath *path, + JoinCostWorkspace *workspace, + SpecialJoinInfo *sjinfo, + SemiAntiJoinFactors *semifactors) +{ + Path *outer_path = path->outerjoinpath; + Path *inner_path = path->innerjoinpath; + double outer_path_rows = outer_path->rows; + double inner_path_rows = inner_path->rows; + Cost startup_cost = workspace->startup_cost; + Cost run_cost = workspace->run_cost; + Cost inner_rescan_run_cost = workspace->inner_rescan_run_cost; + Cost cpu_per_tuple; + List *joinclauses; + QualCost restrict_qual_cost; + double ntuples; + + /* Estimate the number of rows returned by the join */ + if (path->path.required_outer) + { + /* + * The nestloop is (still) parameterized because of upper-level join + * clauses used by the input paths. So the rowcount estimate should + * be less than the joinrel's row count because of the additional + * selectivity of those join clauses. To estimate the size we need + * to know which of the joinrestrictinfo clauses nominally associated + * with the join have been applied in the inner input path. + * + * We should also assume that such clauses won't be evaluated at the + * join node at runtime, so exclude them from restrict_qual_cost. + */ + joinclauses = select_nonredundant_join_clauses(path->joinrestrictinfo, + path->innerjoinpath->param_clauses); + set_joinpath_size_estimate(root, path, sjinfo, joinclauses); + } + else + { + joinclauses = path->joinrestrictinfo; + path->path.rows = path->path.parent->rows; + } + + /* + * We could include disable_cost in the preliminary estimate, but that + * would amount to optimizing for the case where the join method is + * disabled, which doesn't seem like the way to bet. + */ + if (!enable_nestloop) + startup_cost += disable_cost; + + /* cost of source data */ + + if (path->jointype == JOIN_SEMI || path->jointype == JOIN_ANTI) + { + double outer_matched_rows = workspace->outer_matched_rows; + Selectivity inner_scan_frac = workspace->inner_scan_frac; + + /* + * SEMI or ANTI join: executor will stop after first match. + */ + /* Compute number of tuples processed (not number emitted!) */ ntuples = outer_matched_rows * inner_path_rows * inner_scan_frac; @@ -1674,7 +1820,7 @@ cost_nestloop(NestPath *path, PlannerInfo *root, SpecialJoinInfo *sjinfo) * return the first tuple of a nonempty scan. Otherwise, the executor * will have to scan the whole inner rel; not so cheap. */ - if (indexed_join_quals) + if (has_indexed_join_quals(path, joinclauses)) { run_cost += (outer_path_rows - outer_matched_rows) * inner_rescan_run_cost / inner_path_rows; @@ -1694,17 +1840,14 @@ cost_nestloop(NestPath *path, PlannerInfo *root, SpecialJoinInfo *sjinfo) } else { - /* Normal case; we'll scan whole input rel for each outer row */ - run_cost += inner_run_cost; - if (outer_path_rows > 1) - run_cost += (outer_path_rows - 1) * inner_rescan_run_cost; + /* Normal-case source costs were included in preliminary estimate */ /* Compute number of tuples processed (not number emitted!) */ ntuples = outer_path_rows * inner_path_rows; } /* CPU costs */ - cost_qual_eval(&restrict_qual_cost, path->joinrestrictinfo, root); + cost_qual_eval(&restrict_qual_cost, joinclauses, root); startup_cost += restrict_qual_cost.startup; cpu_per_tuple = cpu_tuple_cost + restrict_qual_cost.per_tuple; run_cost += cpu_per_tuple * ntuples; @@ -1714,54 +1857,52 @@ cost_nestloop(NestPath *path, PlannerInfo *root, SpecialJoinInfo *sjinfo) } /* - * cost_mergejoin - * Determines and returns the cost of joining two relations using the - * merge join algorithm. - * - * Unlike other costsize functions, this routine makes one actual decision: - * whether we should materialize the inner path. We do that either because - * the inner path can't support mark/restore, or because it's cheaper to - * use an interposed Material node to handle mark/restore. When the decision - * is cost-based it would be logically cleaner to build and cost two separate - * paths with and without that flag set; but that would require repeating most - * of the calculations here, which are not all that cheap. Since the choice - * will not affect output pathkeys or startup cost, only total cost, there is - * no possibility of wanting to keep both paths. So it seems best to make - * the decision here and record it in the path's materialize_inner field. - * - * 'path' is already filled in except for the cost fields and materialize_inner + * initial_cost_mergejoin + * Preliminary estimate of the cost of a mergejoin path. + * + * This must quickly produce lower-bound estimates of the path's startup and + * total costs. If we are unable to eliminate the proposed path from + * consideration using the lower bounds, final_cost_mergejoin will be called + * to obtain the final estimates. + * + * The exact division of labor between this function and final_cost_mergejoin + * is private to them, and represents a tradeoff between speed of the initial + * estimate and getting a tight lower bound. We choose to not examine the + * join quals here, except for obtaining the scan selectivity estimate which + * is really essential (but fortunately, use of caching keeps the cost of + * getting that down to something reasonable). + * We also assume that cost_sort is cheap enough to use here. + * + * 'workspace' is to be filled with startup_cost, total_cost, and perhaps + * other data to be used by final_cost_mergejoin + * 'jointype' is the type of join to be performed + * 'mergeclauses' is the list of joinclauses to be used as merge clauses + * 'outer_path' is the outer input to the join + * 'inner_path' is the inner input to the join + * 'outersortkeys' is the list of sort keys for the outer path + * 'innersortkeys' is the list of sort keys for the inner path * 'sjinfo' is extra info about the join for selectivity estimation * - * Notes: path's mergeclauses should be a subset of the joinrestrictinfo list; - * outersortkeys and innersortkeys are lists of the keys to be used - * to sort the outer and inner relations, or NIL if no explicit - * sort is needed because the source path is already ordered. + * Note: outersortkeys and innersortkeys should be NIL if no explicit + * sort is needed because the respective source path is already ordered. */ void -cost_mergejoin(MergePath *path, PlannerInfo *root, SpecialJoinInfo *sjinfo) +initial_cost_mergejoin(PlannerInfo *root, JoinCostWorkspace *workspace, + JoinType jointype, + List *mergeclauses, + Path *outer_path, Path *inner_path, + List *outersortkeys, List *innersortkeys, + SpecialJoinInfo *sjinfo) { - Path *outer_path = path->jpath.outerjoinpath; - Path *inner_path = path->jpath.innerjoinpath; - List *mergeclauses = path->path_mergeclauses; - List *outersortkeys = path->outersortkeys; - List *innersortkeys = path->innersortkeys; Cost startup_cost = 0; Cost run_cost = 0; - Cost cpu_per_tuple, - inner_run_cost, - bare_inner_cost, - mat_inner_cost; - QualCost merge_qual_cost; - QualCost qp_qual_cost; - double outer_path_rows = PATH_ROWS(outer_path); - double inner_path_rows = PATH_ROWS(inner_path); + double outer_path_rows = outer_path->rows; + double inner_path_rows = inner_path->rows; + Cost inner_run_cost; double outer_rows, inner_rows, outer_skip_rows, inner_skip_rows; - double mergejointuples, - rescannedtuples; - double rescanratio; Selectivity outerstartsel, outerendsel, innerstartsel, @@ -1774,62 +1915,6 @@ cost_mergejoin(MergePath *path, PlannerInfo *root, SpecialJoinInfo *sjinfo) if (inner_path_rows <= 0 || isnan(inner_path_rows)) inner_path_rows = 1; - if (!enable_mergejoin) - startup_cost += disable_cost; - - /* - * Compute cost of the mergequals and qpquals (other restriction clauses) - * separately. - */ - cost_qual_eval(&merge_qual_cost, mergeclauses, root); - cost_qual_eval(&qp_qual_cost, path->jpath.joinrestrictinfo, root); - qp_qual_cost.startup -= merge_qual_cost.startup; - qp_qual_cost.per_tuple -= merge_qual_cost.per_tuple; - - /* - * Get approx # tuples passing the mergequals. We use approx_tuple_count - * here because we need an estimate done with JOIN_INNER semantics. - */ - mergejointuples = approx_tuple_count(root, &path->jpath, mergeclauses); - - /* - * When there are equal merge keys in the outer relation, the mergejoin - * must rescan any matching tuples in the inner relation. This means - * re-fetching inner tuples; we have to estimate how often that happens. - * - * For regular inner and outer joins, the number of re-fetches can be - * estimated approximately as size of merge join output minus size of - * inner relation. Assume that the distinct key values are 1, 2, ..., and - * denote the number of values of each key in the outer relation as m1, - * m2, ...; in the inner relation, n1, n2, ... Then we have - * - * size of join = m1 * n1 + m2 * n2 + ... - * - * number of rescanned tuples = (m1 - 1) * n1 + (m2 - 1) * n2 + ... = m1 * - * n1 + m2 * n2 + ... - (n1 + n2 + ...) = size of join - size of inner - * relation - * - * This equation works correctly for outer tuples having no inner match - * (nk = 0), but not for inner tuples having no outer match (mk = 0); we - * are effectively subtracting those from the number of rescanned tuples, - * when we should not. Can we do better without expensive selectivity - * computations? - * - * The whole issue is moot if we are working from a unique-ified outer - * input. - */ - if (IsA(outer_path, UniquePath)) - rescannedtuples = 0; - else - { - rescannedtuples = mergejointuples - inner_path_rows; - /* Must clamp because of possible underestimate */ - if (rescannedtuples < 0) - rescannedtuples = 0; - } - /* We'll inflate various costs this much to account for rescanning */ - rescanratio = 1.0 + (rescannedtuples / inner_path_rows); - /* * A merge join will stop as soon as it exhausts either input stream * (unless it's an outer join, in which case the outer side has to be @@ -1841,7 +1926,7 @@ cost_mergejoin(MergePath *path, PlannerInfo *root, SpecialJoinInfo *sjinfo) * mergejoinscansel() is a fairly expensive computation, we cache the * results in the merge clause RestrictInfo. */ - if (mergeclauses && path->jpath.jointype != JOIN_FULL) + if (mergeclauses && jointype != JOIN_FULL) { RestrictInfo *firstclause = (RestrictInfo *) linitial(mergeclauses); List *opathkeys; @@ -1884,13 +1969,13 @@ cost_mergejoin(MergePath *path, PlannerInfo *root, SpecialJoinInfo *sjinfo) innerstartsel = cache->leftstartsel; innerendsel = cache->leftendsel; } - if (path->jpath.jointype == JOIN_LEFT || - path->jpath.jointype == JOIN_ANTI) + if (jointype == JOIN_LEFT || + jointype == JOIN_ANTI) { outerstartsel = 0.0; outerendsel = 1.0; } - else if (path->jpath.jointype == JOIN_RIGHT) + else if (jointype == JOIN_RIGHT) { innerstartsel = 0.0; innerendsel = 1.0; @@ -1982,6 +2067,143 @@ cost_mergejoin(MergePath *path, PlannerInfo *root, SpecialJoinInfo *sjinfo) * (innerendsel - innerstartsel); } + /* + * We can't yet determine whether rescanning occurs, or whether + * materialization of the inner input should be done. The minimum + * possible inner input cost, regardless of rescan and materialization + * considerations, is inner_run_cost. We include that in + * workspace->total_cost, but not yet in run_cost. + */ + + /* CPU costs left for later */ + + /* Public result fields */ + workspace->startup_cost = startup_cost; + workspace->total_cost = startup_cost + run_cost + inner_run_cost; + /* Save private data for final_cost_mergejoin */ + workspace->run_cost = run_cost; + workspace->inner_run_cost = inner_run_cost; + workspace->outer_rows = outer_rows; + workspace->inner_rows = inner_rows; + workspace->outer_skip_rows = outer_skip_rows; + workspace->inner_skip_rows = inner_skip_rows; +} + +/* + * final_cost_mergejoin + * Final estimate of the cost and result size of a mergejoin path. + * + * Unlike other costsize functions, this routine makes one actual decision: + * whether we should materialize the inner path. We do that either because + * the inner path can't support mark/restore, or because it's cheaper to + * use an interposed Material node to handle mark/restore. When the decision + * is cost-based it would be logically cleaner to build and cost two separate + * paths with and without that flag set; but that would require repeating most + * of the cost calculations, which are not all that cheap. Since the choice + * will not affect output pathkeys or startup cost, only total cost, there is + * no possibility of wanting to keep both paths. So it seems best to make + * the decision here and record it in the path's materialize_inner field. + * + * 'path' is already filled in except for the rows and cost fields and + * materialize_inner + * 'workspace' is the result from initial_cost_mergejoin + * 'sjinfo' is extra info about the join for selectivity estimation + */ +void +final_cost_mergejoin(PlannerInfo *root, MergePath *path, + JoinCostWorkspace *workspace, + SpecialJoinInfo *sjinfo) +{ + Path *outer_path = path->jpath.outerjoinpath; + Path *inner_path = path->jpath.innerjoinpath; + double inner_path_rows = inner_path->rows; + List *mergeclauses = path->path_mergeclauses; + List *innersortkeys = path->innersortkeys; + Cost startup_cost = workspace->startup_cost; + Cost run_cost = workspace->run_cost; + Cost inner_run_cost = workspace->inner_run_cost; + double outer_rows = workspace->outer_rows; + double inner_rows = workspace->inner_rows; + double outer_skip_rows = workspace->outer_skip_rows; + double inner_skip_rows = workspace->inner_skip_rows; + Cost cpu_per_tuple, + bare_inner_cost, + mat_inner_cost; + QualCost merge_qual_cost; + QualCost qp_qual_cost; + double mergejointuples, + rescannedtuples; + double rescanratio; + + /* Protect some assumptions below that rowcounts aren't zero or NaN */ + if (inner_path_rows <= 0 || isnan(inner_path_rows)) + inner_path_rows = 1; + + /* Estimate the number of rows returned by the join */ + set_joinpath_size_estimate(root, &path->jpath, sjinfo, + path->jpath.joinrestrictinfo); + + /* + * We could include disable_cost in the preliminary estimate, but that + * would amount to optimizing for the case where the join method is + * disabled, which doesn't seem like the way to bet. + */ + if (!enable_mergejoin) + startup_cost += disable_cost; + + /* + * Compute cost of the mergequals and qpquals (other restriction clauses) + * separately. + */ + cost_qual_eval(&merge_qual_cost, mergeclauses, root); + cost_qual_eval(&qp_qual_cost, path->jpath.joinrestrictinfo, root); + qp_qual_cost.startup -= merge_qual_cost.startup; + qp_qual_cost.per_tuple -= merge_qual_cost.per_tuple; + + /* + * Get approx # tuples passing the mergequals. We use approx_tuple_count + * here because we need an estimate done with JOIN_INNER semantics. + */ + mergejointuples = approx_tuple_count(root, &path->jpath, mergeclauses); + + /* + * When there are equal merge keys in the outer relation, the mergejoin + * must rescan any matching tuples in the inner relation. This means + * re-fetching inner tuples; we have to estimate how often that happens. + * + * For regular inner and outer joins, the number of re-fetches can be + * estimated approximately as size of merge join output minus size of + * inner relation. Assume that the distinct key values are 1, 2, ..., and + * denote the number of values of each key in the outer relation as m1, + * m2, ...; in the inner relation, n1, n2, ... Then we have + * + * size of join = m1 * n1 + m2 * n2 + ... + * + * number of rescanned tuples = (m1 - 1) * n1 + (m2 - 1) * n2 + ... = m1 * + * n1 + m2 * n2 + ... - (n1 + n2 + ...) = size of join - size of inner + * relation + * + * This equation works correctly for outer tuples having no inner match + * (nk = 0), but not for inner tuples having no outer match (mk = 0); we + * are effectively subtracting those from the number of rescanned tuples, + * when we should not. Can we do better without expensive selectivity + * computations? + * + * The whole issue is moot if we are working from a unique-ified outer + * input. + */ + if (IsA(outer_path, UniquePath)) + rescannedtuples = 0; + else + { + rescannedtuples = mergejointuples - inner_path_rows; + /* Must clamp because of possible underestimate */ + if (rescannedtuples < 0) + rescannedtuples = 0; + } + /* We'll inflate various costs this much to account for rescanning */ + rescanratio = 1.0 + (rescannedtuples / inner_path_rows); + /* * Decide whether we want to materialize the inner input to shield it from * mark/restore and performing re-fetches. Our cost model for regular @@ -2148,50 +2370,46 @@ cached_scansel(PlannerInfo *root, RestrictInfo *rinfo, PathKey *pathkey) } /* - * cost_hashjoin - * Determines and returns the cost of joining two relations using the - * hash join algorithm. - * - * 'path' is already filled in except for the cost fields + * initial_cost_hashjoin + * Preliminary estimate of the cost of a hashjoin path. + * + * This must quickly produce lower-bound estimates of the path's startup and + * total costs. If we are unable to eliminate the proposed path from + * consideration using the lower bounds, final_cost_hashjoin will be called + * to obtain the final estimates. + * + * The exact division of labor between this function and final_cost_hashjoin + * is private to them, and represents a tradeoff between speed of the initial + * estimate and getting a tight lower bound. We choose to not examine the + * join quals here (other than by counting the number of hash clauses), + * so we can't do much with CPU costs. We do assume that + * ExecChooseHashTableSize is cheap enough to use here. + * + * 'workspace' is to be filled with startup_cost, total_cost, and perhaps + * other data to be used by final_cost_hashjoin + * 'jointype' is the type of join to be performed + * 'hashclauses' is the list of joinclauses to be used as hash clauses + * 'outer_path' is the outer input to the join + * 'inner_path' is the inner input to the join * 'sjinfo' is extra info about the join for selectivity estimation - * - * Note: path's hashclauses should be a subset of the joinrestrictinfo list + * 'semifactors' contains valid data if jointype is SEMI or ANTI */ void -cost_hashjoin(HashPath *path, PlannerInfo *root, SpecialJoinInfo *sjinfo) +initial_cost_hashjoin(PlannerInfo *root, JoinCostWorkspace *workspace, + JoinType jointype, + List *hashclauses, + Path *outer_path, Path *inner_path, + SpecialJoinInfo *sjinfo, + SemiAntiJoinFactors *semifactors) { - Path *outer_path = path->jpath.outerjoinpath; - Path *inner_path = path->jpath.innerjoinpath; - List *hashclauses = path->path_hashclauses; Cost startup_cost = 0; Cost run_cost = 0; - Cost cpu_per_tuple; - QualCost hash_qual_cost; - QualCost qp_qual_cost; - double hashjointuples; - double outer_path_rows = PATH_ROWS(outer_path); - double inner_path_rows = PATH_ROWS(inner_path); + double outer_path_rows = outer_path->rows; + double inner_path_rows = inner_path->rows; int num_hashclauses = list_length(hashclauses); int numbuckets; int numbatches; int num_skew_mcvs; - double virtualbuckets; - Selectivity innerbucketsize; - Selectivity outer_match_frac; - Selectivity match_count; - ListCell *hcl; - - if (!enable_hashjoin) - startup_cost += disable_cost; - - /* - * Compute cost of the hashquals and qpquals (other restriction clauses) - * separately. - */ - cost_qual_eval(&hash_qual_cost, hashclauses, root); - cost_qual_eval(&qp_qual_cost, path->jpath.joinrestrictinfo, root); - qp_qual_cost.startup -= hash_qual_cost.startup; - qp_qual_cost.per_tuple -= hash_qual_cost.per_tuple; /* cost of source data */ startup_cost += outer_path->startup_cost; @@ -2228,11 +2446,89 @@ cost_hashjoin(HashPath *path, PlannerInfo *root, SpecialJoinInfo *sjinfo) &numbuckets, &numbatches, &num_skew_mcvs); - virtualbuckets = (double) numbuckets *(double) numbatches; + + /* + * If inner relation is too big then we will need to "batch" the join, + * which implies writing and reading most of the tuples to disk an extra + * time. Charge seq_page_cost per page, since the I/O should be nice and + * sequential. Writing the inner rel counts as startup cost, all the rest + * as run cost. + */ + if (numbatches > 1) + { + double outerpages = page_size(outer_path_rows, + outer_path->parent->width); + double innerpages = page_size(inner_path_rows, + inner_path->parent->width); + + startup_cost += seq_page_cost * innerpages; + run_cost += seq_page_cost * (innerpages + 2 * outerpages); + } + + /* CPU costs left for later */ + + /* Public result fields */ + workspace->startup_cost = startup_cost; + workspace->total_cost = startup_cost + run_cost; + /* Save private data for final_cost_hashjoin */ + workspace->run_cost = run_cost; + workspace->numbuckets = numbuckets; + workspace->numbatches = numbatches; +} + +/* + * final_cost_hashjoin + * Final estimate of the cost and result size of a hashjoin path. + * + * Note: the numbatches estimate is also saved into 'path' for use later + * + * 'path' is already filled in except for the rows and cost fields and + * num_batches + * 'workspace' is the result from initial_cost_hashjoin + * 'sjinfo' is extra info about the join for selectivity estimation + * 'semifactors' contains valid data if path->jointype is SEMI or ANTI + */ +void +final_cost_hashjoin(PlannerInfo *root, HashPath *path, + JoinCostWorkspace *workspace, + SpecialJoinInfo *sjinfo, + SemiAntiJoinFactors *semifactors) +{ + Path *outer_path = path->jpath.outerjoinpath; + Path *inner_path = path->jpath.innerjoinpath; + double outer_path_rows = outer_path->rows; + double inner_path_rows = inner_path->rows; + List *hashclauses = path->path_hashclauses; + Cost startup_cost = workspace->startup_cost; + Cost run_cost = workspace->run_cost; + int numbuckets = workspace->numbuckets; + int numbatches = workspace->numbatches; + Cost cpu_per_tuple; + QualCost hash_qual_cost; + QualCost qp_qual_cost; + double hashjointuples; + double virtualbuckets; + Selectivity innerbucketsize; + ListCell *hcl; + + /* Estimate the number of rows returned by the join */ + set_joinpath_size_estimate(root, &path->jpath, sjinfo, + path->jpath.joinrestrictinfo); + + /* + * We could include disable_cost in the preliminary estimate, but that + * would amount to optimizing for the case where the join method is + * disabled, which doesn't seem like the way to bet. + */ + if (!enable_hashjoin) + startup_cost += disable_cost; /* mark the path with estimated # of batches */ path->num_batches = numbatches; + /* and compute the number of "virtual" buckets in the whole join */ + virtualbuckets = (double) numbuckets *(double) numbatches; + /* * Determine bucketsize fraction for inner relation. We use the smallest * bucketsize estimated for any individual hashclause; this is undoubtedly @@ -2301,29 +2597,17 @@ cost_hashjoin(HashPath *path, PlannerInfo *root, SpecialJoinInfo *sjinfo) } /* - * If inner relation is too big then we will need to "batch" the join, - * which implies writing and reading most of the tuples to disk an extra - * time. Charge seq_page_cost per page, since the I/O should be nice and - * sequential. Writing the inner rel counts as startup cost, all the rest - * as run cost. + * Compute cost of the hashquals and qpquals (other restriction clauses) + * separately. */ - if (numbatches > 1) - { - double outerpages = page_size(outer_path_rows, - outer_path->parent->width); - double innerpages = page_size(inner_path_rows, - inner_path->parent->width); - - startup_cost += seq_page_cost * innerpages; - run_cost += seq_page_cost * (innerpages + 2 * outerpages); - } + cost_qual_eval(&hash_qual_cost, hashclauses, root); + cost_qual_eval(&qp_qual_cost, path->jpath.joinrestrictinfo, root); + qp_qual_cost.startup -= hash_qual_cost.startup; + qp_qual_cost.per_tuple -= hash_qual_cost.per_tuple; /* CPU costs */ - if (adjust_semi_join(root, &path->jpath, sjinfo, - &outer_match_frac, - &match_count, - NULL)) + if (path->jpath.jointype == JOIN_SEMI || path->jpath.jointype == JOIN_ANTI) { double outer_matched_rows; Selectivity inner_scan_frac; @@ -2339,8 +2623,8 @@ cost_hashjoin(HashPath *path, PlannerInfo *root, SpecialJoinInfo *sjinfo) * to clamp inner_scan_frac to at most 1.0; but since match_count is * at least 1, no such clamp is needed now.) */ - outer_matched_rows = rint(outer_path_rows * outer_match_frac); - inner_scan_frac = 2.0 / (match_count + 1.0); + outer_matched_rows = rint(outer_path_rows * semifactors->outer_match_frac); + inner_scan_frac = 2.0 / (semifactors->match_count + 1.0); startup_cost += hash_qual_cost.startup; run_cost += hash_qual_cost.per_tuple * outer_matched_rows * @@ -2545,8 +2829,8 @@ cost_rescan(PlannerInfo *root, Path *path, * cpu_tuple_cost per tuple, unless the result is large enough * to spill to disk. */ - Cost run_cost = cpu_tuple_cost * path->parent->rows; - double nbytes = relation_byte_size(path->parent->rows, + Cost run_cost = cpu_tuple_cost * path->rows; + double nbytes = relation_byte_size(path->rows, path->parent->width); long work_mem_bytes = work_mem * 1024L; @@ -2572,8 +2856,8 @@ cost_rescan(PlannerInfo *root, Path *path, * the run_cost charge in cost_sort, and also see comments in * cost_material before you change it.) */ - Cost run_cost = cpu_operator_cost * path->parent->rows; - double nbytes = relation_byte_size(path->parent->rows, + Cost run_cost = cpu_operator_cost * path->rows; + double nbytes = relation_byte_size(path->rows, path->parent->width); long work_mem_bytes = work_mem * 1024L; @@ -2841,7 +3125,7 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context) /* - * adjust_semi_join + * compute_semi_anti_join_factors * Estimate how much of the inner input a SEMI or ANTI join * can be expected to scan. * @@ -2849,30 +3133,28 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context) * inner rows as soon as it finds a match to the current outer row. * We should therefore adjust some of the cost components for this effect. * This function computes some estimates needed for these adjustments. - * - * 'path' is already filled in except for the cost fields - * 'sjinfo' is extra info about the join for selectivity estimation - * - * Returns TRUE if this is a SEMI or ANTI join, FALSE if not. - * - * Output parameters (set only in TRUE-result case): - * *outer_match_frac is set to the fraction of the outer tuples that are - * expected to have at least one match. - * *match_count is set to the average number of matches expected for - * outer tuples that have at least one match. - * *indexed_join_quals is set to TRUE if all the joinquals are used as - * inner index quals, FALSE if not. - * - * indexed_join_quals can be passed as NULL if that information is not - * relevant (it is only useful for the nestloop case). + * These estimates will be the same regardless of the particular paths used + * for the outer and inner relation, so we compute these once and then pass + * them to all the join cost estimation functions. + * + * Input parameters: + * outerrel: outer relation under consideration + * innerrel: inner relation under consideration + * jointype: must be JOIN_SEMI or JOIN_ANTI + * sjinfo: SpecialJoinInfo relevant to this join + * restrictlist: join quals + * Output parameters: + * *semifactors is filled in (see relation.h for field definitions) */ -static bool -adjust_semi_join(PlannerInfo *root, JoinPath *path, SpecialJoinInfo *sjinfo, - Selectivity *outer_match_frac, - Selectivity *match_count, - bool *indexed_join_quals) +void +compute_semi_anti_join_factors(PlannerInfo *root, + RelOptInfo *outerrel, + RelOptInfo *innerrel, + JoinType jointype, + SpecialJoinInfo *sjinfo, + List *restrictlist, + SemiAntiJoinFactors *semifactors) { - JoinType jointype = path->jointype; Selectivity jselec; Selectivity nselec; Selectivity avgmatch; @@ -2880,17 +3162,8 @@ adjust_semi_join(PlannerInfo *root, JoinPath *path, SpecialJoinInfo *sjinfo, List *joinquals; ListCell *l; - /* Fall out if it's not JOIN_SEMI or JOIN_ANTI */ - if (jointype != JOIN_SEMI && jointype != JOIN_ANTI) - return false; - - /* - * Note: it's annoying to repeat this selectivity estimation on each call, - * when the joinclause list will be the same for all path pairs - * implementing a given join. clausesel.c will save us from the worst - * effects of this by caching at the RestrictInfo level; but perhaps it'd - * be worth finding a way to cache the results at a higher level. - */ + /* Should only be called in these cases */ + Assert(jointype == JOIN_SEMI || jointype == JOIN_ANTI); /* * In an ANTI join, we must ignore clauses that are "pushed down", since @@ -2901,7 +3174,7 @@ adjust_semi_join(PlannerInfo *root, JoinPath *path, SpecialJoinInfo *sjinfo, if (jointype == JOIN_ANTI) { joinquals = NIL; - foreach(l, path->joinrestrictinfo) + foreach(l, restrictlist) { RestrictInfo *rinfo = (RestrictInfo *) lfirst(l); @@ -2911,7 +3184,7 @@ adjust_semi_join(PlannerInfo *root, JoinPath *path, SpecialJoinInfo *sjinfo, } } else - joinquals = path->joinrestrictinfo; + joinquals = restrictlist; /* * Get the JOIN_SEMI or JOIN_ANTI selectivity of the join clauses. @@ -2926,10 +3199,10 @@ adjust_semi_join(PlannerInfo *root, JoinPath *path, SpecialJoinInfo *sjinfo, * Also get the normal inner-join selectivity of the join clauses. */ norm_sjinfo.type = T_SpecialJoinInfo; - norm_sjinfo.min_lefthand = path->outerjoinpath->parent->relids; - norm_sjinfo.min_righthand = path->innerjoinpath->parent->relids; - norm_sjinfo.syn_lefthand = path->outerjoinpath->parent->relids; - norm_sjinfo.syn_righthand = path->innerjoinpath->parent->relids; + norm_sjinfo.min_lefthand = outerrel->relids; + norm_sjinfo.min_righthand = innerrel->relids; + norm_sjinfo.syn_lefthand = outerrel->relids; + norm_sjinfo.syn_righthand = innerrel->relids; norm_sjinfo.jointype = JOIN_INNER; /* we don't bother trying to make the remaining fields valid */ norm_sjinfo.lhs_strict = false; @@ -2953,47 +3226,63 @@ adjust_semi_join(PlannerInfo *root, JoinPath *path, SpecialJoinInfo *sjinfo, * number of matches for each outer-rel row that has at least one match is * nselec * inner_rows / jselec. * - * Note: it is correct to use the inner rel's "rows" count here, not - * PATH_ROWS(), even if the inner path under consideration is an inner - * indexscan. This is because we have included all the join clauses in - * the selectivity estimate, even ones used in an inner indexscan. + * Note: it is correct to use the inner rel's "rows" count here, even + * though we might later be considering a parameterized inner path with + * fewer rows. This is because we have included all the join clauses + * in the selectivity estimate. */ if (jselec > 0) /* protect against zero divide */ { - avgmatch = nselec * path->innerjoinpath->parent->rows / jselec; + avgmatch = nselec * innerrel->rows / jselec; /* Clamp to sane range */ avgmatch = Max(1.0, avgmatch); } else avgmatch = 1.0; - *outer_match_frac = jselec; - *match_count = avgmatch; + semifactors->outer_match_frac = jselec; + semifactors->match_count = avgmatch; +} - /* - * If requested, check whether the inner path uses all the joinquals as - * indexquals. (If that's true, we can assume that an unmatched outer - * tuple is cheap to process, whereas otherwise it's probably expensive.) - */ - if (indexed_join_quals) +/* + * has_indexed_join_quals + * Check whether all the joinquals of a nestloop join are used as + * inner index quals. + * + * If the inner path of a SEMI/ANTI join is an indexscan (including bitmap + * indexscan) that uses all the joinquals as indexquals, we can assume that an + * unmatched outer tuple is cheap to process, whereas otherwise it's probably + * expensive. + */ +static bool +has_indexed_join_quals(NestPath *path, List *joinclauses) +{ + NodeTag pathtype = path->innerjoinpath->pathtype; + + if (pathtype == T_IndexScan || + pathtype == T_IndexOnlyScan || + pathtype == T_BitmapHeapScan) { if (path->joinrestrictinfo != NIL) { - List *nrclauses; - - nrclauses = select_nonredundant_join_clauses(root, - path->joinrestrictinfo, - path->innerjoinpath); - *indexed_join_quals = (nrclauses == NIL); + /* OK if all those clauses were found to be redundant */ + return (joinclauses == NIL); } else { /* a clauseless join does NOT qualify */ - *indexed_join_quals = false; + return false; } } - - return true; + else + { + /* + * If it's not a simple indexscan, it probably doesn't run quickly for + * zero rows out, even if it's a parameterized path using all the + * joinquals. + */ + return false; + } } @@ -3025,8 +3314,8 @@ static double approx_tuple_count(PlannerInfo *root, JoinPath *path, List *quals) { double tuples; - double outer_tuples = path->outerjoinpath->parent->rows; - double inner_tuples = path->innerjoinpath->parent->rows; + double outer_tuples = path->outerjoinpath->rows; + double inner_tuples = path->innerjoinpath->rows; SpecialJoinInfo sjinfo; Selectivity selec = 1.0; ListCell *l; @@ -3122,6 +3411,55 @@ set_joinrel_size_estimates(PlannerInfo *root, RelOptInfo *rel, RelOptInfo *inner_rel, SpecialJoinInfo *sjinfo, List *restrictlist) +{ + rel->rows = calc_joinrel_size_estimate(root, + outer_rel->rows, + inner_rel->rows, + sjinfo, + restrictlist); +} + +/* + * set_joinpath_size_estimate + * Set the rows estimate for the given join path. + * + * If the join is not parameterized by any joinclauses from higher joins, the + * estimate is the same as previously computed by set_joinrel_size_estimates. + * Otherwise, we estimate afresh using the identical logic, but with the rows + * estimates from the input paths (which are typically less than their rels' + * regular row estimates) and the restriction clauses actually being applied + * at the join. + */ +static void +set_joinpath_size_estimate(PlannerInfo *root, JoinPath *path, + SpecialJoinInfo *sjinfo, + List *restrictlist) +{ + if (path->path.required_outer) + { + path->path.rows = calc_joinrel_size_estimate(root, + path->outerjoinpath->rows, + path->innerjoinpath->rows, + sjinfo, + restrictlist); + /* For safety, make sure result is not more than the base estimate */ + if (path->path.rows > path->path.parent->rows) + path->path.rows = path->path.parent->rows; + } + else + path->path.rows = path->path.parent->rows; +} + +/* + * calc_joinrel_size_estimate + * Workhorse for set_joinrel_size_estimates and set_joinpath_size_estimate + */ +static double +calc_joinrel_size_estimate(PlannerInfo *root, + double outer_rows, + double inner_rows, + SpecialJoinInfo *sjinfo, + List *restrictlist) { JoinType jointype = sjinfo->jointype; Selectivity jselec; @@ -3197,28 +3535,28 @@ set_joinrel_size_estimates(PlannerInfo *root, RelOptInfo *rel, switch (jointype) { case JOIN_INNER: - nrows = outer_rel->rows * inner_rel->rows * jselec; + nrows = outer_rows * inner_rows * jselec; break; case JOIN_LEFT: - nrows = outer_rel->rows * inner_rel->rows * jselec; - if (nrows < outer_rel->rows) - nrows = outer_rel->rows; + nrows = outer_rows * inner_rows * jselec; + if (nrows < outer_rows) + nrows = outer_rows; nrows *= pselec; break; case JOIN_FULL: - nrows = outer_rel->rows * inner_rel->rows * jselec; - if (nrows < outer_rel->rows) - nrows = outer_rel->rows; - if (nrows < inner_rel->rows) - nrows = inner_rel->rows; + nrows = outer_rows * inner_rows * jselec; + if (nrows < outer_rows) + nrows = outer_rows; + if (nrows < inner_rows) + nrows = inner_rows; nrows *= pselec; break; case JOIN_SEMI: - nrows = outer_rel->rows * jselec; + nrows = outer_rows * jselec; /* pselec not used */ break; case JOIN_ANTI: - nrows = outer_rel->rows * (1.0 - jselec); + nrows = outer_rows * (1.0 - jselec); nrows *= pselec; break; default: @@ -3228,7 +3566,7 @@ set_joinrel_size_estimates(PlannerInfo *root, RelOptInfo *rel, break; } - rel->rows = clamp_row_est(nrows); + return clamp_row_est(nrows); } /* diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c index 17fbe01a01f967705cd84134741035573883f9e4..2f22efabb53d341618289460e015ae09930352b2 100644 --- a/src/backend/optimizer/path/equivclass.c +++ b/src/backend/optimizer/path/equivclass.c @@ -59,6 +59,7 @@ static bool reconsider_outer_join_clause(PlannerInfo *root, bool outer_on_left); static bool reconsider_full_join_clause(PlannerInfo *root, RestrictInfo *rinfo); +static Index get_parent_relid(PlannerInfo *root, RelOptInfo *rel); /* @@ -892,7 +893,7 @@ generate_base_implied_equalities_broken(PlannerInfo *root, * * The results are sufficient for use in merge, hash, and plain nestloop join * methods. We do not worry here about selecting clauses that are optimal - * for use in a nestloop-with-inner-indexscan join, however. indxpath.c makes + * for use in a nestloop-with-parameterized-inner-scan. indxpath.c makes * its own selections of clauses to use, and if the ones we pick here are * redundant with those, the extras will be eliminated in createplan.c. * @@ -1858,21 +1859,40 @@ mutate_eclass_expressions(PlannerInfo *root, /* - * find_eclass_clauses_for_index_join - * Create joinclauses usable for a nestloop-with-inner-indexscan - * scanning the given inner rel with the specified set of outer rels. + * generate_implied_equalities_for_indexcol + * Create EC-derived joinclauses usable with a specific index column. + * + * We assume that any given index column could appear in only one EC. + * (This should be true in all but the most pathological cases, and if it + * isn't, we stop on the first match anyway.) Therefore, what we return + * is a redundant list of clauses equating the index column to each of + * the other-relation values it is known to be equal to. Any one of + * these clauses can be used to create a parameterized indexscan, and there + * is no value in using more than one. (But it *is* worthwhile to create + * a separate parameterized path for each one, since that leads to different + * join orders.) */ List * -find_eclass_clauses_for_index_join(PlannerInfo *root, RelOptInfo *rel, - Relids outer_relids) +generate_implied_equalities_for_indexcol(PlannerInfo *root, + IndexOptInfo *index, + int indexcol) { List *result = NIL; + RelOptInfo *rel = index->rel; bool is_child_rel = (rel->reloptkind == RELOPT_OTHER_MEMBER_REL); + Index parent_relid; ListCell *lc1; + /* If it's a child rel, we'll need to know what its parent is */ + if (is_child_rel) + parent_relid = get_parent_relid(root, rel); + else + parent_relid = 0; /* not used, but keep compiler quiet */ + foreach(lc1, root->eq_classes) { EquivalenceClass *cur_ec = (EquivalenceClass *) lfirst(lc1); + EquivalenceMember *cur_em; ListCell *lc2; /* @@ -1889,71 +1909,94 @@ find_eclass_clauses_for_index_join(PlannerInfo *root, RelOptInfo *rel, if (!is_child_rel && !bms_is_subset(rel->relids, cur_ec->ec_relids)) continue; - /* ... nor if no overlap with outer_relids */ - if (!bms_overlap(outer_relids, cur_ec->ec_relids)) + + /* Scan members, looking for a match to the indexable column */ + cur_em = NULL; + foreach(lc2, cur_ec->ec_members) + { + cur_em = (EquivalenceMember *) lfirst(lc2); + if (bms_equal(cur_em->em_relids, rel->relids) && + eclass_member_matches_indexcol(cur_ec, cur_em, + index, indexcol)) + break; + cur_em = NULL; + } + + if (!cur_em) continue; - /* Scan members, looking for indexable columns */ + /* + * Found our match. Scan the other EC members and attempt to generate + * joinclauses. + */ foreach(lc2, cur_ec->ec_members) { - EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc2); - EquivalenceMember *best_outer_em = NULL; - Oid best_eq_op = InvalidOid; - ListCell *lc3; + EquivalenceMember *other_em = (EquivalenceMember *) lfirst(lc2); + Oid eq_op; + RestrictInfo *rinfo; - if (!bms_equal(cur_em->em_relids, rel->relids) || - !eclass_matches_any_index(cur_ec, cur_em, rel)) + /* Make sure it'll be a join to a different rel */ + if (other_em == cur_em || + bms_overlap(other_em->em_relids, rel->relids)) continue; /* - * Found one, so try to generate a join clause. This is like - * generate_join_implied_equalities_normal, except simpler since - * our only preference item is to pick a Var on the outer side. We - * only need one join clause per index col. + * Also, if this is a child rel, avoid generating a useless join + * to its parent rel. */ - foreach(lc3, cur_ec->ec_members) - { - EquivalenceMember *outer_em = (EquivalenceMember *) lfirst(lc3); - Oid eq_op; - - if (!bms_is_subset(outer_em->em_relids, outer_relids)) - continue; - eq_op = select_equality_operator(cur_ec, - cur_em->em_datatype, - outer_em->em_datatype); - if (!OidIsValid(eq_op)) - continue; - best_outer_em = outer_em; - best_eq_op = eq_op; - if (IsA(outer_em->em_expr, Var) || - (IsA(outer_em->em_expr, RelabelType) && - IsA(((RelabelType *) outer_em->em_expr)->arg, Var))) - break; /* no need to look further */ - } + if (is_child_rel && + bms_is_member(parent_relid, other_em->em_relids)) + continue; - if (best_outer_em) - { - /* Found a suitable joinclause */ - RestrictInfo *rinfo; + eq_op = select_equality_operator(cur_ec, + cur_em->em_datatype, + other_em->em_datatype); + if (!OidIsValid(eq_op)) + continue; - /* set parent_ec to mark as redundant with other joinclauses */ - rinfo = create_join_clause(root, cur_ec, best_eq_op, - cur_em, best_outer_em, - cur_ec); + /* set parent_ec to mark as redundant with other joinclauses */ + rinfo = create_join_clause(root, cur_ec, eq_op, + cur_em, other_em, + cur_ec); - result = lappend(result, rinfo); - - /* - * Note: we keep scanning here because we want to provide a - * clause for every possible indexcol. - */ - } + result = lappend(result, rinfo); } + + /* + * If somehow we failed to create any join clauses, we might as well + * keep scanning the ECs for another match. But if we did make any, + * we're done, because we don't want to return non-redundant clauses. + */ + if (result) + break; } return result; } +/* + * get_parent_relid + * Get the relid of a child rel's parent appendrel + * + * Possibly this should be somewhere else, but right now the logic is only + * needed here. + */ +static Index +get_parent_relid(PlannerInfo *root, RelOptInfo *rel) +{ + ListCell *lc; + + foreach(lc, root->append_rel_list) + { + AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc); + + if (appinfo->child_relid == rel->relid) + return appinfo->parent_relid; + } + /* should have found the entry ... */ + elog(ERROR, "child rel not found in append_rel_list"); + return 0; +} /* * have_relevant_eclass_joinclause diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c index 11ee2317376b03d443d7ec25c745f51497d8a808..920593781b2a2cdef83496b2ae90f1870b21bbc5 100644 --- a/src/backend/optimizer/path/indxpath.c +++ b/src/backend/optimizer/path/indxpath.c @@ -61,6 +61,14 @@ typedef enum ST_ANYSCAN /* either is okay */ } ScanTypeControl; +/* Data structure for collecting qual clauses that match an index */ +typedef struct +{ + bool nonempty; /* True if lists are not all empty */ + /* Lists of RestrictInfos, one per index column */ + List *indexclauses[INDEX_MAX_KEYS]; +} IndexClauseSet; + /* Per-path data used within choose_bitmap_and() */ typedef struct { @@ -71,55 +79,73 @@ typedef struct } PathClauseUsage; -static List *find_usable_indexes(PlannerInfo *root, RelOptInfo *rel, - List *clauses, List *outer_clauses, - bool istoplevel, RelOptInfo *outer_rel, - SaOpControl saop_control, ScanTypeControl scantype); -static List *find_saop_paths(PlannerInfo *root, RelOptInfo *rel, - List *clauses, List *outer_clauses, - bool istoplevel, RelOptInfo *outer_rel); +static void consider_index_join_clauses(PlannerInfo *root, RelOptInfo *rel, + IndexOptInfo *index, + IndexClauseSet *rclauseset, + IndexClauseSet *jclauseset, + IndexClauseSet *eclauseset, + List **bitindexpaths); +static void expand_eclass_clause_combinations(PlannerInfo *root, + RelOptInfo *rel, + IndexOptInfo *index, + int thiscol, int lastcol, + IndexClauseSet *clauseset, + IndexClauseSet *eclauseset, + List **bitindexpaths); +static void get_index_paths(PlannerInfo *root, RelOptInfo *rel, + IndexOptInfo *index, IndexClauseSet *clauses, + List **bitindexpaths); +static List *build_index_paths(PlannerInfo *root, RelOptInfo *rel, + IndexOptInfo *index, IndexClauseSet *clauses, + bool useful_predicate, + SaOpControl saop_control, ScanTypeControl scantype); +static List *build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel, + List *clauses, List *other_clauses); +static List *drop_indexable_join_clauses(RelOptInfo *rel, List *clauses); static Path *choose_bitmap_and(PlannerInfo *root, RelOptInfo *rel, - List *paths, RelOptInfo *outer_rel); + List *paths); static int path_usage_comparator(const void *a, const void *b); static Cost bitmap_scan_cost_est(PlannerInfo *root, RelOptInfo *rel, - Path *ipath, RelOptInfo *outer_rel); + Path *ipath); static Cost bitmap_and_cost_est(PlannerInfo *root, RelOptInfo *rel, - List *paths, RelOptInfo *outer_rel); + List *paths); static PathClauseUsage *classify_index_clause_usage(Path *path, List **clauselist); static void find_indexpath_quals(Path *bitmapqual, List **quals, List **preds); static int find_list_position(Node *node, List **nodelist); static bool check_index_only(RelOptInfo *rel, IndexOptInfo *index); +static double get_loop_count(PlannerInfo *root, Relids outer_relids); +static void match_restriction_clauses_to_index(RelOptInfo *rel, + IndexOptInfo *index, + IndexClauseSet *clauseset); +static void match_join_clauses_to_index(PlannerInfo *root, + RelOptInfo *rel, IndexOptInfo *index, + IndexClauseSet *clauseset, + List **joinorclauses); +static void match_eclass_clauses_to_index(PlannerInfo *root, + IndexOptInfo *index, + IndexClauseSet *clauseset); static void match_clauses_to_index(IndexOptInfo *index, - List *clauses, List *outer_clauses, - Relids outer_relids, - SaOpControl saop_control, - List **index_clauses_p, - List **clause_columns_p, - bool *found_clause); + List *clauses, + IndexClauseSet *clauseset); +static void match_clause_to_index(IndexOptInfo *index, + RestrictInfo *rinfo, + IndexClauseSet *clauseset); static bool match_clause_to_indexcol(IndexOptInfo *index, int indexcol, - RestrictInfo *rinfo, - Relids outer_relids, - SaOpControl saop_control); + RestrictInfo *rinfo); static bool is_indexable_operator(Oid expr_op, Oid opfamily, bool indexkey_on_left); static bool match_rowcompare_to_indexcol(IndexOptInfo *index, int indexcol, Oid opfamily, Oid idxcollation, - RowCompareExpr *clause, - Relids outer_relids); + RowCompareExpr *clause); static void match_pathkeys_to_index(IndexOptInfo *index, List *pathkeys, List **orderby_clauses_p, List **clause_columns_p); static Expr *match_clause_to_ordering_op(IndexOptInfo *index, int indexcol, Expr *clause, Oid pk_opfamily); -static Relids indexable_outerrelids(PlannerInfo *root, RelOptInfo *rel); -static bool matches_any_index(RestrictInfo *rinfo, RelOptInfo *rel, - Relids outer_relids); -static List *find_clauses_for_join(PlannerInfo *root, RelOptInfo *rel, - Relids outer_relids, bool isouterjoin); static bool match_boolean_index_clause(Node *clause, int indexcol, IndexOptInfo *index); static bool match_special_index_operator(Expr *clause, @@ -152,21 +178,17 @@ static Const *string_to_const(const char *str, Oid datatype); * * There are two basic kinds of index scans. A "plain" index scan uses * only restriction clauses (possibly none at all) in its indexqual, - * so it can be applied in any context. An "innerjoin" index scan uses + * so it can be applied in any context. A "parameterized" index scan uses * join clauses (plus restriction clauses, if available) in its indexqual. - * Therefore it can only be used as the inner relation of a nestloop - * join against an outer rel that includes all the other rels mentioned - * in its join clauses. In that context, values for the other rels' + * When joining such a scan to one of the relations supplying the other + * variables used in its indexqual, the parameterized scan must appear as + * the inner relation of a nestloop join; it can't be used on the outer side, + * nor in a merge or hash join. In that context, values for the other rels' * attributes are available and fixed during any one scan of the indexpath. * - * An IndexPath is generated and submitted to add_path() for each plain index - * scan this routine deems potentially interesting for the current query. - * - * We also determine the set of other relids that participate in join - * clauses that could be used with each index. The actually best innerjoin - * path will be generated for each outer relation later on, but knowing the - * set of potential otherrels allows us to identify equivalent outer relations - * and avoid repeated computation. + * An IndexPath is generated and submitted to add_path() for each plain or + * parameterized index scan this routine deems potentially interesting for + * the current query. * * 'rel' is the relation for which we want to generate index paths * @@ -177,98 +199,631 @@ create_index_paths(PlannerInfo *root, RelOptInfo *rel) { List *indexpaths; List *bitindexpaths; - ListCell *l; + List *bitjoinpaths; + List *joinorclauses; + IndexClauseSet rclauseset; + IndexClauseSet jclauseset; + IndexClauseSet eclauseset; + ListCell *ilist; /* Skip the whole mess if no indexes */ if (rel->indexlist == NIL) - { - rel->index_outer_relids = NULL; return; + + /* Bitmap paths are collected and then dealt with at the end */ + bitindexpaths = bitjoinpaths = joinorclauses = NIL; + + /* Examine each index in turn */ + foreach(ilist, rel->indexlist) + { + IndexOptInfo *index = (IndexOptInfo *) lfirst(ilist); + + /* Protect limited-size array in IndexClauseSets */ + Assert(index->ncolumns <= INDEX_MAX_KEYS); + + /* + * Ignore partial indexes that do not match the query. + * (generate_bitmap_or_paths() might be able to do something with + * them, but that's of no concern here.) + */ + if (index->indpred != NIL && !index->predOK) + continue; + + /* + * Identify the restriction clauses that can match the index. + */ + MemSet(&rclauseset, 0, sizeof(rclauseset)); + match_restriction_clauses_to_index(rel, index, &rclauseset); + + /* + * Build index paths from the restriction clauses. These will be + * non-parameterized paths. Plain paths go directly to add_path(), + * bitmap paths are added to bitindexpaths to be handled below. + */ + get_index_paths(root, rel, index, &rclauseset, + &bitindexpaths); + + /* + * Identify the join clauses that can match the index. For the moment + * we keep them separate from the restriction clauses. Note that + * this finds only "loose" join clauses that have not been merged + * into EquivalenceClasses. Also, collect join OR clauses for later. + */ + MemSet(&jclauseset, 0, sizeof(jclauseset)); + match_join_clauses_to_index(root, rel, index, + &jclauseset, &joinorclauses); + + /* + * Look for EquivalenceClasses that can generate joinclauses + * matching the index. + */ + MemSet(&eclauseset, 0, sizeof(eclauseset)); + match_eclass_clauses_to_index(root, index, &eclauseset); + + /* + * If we found any plain or eclass join clauses, decide what to + * do with 'em. + */ + if (jclauseset.nonempty || eclauseset.nonempty) + consider_index_join_clauses(root, rel, index, + &rclauseset, + &jclauseset, + &eclauseset, + &bitjoinpaths); } /* - * Examine join clauses to see which ones are potentially usable with - * indexes of this rel, and generate the set of all other relids that - * participate in such join clauses. We'll use this set later to - * recognize outer rels that are equivalent for joining purposes. + * Generate BitmapOrPaths for any suitable OR-clauses present in the + * restriction list. Add these to bitindexpaths. */ - rel->index_outer_relids = indexable_outerrelids(root, rel); + indexpaths = generate_bitmap_or_paths(root, rel, + rel->baserestrictinfo, NIL, + false); + bitindexpaths = list_concat(bitindexpaths, indexpaths); /* - * Find all the index paths that are directly usable for this relation - * (ie, are valid without considering OR or JOIN clauses). + * Likewise, generate BitmapOrPaths for any suitable OR-clauses present in + * the joinclause list. Add these to bitjoinpaths. */ - indexpaths = find_usable_indexes(root, rel, - rel->baserestrictinfo, NIL, - true, NULL, SAOP_PER_AM, ST_ANYSCAN); + indexpaths = generate_bitmap_or_paths(root, rel, + joinorclauses, rel->baserestrictinfo, + false); + bitjoinpaths = list_concat(bitjoinpaths, indexpaths); + + /* + * If we found anything usable, generate a BitmapHeapPath for the most + * promising combination of restriction bitmap index paths. Note there + * will be only one such path no matter how many indexes exist. This + * should be sufficient since there's basically only one figure of merit + * (total cost) for such a path. + */ + if (bitindexpaths != NIL) + { + Path *bitmapqual; + BitmapHeapPath *bpath; + + bitmapqual = choose_bitmap_and(root, rel, bitindexpaths); + bpath = create_bitmap_heap_path(root, rel, bitmapqual, 1.0); + add_path(rel, (Path *) bpath); + } + + /* + * Likewise, if we found anything usable, generate a BitmapHeapPath for + * the most promising combination of join bitmap index paths. Note there + * will be only one such path no matter how many join clauses are + * available. (XXX is that good enough, or do we need to consider even + * more paths for different subsets of possible join partners? Also, + * should we add in restriction bitmap paths as well?) + */ + if (bitjoinpaths != NIL) + { + Path *bitmapqual; + double loop_count; + BitmapHeapPath *bpath; + + bitmapqual = choose_bitmap_and(root, rel, bitjoinpaths); + loop_count = get_loop_count(root, bitmapqual->required_outer); + bpath = create_bitmap_heap_path(root, rel, bitmapqual, loop_count); + add_path(rel, (Path *) bpath); + } +} + +/* + * consider_index_join_clauses + * Given sets of join clauses for an index, decide which parameterized + * index paths to build. + * + * Plain indexpaths are sent directly to add_path, while potential + * bitmap indexpaths are added to *bitindexpaths for later processing. + * + * 'rel' is the index's heap relation + * 'index' is the index for which we want to generate paths + * 'rclauseset' is the collection of indexable restriction clauses + * 'jclauseset' is the collection of indexable simple join clauses + * 'eclauseset' is the collection of indexable clauses from EquivalenceClasses + * '*bitindexpaths' is the list to add bitmap paths to + * + * Note: this changes the clause lists contained in the passed clausesets, + * but we don't care since the caller is done with them. + */ +static void +consider_index_join_clauses(PlannerInfo *root, RelOptInfo *rel, + IndexOptInfo *index, + IndexClauseSet *rclauseset, + IndexClauseSet *jclauseset, + IndexClauseSet *eclauseset, + List **bitindexpaths) +{ + IndexClauseSet clauseset; + int last_eclass_col; + int indexcol; + + /* + * We can always include any restriction clauses in the index clauses. + * However, it's not obvious which subsets of the join clauses are worth + * generating paths from, and it's unlikely that considering every + * possible subset is worth the cycles. Our current heuristic is based + * on the index columns, with the idea that later index columns are less + * useful than earlier ones; therefore it's unlikely to be worth trying + * combinations that would remove a clause from an earlier index column + * while adding one to a later column. Also, we know that all the + * eclass clauses for a particular column are redundant, so we should + * use only one of them. However, eclass clauses will always represent + * equality which is the strongest type of index constraint, so those + * are high-value and we should try every available combination when we + * have eclass clauses for more than one column. Furthermore, it's + * unlikely to be useful to combine an eclass clause with non-eclass + * clauses for the same index column. These considerations lead to the + * following heuristics: + * + * First, start with the restriction clauses, and add on all simple join + * clauses for column 1. If there are any such join clauses, generate + * paths with this collection of clauses. Then, if there are eclass + * clauses for column 1, generate paths with each one of them replacing + * any other clauses we have for column 1. + * + * Next, add on all simple join clauses for column 2. If there are any + * such join clauses, generate paths with this collection. If there are + * eclass clauses for columns 1 or 2, generate paths with each such clause + * replacing other clauses for its index column, including cases where we + * use restriction or simple join clauses for one column and an eclass + * clause for the other. + * + * Repeat for each additional index column. + */ + + /* Set up working set with just the restriction clauses */ + memcpy(&clauseset, rclauseset, sizeof(clauseset)); + /* Even if it's empty right now, it won't be by the time we use it */ + clauseset.nonempty = true; + + last_eclass_col = -1; + for (indexcol = 0; indexcol < index->ncolumns; indexcol++) + { + /* + * If we don't have either simple join clauses or eclass clauses for + * this column, no new paths can be created in this iteration. + */ + if (jclauseset->indexclauses[indexcol] == NIL && + eclauseset->indexclauses[indexcol] == NIL) + continue; + + /* Add any simple join clauses to the working set */ + clauseset.indexclauses[indexcol] = + list_concat(clauseset.indexclauses[indexcol], + jclauseset->indexclauses[indexcol]); + + /* Set recursion depth to reach last col with eclass clauses */ + if (eclauseset->indexclauses[indexcol] != NIL) + last_eclass_col = indexcol; + + /* Do we have eclass clauses for any column now under consideration? */ + if (last_eclass_col >= 0) + { + /* Yes, so recursively generate all eclass clause combinations */ + expand_eclass_clause_combinations(root, rel, index, + 0, last_eclass_col, + &clauseset, eclauseset, + bitindexpaths); + } + else + { + /* No, consider the newly-enlarged set of simple join clauses */ + get_index_paths(root, rel, index, &clauseset, bitindexpaths); + } + } +} + +/* + * expand_eclass_clause_combinations + * Generate all combinations of eclass join clauses for first N columns, + * and construct parameterized index paths for each combination. + * + * Workhorse for consider_index_join_clauses; see notes therein for rationale. + * It's convenient to use recursion to implement the enumeration, since we + * can have at most INDEX_MAX_KEYS recursion levels. + * + * 'rel', 'index', 'eclauseset', 'bitindexpaths' as above + * 'thiscol' is the current index column number/recursion level + * 'lastcol' is the last index column we should consider eclass clauses for + * 'clauseset' is the current collection of indexable clauses + */ +static void +expand_eclass_clause_combinations(PlannerInfo *root, RelOptInfo *rel, + IndexOptInfo *index, + int thiscol, int lastcol, + IndexClauseSet *clauseset, + IndexClauseSet *eclauseset, + List **bitindexpaths) +{ + List *save_clauses; + ListCell *lc; + + /* If past last eclass column, end the recursion and generate paths */ + if (thiscol > lastcol) + { + get_index_paths(root, rel, index, clauseset, bitindexpaths); + return; + } + + /* If no eclass clauses to consider for this column, just recurse */ + if (eclauseset->indexclauses[thiscol] == NIL) + { + Assert(thiscol < lastcol); + expand_eclass_clause_combinations(root, rel, index, + thiscol + 1, lastcol, + clauseset, eclauseset, + bitindexpaths); + return; + } + + /* We'll momentarily save and restore the list of non-eclass clauses */ + save_clauses = clauseset->indexclauses[thiscol]; + + /* If we have non-eclass clauses for this column, first try with those */ + if (save_clauses) + expand_eclass_clause_combinations(root, rel, index, + thiscol + 1, lastcol, + clauseset, eclauseset, + bitindexpaths); + + /* For each eclass clause alternative ... */ + foreach(lc, eclauseset->indexclauses[thiscol]) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + + /* Replace any existing clauses with the eclass clause */ + clauseset->indexclauses[thiscol] = list_make1(rinfo); + + /* Recurse to advance to next column */ + expand_eclass_clause_combinations(root, rel, index, + thiscol + 1, lastcol, + clauseset, eclauseset, + bitindexpaths); + } + + /* Restore previous list contents */ + clauseset->indexclauses[thiscol] = save_clauses; +} + + +/* + * get_index_paths + * Given an index and a set of index clauses for it, construct IndexPaths. + * + * Plain indexpaths are sent directly to add_path, while potential + * bitmap indexpaths are added to *bitindexpaths for later processing. + * + * This is a fairly simple frontend to build_index_paths(). Its reason for + * existence is mainly to handle ScalarArrayOpExpr quals properly. If the + * index AM supports them natively, we should just include them in simple + * index paths. If not, we should exclude them while building simple index + * paths, and then make a separate attempt to include them in bitmap paths. + */ +static void +get_index_paths(PlannerInfo *root, RelOptInfo *rel, + IndexOptInfo *index, IndexClauseSet *clauses, + List **bitindexpaths) +{ + List *indexpaths; + ListCell *lc; + + /* + * Build simple index paths using the clauses. Allow ScalarArrayOpExpr + * clauses only if the index AM supports them natively. + */ + indexpaths = build_index_paths(root, rel, + index, clauses, + index->predOK, + SAOP_PER_AM, ST_ANYSCAN); /* * Submit all the ones that can form plain IndexScan plans to add_path. - * (A plain IndexPath might represent either a plain IndexScan or an - * IndexOnlyScan, but for our purposes here the distinction does not + * (A plain IndexPath can represent either a plain IndexScan or an + * IndexOnlyScan, but for our purposes here that distinction does not * matter. However, some of the indexes might support only bitmap scans, - * and those we mustn't submit to add_path here.) Also, pick out the ones - * that might be useful as bitmap scans. For that, we must discard - * indexes that don't support bitmap scans, and we also are only - * interested in paths that have some selectivity; we should discard - * anything that was generated solely for ordering purposes. + * and those we mustn't submit to add_path here.) + * + * Also, pick out the ones that are usable as bitmap scans. For that, + * we must discard indexes that don't support bitmap scans, and we + * also are only interested in paths that have some selectivity; we + * should discard anything that was generated solely for ordering + * purposes. */ - bitindexpaths = NIL; - foreach(l, indexpaths) + foreach(lc, indexpaths) { - IndexPath *ipath = (IndexPath *) lfirst(l); + IndexPath *ipath = (IndexPath *) lfirst(lc); - if (ipath->indexinfo->amhasgettuple) + if (index->amhasgettuple) add_path(rel, (Path *) ipath); - if (ipath->indexinfo->amhasgetbitmap && + if (index->amhasgetbitmap && (ipath->path.pathkeys == NIL || ipath->indexselectivity < 1.0)) - bitindexpaths = lappend(bitindexpaths, ipath); + *bitindexpaths = lappend(*bitindexpaths, ipath); } /* - * Generate BitmapOrPaths for any suitable OR-clauses present in the - * restriction list. Add these to bitindexpaths. + * If the index doesn't handle ScalarArrayOpExpr clauses natively, + * check to see if there are any such clauses, and if so generate + * bitmap scan paths relying on executor-managed ScalarArrayOpExpr. */ - indexpaths = generate_bitmap_or_paths(root, rel, - rel->baserestrictinfo, NIL, - NULL); - bitindexpaths = list_concat(bitindexpaths, indexpaths); + if (!index->amsearcharray) + { + indexpaths = build_index_paths(root, rel, + index, clauses, + false, + SAOP_REQUIRE, ST_BITMAPSCAN); + *bitindexpaths = list_concat(*bitindexpaths, indexpaths); + } +} + +/* + * build_index_paths + * Given an index and a set of index clauses for it, construct zero + * or more IndexPaths. + * + * We return a list of paths because (1) this routine checks some cases + * that should cause us to not generate any IndexPath, and (2) in some + * cases we want to consider both a forward and a backward scan, so as + * to obtain both sort orders. Note that the paths are just returned + * to the caller and not immediately fed to add_path(). + * + * At top level, useful_predicate should be exactly the index's predOK flag + * (ie, true if it has a predicate that was proven from the restriction + * clauses). When working on an arm of an OR clause, useful_predicate + * should be true if the predicate required the current OR list to be proven. + * Note that this routine should never be called at all if the index has an + * unprovable predicate. + * + * saop_control indicates whether ScalarArrayOpExpr clauses can be used. + * When it's SAOP_REQUIRE, index paths are created only if we found at least + * one ScalarArrayOpExpr clause. + * + * scantype indicates whether we want to create plain indexscans, bitmap + * indexscans, or both. When it's ST_BITMAPSCAN, we will not consider + * index ordering while deciding if a Path is worth generating. + * + * 'rel' is the index's heap relation + * 'index' is the index for which we want to generate paths + * 'clauses' is the collection of indexable clauses (RestrictInfo nodes) + * 'useful_predicate' indicates whether the index has a useful predicate + * 'saop_control' indicates whether ScalarArrayOpExpr clauses can be used + * 'scantype' indicates whether we need plain or bitmap scan support + */ +static List * +build_index_paths(PlannerInfo *root, RelOptInfo *rel, + IndexOptInfo *index, IndexClauseSet *clauses, + bool useful_predicate, + SaOpControl saop_control, ScanTypeControl scantype) +{ + List *result = NIL; + IndexPath *ipath; + List *index_clauses; + List *clause_columns; + Relids outer_relids; + double loop_count; + List *orderbyclauses; + List *orderbyclausecols; + List *index_pathkeys; + List *useful_pathkeys; + bool found_clause; + bool pathkeys_possibly_useful; + bool index_is_ordered; + bool index_only_scan; + int indexcol; /* - * Likewise, generate paths using executor-managed ScalarArrayOpExpr - * clauses; these can't be simple indexscans but they can be used in - * bitmap scans. + * Check that index supports the desired scan type(s) */ - indexpaths = find_saop_paths(root, rel, - rel->baserestrictinfo, NIL, - true, NULL); - bitindexpaths = list_concat(bitindexpaths, indexpaths); + switch (scantype) + { + case ST_INDEXSCAN: + if (!index->amhasgettuple) + return NIL; + break; + case ST_BITMAPSCAN: + if (!index->amhasgetbitmap) + return NIL; + break; + case ST_ANYSCAN: + /* either or both are OK */ + break; + } /* - * If we found anything usable, generate a BitmapHeapPath for the most - * promising combination of bitmap index paths. + * 1. Collect the index clauses into a single list. + * + * We build a list of RestrictInfo nodes for clauses to be used with + * this index, along with an integer list of the index column numbers + * (zero based) that each clause should be used with. The clauses are + * ordered by index key, so that the column numbers form a nondecreasing + * sequence. (This order is depended on by btree and possibly other + * places.) The lists can be empty, if the index AM allows that. + * + * found_clause is set true only if there's at least one index clause; + * and if saop_control is SAOP_REQUIRE, it has to be a ScalarArrayOpExpr + * clause. + * + * We also build a Relids set showing which outer rels are required + * by the selected clauses. */ - if (bitindexpaths != NIL) + index_clauses = NIL; + clause_columns = NIL; + found_clause = false; + outer_relids = NULL; + for (indexcol = 0; indexcol < index->ncolumns; indexcol++) { - Path *bitmapqual; - BitmapHeapPath *bpath; + ListCell *lc; - bitmapqual = choose_bitmap_and(root, rel, bitindexpaths, NULL); - bpath = create_bitmap_heap_path(root, rel, bitmapqual, NULL); - add_path(rel, (Path *) bpath); + foreach(lc, clauses->indexclauses[indexcol]) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + + if (IsA(rinfo->clause, ScalarArrayOpExpr)) + { + /* Ignore if not supported by index */ + if (saop_control == SAOP_PER_AM && !index->amsearcharray) + continue; + found_clause = true; + } + else + { + if (saop_control != SAOP_REQUIRE) + found_clause = true; + } + index_clauses = lappend(index_clauses, rinfo); + clause_columns = lappend_int(clause_columns, indexcol); + outer_relids = bms_add_members(outer_relids, + rinfo->clause_relids); + } + + /* + * If no clauses match the first index column, check for amoptionalkey + * restriction. We can't generate a scan over an index with + * amoptionalkey = false unless there's at least one index clause. + * (When working on columns after the first, this test cannot fail. + * It is always okay for columns after the first to not have any + * clauses.) + */ + if (index_clauses == NIL && !index->amoptionalkey) + return NIL; + } + + /* We do not want the index's rel itself listed in outer_relids */ + outer_relids = bms_del_member(outer_relids, rel->relid); + /* Enforce convention that outer_relids is exactly NULL if empty */ + if (bms_is_empty(outer_relids)) + outer_relids = NULL; + + /* Compute loop_count for cost estimation purposes */ + loop_count = get_loop_count(root, outer_relids); + + /* + * 2. Compute pathkeys describing index's ordering, if any, then see how + * many of them are actually useful for this query. This is not relevant + * if we are only trying to build bitmap indexscans. + */ + pathkeys_possibly_useful = (scantype != ST_BITMAPSCAN && + has_useful_pathkeys(root, rel)); + index_is_ordered = (index->sortopfamily != NULL); + if (index_is_ordered && pathkeys_possibly_useful) + { + index_pathkeys = build_index_pathkeys(root, index, + ForwardScanDirection); + useful_pathkeys = truncate_useless_pathkeys(root, rel, + index_pathkeys); + orderbyclauses = NIL; + orderbyclausecols = NIL; + } + else if (index->amcanorderbyop && pathkeys_possibly_useful) + { + /* see if we can generate ordering operators for query_pathkeys */ + match_pathkeys_to_index(index, root->query_pathkeys, + &orderbyclauses, + &orderbyclausecols); + if (orderbyclauses) + useful_pathkeys = root->query_pathkeys; + else + useful_pathkeys = NIL; + } + else + { + useful_pathkeys = NIL; + orderbyclauses = NIL; + orderbyclausecols = NIL; + } + + /* + * 3. Check if an index-only scan is possible. If we're not building + * plain indexscans, this isn't relevant since bitmap scans don't support + * index data retrieval anyway. + */ + index_only_scan = (scantype != ST_BITMAPSCAN && + check_index_only(rel, index)); + + /* + * 4. Generate an indexscan path if there are relevant restriction clauses + * in the current clauses, OR the index ordering is potentially useful for + * later merging or final output ordering, OR the index has a useful + * predicate, OR an index-only scan is possible. + */ + if (found_clause || useful_pathkeys != NIL || useful_predicate || + index_only_scan) + { + ipath = create_index_path(root, index, + index_clauses, + clause_columns, + orderbyclauses, + orderbyclausecols, + useful_pathkeys, + index_is_ordered ? + ForwardScanDirection : + NoMovementScanDirection, + index_only_scan, + outer_relids, + loop_count); + result = lappend(result, ipath); + } + + /* + * 5. If the index is ordered, a backwards scan might be interesting. + */ + if (index_is_ordered && pathkeys_possibly_useful) + { + index_pathkeys = build_index_pathkeys(root, index, + BackwardScanDirection); + useful_pathkeys = truncate_useless_pathkeys(root, rel, + index_pathkeys); + if (useful_pathkeys != NIL) + { + ipath = create_index_path(root, index, + index_clauses, + clause_columns, + NIL, + NIL, + useful_pathkeys, + BackwardScanDirection, + index_only_scan, + outer_relids, + loop_count); + result = lappend(result, ipath); + } } -} + return result; +} -/*---------- - * find_usable_indexes - * Given a list of restriction clauses, find all the potentially usable - * indexes for the given relation, and return a list of IndexPaths. +/* + * build_paths_for_OR + * Given a list of restriction clauses from one arm of an OR clause, + * construct all matching IndexPaths for the relation. + * + * Here we must scan all indexes of the relation, since a bitmap OR tree + * can use multiple indexes. * * The caller actually supplies two lists of restriction clauses: some - * "current" ones and some "outer" ones. Both lists can be used freely + * "current" ones and some "other" ones. Both lists can be used freely * to match keys of the index, but an index must use at least one of the * "current" clauses to be considered usable. The motivation for this is * examples like @@ -281,85 +836,34 @@ create_index_paths(PlannerInfo *root, RelOptInfo *rel) * When dealing with a partial index, a match of the index predicate to * one of the "current" clauses also makes the index usable. * - * If istoplevel is true (indicating we are considering the top level of a - * rel's restriction clauses), we will include indexes in the result that - * have an interesting sort order, even if they have no matching restriction - * clauses. - * * 'rel' is the relation for which we want to generate index paths * 'clauses' is the current list of clauses (RestrictInfo nodes) - * 'outer_clauses' is the list of additional upper-level clauses - * 'istoplevel' is true if clauses are the rel's top-level restriction list - * (outer_clauses must be NIL when this is true) - * 'outer_rel' is the outer side of the join if forming an inner indexscan - * (so some of the given clauses are join clauses); NULL if not - * 'saop_control' indicates whether ScalarArrayOpExpr clauses can be used - * 'scantype' indicates whether we need plain or bitmap scan support - * - * Note: check_partial_indexes() must have been run previously. - *---------- + * 'other_clauses' is the list of additional upper-level clauses */ static List * -find_usable_indexes(PlannerInfo *root, RelOptInfo *rel, - List *clauses, List *outer_clauses, - bool istoplevel, RelOptInfo *outer_rel, - SaOpControl saop_control, ScanTypeControl scantype) +build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel, + List *clauses, List *other_clauses) { - Relids outer_relids = outer_rel ? outer_rel->relids : NULL; - bool possibly_useful_pathkeys = has_useful_pathkeys(root, rel); List *result = NIL; List *all_clauses = NIL; /* not computed till needed */ - ListCell *ilist; + ListCell *lc; - foreach(ilist, rel->indexlist) + foreach(lc, rel->indexlist) { - IndexOptInfo *index = (IndexOptInfo *) lfirst(ilist); - IndexPath *ipath; - List *restrictclauses; - List *restrictclausecols; - List *orderbyclauses; - List *orderbyclausecols; - List *index_pathkeys; - List *useful_pathkeys; + IndexOptInfo *index = (IndexOptInfo *) lfirst(lc); + IndexClauseSet clauseset; + List *indexpaths; bool useful_predicate; - bool found_clause; - bool index_is_ordered; - bool index_only_scan; - /* - * Check that index supports the desired scan type(s) - */ - switch (scantype) - { - case ST_INDEXSCAN: - if (!index->amhasgettuple) - continue; - break; - case ST_BITMAPSCAN: - if (!index->amhasgetbitmap) - continue; - break; - case ST_ANYSCAN: - /* either or both are OK */ - break; - } - - /* - * If we're doing find_saop_paths(), we can skip indexes that support - * ScalarArrayOpExpr natively. We already generated all the potential - * indexpaths for them, so no need to do anything more. - */ - if (saop_control == SAOP_REQUIRE && index->amsearcharray) + /* Ignore index if it doesn't support bitmap scans */ + if (!index->amhasgetbitmap) continue; /* * Ignore partial indexes that do not match the query. If a partial - * index is marked predOK then we know it's OK; otherwise, if we are - * at top level we know it's not OK (since predOK is exactly whether - * its predicate could be proven from the toplevel clauses). - * Otherwise, we have to test whether the added clauses are sufficient - * to imply the predicate. If so, we could use the index in the - * current context. + * index is marked predOK then we know it's OK. Otherwise, we have + * to test whether the added clauses are sufficient to imply the + * predicate. If so, we can use the index in the current context. * * We set useful_predicate to true iff the predicate was proven using * the current set of clauses. This is needed to prevent matching a @@ -372,218 +876,87 @@ find_usable_indexes(PlannerInfo *root, RelOptInfo *rel, { if (index->predOK) { - if (istoplevel) - { - /* we know predicate was proven from these clauses */ - useful_predicate = true; - } + /* Usable, but don't set useful_predicate */ } else { - if (istoplevel) - continue; /* no point in trying to prove it */ - /* Form all_clauses if not done already */ if (all_clauses == NIL) all_clauses = list_concat(list_copy(clauses), - outer_clauses); + other_clauses); if (!predicate_implied_by(index->indpred, all_clauses)) continue; /* can't use it at all */ - if (!predicate_implied_by(index->indpred, outer_clauses)) + if (!predicate_implied_by(index->indpred, other_clauses)) useful_predicate = true; } } /* - * 1. Match the index against the available restriction clauses. - * found_clause is set true only if at least one of the current - * clauses was used (and, if saop_control is SAOP_REQUIRE, it has to - * have been a ScalarArrayOpExpr clause). + * Identify the restriction clauses that can match the index. */ - match_clauses_to_index(index, - clauses, - outer_clauses, - outer_relids, - saop_control, - &restrictclauses, - &restrictclausecols, - &found_clause); + MemSet(&clauseset, 0, sizeof(clauseset)); + match_clauses_to_index(index, clauses, &clauseset); /* - * Not all index AMs support scans with no restriction clauses. We - * can't generate a scan over an index with amoptionalkey = false - * unless there's at least one restriction clause. + * If no matches so far, and the index predicate isn't useful, + * we don't want it. */ - if (restrictclauses == NIL && !index->amoptionalkey) + if (!clauseset.nonempty && !useful_predicate) continue; /* - * 2. Compute pathkeys describing index's ordering, if any, then see - * how many of them are actually useful for this query. This is not - * relevant unless we are at top level. + * Add "other" restriction clauses to the clauseset. */ - index_is_ordered = (index->sortopfamily != NULL); - if (index_is_ordered && possibly_useful_pathkeys && - istoplevel && outer_rel == NULL) - { - index_pathkeys = build_index_pathkeys(root, index, - ForwardScanDirection); - useful_pathkeys = truncate_useless_pathkeys(root, rel, - index_pathkeys); - orderbyclauses = NIL; - orderbyclausecols = NIL; - } - else if (index->amcanorderbyop && possibly_useful_pathkeys && - istoplevel && outer_rel == NULL && scantype != ST_BITMAPSCAN) - { - /* see if we can generate ordering operators for query_pathkeys */ - match_pathkeys_to_index(index, root->query_pathkeys, - &orderbyclauses, - &orderbyclausecols); - if (orderbyclauses) - useful_pathkeys = root->query_pathkeys; - else - useful_pathkeys = NIL; - } - else - { - useful_pathkeys = NIL; - orderbyclauses = NIL; - orderbyclausecols = NIL; - } - - /* - * 3. Check if an index-only scan is possible. - */ - index_only_scan = check_index_only(rel, index); - - /* - * 4. Generate an indexscan path if there are relevant restriction - * clauses in the current clauses, OR the index ordering is - * potentially useful for later merging or final output ordering, OR - * the index has a predicate that was proven by the current clauses, - * OR an index-only scan is possible. - */ - if (found_clause || useful_pathkeys != NIL || useful_predicate || - index_only_scan) - { - ipath = create_index_path(root, index, - restrictclauses, - restrictclausecols, - orderbyclauses, - orderbyclausecols, - useful_pathkeys, - index_is_ordered ? - ForwardScanDirection : - NoMovementScanDirection, - index_only_scan, - outer_rel); - result = lappend(result, ipath); - } + match_clauses_to_index(index, other_clauses, &clauseset); /* - * 5. If the index is ordered, a backwards scan might be interesting. - * Again, this is only interesting at top level. + * Construct paths if possible. */ - if (index_is_ordered && possibly_useful_pathkeys && - istoplevel && outer_rel == NULL) - { - index_pathkeys = build_index_pathkeys(root, index, - BackwardScanDirection); - useful_pathkeys = truncate_useless_pathkeys(root, rel, - index_pathkeys); - if (useful_pathkeys != NIL) - { - ipath = create_index_path(root, index, - restrictclauses, - restrictclausecols, - NIL, - NIL, - useful_pathkeys, - BackwardScanDirection, - index_only_scan, - outer_rel); - result = lappend(result, ipath); - } - } + indexpaths = build_index_paths(root, rel, + index, &clauseset, + useful_predicate, + SAOP_ALLOW, ST_BITMAPSCAN); + result = list_concat(result, indexpaths); } return result; } - -/* - * find_saop_paths - * Find all the potential indexpaths that make use of executor-managed - * ScalarArrayOpExpr clauses. The executor only supports these in bitmap - * scans, not plain indexscans, so we need to segregate them from the - * normal case. Otherwise, same API as find_usable_indexes(). - * Returns a list of IndexPaths. - */ -static List * -find_saop_paths(PlannerInfo *root, RelOptInfo *rel, - List *clauses, List *outer_clauses, - bool istoplevel, RelOptInfo *outer_rel) -{ - bool have_saop = false; - ListCell *l; - - /* - * Since find_usable_indexes is relatively expensive, don't bother to run - * it unless there are some top-level ScalarArrayOpExpr clauses. - */ - foreach(l, clauses) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(l); - - Assert(IsA(rinfo, RestrictInfo)); - if (IsA(rinfo->clause, ScalarArrayOpExpr)) - { - have_saop = true; - break; - } - } - if (!have_saop) - return NIL; - - return find_usable_indexes(root, rel, - clauses, outer_clauses, - istoplevel, outer_rel, - SAOP_REQUIRE, ST_BITMAPSCAN); -} - - /* * generate_bitmap_or_paths * Look through the list of clauses to find OR clauses, and generate * a BitmapOrPath for each one we can handle that way. Return a list * of the generated BitmapOrPaths. * - * outer_clauses is a list of additional clauses that can be assumed true + * other_clauses is a list of additional clauses that can be assumed true * for the purpose of generating indexquals, but are not to be searched for - * ORs. (See find_usable_indexes() for motivation.) outer_rel is the outer - * side when we are considering a nestloop inner indexpath. + * ORs. (See build_paths_for_OR() for motivation.) + * + * If restriction_only is true, ignore OR elements that are join clauses. + * When using this feature it is caller's responsibility that neither clauses + * nor other_clauses contain any join clauses that are not ORs, as we do not + * re-filter those lists. */ List * generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel, - List *clauses, List *outer_clauses, - RelOptInfo *outer_rel) + List *clauses, List *other_clauses, + bool restriction_only) { List *result = NIL; List *all_clauses; - ListCell *l; + ListCell *lc; /* - * We can use both the current and outer clauses as context for - * find_usable_indexes + * We can use both the current and other clauses as context for + * build_paths_for_OR; no need to remove ORs from the lists. */ - all_clauses = list_concat(list_copy(clauses), outer_clauses); + all_clauses = list_concat(list_copy(clauses), other_clauses); - foreach(l, clauses) + foreach(lc, clauses) { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(l); + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); List *pathlist; Path *bitmapqual; ListCell *j; @@ -608,31 +981,34 @@ generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel, { List *andargs = ((BoolExpr *) orarg)->args; - indlist = find_usable_indexes(root, rel, - andargs, - all_clauses, - false, - outer_rel, - SAOP_ALLOW, - ST_BITMAPSCAN); + if (restriction_only) + andargs = drop_indexable_join_clauses(rel, andargs); + + indlist = build_paths_for_OR(root, rel, + andargs, + all_clauses); + /* Recurse in case there are sub-ORs */ indlist = list_concat(indlist, generate_bitmap_or_paths(root, rel, andargs, all_clauses, - outer_rel)); + restriction_only)); } else { + List *orargs; + Assert(IsA(orarg, RestrictInfo)); Assert(!restriction_is_or_clause((RestrictInfo *) orarg)); - indlist = find_usable_indexes(root, rel, - list_make1(orarg), - all_clauses, - false, - outer_rel, - SAOP_ALLOW, - ST_BITMAPSCAN); + orargs = list_make1(orarg); + + if (restriction_only) + orargs = drop_indexable_join_clauses(rel, orargs); + + indlist = build_paths_for_OR(root, rel, + orargs, + all_clauses); } /* @@ -649,7 +1025,7 @@ generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel, * OK, pick the most promising AND combination, and add it to * pathlist. */ - bitmapqual = choose_bitmap_and(root, rel, indlist, outer_rel); + bitmapqual = choose_bitmap_and(root, rel, indlist); pathlist = lappend(pathlist, bitmapqual); } @@ -667,6 +1043,34 @@ generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel, return result; } +/* + * drop_indexable_join_clauses + * Remove any indexable join clauses from the list. + * + * This is a helper for generate_bitmap_or_paths(). We leave OR clauses + * in the list whether they are joins or not, since we might be able to + * extract a restriction item from an OR list. It's safe to leave such + * clauses in the list because match_clauses_to_index() will ignore them, + * so there's no harm in passing such clauses to build_paths_for_OR(). + */ +static List * +drop_indexable_join_clauses(RelOptInfo *rel, List *clauses) +{ + List *result = NIL; + ListCell *lc; + + foreach(lc, clauses) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + + Assert(IsA(rinfo, RestrictInfo)); + if (restriction_is_or_clause(rinfo) || + bms_is_subset(rinfo->clause_relids, rel->relids)) + result = lappend(result, rinfo); + } + return result; +} + /* * choose_bitmap_and @@ -680,8 +1084,7 @@ generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel, * combining multiple inputs. */ static Path * -choose_bitmap_and(PlannerInfo *root, RelOptInfo *rel, - List *paths, RelOptInfo *outer_rel) +choose_bitmap_and(PlannerInfo *root, RelOptInfo *rel, List *paths) { int npaths = list_length(paths); PathClauseUsage **pathinfoarray; @@ -729,7 +1132,7 @@ choose_bitmap_and(PlannerInfo *root, RelOptInfo *rel, * reduces the total cost. Perhaps someday that code will be smarter and * we can remove this limitation. (But note that this also defends * against flat-out duplicate input paths, which can happen because - * best_inner_indexscan will find the same OR join clauses that + * match_join_clauses_to_index will find the same OR join clauses that * create_or_index_quals has pulled OR restriction clauses out of.) * * For the same reason, we reject AND combinations in which an index @@ -807,7 +1210,7 @@ choose_bitmap_and(PlannerInfo *root, RelOptInfo *rel, pathinfo = pathinfoarray[i]; paths = list_make1(pathinfo->path); - costsofar = bitmap_scan_cost_est(root, rel, pathinfo->path, outer_rel); + costsofar = bitmap_scan_cost_est(root, rel, pathinfo->path); qualsofar = list_concat(list_copy(pathinfo->quals), list_copy(pathinfo->preds)); clauseidsofar = bms_copy(pathinfo->clauseids); @@ -841,7 +1244,7 @@ choose_bitmap_and(PlannerInfo *root, RelOptInfo *rel, } /* tentatively add new path to paths, so we can estimate cost */ paths = lappend(paths, pathinfo->path); - newcost = bitmap_and_cost_est(root, rel, paths, outer_rel); + newcost = bitmap_and_cost_est(root, rel, paths); if (newcost < costsofar) { /* keep new path in paths, update subsidiary variables */ @@ -913,14 +1316,23 @@ path_usage_comparator(const void *a, const void *b) * index path (no BitmapAnd, at least not at this level). */ static Cost -bitmap_scan_cost_est(PlannerInfo *root, RelOptInfo *rel, - Path *ipath, RelOptInfo *outer_rel) +bitmap_scan_cost_est(PlannerInfo *root, RelOptInfo *rel, Path *ipath) { - Path bpath; + BitmapHeapPath bpath; + + /* Set up a dummy BitmapHeapPath */ + bpath.path.type = T_BitmapHeapPath; + bpath.path.pathtype = T_BitmapHeapScan; + bpath.path.parent = rel; + bpath.path.pathkeys = NIL; + bpath.path.required_outer = ipath->required_outer; + bpath.path.param_clauses = ipath->param_clauses; + bpath.bitmapqual = ipath; - cost_bitmap_heap_scan(&bpath, root, rel, ipath, outer_rel); + cost_bitmap_heap_scan((Path *) &bpath, root, rel, ipath, + get_loop_count(root, bpath.path.required_outer)); - return bpath.total_cost; + return bpath.path.total_cost; } /* @@ -928,22 +1340,32 @@ bitmap_scan_cost_est(PlannerInfo *root, RelOptInfo *rel, * inputs. */ static Cost -bitmap_and_cost_est(PlannerInfo *root, RelOptInfo *rel, - List *paths, RelOptInfo *outer_rel) +bitmap_and_cost_est(PlannerInfo *root, RelOptInfo *rel, List *paths) { - BitmapAndPath apath; - Path bpath; + BitmapAndPath *apath; + BitmapHeapPath bpath; + + /* + * Create a temporary BitmapAndPath. (Because it needs realistic + * required_outer and param_clauses values, making a dummy one would + * take more code than it's worth.) + */ + apath = create_bitmap_and_path(root, rel, paths); - /* Set up a dummy BitmapAndPath */ - apath.path.type = T_BitmapAndPath; - apath.path.parent = rel; - apath.bitmapquals = paths; - cost_bitmap_and_node(&apath, root); + /* Set up a dummy BitmapHeapPath */ + bpath.path.type = T_BitmapHeapPath; + bpath.path.pathtype = T_BitmapHeapScan; + bpath.path.parent = rel; + bpath.path.pathkeys = NIL; + bpath.path.required_outer = apath->path.required_outer; + bpath.path.param_clauses = apath->path.param_clauses; + bpath.bitmapqual = (Path *) apath; /* Now we can do cost_bitmap_heap_scan */ - cost_bitmap_heap_scan(&bpath, root, rel, (Path *) &apath, outer_rel); + cost_bitmap_heap_scan((Path *) &bpath, root, rel, (Path *) apath, + get_loop_count(root, bpath.path.required_outer)); - return bpath.total_cost; + return bpath.path.total_cost; } @@ -1150,128 +1572,253 @@ check_index_only(RelOptInfo *rel, IndexOptInfo *index) return result; } +/* + * get_loop_count + * Choose the loop count estimate to use for costing a parameterized path + * with the given set of outer relids. + * + * Since we produce parameterized paths before we've begun to generate join + * relations, it's impossible to predict exactly how many times a parameterized + * path will be iterated; we don't know the size of the relation that will be + * on the outside of the nestloop. However, we should try to account for + * multiple iterations somehow in costing the path. The heuristic embodied + * here is to use the rowcount of the smallest other base relation needed in + * the join clauses used by the path. (We could alternatively consider the + * largest one, but that seems too optimistic.) This is of course the right + * answer for single-other-relation cases, and it seems like a reasonable + * zero-order approximation for multiway-join cases. + * + * Note: for this to work, allpaths.c must establish all baserel size + * estimates before it begins to compute paths, or at least before it + * calls create_index_paths(). + */ +static double +get_loop_count(PlannerInfo *root, Relids outer_relids) +{ + double result = 1.0; + + /* For a non-parameterized path, just return 1.0 quickly */ + if (outer_relids != NULL) + { + int relid; + + /* Need a working copy since bms_first_member is destructive */ + outer_relids = bms_copy(outer_relids); + while ((relid = bms_first_member(outer_relids)) >= 0) + { + RelOptInfo *outer_rel; + + /* Paranoia: ignore bogus relid indexes */ + if (relid >= root->simple_rel_array_size) + continue; + outer_rel = root->simple_rel_array[relid]; + if (outer_rel == NULL) + continue; + Assert(outer_rel->relid == relid); /* sanity check on array */ + + /* Other relation could be proven empty, if so ignore */ + if (IS_DUMMY_REL(outer_rel)) + continue; + + /* Otherwise, rel's rows estimate should be valid by now */ + Assert(outer_rel->rows > 0); + + /* Remember smallest row count estimate among the outer rels */ + if (result == 1.0 || result > outer_rel->rows) + result = outer_rel->rows; + } + bms_free(outer_relids); + } + return result; +} + + +/**************************************************************************** + * ---- ROUTINES TO CHECK QUERY CLAUSES ---- + ****************************************************************************/ + +/* + * match_restriction_clauses_to_index + * Identify restriction clauses for the rel that match the index. + * Matching clauses are added to *clauseset. + */ +static void +match_restriction_clauses_to_index(RelOptInfo *rel, IndexOptInfo *index, + IndexClauseSet *clauseset) +{ + match_clauses_to_index(index, rel->baserestrictinfo, clauseset); +} + +/* + * match_join_clauses_to_index + * Identify join clauses for the rel that match the index. + * Matching clauses are added to *clauseset. + * Also, add any potentially usable join OR clauses to *joinorclauses. + */ +static void +match_join_clauses_to_index(PlannerInfo *root, + RelOptInfo *rel, IndexOptInfo *index, + IndexClauseSet *clauseset, + List **joinorclauses) +{ + Relids inner_baserels; + ListCell *lc; + + /* + * There is no value in considering join clauses for outer joins in which + * the indexed relation is on the outside, since there'd be no way to + * perform such a join with a parameterized nestloop. So first, identify + * all baserels that are on the inside of such joins. + */ + inner_baserels = NULL; + foreach(lc, root->join_info_list) + { + SpecialJoinInfo *sjinfo = (SpecialJoinInfo *) lfirst(lc); + + if (bms_overlap(rel->relids, sjinfo->min_lefthand)) + inner_baserels = bms_add_members(inner_baserels, + sjinfo->min_righthand); + /* full joins constrain both sides symmetrically */ + if (sjinfo->jointype == JOIN_FULL && + bms_overlap(rel->relids, sjinfo->min_righthand)) + inner_baserels = bms_add_members(inner_baserels, + sjinfo->min_lefthand); + } + + /* Now scan the rel's join clauses */ + foreach(lc, rel->joininfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + + /* Ignore if it mentions anything from wrong side of an outer join */ + if (bms_overlap(rinfo->clause_relids, inner_baserels)) + continue; + + /* + * Note that we ignore required_relids; that's okay because we are + * intentionally ignoring the normal rules for placing evaluation of + * join clauses. The whole point here is to evaluate join clauses + * below their join, even if they would normally be delayed by + * outer join rules. + * + * Instead of considering required_relids, we ignore clauses for which + * any referenced rel is in nullable_relids; that means there's an + * outer join below the clause and so it can't be checked at the + * relation scan level. + * + * Note: unlike create_or_index_quals(), we can accept clauses that + * are marked !is_pushed_down (ie they are themselves outer-join + * clauses). This is OK because any path generated with these clauses + * could only be used in the inside of a nestloop join, which will be + * the nullable side. + */ + if (bms_overlap(rinfo->clause_relids, rinfo->nullable_relids)) + continue; + + /* Potentially usable, so see if it matches the index or is an OR */ + if (restriction_is_or_clause(rinfo)) + *joinorclauses = lappend(*joinorclauses, rinfo); + else + match_clause_to_index(index, rinfo, clauseset); + } +} + +/* + * match_eclass_clauses_to_index + * Identify EquivalenceClass join clauses for the rel that match the index. + * Matching clauses are added to *clauseset. + */ +static void +match_eclass_clauses_to_index(PlannerInfo *root, IndexOptInfo *index, + IndexClauseSet *clauseset) +{ + int indexcol; + + /* No work if rel is not in any such ECs */ + if (!index->rel->has_eclass_joins) + return; + + for (indexcol = 0; indexcol < index->ncolumns; indexcol++) + { + List *clauses; -/**************************************************************************** - * ---- ROUTINES TO CHECK RESTRICTIONS ---- - ****************************************************************************/ + clauses = generate_implied_equalities_for_indexcol(root, + index, + indexcol); + /* + * We have to check whether the results actually do match the index, + * since for non-btree indexes the EC's equality operators might not + * be in the index opclass (cf eclass_member_matches_indexcol). + */ + match_clauses_to_index(index, clauses, clauseset); + } +} /* * match_clauses_to_index - * Find restriction clauses that can be used with an index. - * - * Returns a list of RestrictInfo nodes for clauses that can be used with - * this index, along with an integer list of the index column numbers - * (zero based) that each clause would be used with. The clauses are - * ordered by index key, so that the column numbers form a nondecreasing - * sequence. (This order is depended on by btree and possibly other places.) - * NIL lists are returned if there are no matching clauses. - * - * We can use clauses from either the current clauses or outer_clauses lists, - * but *found_clause is set TRUE only if we used at least one clause from - * the "current clauses" list. See find_usable_indexes() for motivation. - * - * outer_relids determines what Vars will be allowed on the other side - * of a possible index qual; see match_clause_to_indexcol(). - * - * 'saop_control' indicates whether ScalarArrayOpExpr clauses can be used. - * When it's SAOP_REQUIRE, *found_clause is set TRUE only if we used at least - * one ScalarArrayOpExpr from the current clauses list. - * - * If the index has amoptionalkey = false, we give up and return NIL when - * there are no restriction clauses matching the first index key. Otherwise, - * we return NIL only if there are no restriction clauses matching any index - * key. There could be unused index keys after the first one in any case. - * - * Note: in some circumstances we may find the same RestrictInfos coming - * from multiple places. Defend against redundant outputs by refusing to - * match an already-used clause (pointer equality should be a good enough - * check for this). This also keeps us from matching the same clause to - * multiple columns of a badly-defined index, which is unlikely to be helpful - * and is likely to give us an inflated idea of the index's selectivity. + * Perform match_clause_to_index() for each clause in a list. + * Matching clauses are added to *clauseset. */ static void match_clauses_to_index(IndexOptInfo *index, - List *clauses, List *outer_clauses, - Relids outer_relids, - SaOpControl saop_control, - List **index_clauses_p, - List **clause_columns_p, - bool *found_clause) + List *clauses, + IndexClauseSet *clauseset) { - List *index_clauses = NIL; - List *clause_columns = NIL; - int indexcol; - - *index_clauses_p = NIL; /* set default results */ - *clause_columns_p = NIL; - *found_clause = false; - - if (clauses == NIL && outer_clauses == NIL) - return; /* cannot succeed */ + ListCell *lc; - for (indexcol = 0; indexcol < index->ncolumns; indexcol++) + foreach(lc, clauses) { - ListCell *l; + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); - /* check the current clauses */ - foreach(l, clauses) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(l); + Assert(IsA(rinfo, RestrictInfo)); + match_clause_to_index(index, rinfo, clauseset); + } +} - Assert(IsA(rinfo, RestrictInfo)); - if (list_member_ptr(index_clauses, rinfo)) - continue; - if (match_clause_to_indexcol(index, - indexcol, - rinfo, - outer_relids, - saop_control)) - { - index_clauses = lappend(index_clauses, rinfo); - clause_columns = lappend_int(clause_columns, indexcol); - if (saop_control != SAOP_REQUIRE || - IsA(rinfo->clause, ScalarArrayOpExpr)) - *found_clause = true; - } - } +/* + * match_clause_to_index + * Test whether a qual clause can be used with an index. + * + * If the clause is usable, add it to the appropriate list in *clauseset. + * *clauseset must be initialized to zeroes before first call. + * + * Note: in some circumstances we may find the same RestrictInfos coming from + * multiple places. Defend against redundant outputs by refusing to add a + * clause twice (pointer equality should be a good enough check for this). + * + * Note: it's possible that a badly-defined index could have multiple matching + * columns. We always select the first match if so; this avoids scenarios + * wherein we get an inflated idea of the index's selectivity by using the + * same clause multiple times with different index columns. + */ +static void +match_clause_to_index(IndexOptInfo *index, + RestrictInfo *rinfo, + IndexClauseSet *clauseset) +{ + int indexcol; - /* check the outer clauses */ - foreach(l, outer_clauses) + for (indexcol = 0; indexcol < index->ncolumns; indexcol++) + { + if (match_clause_to_indexcol(index, + indexcol, + rinfo)) { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(l); - - Assert(IsA(rinfo, RestrictInfo)); - if (list_member_ptr(index_clauses, rinfo)) - continue; - if (match_clause_to_indexcol(index, - indexcol, - rinfo, - outer_relids, - saop_control)) - { - index_clauses = lappend(index_clauses, rinfo); - clause_columns = lappend_int(clause_columns, indexcol); - } - } - - /* - * If no clauses match this key, check for amoptionalkey restriction. - */ - if (index_clauses == NIL && !index->amoptionalkey) + clauseset->indexclauses[indexcol] = + list_append_unique_ptr(clauseset->indexclauses[indexcol], + rinfo); + clauseset->nonempty = true; return; + } } - - *index_clauses_p = index_clauses; - *clause_columns_p = clause_columns; } - /* * match_clause_to_indexcol() * Determines whether a restriction clause matches a column of an index. * - * To match a normal index, the clause: + * To match an index normally, the clause: * * (1) must be in the form (indexkey op const) or (const op indexkey); * and @@ -1281,18 +1828,17 @@ match_clauses_to_index(IndexOptInfo *index, * and * (3) must match the collation of the index, if collation is relevant. * - * Our definition of "const" is pretty liberal: we allow Vars belonging - * to the caller-specified outer_relids relations (which had better not - * include the relation whose index is being tested). outer_relids should - * be NULL when checking simple restriction clauses, and the outer side - * of the join when building a join inner scan. Other than that, the - * only thing we don't like is volatile functions. + * Our definition of "const" is exceedingly liberal: we allow anything that + * doesn't involve a volatile function or a Var of the index's relation. + * In particular, Vars belonging to other relations of the query are + * accepted here, since a clause of that form can be used in a + * parameterized indexscan. It's the responsibility of higher code levels + * to manage restriction and join clauses appropriately. * - * Note: in most cases we already know that the clause as a whole uses - * vars from the interesting set of relations. The reason for the - * outer_relids test is to reject clauses like (a.f1 OP (b.f2 OP a.f3)); - * that's not processable by an indexscan nestloop join on A, whereas - * (a.f1 OP (b.f2 OP c.f3)) is. + * Note: we do need to check for Vars of the index's relation on the + * "const" side of the clause, since clauses like (a.f1 OP (b.f2 OP a.f3)) + * are not processable by a parameterized indexscan on a.f1, whereas + * something like (a.f1 OP (b.f2 OP c.f3)) is. * * Presently, the executor can only deal with indexquals that have the * indexkey on the left, so we can only use clauses that have the indexkey @@ -1316,10 +1862,7 @@ match_clauses_to_index(IndexOptInfo *index, * adjust_rowcompare_for_index(). * * It is also possible to match ScalarArrayOpExpr clauses to indexes, when - * the clause is of the form "indexkey op ANY (arrayconst)". Since not - * all indexes handle these natively, and the executor implements them - * only in the context of bitmap index scans, our caller specifies whether - * to allow these or not. + * the clause is of the form "indexkey op ANY (arrayconst)". * * For boolean indexes, it is also possible to match the clause directly * to the indexkey; or perhaps the clause is (NOT indexkey). @@ -1327,8 +1870,6 @@ match_clauses_to_index(IndexOptInfo *index, * 'index' is the index of interest. * 'indexcol' is a column number of 'index' (counting from 0). * 'rinfo' is the clause to be tested (as a RestrictInfo node). - * 'outer_relids' lists rels whose Vars can be considered pseudoconstant. - * 'saop_control' indicates whether ScalarArrayOpExpr clauses can be used. * * Returns true if the clause can be used with this index key. * @@ -1338,11 +1879,10 @@ match_clauses_to_index(IndexOptInfo *index, static bool match_clause_to_indexcol(IndexOptInfo *index, int indexcol, - RestrictInfo *rinfo, - Relids outer_relids, - SaOpControl saop_control) + RestrictInfo *rinfo) { Expr *clause = rinfo->clause; + Index index_relid = index->rel->relid; Oid opfamily = index->opfamily[indexcol]; Oid idxcollation = index->indexcollations[indexcol]; Node *leftop, @@ -1387,8 +1927,7 @@ match_clause_to_indexcol(IndexOptInfo *index, expr_coll = ((OpExpr *) clause)->inputcollid; plain_op = true; } - else if (clause && IsA(clause, ScalarArrayOpExpr) && - (index->amsearcharray || saop_control != SAOP_PER_AM)) + else if (clause && IsA(clause, ScalarArrayOpExpr)) { ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause; @@ -1407,8 +1946,7 @@ match_clause_to_indexcol(IndexOptInfo *index, { return match_rowcompare_to_indexcol(index, indexcol, opfamily, idxcollation, - (RowCompareExpr *) clause, - outer_relids); + (RowCompareExpr *) clause); } else if (index->amsearchnulls && IsA(clause, NullTest)) { @@ -1427,7 +1965,7 @@ match_clause_to_indexcol(IndexOptInfo *index, * (constant operator indexkey). See above notes about const-ness. */ if (match_index_to_operand(leftop, indexcol, index) && - bms_is_subset(right_relids, outer_relids) && + !bms_is_member(index_relid, right_relids) && !contain_volatile_functions(rightop)) { if (IndexCollMatchesExprColl(idxcollation, expr_coll) && @@ -1439,14 +1977,15 @@ match_clause_to_indexcol(IndexOptInfo *index, * is a "special" indexable operator. */ if (plain_op && - match_special_index_operator(clause, opfamily, idxcollation, true)) + match_special_index_operator(clause, opfamily, + idxcollation, true)) return true; return false; } if (plain_op && match_index_to_operand(rightop, indexcol, index) && - bms_is_subset(left_relids, outer_relids) && + !bms_is_member(index_relid, left_relids) && !contain_volatile_functions(leftop)) { if (IndexCollMatchesExprColl(idxcollation, expr_coll) && @@ -1457,7 +1996,8 @@ match_clause_to_indexcol(IndexOptInfo *index, * If we didn't find a member of the index's opfamily, see whether it * is a "special" indexable operator. */ - if (match_special_index_operator(clause, opfamily, idxcollation, false)) + if (match_special_index_operator(clause, opfamily, + idxcollation, false)) return true; return false; } @@ -1498,9 +2038,9 @@ match_rowcompare_to_indexcol(IndexOptInfo *index, int indexcol, Oid opfamily, Oid idxcollation, - RowCompareExpr *clause, - Relids outer_relids) + RowCompareExpr *clause) { + Index index_relid = index->rel->relid; Node *leftop, *rightop; Oid expr_op; @@ -1533,13 +2073,13 @@ match_rowcompare_to_indexcol(IndexOptInfo *index, * These syntactic tests are the same as in match_clause_to_indexcol() */ if (match_index_to_operand(leftop, indexcol, index) && - bms_is_subset(pull_varnos(rightop), outer_relids) && + !bms_is_member(index_relid, pull_varnos(rightop)) && !contain_volatile_functions(rightop)) { /* OK, indexkey is on left */ } else if (match_index_to_operand(rightop, indexcol, index) && - bms_is_subset(pull_varnos(leftop), outer_relids) && + !bms_is_member(index_relid, pull_varnos(leftop)) && !contain_volatile_functions(leftop)) { /* indexkey is on right, so commute the operator */ @@ -1800,484 +2340,41 @@ check_partial_indexes(PlannerInfo *root, RelOptInfo *rel) } /**************************************************************************** - * ---- ROUTINES TO CHECK JOIN CLAUSES ---- + * ---- ROUTINES TO CHECK EXTERNALLY-VISIBLE CONDITIONS ---- ****************************************************************************/ /* - * indexable_outerrelids - * Finds all other relids that participate in any indexable join clause - * for the specified table. Returns a set of relids. - */ -static Relids -indexable_outerrelids(PlannerInfo *root, RelOptInfo *rel) -{ - Relids outer_relids = NULL; - bool is_child_rel = (rel->reloptkind == RELOPT_OTHER_MEMBER_REL); - ListCell *lc1; - - /* - * Examine each joinclause in the joininfo list to see if it matches any - * key of any index. If so, add the clause's other rels to the result. - */ - foreach(lc1, rel->joininfo) - { - RestrictInfo *joininfo = (RestrictInfo *) lfirst(lc1); - Relids other_rels; - - other_rels = bms_difference(joininfo->required_relids, rel->relids); - if (matches_any_index(joininfo, rel, other_rels)) - outer_relids = bms_join(outer_relids, other_rels); - else - bms_free(other_rels); - } - - /* - * We also have to look through the query's EquivalenceClasses to see if - * any of them could generate indexable join conditions for this rel. - */ - if (rel->has_eclass_joins) - { - foreach(lc1, root->eq_classes) - { - EquivalenceClass *cur_ec = (EquivalenceClass *) lfirst(lc1); - Relids other_rels = NULL; - bool found_index = false; - ListCell *lc2; - - /* - * Won't generate joinclauses if const or single-member (the - * latter test covers the volatile case too) - */ - if (cur_ec->ec_has_const || list_length(cur_ec->ec_members) <= 1) - continue; - - /* - * Note we don't test ec_broken; if we did, we'd need a separate - * code path to look through ec_sources. Checking the members - * anyway is OK as a possibly-overoptimistic heuristic. - */ - - /* - * No point in searching if rel not mentioned in eclass (but we - * can't tell that for a child rel). - */ - if (!is_child_rel && - !bms_is_subset(rel->relids, cur_ec->ec_relids)) - continue; - - /* - * Scan members, looking for both an index match and join - * candidates - */ - foreach(lc2, cur_ec->ec_members) - { - EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc2); - - /* Join candidate? */ - if (!cur_em->em_is_child && - !bms_overlap(cur_em->em_relids, rel->relids)) - { - other_rels = bms_add_members(other_rels, - cur_em->em_relids); - continue; - } - - /* Check for index match (only need one) */ - if (!found_index && - bms_equal(cur_em->em_relids, rel->relids) && - eclass_matches_any_index(cur_ec, cur_em, rel)) - found_index = true; - } - - if (found_index) - outer_relids = bms_join(outer_relids, other_rels); - else - bms_free(other_rels); - } - } - - return outer_relids; -} - -/* - * matches_any_index - * Workhorse for indexable_outerrelids: see if a joinclause can be - * matched to any index of the given rel. - */ -static bool -matches_any_index(RestrictInfo *rinfo, RelOptInfo *rel, Relids outer_relids) -{ - ListCell *l; - - Assert(IsA(rinfo, RestrictInfo)); - - if (restriction_is_or_clause(rinfo)) - { - foreach(l, ((BoolExpr *) rinfo->orclause)->args) - { - Node *orarg = (Node *) lfirst(l); - - /* OR arguments should be ANDs or sub-RestrictInfos */ - if (and_clause(orarg)) - { - ListCell *j; - - /* Recurse to examine AND items and sub-ORs */ - foreach(j, ((BoolExpr *) orarg)->args) - { - RestrictInfo *arinfo = (RestrictInfo *) lfirst(j); - - if (matches_any_index(arinfo, rel, outer_relids)) - return true; - } - } - else - { - /* Recurse to examine simple clause */ - Assert(IsA(orarg, RestrictInfo)); - Assert(!restriction_is_or_clause((RestrictInfo *) orarg)); - if (matches_any_index((RestrictInfo *) orarg, rel, - outer_relids)) - return true; - } - } - - return false; - } - - /* Normal case for a simple restriction clause */ - foreach(l, rel->indexlist) - { - IndexOptInfo *index = (IndexOptInfo *) lfirst(l); - int indexcol; - - for (indexcol = 0; indexcol < index->ncolumns; indexcol++) - { - if (match_clause_to_indexcol(index, - indexcol, - rinfo, - outer_relids, - SAOP_ALLOW)) - return true; - } - } - - return false; -} - -/* - * eclass_matches_any_index - * Workhorse for indexable_outerrelids: see if an EquivalenceClass member - * can be matched to any index column of the given rel. + * eclass_member_matches_indexcol + * Test whether an EquivalenceClass member matches an index column. * - * This is also exported for use by find_eclass_clauses_for_index_join. + * This is exported for use by generate_implied_equalities_for_indexcol. */ bool -eclass_matches_any_index(EquivalenceClass *ec, EquivalenceMember *em, - RelOptInfo *rel) -{ - ListCell *l; - - foreach(l, rel->indexlist) - { - IndexOptInfo *index = (IndexOptInfo *) lfirst(l); - int indexcol; - - for (indexcol = 0; indexcol < index->ncolumns; indexcol++) - { - Oid curFamily = index->opfamily[indexcol]; - Oid curCollation = index->indexcollations[indexcol]; - - /* - * If it's a btree index, we can reject it if its opfamily isn't - * compatible with the EC, since no clause generated from the EC - * could be used with the index. For non-btree indexes, we can't - * easily tell whether clauses generated from the EC could be used - * with the index, so only check for expression match. This might - * mean we return "true" for a useless index, but that will just - * cause some wasted planner cycles; it's better than ignoring - * useful indexes. - * - * We insist on collation match for all index types, though. - */ - if ((index->relam != BTREE_AM_OID || - list_member_oid(ec->ec_opfamilies, curFamily)) && - IndexCollMatchesExprColl(curCollation, ec->ec_collation) && - match_index_to_operand((Node *) em->em_expr, indexcol, index)) - return true; - } - } - - return false; -} - - -/* - * best_inner_indexscan - * Finds the best available inner indexscans for a nestloop join - * with the given rel on the inside and the given outer_rel outside. - * - * *cheapest_startup gets the path with least startup cost - * *cheapest_total gets the path with least total cost (often the same path) - * Both are set to NULL if there are no possible inner indexscans. - * - * We ignore ordering considerations, since a nestloop's inner scan's order - * is uninteresting. Hence startup cost and total cost are the only figures - * of merit to consider. - * - * Note: create_index_paths() must have been run previously for this rel, - * else the results will always be NULL. - */ -void -best_inner_indexscan(PlannerInfo *root, RelOptInfo *rel, - RelOptInfo *outer_rel, JoinType jointype, - Path **cheapest_startup, Path **cheapest_total) -{ - Relids outer_relids; - bool isouterjoin; - List *clause_list; - List *indexpaths; - List *bitindexpaths; - List *allindexpaths; - ListCell *l; - InnerIndexscanInfo *info; - MemoryContext oldcontext; - - /* Initialize results for failure returns */ - *cheapest_startup = *cheapest_total = NULL; - - /* - * Nestloop only supports inner, left, semi, and anti joins. - */ - switch (jointype) - { - case JOIN_INNER: - case JOIN_SEMI: - isouterjoin = false; - break; - case JOIN_LEFT: - case JOIN_ANTI: - isouterjoin = true; - break; - default: - return; - } - - /* - * If there are no indexable joinclauses for this rel, exit quickly. - */ - if (bms_is_empty(rel->index_outer_relids)) - return; - - /* - * Otherwise, we have to do path selection in the main planning context, - * so that any created path can be safely attached to the rel's cache of - * best inner paths. (This is not currently an issue for normal planning, - * but it is an issue for GEQO planning.) - */ - oldcontext = MemoryContextSwitchTo(root->planner_cxt); - - /* - * Intersect the given outer relids with index_outer_relids to find the - * set of outer relids actually relevant for this rel. If there are none, - * again we can fail immediately. - */ - outer_relids = bms_intersect(rel->index_outer_relids, outer_rel->relids); - if (bms_is_empty(outer_relids)) - { - bms_free(outer_relids); - MemoryContextSwitchTo(oldcontext); - return; - } - - /* - * Look to see if we already computed the result for this set of relevant - * outerrels. (We include the isouterjoin status in the cache lookup key - * for safety. In practice I suspect this is not necessary because it - * should always be the same for a given combination of rels.) - * - * NOTE: because we cache on outer_relids rather than outer_rel->relids, - * we will report the same paths and hence path cost for joins with - * different sets of irrelevant rels on the outside. Now that cost_index - * is sensitive to outer_rel->rows, this is not really right. However the - * error is probably not large. Is it worth establishing a separate cache - * entry for each distinct outer_rel->relids set to get this right? - */ - foreach(l, rel->index_inner_paths) - { - info = (InnerIndexscanInfo *) lfirst(l); - if (bms_equal(info->other_relids, outer_relids) && - info->isouterjoin == isouterjoin) - { - bms_free(outer_relids); - MemoryContextSwitchTo(oldcontext); - *cheapest_startup = info->cheapest_startup_innerpath; - *cheapest_total = info->cheapest_total_innerpath; - return; - } - } - - /* - * Find all the relevant restriction and join clauses. - * - * Note: because we include restriction clauses, we will find indexscans - * that could be plain indexscans, ie, they don't require the join context - * at all. This may seem redundant, but we need to include those scans in - * the input given to choose_bitmap_and() to be sure we find optimal AND - * combinations of join and non-join scans. Also, even if the "best inner - * indexscan" is just a plain indexscan, it will have a different cost - * estimate because of cache effects. - */ - clause_list = find_clauses_for_join(root, rel, outer_relids, isouterjoin); - - /* - * Find all the index paths that are usable for this join, except for - * stuff involving OR and executor-managed ScalarArrayOpExpr clauses. - */ - allindexpaths = find_usable_indexes(root, rel, - clause_list, NIL, - false, outer_rel, - SAOP_PER_AM, - ST_ANYSCAN); - - /* - * Include the ones that are usable as plain indexscans in indexpaths, and - * include the ones that are usable as bitmap scans in bitindexpaths. - */ - indexpaths = bitindexpaths = NIL; - foreach(l, allindexpaths) - { - IndexPath *ipath = (IndexPath *) lfirst(l); - - if (ipath->indexinfo->amhasgettuple) - indexpaths = lappend(indexpaths, ipath); - - if (ipath->indexinfo->amhasgetbitmap) - bitindexpaths = lappend(bitindexpaths, ipath); - } - - /* - * Generate BitmapOrPaths for any suitable OR-clauses present in the - * clause list. - */ - bitindexpaths = list_concat(bitindexpaths, - generate_bitmap_or_paths(root, rel, - clause_list, NIL, - outer_rel)); - - /* - * Likewise, generate paths using executor-managed ScalarArrayOpExpr - * clauses; these can't be simple indexscans but they can be used in - * bitmap scans. - */ - bitindexpaths = list_concat(bitindexpaths, - find_saop_paths(root, rel, - clause_list, NIL, - false, outer_rel)); - - /* - * If we found anything usable, generate a BitmapHeapPath for the most - * promising combination of bitmap index paths. - */ - if (bitindexpaths != NIL) - { - Path *bitmapqual; - BitmapHeapPath *bpath; - - bitmapqual = choose_bitmap_and(root, rel, bitindexpaths, outer_rel); - bpath = create_bitmap_heap_path(root, rel, bitmapqual, outer_rel); - indexpaths = lappend(indexpaths, bpath); - } - - /* - * Now choose the cheapest members of indexpaths. - */ - if (indexpaths != NIL) - { - *cheapest_startup = *cheapest_total = (Path *) linitial(indexpaths); - - for_each_cell(l, lnext(list_head(indexpaths))) - { - Path *path = (Path *) lfirst(l); - - if (compare_path_costs(path, *cheapest_startup, STARTUP_COST) < 0) - *cheapest_startup = path; - if (compare_path_costs(path, *cheapest_total, TOTAL_COST) < 0) - *cheapest_total = path; - } - } - - /* Cache the results --- whether positive or negative */ - info = makeNode(InnerIndexscanInfo); - info->other_relids = outer_relids; - info->isouterjoin = isouterjoin; - info->cheapest_startup_innerpath = *cheapest_startup; - info->cheapest_total_innerpath = *cheapest_total; - rel->index_inner_paths = lcons(info, rel->index_inner_paths); - - MemoryContextSwitchTo(oldcontext); -} - -/* - * find_clauses_for_join - * Generate a list of clauses that are potentially useful for - * scanning rel as the inner side of a nestloop join. - * - * We consider both join and restriction clauses. Any joinclause that uses - * only otherrels in the specified outer_relids is fair game. But there must - * be at least one such joinclause in the final list, otherwise we return NIL - * indicating that there isn't any potential win here. - */ -static List * -find_clauses_for_join(PlannerInfo *root, RelOptInfo *rel, - Relids outer_relids, bool isouterjoin) +eclass_member_matches_indexcol(EquivalenceClass *ec, EquivalenceMember *em, + IndexOptInfo *index, int indexcol) { - List *clause_list = NIL; - Relids join_relids; - ListCell *l; + Oid curFamily = index->opfamily[indexcol]; + Oid curCollation = index->indexcollations[indexcol]; /* - * Look for joinclauses that are usable with given outer_relids. Note - * we'll take anything that's applicable to the join whether it has - * anything to do with an index or not; since we're only building a list, - * it's not worth filtering more finely here. + * If it's a btree index, we can reject it if its opfamily isn't + * compatible with the EC, since no clause generated from the EC could be + * used with the index. For non-btree indexes, we can't easily tell + * whether clauses generated from the EC could be used with the index, + * so don't check the opfamily. This might mean we return "true" for a + * useless EC, so we have to recheck the results of + * generate_implied_equalities_for_indexcol; see + * match_eclass_clauses_to_index. */ - join_relids = bms_union(rel->relids, outer_relids); - - foreach(l, rel->joininfo) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(l); - - /* Can't use pushed-down join clauses in outer join */ - if (isouterjoin && rinfo->is_pushed_down) - continue; - if (!bms_is_subset(rinfo->required_relids, join_relids)) - continue; - - clause_list = lappend(clause_list, rinfo); - } - - bms_free(join_relids); - - /* - * Also check to see if any EquivalenceClasses can produce a relevant - * joinclause. Since all such clauses are effectively pushed-down, this - * doesn't apply to outer joins. - */ - if (!isouterjoin && rel->has_eclass_joins) - clause_list = list_concat(clause_list, - find_eclass_clauses_for_index_join(root, - rel, - outer_relids)); - - /* If no join clause was matched then forget it, per comments above */ - if (clause_list == NIL) - return NIL; + if (index->relam == BTREE_AM_OID && + !list_member_oid(ec->ec_opfamilies, curFamily)) + return false; - /* We can also use any plain restriction clauses for the rel */ - clause_list = list_concat(list_copy(rel->baserestrictinfo), clause_list); + /* We insist on collation match for all index types, though */ + if (!IndexCollMatchesExprColl(curCollation, ec->ec_collation)) + return false; - return clause_list; + return match_index_to_operand((Node *) em->em_expr, indexcol, index); } /* @@ -2473,6 +2570,8 @@ relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel, * * Note that we aren't interested in collations here; the caller must check * for a collation match, if it's dealing with an operator where that matters. + * + * This is exported for use in selfuncs.c. */ bool match_index_to_operand(Node *operand, @@ -2543,7 +2642,7 @@ match_index_to_operand(Node *operand, * ---- ROUTINES FOR "SPECIAL" INDEXABLE OPERATORS ---- ****************************************************************************/ -/*---------- +/* * These routines handle special optimization of operators that can be * used with index scans even though they are not known to the executor's * indexscan machinery. The key idea is that these operators allow us @@ -2590,7 +2689,6 @@ match_index_to_operand(Node *operand, * the index's opfamily this transformation is a no-op, but clauses recognized * by match_special_index_operator() or match_boolean_index_clause() must be * converted into one or more "regular" indexqual conditions. - *---------- */ /* @@ -3168,9 +3266,7 @@ adjust_rowcompare_for_index(RowCompareExpr *clause, /* * See how many of the remaining columns match some index column in the - * same way. A note about rel membership tests: we assume that the clause - * as a whole is already known to use only Vars from the indexed relation - * and possibly some acceptable outer relations. So the "other" side of + * same way. As in match_clause_to_indexcol(), the "other" side of * any potential index condition is OK as long as it doesn't use Vars from * the indexed relation. */ diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c index 65caeb86c9bd419e3485bbe889f44e559235083a..1d537d84ee6a37f89e5bcfc2df93001ab8426fc6 100644 --- a/src/backend/optimizer/path/joinpath.c +++ b/src/backend/optimizer/path/joinpath.c @@ -25,17 +25,20 @@ static void sort_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel, RelOptInfo *outerrel, RelOptInfo *innerrel, List *restrictlist, List *mergeclause_list, - JoinType jointype, SpecialJoinInfo *sjinfo); + JoinType jointype, SpecialJoinInfo *sjinfo, + Relids param_source_rels); static void match_unsorted_outer(PlannerInfo *root, RelOptInfo *joinrel, RelOptInfo *outerrel, RelOptInfo *innerrel, List *restrictlist, List *mergeclause_list, - JoinType jointype, SpecialJoinInfo *sjinfo); + JoinType jointype, SpecialJoinInfo *sjinfo, + SemiAntiJoinFactors *semifactors, + Relids param_source_rels); static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel, RelOptInfo *outerrel, RelOptInfo *innerrel, List *restrictlist, - JoinType jointype, SpecialJoinInfo *sjinfo); -static Path *best_appendrel_indexscan(PlannerInfo *root, RelOptInfo *rel, - RelOptInfo *outer_rel, JoinType jointype); + JoinType jointype, SpecialJoinInfo *sjinfo, + SemiAntiJoinFactors *semifactors, + Relids param_source_rels); static List *select_mergejoin_clauses(PlannerInfo *root, RelOptInfo *joinrel, RelOptInfo *outerrel, @@ -79,6 +82,9 @@ add_paths_to_joinrel(PlannerInfo *root, { List *mergeclause_list = NIL; bool mergejoin_allowed = true; + SemiAntiJoinFactors semifactors; + Relids param_source_rels = NULL; + ListCell *lc; /* * Find potential mergejoin clauses. We can skip this if we are not @@ -95,13 +101,60 @@ add_paths_to_joinrel(PlannerInfo *root, jointype, &mergejoin_allowed); + /* + * If it's SEMI or ANTI join, compute correction factors for cost + * estimation. These will be the same for all paths. + */ + if (jointype == JOIN_SEMI || jointype == JOIN_ANTI) + compute_semi_anti_join_factors(root, outerrel, innerrel, + jointype, sjinfo, restrictlist, + &semifactors); + + /* + * Decide whether it's sensible to generate parameterized paths for this + * joinrel, and if so, which relations such paths should require. There + * is no need to create a parameterized result path unless there is a join + * order restriction that prevents joining one of our input rels directly + * to the parameter source rel instead of joining to the other input rel. + * This restriction reduces the number of parameterized paths we have to + * deal with at higher join levels, without compromising the quality of + * the resulting plan. We express the restriction as a Relids set that + * must overlap the parameterization of any proposed join path. + */ + foreach(lc, root->join_info_list) + { + SpecialJoinInfo *sjinfo = (SpecialJoinInfo *) lfirst(lc); + + /* + * SJ is relevant to this join if we have some part of its RHS + * (possibly not all of it), and haven't yet joined to its LHS. (This + * test is pretty simplistic, but should be sufficient considering the + * join has already been proven legal.) If the SJ is relevant, it + * presents constraints for joining to anything not in its RHS. + */ + if (bms_overlap(joinrel->relids, sjinfo->min_righthand) && + !bms_overlap(joinrel->relids, sjinfo->min_lefthand)) + param_source_rels = bms_join(param_source_rels, + bms_difference(root->all_baserels, + sjinfo->min_righthand)); + + /* full joins constrain both sides symmetrically */ + if (sjinfo->jointype == JOIN_FULL && + bms_overlap(joinrel->relids, sjinfo->min_lefthand) && + !bms_overlap(joinrel->relids, sjinfo->min_righthand)) + param_source_rels = bms_join(param_source_rels, + bms_difference(root->all_baserels, + sjinfo->min_lefthand)); + } + /* * 1. Consider mergejoin paths where both relations must be explicitly * sorted. Skip this if we can't mergejoin. */ if (mergejoin_allowed) sort_inner_and_outer(root, joinrel, outerrel, innerrel, - restrictlist, mergeclause_list, jointype, sjinfo); + restrictlist, mergeclause_list, jointype, + sjinfo, param_source_rels); /* * 2. Consider paths where the outer relation need not be explicitly @@ -112,7 +165,8 @@ add_paths_to_joinrel(PlannerInfo *root, */ if (mergejoin_allowed) match_unsorted_outer(root, joinrel, outerrel, innerrel, - restrictlist, mergeclause_list, jointype, sjinfo); + restrictlist, mergeclause_list, jointype, + sjinfo, &semifactors, param_source_rels); #ifdef NOT_USED @@ -129,7 +183,8 @@ add_paths_to_joinrel(PlannerInfo *root, */ if (mergejoin_allowed) match_unsorted_inner(root, joinrel, outerrel, innerrel, - restrictlist, mergeclause_list, jointype, sjinfo); + restrictlist, mergeclause_list, jointype, + sjinfo, &semifactors, param_source_rels); #endif /* @@ -139,7 +194,226 @@ add_paths_to_joinrel(PlannerInfo *root, */ if (enable_hashjoin || jointype == JOIN_FULL) hash_inner_and_outer(root, joinrel, outerrel, innerrel, - restrictlist, jointype, sjinfo); + restrictlist, jointype, + sjinfo, &semifactors, param_source_rels); +} + +/* + * try_nestloop_path + * Consider a nestloop join path; if it appears useful, push it into + * the joinrel's pathlist via add_path(). + */ +static void +try_nestloop_path(PlannerInfo *root, + RelOptInfo *joinrel, + JoinType jointype, + SpecialJoinInfo *sjinfo, + SemiAntiJoinFactors *semifactors, + Relids param_source_rels, + Path *outer_path, + Path *inner_path, + List *restrict_clauses, + List *pathkeys) +{ + Relids required_outer; + JoinCostWorkspace workspace; + + /* + * Check to see if proposed path is still parameterized, and reject if + * the parameterization wouldn't be sensible. + */ + required_outer = calc_nestloop_required_outer(outer_path, + inner_path); + if (required_outer && + !bms_overlap(required_outer, param_source_rels)) + { + /* Waste no memory when we reject a path here */ + bms_free(required_outer); + return; + } + + /* + * Do a precheck to quickly eliminate obviously-inferior paths. We + * calculate a cheap lower bound on the path's cost and then use + * add_path_precheck() to see if the path is clearly going to be dominated + * by some existing path for the joinrel. If not, do the full pushup with + * creating a fully valid path structure and submitting it to add_path(). + * The latter two steps are expensive enough to make this two-phase + * methodology worthwhile. + */ + initial_cost_nestloop(root, &workspace, jointype, + outer_path, inner_path, + sjinfo, semifactors); + + if (add_path_precheck(joinrel, + workspace.startup_cost, workspace.total_cost, + pathkeys, required_outer)) + { + add_path(joinrel, (Path *) + create_nestloop_path(root, + joinrel, + jointype, + &workspace, + sjinfo, + semifactors, + outer_path, + inner_path, + restrict_clauses, + pathkeys, + required_outer)); + } + else + { + /* Waste no memory when we reject a path here */ + bms_free(required_outer); + } +} + +/* + * try_mergejoin_path + * Consider a merge join path; if it appears useful, push it into + * the joinrel's pathlist via add_path(). + */ +static void +try_mergejoin_path(PlannerInfo *root, + RelOptInfo *joinrel, + JoinType jointype, + SpecialJoinInfo *sjinfo, + Relids param_source_rels, + Path *outer_path, + Path *inner_path, + List *restrict_clauses, + List *pathkeys, + List *mergeclauses, + List *outersortkeys, + List *innersortkeys) +{ + Relids required_outer; + JoinCostWorkspace workspace; + + /* + * Check to see if proposed path is still parameterized, and reject if + * the parameterization wouldn't be sensible. + */ + required_outer = calc_non_nestloop_required_outer(outer_path, + inner_path); + if (required_outer && + !bms_overlap(required_outer, param_source_rels)) + { + /* Waste no memory when we reject a path here */ + bms_free(required_outer); + return; + } + + /* + * If the given paths are already well enough ordered, we can skip doing + * an explicit sort. + */ + if (outersortkeys && + pathkeys_contained_in(outersortkeys, outer_path->pathkeys)) + outersortkeys = NIL; + if (innersortkeys && + pathkeys_contained_in(innersortkeys, inner_path->pathkeys)) + innersortkeys = NIL; + + /* + * See comments in try_nestloop_path(). + */ + initial_cost_mergejoin(root, &workspace, jointype, mergeclauses, + outer_path, inner_path, + outersortkeys, innersortkeys, + sjinfo); + + if (add_path_precheck(joinrel, + workspace.startup_cost, workspace.total_cost, + pathkeys, required_outer)) + { + add_path(joinrel, (Path *) + create_mergejoin_path(root, + joinrel, + jointype, + &workspace, + sjinfo, + outer_path, + inner_path, + restrict_clauses, + pathkeys, + required_outer, + mergeclauses, + outersortkeys, + innersortkeys)); + } + else + { + /* Waste no memory when we reject a path here */ + bms_free(required_outer); + } +} + +/* + * try_hashjoin_path + * Consider a hash join path; if it appears useful, push it into + * the joinrel's pathlist via add_path(). + */ +static void +try_hashjoin_path(PlannerInfo *root, + RelOptInfo *joinrel, + JoinType jointype, + SpecialJoinInfo *sjinfo, + SemiAntiJoinFactors *semifactors, + Relids param_source_rels, + Path *outer_path, + Path *inner_path, + List *restrict_clauses, + List *hashclauses) +{ + Relids required_outer; + JoinCostWorkspace workspace; + + /* + * Check to see if proposed path is still parameterized, and reject if + * the parameterization wouldn't be sensible. + */ + required_outer = calc_non_nestloop_required_outer(outer_path, + inner_path); + if (required_outer && + !bms_overlap(required_outer, param_source_rels)) + { + /* Waste no memory when we reject a path here */ + bms_free(required_outer); + return; + } + + /* + * See comments in try_nestloop_path(). Also note that hashjoin paths + * never have any output pathkeys, per comments in create_hashjoin_path. + */ + initial_cost_hashjoin(root, &workspace, jointype, hashclauses, + outer_path, inner_path, + sjinfo, semifactors); + + if (add_path_precheck(joinrel, + workspace.startup_cost, workspace.total_cost, + NIL, required_outer)) + { + add_path(joinrel, (Path *) + create_hashjoin_path(root, + joinrel, + jointype, + &workspace, + sjinfo, + semifactors, + outer_path, + inner_path, + restrict_clauses, + required_outer, + hashclauses)); + } + else + { + /* Waste no memory when we reject a path here */ + bms_free(required_outer); + } } /* @@ -187,6 +461,7 @@ clause_sides_match_join(RestrictInfo *rinfo, RelOptInfo *outerrel, * mergejoin clauses in this join * 'jointype' is the type of join to do * 'sjinfo' is extra info about the join for selectivity estimation + * 'param_source_rels' are OK targets for parameterization of result paths */ static void sort_inner_and_outer(PlannerInfo *root, @@ -196,7 +471,8 @@ sort_inner_and_outer(PlannerInfo *root, List *restrictlist, List *mergeclause_list, JoinType jointype, - SpecialJoinInfo *sjinfo) + SpecialJoinInfo *sjinfo, + Relids param_source_rels) { Path *outer_path; Path *inner_path; @@ -209,6 +485,13 @@ sort_inner_and_outer(PlannerInfo *root, * cheapest-startup-cost input paths later, and only if they don't need a * sort. * + * This function intentionally does not consider parameterized input paths + * (implicit in the fact that it only looks at cheapest_total_path, which + * is always unparameterized). If we did so, we'd have a combinatorial + * 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. */ @@ -299,21 +582,21 @@ sort_inner_and_outer(PlannerInfo *root, * And now we can make the path. * * Note: it's possible that the cheapest paths will already be sorted - * properly. create_mergejoin_path will detect that case and suppress + * properly. try_mergejoin_path will detect that case and suppress * an explicit sort step, so we needn't do so here. */ - add_path(joinrel, (Path *) - create_mergejoin_path(root, - joinrel, - jointype, - sjinfo, - outer_path, - inner_path, - restrictlist, - merge_pathkeys, - cur_mergeclauses, - outerkeys, - innerkeys)); + try_mergejoin_path(root, + joinrel, + jointype, + sjinfo, + param_source_rels, + outer_path, + inner_path, + restrictlist, + merge_pathkeys, + cur_mergeclauses, + outerkeys, + innerkeys); } } @@ -350,6 +633,8 @@ sort_inner_and_outer(PlannerInfo *root, * mergejoin clauses in this join * 'jointype' is the type of join to do * 'sjinfo' is extra info about the join for selectivity estimation + * 'semifactors' contains valid data if jointype is SEMI or ANTI + * 'param_source_rels' are OK targets for parameterization of result paths */ static void match_unsorted_outer(PlannerInfo *root, @@ -359,17 +644,16 @@ match_unsorted_outer(PlannerInfo *root, List *restrictlist, List *mergeclause_list, JoinType jointype, - SpecialJoinInfo *sjinfo) + SpecialJoinInfo *sjinfo, + SemiAntiJoinFactors *semifactors, + Relids param_source_rels) { JoinType save_jointype = jointype; bool nestjoinOK; bool useallclauses; - Path *inner_cheapest_startup = innerrel->cheapest_startup_path; Path *inner_cheapest_total = innerrel->cheapest_total_path; Path *matpath = NULL; - Path *index_cheapest_startup = NULL; - Path *index_cheapest_total = NULL; - ListCell *l; + ListCell *lc1; /* * Nestloop only supports inner, left, semi, and anti joins. Also, if we @@ -408,14 +692,13 @@ match_unsorted_outer(PlannerInfo *root, /* * If we need to unique-ify the inner path, we will consider only the - * cheapest inner. + * cheapest-total inner. */ if (save_jointype == JOIN_UNIQUE_INNER) { inner_cheapest_total = (Path *) create_unique_path(root, innerrel, inner_cheapest_total, sjinfo); Assert(inner_cheapest_total); - inner_cheapest_startup = inner_cheapest_total; } else if (nestjoinOK) { @@ -428,28 +711,11 @@ match_unsorted_outer(PlannerInfo *root, !ExecMaterializesOutput(inner_cheapest_total->pathtype)) matpath = (Path *) create_material_path(innerrel, inner_cheapest_total); - - /* - * Get the best innerjoin indexpaths (if any) for this outer rel. - * They're the same for all outer paths. - */ - if (innerrel->reloptkind != RELOPT_JOINREL) - { - if (IsA(inner_cheapest_total, AppendPath)) - index_cheapest_total = best_appendrel_indexscan(root, - innerrel, - outerrel, - jointype); - else if (innerrel->rtekind == RTE_RELATION) - best_inner_indexscan(root, innerrel, outerrel, jointype, - &index_cheapest_startup, - &index_cheapest_total); - } } - foreach(l, outerrel->pathlist) + foreach(lc1, outerrel->pathlist) { - Path *outerpath = (Path *) lfirst(l); + Path *outerpath = (Path *) lfirst(lc1); List *merge_pathkeys; List *mergeclauses; List *innersortkeys; @@ -459,9 +725,16 @@ match_unsorted_outer(PlannerInfo *root, int num_sortkeys; int sortkeycnt; + /* + * We cannot use an outer path that is parameterized by the inner rel. + */ + if (bms_overlap(outerpath->required_outer, innerrel->relids)) + continue; + /* * If we need to unique-ify the outer path, it's pointless to consider - * any but the cheapest outer. + * any but the cheapest outer. (XXX we don't consider parameterized + * outers, nor inners, for unique-ified cases. Should we?) */ if (save_jointype == JOIN_UNIQUE_OUTER) { @@ -480,65 +753,61 @@ match_unsorted_outer(PlannerInfo *root, merge_pathkeys = build_join_pathkeys(root, joinrel, jointype, outerpath->pathkeys); - if (nestjoinOK) + if (save_jointype == JOIN_UNIQUE_INNER) + { + /* + * Consider nestloop join, but only with the unique-ified cheapest + * inner path + */ + try_nestloop_path(root, + joinrel, + jointype, + sjinfo, + semifactors, + param_source_rels, + outerpath, + inner_cheapest_total, + restrictlist, + merge_pathkeys); + } + else if (nestjoinOK) { /* - * Always consider a nestloop join with this outer and - * cheapest-total-cost inner. When appropriate, also consider - * using the materialized form of the cheapest inner, the - * cheapest-startup-cost inner path, and the cheapest innerjoin - * indexpaths. + * Consider nestloop joins using this outer path and various + * available paths for the inner relation. We consider the + * cheapest-total paths for each available parameterization of + * the inner relation, including the unparameterized case. */ - add_path(joinrel, (Path *) - create_nestloop_path(root, - joinrel, - jointype, - sjinfo, - outerpath, - inner_cheapest_total, - restrictlist, - merge_pathkeys)); + ListCell *lc2; + + foreach(lc2, innerrel->cheapest_parameterized_paths) + { + Path *innerpath = (Path *) lfirst(lc2); + + try_nestloop_path(root, + joinrel, + jointype, + sjinfo, + semifactors, + param_source_rels, + outerpath, + innerpath, + restrictlist, + merge_pathkeys); + } + + /* Also consider materialized form of the cheapest inner path */ if (matpath != NULL) - add_path(joinrel, (Path *) - create_nestloop_path(root, - joinrel, - jointype, - sjinfo, - outerpath, - matpath, - restrictlist, - merge_pathkeys)); - if (inner_cheapest_startup != inner_cheapest_total) - add_path(joinrel, (Path *) - create_nestloop_path(root, - joinrel, - jointype, - sjinfo, - outerpath, - inner_cheapest_startup, - restrictlist, - merge_pathkeys)); - if (index_cheapest_total != NULL) - add_path(joinrel, (Path *) - create_nestloop_path(root, - joinrel, - jointype, - sjinfo, - outerpath, - index_cheapest_total, - restrictlist, - merge_pathkeys)); - if (index_cheapest_startup != NULL && - index_cheapest_startup != index_cheapest_total) - add_path(joinrel, (Path *) - create_nestloop_path(root, - joinrel, - jointype, - sjinfo, - outerpath, - index_cheapest_startup, - restrictlist, - merge_pathkeys)); + try_nestloop_path(root, + joinrel, + jointype, + sjinfo, + semifactors, + param_source_rels, + outerpath, + matpath, + restrictlist, + merge_pathkeys); } /* Can't do anything else if outer path needs to be unique'd */ @@ -578,21 +847,21 @@ match_unsorted_outer(PlannerInfo *root, /* * Generate a mergejoin on the basis of sorting the cheapest inner. * Since a sort will be needed, only cheapest total cost matters. (But - * create_mergejoin_path will do the right thing if + * try_mergejoin_path will do the right thing if * inner_cheapest_total is already correctly sorted.) */ - add_path(joinrel, (Path *) - create_mergejoin_path(root, - joinrel, - jointype, - sjinfo, - outerpath, - inner_cheapest_total, - restrictlist, - merge_pathkeys, - mergeclauses, - NIL, - innersortkeys)); + try_mergejoin_path(root, + joinrel, + jointype, + sjinfo, + param_source_rels, + outerpath, + inner_cheapest_total, + restrictlist, + merge_pathkeys, + mergeclauses, + NIL, + innersortkeys); /* Can't do anything else if inner path needs to be unique'd */ if (save_jointype == JOIN_UNIQUE_INNER) @@ -604,6 +873,11 @@ match_unsorted_outer(PlannerInfo *root, * mergejoin using a subset of the merge clauses. Here, we consider * both cheap startup cost and cheap total cost. * + * Currently we do not consider parameterized inner paths here. + * This interacts with decisions elsewhere that also discriminate + * against mergejoins with parameterized inputs; see comments in + * src/backend/optimizer/README. + * * As we shorten the sortkey list, we should consider only paths that * are strictly cheaper than (in particular, not the same as) any path * found in an earlier iteration. Otherwise we'd be intentionally @@ -654,6 +928,7 @@ match_unsorted_outer(PlannerInfo *root, trialsortkeys = list_truncate(trialsortkeys, sortkeycnt); innerpath = get_cheapest_path_for_pathkeys(innerrel->pathlist, trialsortkeys, + NULL, TOTAL_COST); if (innerpath != NULL && (cheapest_total_inner == NULL || @@ -673,23 +948,24 @@ match_unsorted_outer(PlannerInfo *root, } else newclauses = mergeclauses; - add_path(joinrel, (Path *) - create_mergejoin_path(root, - joinrel, - jointype, - sjinfo, - outerpath, - innerpath, - restrictlist, - merge_pathkeys, - newclauses, - NIL, - NIL)); + try_mergejoin_path(root, + joinrel, + jointype, + sjinfo, + param_source_rels, + outerpath, + innerpath, + restrictlist, + merge_pathkeys, + newclauses, + NIL, + NIL); cheapest_total_inner = innerpath; } /* Same on the basis of cheapest startup cost ... */ innerpath = get_cheapest_path_for_pathkeys(innerrel->pathlist, trialsortkeys, + NULL, STARTUP_COST); if (innerpath != NULL && (cheapest_startup_inner == NULL || @@ -717,18 +993,18 @@ match_unsorted_outer(PlannerInfo *root, else newclauses = mergeclauses; } - add_path(joinrel, (Path *) - create_mergejoin_path(root, - joinrel, - jointype, - sjinfo, - outerpath, - innerpath, - restrictlist, - merge_pathkeys, - newclauses, - NIL, - NIL)); + try_mergejoin_path(root, + joinrel, + jointype, + sjinfo, + param_source_rels, + outerpath, + innerpath, + restrictlist, + merge_pathkeys, + newclauses, + NIL, + NIL); } cheapest_startup_inner = innerpath; } @@ -754,6 +1030,8 @@ match_unsorted_outer(PlannerInfo *root, * clauses that apply to this join * 'jointype' is the type of join to do * 'sjinfo' is extra info about the join for selectivity estimation + * 'semifactors' contains valid data if jointype is SEMI or ANTI + * 'param_source_rels' are OK targets for parameterization of result paths */ static void hash_inner_and_outer(PlannerInfo *root, @@ -762,15 +1040,17 @@ hash_inner_and_outer(PlannerInfo *root, RelOptInfo *innerrel, List *restrictlist, JoinType jointype, - SpecialJoinInfo *sjinfo) + SpecialJoinInfo *sjinfo, + SemiAntiJoinFactors *semifactors, + Relids param_source_rels) { bool isouterjoin = IS_OUTER_JOIN(jointype); List *hashclauses; ListCell *l; /* - * We need to build only one hashpath for any given pair of outer and - * inner relations; all of the hashable clauses will be used as keys. + * We need to build only one hashclauses list for any given pair of outer + * and inner relations; all of the hashable clauses will be used as keys. * * Scan the join's restrictinfo list to find hashjoinable clauses that are * usable with this pair of sub-relations. @@ -800,7 +1080,7 @@ hash_inner_and_outer(PlannerInfo *root, hashclauses = lappend(hashclauses, restrictinfo); } - /* If we found any usable hashclauses, make a path */ + /* If we found any usable hashclauses, make paths */ if (hashclauses) { /* @@ -812,15 +1092,25 @@ hash_inner_and_outer(PlannerInfo *root, Path *cheapest_total_outer = outerrel->cheapest_total_path; Path *cheapest_total_inner = innerrel->cheapest_total_path; - /* Unique-ify if need be */ + /* Unique-ify if need be; we ignore parameterized possibilities */ if (jointype == JOIN_UNIQUE_OUTER) { cheapest_total_outer = (Path *) create_unique_path(root, outerrel, cheapest_total_outer, sjinfo); Assert(cheapest_total_outer); - cheapest_startup_outer = cheapest_total_outer; jointype = JOIN_INNER; + try_hashjoin_path(root, + joinrel, + jointype, + sjinfo, + semifactors, + param_source_rels, + cheapest_total_outer, + cheapest_total_inner, + restrictlist, + hashclauses); + /* no possibility of cheap startup here */ } else if (jointype == JOIN_UNIQUE_INNER) { @@ -829,97 +1119,92 @@ hash_inner_and_outer(PlannerInfo *root, cheapest_total_inner, sjinfo); Assert(cheapest_total_inner); jointype = JOIN_INNER; + try_hashjoin_path(root, + joinrel, + jointype, + sjinfo, + semifactors, + param_source_rels, + cheapest_total_outer, + cheapest_total_inner, + restrictlist, + hashclauses); + if (cheapest_startup_outer != cheapest_total_outer) + try_hashjoin_path(root, + joinrel, + jointype, + sjinfo, + semifactors, + param_source_rels, + cheapest_startup_outer, + cheapest_total_inner, + restrictlist, + hashclauses); } + else + { + /* + * For other jointypes, we consider the cheapest startup outer + * together with the cheapest total inner, and then consider + * pairings of cheapest-total paths including parameterized ones. + * There is no use in generating parameterized paths on the basis + * of possibly cheap startup cost, so this is sufficient. + */ + ListCell *lc1; + ListCell *lc2; + + try_hashjoin_path(root, + joinrel, + jointype, + sjinfo, + semifactors, + param_source_rels, + cheapest_startup_outer, + cheapest_total_inner, + restrictlist, + hashclauses); + + foreach(lc1, outerrel->cheapest_parameterized_paths) + { + Path *outerpath = (Path *) lfirst(lc1); - add_path(joinrel, (Path *) - create_hashjoin_path(root, - joinrel, - jointype, - sjinfo, - cheapest_total_outer, - cheapest_total_inner, - restrictlist, - hashclauses)); - if (cheapest_startup_outer != cheapest_total_outer) - add_path(joinrel, (Path *) - create_hashjoin_path(root, - joinrel, - jointype, - sjinfo, - cheapest_startup_outer, - cheapest_total_inner, - restrictlist, - hashclauses)); - } -} - -/* - * best_appendrel_indexscan - * Finds the best available set of inner indexscans for a nestloop join - * with the given append relation on the inside and the given outer_rel - * outside. Returns an AppendPath comprising the best inner scans, or - * NULL if there are no possible inner indexscans. - * - * Note that we currently consider only cheapest-total-cost. It's not - * very clear what cheapest-startup-cost might mean for an AppendPath. - */ -static Path * -best_appendrel_indexscan(PlannerInfo *root, RelOptInfo *rel, - RelOptInfo *outer_rel, JoinType jointype) -{ - int parentRTindex = rel->relid; - List *append_paths = NIL; - bool found_indexscan = false; - ListCell *l; - - foreach(l, root->append_rel_list) - { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); - int childRTindex; - RelOptInfo *childrel; - Path *index_cheapest_startup; - Path *index_cheapest_total; - - /* append_rel_list contains all append rels; ignore others */ - if (appinfo->parent_relid != parentRTindex) - continue; - - childRTindex = appinfo->child_relid; - childrel = find_base_rel(root, childRTindex); - Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); + /* + * We cannot use an outer path that is parameterized by the + * inner rel. + */ + if (bms_overlap(outerpath->required_outer, innerrel->relids)) + continue; - /* - * Check to see if child was rejected by constraint exclusion. If so, - * it will have a cheapest_total_path that's a "dummy" path. - */ - if (IS_DUMMY_PATH(childrel->cheapest_total_path)) - continue; /* OK, we can ignore it */ + foreach(lc2, innerrel->cheapest_parameterized_paths) + { + Path *innerpath = (Path *) lfirst(lc2); - /* - * Get the best innerjoin indexpaths (if any) for this child rel. - */ - best_inner_indexscan(root, childrel, outer_rel, jointype, - &index_cheapest_startup, &index_cheapest_total); + /* + * We cannot use an inner path that is parameterized by + * the outer rel, either. + */ + if (bms_overlap(innerpath->required_outer, + outerrel->relids)) + continue; - /* - * If no luck on an indexpath for this rel, we'll still consider an - * Append substituting the cheapest-total inner path. However we must - * find at least one indexpath, else there's not going to be any - * improvement over the base path for the appendrel. - */ - if (index_cheapest_total) - found_indexscan = true; - else - index_cheapest_total = childrel->cheapest_total_path; + if (outerpath == cheapest_startup_outer && + innerpath == cheapest_total_inner) + continue; /* already tried it */ - append_paths = lappend(append_paths, index_cheapest_total); + try_hashjoin_path(root, + joinrel, + jointype, + sjinfo, + semifactors, + param_source_rels, + outerpath, + innerpath, + restrictlist, + hashclauses); + } + } + } } - - if (!found_indexscan) - return NULL; - - /* Form and return the completed Append path. */ - return (Path *) create_append_path(rel, append_paths); } /* diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index 9656e5b8a60e507ad6a3b3cfa417d134511d321c..4a35d8d3a48d07dec2085e10783b690eb917886f 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -935,14 +935,11 @@ has_legal_joinclause(PlannerInfo *root, RelOptInfo *rel) /* * is_dummy_rel --- has relation been proven empty? - * - * If so, it will have a single path that is dummy. */ static bool is_dummy_rel(RelOptInfo *rel) { - return (rel->cheapest_total_path != NULL && - IS_DUMMY_PATH(rel->cheapest_total_path)); + return IS_DUMMY_REL(rel); } /* @@ -981,7 +978,7 @@ mark_dummy_rel(RelOptInfo *rel) /* Set up the dummy path */ add_path(rel, (Path *) create_append_path(rel, NIL)); - /* Set or update cheapest_total_path */ + /* Set or update cheapest_total_path and related fields */ set_cheapest(rel); MemoryContextSwitchTo(oldcontext); diff --git a/src/backend/optimizer/path/orindxpath.c b/src/backend/optimizer/path/orindxpath.c index d4cc567892da164fae658ef9baed253f7e2bf0bd..02bd362c5c8a6369fadb2636e886e27f9a3eaf35 100644 --- a/src/backend/optimizer/path/orindxpath.c +++ b/src/backend/optimizer/path/orindxpath.c @@ -88,6 +88,10 @@ create_or_index_quals(PlannerInfo *root, RelOptInfo *rel) orig_selec; ListCell *i; + /* Skip the whole mess if no indexes */ + if (rel->indexlist == NIL) + return false; + /* * Find potentially interesting OR joinclauses. * @@ -114,8 +118,8 @@ create_or_index_quals(PlannerInfo *root, RelOptInfo *rel) * Use the generate_bitmap_or_paths() machinery to estimate the * value of each OR clause. We can use regular restriction * clauses along with the OR clause contents to generate - * indexquals. We pass outer_rel = NULL so that sub-clauses that - * are actually joins will be ignored. + * indexquals. We pass restriction_only = true so that any + * sub-clauses that are actually joins will be ignored. */ List *orpaths; ListCell *k; @@ -123,7 +127,7 @@ create_or_index_quals(PlannerInfo *root, RelOptInfo *rel) orpaths = generate_bitmap_or_paths(root, rel, list_make1(rinfo), rel->baserestrictinfo, - NULL); + true); /* Locate the cheapest OR path */ foreach(k, orpaths) diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c index 0ebdead6c84d48baf8f8c0fc93f2775e56ec3a2a..653387e582d8900bd9353d8f78ccb7764f5dc727 100644 --- a/src/backend/optimizer/path/pathkeys.c +++ b/src/backend/optimizer/path/pathkeys.c @@ -403,14 +403,17 @@ pathkeys_contained_in(List *keys1, List *keys2) /* * get_cheapest_path_for_pathkeys * Find the cheapest path (according to the specified criterion) that - * satisfies the given pathkeys. Return NULL if no such path. + * satisfies the given pathkeys and parameterization. + * Return NULL if no such path. * * 'paths' is a list of possible paths that all generate the same relation * 'pathkeys' represents a required ordering (already canonicalized!) + * 'required_outer' denotes allowable outer relations for parameterized paths * 'cost_criterion' is STARTUP_COST or TOTAL_COST */ Path * get_cheapest_path_for_pathkeys(List *paths, List *pathkeys, + Relids required_outer, CostSelector cost_criterion) { Path *matched_path = NULL; @@ -428,7 +431,8 @@ get_cheapest_path_for_pathkeys(List *paths, List *pathkeys, compare_path_costs(matched_path, path, cost_criterion) <= 0) continue; - if (pathkeys_contained_in(pathkeys, path->pathkeys)) + if (pathkeys_contained_in(pathkeys, path->pathkeys) && + bms_is_subset(path->required_outer, required_outer)) matched_path = path; } return matched_path; @@ -437,7 +441,7 @@ get_cheapest_path_for_pathkeys(List *paths, List *pathkeys, /* * get_cheapest_fractional_path_for_pathkeys * Find the cheapest path (for retrieving a specified fraction of all - * the tuples) that satisfies the given pathkeys. + * the tuples) that satisfies the given pathkeys and parameterization. * Return NULL if no such path. * * See compare_fractional_path_costs() for the interpretation of the fraction @@ -445,11 +449,13 @@ get_cheapest_path_for_pathkeys(List *paths, List *pathkeys, * * 'paths' is a list of possible paths that all generate the same relation * 'pathkeys' represents a required ordering (already canonicalized!) + * 'required_outer' denotes allowable outer relations for parameterized paths * 'fraction' is the fraction of the total tuples expected to be retrieved */ Path * get_cheapest_fractional_path_for_pathkeys(List *paths, List *pathkeys, + Relids required_outer, double fraction) { Path *matched_path = NULL; @@ -461,13 +467,14 @@ get_cheapest_fractional_path_for_pathkeys(List *paths, /* * Since cost comparison is a lot cheaper than pathkey comparison, do - * that first. + * that first. (XXX is that still true?) */ if (matched_path != NULL && compare_fractional_path_costs(matched_path, path, fraction) <= 0) continue; - if (pathkeys_contained_in(pathkeys, path->pathkeys)) + if (pathkeys_contained_in(pathkeys, path->pathkeys) && + bms_is_subset(path->required_outer, required_outer)) matched_path = path; } return matched_path; diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index e41d2a6eb99a48cf47e2f95e2c4863f58e9098c2..aea42b1dd461a3ef5d94bee79b980bdadfd57551 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -932,7 +932,7 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path) long numGroups; Oid *groupOperators; - numGroups = (long) Min(best_path->rows, (double) LONG_MAX); + numGroups = (long) Min(best_path->path.rows, (double) LONG_MAX); /* * Get the hashable equality operators for the Agg node to use. @@ -1018,7 +1018,7 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path) } /* Adjust output size estimate (other fields should be OK already) */ - plan->plan_rows = best_path->rows; + plan->plan_rows = best_path->path.rows; return plan; } @@ -1112,7 +1112,7 @@ create_indexscan_plan(PlannerInfo *root, fixed_indexorderbys = fix_indexorderby_references(root, best_path); /* - * If this is an innerjoin scan, the indexclauses will contain join + * If this is a parameterized scan, the indexclauses will contain join * clauses that are not present in scan_clauses (since the passed-in value * is just the rel's baserestrictinfo list). We must add these clauses to * scan_clauses to ensure they get checked. In most cases we will remove @@ -1122,7 +1122,7 @@ create_indexscan_plan(PlannerInfo *root, * Note: pointer comparison should be enough to determine RestrictInfo * matches. */ - if (best_path->isjoininner) + if (best_path->path.required_outer) scan_clauses = list_union_ptr(scan_clauses, best_path->indexclauses); /* @@ -1189,7 +1189,7 @@ create_indexscan_plan(PlannerInfo *root, * it'd break the comparisons to predicates above ... (or would it? Those * wouldn't have outer refs) */ - if (best_path->isjoininner) + if (best_path->path.required_outer) { stripped_indexquals = (List *) replace_nestloop_params(root, (Node *) stripped_indexquals); @@ -1221,8 +1221,6 @@ create_indexscan_plan(PlannerInfo *root, best_path->indexscandir); copy_path_costsize(&scan_plan->plan, &best_path->path); - /* use the indexscan-specific rows estimate, not the parent rel's */ - scan_plan->plan.plan_rows = best_path->rows; return scan_plan; } @@ -1258,14 +1256,14 @@ create_bitmap_scan_plan(PlannerInfo *root, scan_clauses = extract_actual_clauses(scan_clauses, false); /* - * If this is a innerjoin scan, the indexclauses will contain join clauses + * If this is a parameterized scan, the indexclauses will contain join clauses * that are not present in scan_clauses (since the passed-in value is just * the rel's baserestrictinfo list). We must add these clauses to * scan_clauses to ensure they get checked. In most cases we will remove * the join clauses again below, but if a join clause contains a special * operator, we need to make sure it gets into the scan_clauses. */ - if (best_path->isjoininner) + if (best_path->path.required_outer) { scan_clauses = list_concat_unique(scan_clauses, bitmapqualorig); } @@ -1328,8 +1326,6 @@ create_bitmap_scan_plan(PlannerInfo *root, baserelid); copy_path_costsize(&scan_plan->scan.plan, &best_path->path); - /* use the indexscan-specific rows estimate, not the parent rel's */ - scan_plan->scan.plan.plan_rows = best_path->rows; return scan_plan; } @@ -1510,7 +1506,7 @@ create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual, * Replace outer-relation variables with nestloop params, but only * after doing the above comparisons to index predicates. */ - if (ipath->isjoininner) + if (ipath->path.required_outer) { *qual = (List *) replace_nestloop_params(root, (Node *) *qual); @@ -1883,14 +1879,13 @@ create_nestloop_plan(PlannerInfo *root, ListCell *next; /* - * If the inner path is a nestloop inner indexscan, it might be using some - * of the join quals as index quals, in which case we don't have to check - * them again at the join node. Remove any join quals that are redundant. + * If the inner path is parameterized, it might have already used some of + * the join quals, in which case we don't have to check them again at the + * join node. Remove any join quals that are redundant. */ joinrestrictclauses = - select_nonredundant_join_clauses(root, - joinrestrictclauses, - best_path->innerjoinpath); + select_nonredundant_join_clauses(joinrestrictclauses, + best_path->innerjoinpath->param_clauses); /* Sort join qual clauses into best execution order */ joinrestrictclauses = order_qual_clauses(root, joinrestrictclauses); @@ -2054,7 +2049,7 @@ create_mergejoin_plan(PlannerInfo *root, /* * We assume the materialize will not spill to disk, and therefore * charge just cpu_operator_cost per tuple. (Keep this estimate in - * sync with cost_mergejoin.) + * sync with final_cost_mergejoin.) */ copy_plan_costsize(matplan, inner_plan); matplan->total_cost += cpu_operator_cost * matplan->plan_rows; @@ -2885,7 +2880,7 @@ copy_path_costsize(Plan *dest, Path *src) { dest->startup_cost = src->startup_cost; dest->total_cost = src->total_cost; - dest->plan_rows = src->parent->rows; + dest->plan_rows = src->rows; dest->plan_width = src->parent->width; } else diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index 4db43dc6aa0b3d70e8b3bb1e57c5f79e9ba83537..68968d9655a43f5b33e578406cc6a72e1995067d 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -375,6 +375,7 @@ query_planner(PlannerInfo *root, List *tlist, sortedpath = get_cheapest_fractional_path_for_pathkeys(final_rel->pathlist, root->query_pathkeys, + NULL, tuple_fraction); /* Don't return same path in both guises; just wastes effort */ diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index b66a508c22da934e96771c99a0a5010dc6c64177..921262948bb8cc5b42c32170c447a0d8008bea34 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -3297,7 +3297,8 @@ plan_cluster_use_sort(Oid tableOid, Oid indexOid) /* Estimate the cost of index scan */ indexScanPath = create_index_path(root, indexInfo, NIL, NIL, NIL, NIL, NIL, - ForwardScanDirection, false, NULL); + ForwardScanDirection, false, + NULL, 1.0); return (seqScanAndSortPath.total_cost < indexScanPath->path.total_cost); } diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index 0ca161a31dc99a3ee7384f47dde75e3fcd628cb5..d29b454f7249e8552ca669795ae36e4ff51dcd49 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -29,6 +29,15 @@ #include "utils/selfuncs.h" +typedef enum +{ + COSTS_EQUAL, /* path costs are fuzzily equal */ + COSTS_BETTER1, /* first path is cheaper than second */ + COSTS_BETTER2, /* second path is cheaper than first */ + COSTS_DIFFERENT /* neither path dominates the other on cost */ +} PathCostComparison; + +static void add_parameterized_path(RelOptInfo *parent_rel, Path *new_path); static List *translate_sub_tlist(List *tlist, int relid); static bool query_is_distinct_for(Query *query, List *colnos, List *opids); static Oid distinct_col_search(int colno, List *colnos, List *opids); @@ -80,58 +89,6 @@ compare_path_costs(Path *path1, Path *path2, CostSelector criterion) return 0; } -/* - * compare_fuzzy_path_costs - * Return -1, 0, or +1 according as path1 is cheaper, the same cost, - * or more expensive than path2 for the specified criterion. - * - * This differs from compare_path_costs in that we consider the costs the - * same if they agree to within a "fuzz factor". This is used by add_path - * to avoid keeping both of a pair of paths that really have insignificantly - * different cost. - */ -static int -compare_fuzzy_path_costs(Path *path1, Path *path2, CostSelector criterion) -{ - /* - * We use a fuzz factor of 1% of the smaller cost. - * - * XXX does this percentage need to be user-configurable? - */ - if (criterion == STARTUP_COST) - { - if (path1->startup_cost > path2->startup_cost * 1.01) - return +1; - if (path2->startup_cost > path1->startup_cost * 1.01) - return -1; - - /* - * If paths have the same startup cost (not at all unlikely), order - * them by total cost. - */ - if (path1->total_cost > path2->total_cost * 1.01) - return +1; - if (path2->total_cost > path1->total_cost * 1.01) - return -1; - } - else - { - if (path1->total_cost > path2->total_cost * 1.01) - return +1; - if (path2->total_cost > path1->total_cost * 1.01) - return -1; - - /* - * If paths have the same total cost, order them by startup cost. - */ - if (path1->startup_cost > path2->startup_cost * 1.01) - return +1; - if (path2->startup_cost > path1->startup_cost * 1.01) - return -1; - } - return 0; -} - /* * compare_path_fractional_costs * Return -1, 0, or +1 according as path1 is cheaper, the same cost, @@ -161,39 +118,120 @@ compare_fractional_path_costs(Path *path1, Path *path2, return 0; } +/* + * compare_path_costs_fuzzily + * Compare the costs of two paths to see if either can be said to + * dominate the other. + * + * We use fuzzy comparisons so that add_path() can avoid keeping both of + * a pair of paths that really have insignificantly different cost. + * The fuzz factor is 1% of the smaller cost. (XXX does this percentage + * need to be user-configurable?) + * + * The two paths are said to have "equal" costs if both startup and total + * costs are fuzzily the same. Path1 is said to be better than path2 if + * it has fuzzily better startup cost and fuzzily no worse total cost, + * or if it has fuzzily better total cost and fuzzily no worse startup cost. + * Path2 is better than path1 if the reverse holds. Finally, if one path + * is fuzzily better than the other on startup cost and fuzzily worse on + * total cost, we just say that their costs are "different", since neither + * dominates the other across the whole performance spectrum. + */ +static PathCostComparison +compare_path_costs_fuzzily(Path *path1, Path *path2) +{ + /* + * Check total cost first since it's more likely to be different; many + * paths have zero startup cost. + */ + if (path1->total_cost > path2->total_cost * 1.01) + { + /* path1 fuzzily worse on total cost */ + if (path2->startup_cost > path1->startup_cost * 1.01) + { + /* ... but path2 fuzzily worse on startup, so DIFFERENT */ + return COSTS_DIFFERENT; + } + /* else path2 dominates */ + return COSTS_BETTER2; + } + if (path2->total_cost > path1->total_cost * 1.01) + { + /* path2 fuzzily worse on total cost */ + if (path1->startup_cost > path2->startup_cost * 1.01) + { + /* ... but path1 fuzzily worse on startup, so DIFFERENT */ + return COSTS_DIFFERENT; + } + /* else path1 dominates */ + return COSTS_BETTER1; + } + /* fuzzily the same on total cost */ + if (path1->startup_cost > path2->startup_cost * 1.01) + { + /* ... but path1 fuzzily worse on startup, so path2 wins */ + return COSTS_BETTER2; + } + if (path2->startup_cost > path1->startup_cost * 1.01) + { + /* ... but path2 fuzzily worse on startup, so path1 wins */ + return COSTS_BETTER1; + } + /* fuzzily the same on both costs */ + return COSTS_EQUAL; +} + /* * set_cheapest * Find the minimum-cost paths from among a relation's paths, * and save them in the rel's cheapest-path fields. * + * Only unparameterized paths are considered candidates for cheapest_startup + * 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. + * * This is normally called only after we've finished constructing the path * list for the rel node. - * - * If we find two paths of identical costs, try to keep the better-sorted one. - * The paths might have unrelated sort orderings, in which case we can only - * guess which might be better to keep, but if one is superior then we - * definitely should keep it. */ void set_cheapest(RelOptInfo *parent_rel) { - List *pathlist = parent_rel->pathlist; - ListCell *p; Path *cheapest_startup_path; Path *cheapest_total_path; + bool have_parameterized_paths; + ListCell *p; Assert(IsA(parent_rel, RelOptInfo)); - if (pathlist == NIL) - elog(ERROR, "could not devise a query plan for the given query"); - - cheapest_startup_path = cheapest_total_path = (Path *) linitial(pathlist); + cheapest_startup_path = cheapest_total_path = NULL; + have_parameterized_paths = false; - for_each_cell(p, lnext(list_head(pathlist))) + foreach(p, parent_rel->pathlist) { Path *path = (Path *) lfirst(p); int cmp; + /* We only consider unparameterized paths in this step */ + if (path->required_outer) + { + have_parameterized_paths = true; + continue; + } + + if (cheapest_total_path == NULL) + { + cheapest_startup_path = cheapest_total_path = path; + continue; + } + + /* + * If we find two paths of identical costs, try to keep the + * better-sorted one. The paths might have unrelated sort orderings, + * in which case we can only guess which might be better to keep, but + * if one is superior then we definitely should keep that one. + */ cmp = compare_path_costs(cheapest_startup_path, path, STARTUP_COST); if (cmp > 0 || (cmp == 0 && @@ -209,9 +247,27 @@ set_cheapest(RelOptInfo *parent_rel) cheapest_total_path = path; } + if (cheapest_total_path == NULL) + 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); + + /* And, if there are any parameterized paths, add them in one at a time */ + if (have_parameterized_paths) + { + foreach(p, parent_rel->pathlist) + { + Path *path = (Path *) lfirst(p); + + if (path->required_outer) + add_parameterized_path(parent_rel, path); + } + } } /* @@ -219,17 +275,25 @@ set_cheapest(RelOptInfo *parent_rel) * Consider a potential implementation path for the specified parent rel, * and add it to the rel's pathlist if it is worthy of consideration. * A path is worthy if it has either a better sort order (better pathkeys) - * or cheaper cost (on either dimension) than any of the existing old paths. + * or cheaper cost (on either dimension) than any of the existing old paths + * that have the same or superset required_outer rels. * * We also remove from the rel's pathlist any old paths that are dominated - * by new_path --- that is, new_path is both cheaper and at least as well - * ordered. + * by new_path --- that is, new_path is cheaper, at least as well ordered, + * and requires no outer rels not required by old path. + * + * There is one policy decision embedded in this function, along with its + * sibling add_path_precheck: we treat all parameterized paths as having + * NIL pathkeys, so that they compete only on cost. This is to reduce + * the number of parameterized paths that are kept. See discussion in + * src/backend/optimizer/README. * - * The pathlist is kept sorted by TOTAL_COST metric, with cheaper paths - * at the front. No code depends on that for correctness; it's simply - * a speed hack within this routine. Doing it that way makes it more - * likely that we will reject an inferior path after a few comparisons, - * rather than many comparisons. + * The pathlist is kept sorted by total_cost, with cheaper paths + * at the front. Within this routine, that's simply a speed hack: + * doing it that way makes it more likely that we will reject an inferior + * path after a few comparisons, rather than many comparisons. + * However, add_path_precheck relies on this ordering to exit early + * when possible. * * NOTE: discarded Path objects are immediately pfree'd to reduce planner * memory consumption. We dare not try to free the substructure of a Path, @@ -250,6 +314,7 @@ add_path(RelOptInfo *parent_rel, Path *new_path) { bool accept_new = true; /* unless we find a superior old path */ ListCell *insert_after = NULL; /* where to insert new item */ + List *new_path_pathkeys; ListCell *p1; ListCell *p1_prev; ListCell *p1_next; @@ -260,6 +325,9 @@ add_path(RelOptInfo *parent_rel, Path *new_path) */ CHECK_FOR_INTERRUPTS(); + /* Pretend parameterized paths have no pathkeys, per comment above */ + new_path_pathkeys = new_path->required_outer ? NIL : new_path->pathkeys; + /* * Loop to check proposed new path against old paths. Note it is possible * for more than one old path to be tossed out because new_path dominates @@ -273,62 +341,99 @@ add_path(RelOptInfo *parent_rel, Path *new_path) { Path *old_path = (Path *) lfirst(p1); bool remove_old = false; /* unless new proves superior */ - int costcmp; + PathCostComparison costcmp; + PathKeysComparison keyscmp; + BMS_Comparison outercmp; p1_next = lnext(p1); - /* - * As of Postgres 8.0, we use fuzzy cost comparison to avoid wasting - * cycles keeping paths that are really not significantly different in - * cost. - */ - costcmp = compare_fuzzy_path_costs(new_path, old_path, TOTAL_COST); + costcmp = compare_path_costs_fuzzily(new_path, old_path); /* * If the two paths compare differently for startup and total cost, - * then we want to keep both, and we can skip the (much slower) - * comparison of pathkeys. If they compare the same, proceed with the - * pathkeys comparison. Note: this test relies on the fact that - * compare_fuzzy_path_costs will only return 0 if both costs are - * effectively equal (and, therefore, there's no need to call it twice - * in that case). + * then we want to keep both, and we can skip comparing pathkeys and + * required_outer rels. If they compare the same, proceed with the + * other comparisons. (We make the tests in this order because the + * cost comparison is most likely to turn out "different", and the + * pathkeys comparison next most likely.) */ - if (costcmp == 0 || - costcmp == compare_fuzzy_path_costs(new_path, old_path, - STARTUP_COST)) + if (costcmp != COSTS_DIFFERENT) { - switch (compare_pathkeys(new_path->pathkeys, old_path->pathkeys)) + /* Similarly check to see if either dominates on pathkeys */ + List *old_path_pathkeys; + + old_path_pathkeys = old_path->required_outer ? NIL : old_path->pathkeys; + keyscmp = compare_pathkeys(new_path_pathkeys, + old_path_pathkeys); + if (keyscmp != PATHKEYS_DIFFERENT) { - case PATHKEYS_EQUAL: - if (costcmp < 0) - remove_old = true; /* new dominates old */ - else if (costcmp > 0) - accept_new = false; /* old dominates new */ - else - { + switch (costcmp) + { + case COSTS_EQUAL: + outercmp = bms_subset_compare(new_path->required_outer, + old_path->required_outer); + if (keyscmp == PATHKEYS_BETTER1) + { + if (outercmp == BMS_EQUAL || + outercmp == BMS_SUBSET1) + remove_old = true; /* new dominates old */ + } + else if (keyscmp == PATHKEYS_BETTER2) + { + if (outercmp == BMS_EQUAL || + outercmp == BMS_SUBSET2) + accept_new = false; /* old dominates new */ + } + else /* keyscmp == PATHKEYS_EQUAL */ + { + if (outercmp == BMS_EQUAL) + { + /* + * Same pathkeys and outer rels, and fuzzily + * the same cost, so keep just one --- but + * we'll do an exact cost comparison to decide + * which. + */ + if (compare_path_costs(new_path, old_path, + TOTAL_COST) < 0) + remove_old = true; /* new dominates old */ + else + accept_new = false; /* old equals or dominates new */ + } + else if (outercmp == BMS_SUBSET1) + remove_old = true; /* new dominates old */ + else if (outercmp == BMS_SUBSET2) + accept_new = false; /* old dominates new */ + /* else different parameterizations, keep both */ + } + break; + case COSTS_BETTER1: + if (keyscmp != PATHKEYS_BETTER2) + { + outercmp = bms_subset_compare(new_path->required_outer, + old_path->required_outer); + if (outercmp == BMS_EQUAL || + outercmp == BMS_SUBSET1) + remove_old = true; /* new dominates old */ + } + break; + case COSTS_BETTER2: + if (keyscmp != PATHKEYS_BETTER1) + { + outercmp = bms_subset_compare(new_path->required_outer, + old_path->required_outer); + if (outercmp == BMS_EQUAL || + outercmp == BMS_SUBSET2) + accept_new = false; /* old dominates new */ + } + break; + case COSTS_DIFFERENT: /* - * Same pathkeys, and fuzzily the same cost, so keep - * just one --- but we'll do an exact cost comparison - * to decide which. + * can't get here, but keep this case to keep compiler + * quiet */ - if (compare_path_costs(new_path, old_path, - TOTAL_COST) < 0) - remove_old = true; /* new dominates old */ - else - accept_new = false; /* old equals or dominates new */ - } - break; - case PATHKEYS_BETTER1: - if (costcmp <= 0) - remove_old = true; /* new dominates old */ - break; - case PATHKEYS_BETTER2: - if (costcmp >= 0) - accept_new = false; /* old dominates new */ - break; - case PATHKEYS_DIFFERENT: - /* keep both paths, since they have different ordering */ - break; + break; + } } } @@ -350,7 +455,7 @@ add_path(RelOptInfo *parent_rel, Path *new_path) else { /* new belongs after this old path if it has cost >= old's */ - if (costcmp >= 0) + if (new_path->total_cost >= old_path->total_cost) insert_after = p1; /* p1_prev advances */ p1_prev = p1; @@ -381,6 +486,185 @@ add_path(RelOptInfo *parent_rel, Path *new_path) } } +/* + * add_path_precheck + * Check whether a proposed new path could possibly get accepted. + * We assume we know the path's pathkeys and parameterization accurately, + * and have lower bounds for its costs. + * + * At the time this is called, we haven't actually built a Path structure, + * so the required information has to be passed piecemeal. + */ +bool +add_path_precheck(RelOptInfo *parent_rel, + Cost startup_cost, Cost total_cost, + List *pathkeys, Relids required_outer) +{ + List *new_path_pathkeys; + ListCell *p1; + + /* Pretend parameterized paths have no pathkeys, per comment above */ + new_path_pathkeys = required_outer ? NIL : pathkeys; + + foreach(p1, parent_rel->pathlist) + { + Path *old_path = (Path *) lfirst(p1); + PathKeysComparison keyscmp; + BMS_Comparison outercmp; + + /* + * We are looking for an old_path that dominates the new path across + * all four metrics. If we find one, we can reject the new path. + * + * For speed, we make exact rather than fuzzy cost comparisons. + * If an old path dominates the new path exactly on both costs, it + * will surely do so fuzzily. + */ + if (total_cost >= old_path->total_cost) + { + if (startup_cost >= old_path->startup_cost) + { + List *old_path_pathkeys; + + old_path_pathkeys = old_path->required_outer ? NIL : old_path->pathkeys; + keyscmp = compare_pathkeys(new_path_pathkeys, + old_path_pathkeys); + if (keyscmp == PATHKEYS_EQUAL || + keyscmp == PATHKEYS_BETTER2) + { + outercmp = bms_subset_compare(required_outer, + old_path->required_outer); + if (outercmp == BMS_EQUAL || + outercmp == BMS_SUBSET2) + return false; + } + } + } + else + { + /* + * Since the pathlist is sorted by total_cost, we can stop + * looking once we reach a path with a total_cost larger + * than the new path's. + */ + break; + } + } + + return true; +} + +/* + * add_parameterized_path + * Consider a parameterized implementation path for the specified rel, + * and add it to the rel's cheapest_parameterized_paths list if it + * belongs there, removing any old entries that it dominates. + * + * This is essentially a cut-down form of add_path(): we do not care about + * startup cost or sort ordering, only total cost and parameterization. + * Also, we should not recycle rejected paths, since they will still be + * present in the rel's pathlist. + * + * 'parent_rel' is the relation entry to which the path corresponds. + * 'new_path' is a parameterized path for parent_rel. + * + * Returns nothing, but modifies parent_rel->cheapest_parameterized_paths. + */ +static void +add_parameterized_path(RelOptInfo *parent_rel, Path *new_path) +{ + bool accept_new = true; /* unless we find a superior old path */ + ListCell *insert_after = NULL; /* where to insert new item */ + ListCell *p1; + ListCell *p1_prev; + ListCell *p1_next; + + /* + * Loop to check proposed new path against old paths. Note it is possible + * for more than one old path to be tossed out because new_path dominates + * it. + * + * We can't use foreach here because the loop body may delete the current + * list cell. + */ + p1_prev = NULL; + for (p1 = list_head(parent_rel->cheapest_parameterized_paths); + p1 != NULL; p1 = p1_next) + { + Path *old_path = (Path *) lfirst(p1); + bool remove_old = false; /* unless new proves superior */ + int costcmp; + BMS_Comparison outercmp; + + p1_next = lnext(p1); + + costcmp = compare_path_costs(new_path, old_path, TOTAL_COST); + outercmp = bms_subset_compare(new_path->required_outer, + old_path->required_outer); + if (outercmp != BMS_DIFFERENT) + { + if (costcmp < 0) + { + if (outercmp != BMS_SUBSET2) + remove_old = true; /* new dominates old */ + } + else if (costcmp > 0) + { + if (outercmp != BMS_SUBSET1) + accept_new = false; /* old dominates new */ + } + else if (outercmp == BMS_SUBSET1) + remove_old = true; /* new dominates old */ + else if (outercmp == BMS_SUBSET2) + accept_new = false; /* old dominates new */ + else + { + /* Same cost and outer rels, arbitrarily keep the old */ + accept_new = false; /* old equals or dominates new */ + } + } + + /* + * Remove current element from cheapest_parameterized_paths if + * dominated by new. + */ + if (remove_old) + { + parent_rel->cheapest_parameterized_paths = + list_delete_cell(parent_rel->cheapest_parameterized_paths, + p1, p1_prev); + /* p1_prev does not advance */ + } + else + { + /* new belongs after this old path if it has cost >= old's */ + if (costcmp >= 0) + insert_after = p1; + /* p1_prev advances */ + p1_prev = p1; + } + + /* + * If we found an old path that dominates new_path, we can quit + * scanning the list; we will not add new_path, and we assume + * new_path cannot dominate any other elements of the list. + */ + if (!accept_new) + break; + } + + if (accept_new) + { + /* Accept the new path: insert it at proper place in list */ + if (insert_after) + lappend_cell(parent_rel->cheapest_parameterized_paths, + insert_after, new_path); + else + parent_rel->cheapest_parameterized_paths = + lcons(new_path, parent_rel->cheapest_parameterized_paths); + } +} + /***************************************************************************** * PATH NODE CREATION ROUTINES @@ -399,6 +683,8 @@ create_seqscan_path(PlannerInfo *root, RelOptInfo *rel) pathnode->pathtype = T_SeqScan; pathnode->parent = rel; pathnode->pathkeys = NIL; /* seqscan has unordered result */ + pathnode->required_outer = NULL; + pathnode->param_clauses = NIL; cost_seqscan(pathnode, root, rel); @@ -423,8 +709,9 @@ create_seqscan_path(PlannerInfo *root, RelOptInfo *rel) * for an ordered index, or NoMovementScanDirection for * an unordered index. * 'indexonly' is true if an index-only scan is wanted. - * 'outer_rel' is the outer relation if this is a join inner indexscan path. - * (pathkeys and indexscandir are ignored if so.) NULL if not. + * 'required_outer' is the set of outer relids referenced in indexclauses. + * 'loop_count' is the number of repetitions of the indexscan to factor into + * estimates of caching behavior. * * Returns the new path node. */ @@ -438,29 +725,35 @@ create_index_path(PlannerInfo *root, List *pathkeys, ScanDirection indexscandir, bool indexonly, - RelOptInfo *outer_rel) + Relids required_outer, + double loop_count) { IndexPath *pathnode = makeNode(IndexPath); RelOptInfo *rel = index->rel; List *indexquals, *indexqualcols; - /* - * For a join inner scan, there's no point in marking the path with any - * pathkeys, since it will only ever be used as the inner path of a - * nestloop, and so its ordering does not matter. For the same reason we - * don't really care what order it's scanned in. (We could expect the - * caller to supply the correct values, but it's easier to force it here.) - */ - if (outer_rel != NULL) - { - pathkeys = NIL; - indexscandir = NoMovementScanDirection; - } - pathnode->path.pathtype = indexonly ? T_IndexOnlyScan : T_IndexScan; pathnode->path.parent = rel; pathnode->path.pathkeys = pathkeys; + pathnode->path.required_outer = required_outer; + if (required_outer) + { + /* Identify index clauses that are join clauses */ + List *jclauses = NIL; + ListCell *lc; + + foreach(lc, indexclauses) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + + if (!bms_is_subset(rinfo->clause_relids, rel->relids)) + jclauses = lappend(jclauses, rinfo); + } + pathnode->path.param_clauses = jclauses; + } + else + pathnode->path.param_clauses = NIL; /* Convert clauses to indexquals the executor can handle */ expand_indexqual_conditions(index, indexclauses, indexclausecols, @@ -473,50 +766,9 @@ create_index_path(PlannerInfo *root, pathnode->indexqualcols = indexqualcols; pathnode->indexorderbys = indexorderbys; pathnode->indexorderbycols = indexorderbycols; - - pathnode->isjoininner = (outer_rel != NULL); pathnode->indexscandir = indexscandir; - if (outer_rel != NULL) - { - /* - * We must compute the estimated number of output rows for the - * indexscan. This is less than rel->rows because of the additional - * selectivity of the join clauses. Since indexclauses may contain - * both restriction and join clauses, we have to do a set union to get - * the full set of clauses that must be considered to compute the - * correct selectivity. (Without the union operation, we might have - * some restriction clauses appearing twice, which'd mislead - * clauselist_selectivity into double-counting their selectivity. - * However, since RestrictInfo nodes aren't copied when linking them - * into different lists, it should be sufficient to use pointer - * comparison to remove duplicates.) - * - * Note that we force the clauses to be treated as non-join clauses - * during selectivity estimation. - */ - List *allclauses; - - allclauses = list_union_ptr(rel->baserestrictinfo, indexclauses); - pathnode->rows = rel->tuples * - clauselist_selectivity(root, - allclauses, - rel->relid, /* do not use 0! */ - JOIN_INNER, - NULL); - /* Like costsize.c, force estimate to be at least one row */ - pathnode->rows = clamp_row_est(pathnode->rows); - } - else - { - /* - * The number of rows is the same as the parent rel's estimate, since - * this isn't a join inner indexscan. - */ - pathnode->rows = rel->rows; - } - - cost_index(pathnode, root, outer_rel); + cost_index(pathnode, root, loop_count); return pathnode; } @@ -526,55 +778,29 @@ create_index_path(PlannerInfo *root, * Creates a path node for a bitmap scan. * * 'bitmapqual' is a tree of IndexPath, BitmapAndPath, and BitmapOrPath nodes. + * 'loop_count' is the number of repetitions of the indexscan to factor into + * estimates of caching behavior. * - * If this is a join inner indexscan path, 'outer_rel' is the outer relation, - * and all the component IndexPaths should have been costed accordingly. + * loop_count should match the value used when creating the component + * IndexPaths. */ BitmapHeapPath * create_bitmap_heap_path(PlannerInfo *root, RelOptInfo *rel, Path *bitmapqual, - RelOptInfo *outer_rel) + double loop_count) { BitmapHeapPath *pathnode = makeNode(BitmapHeapPath); pathnode->path.pathtype = T_BitmapHeapScan; pathnode->path.parent = rel; pathnode->path.pathkeys = NIL; /* always unordered */ + pathnode->path.required_outer = bitmapqual->required_outer; + pathnode->path.param_clauses = bitmapqual->param_clauses; pathnode->bitmapqual = bitmapqual; - pathnode->isjoininner = (outer_rel != NULL); - - if (pathnode->isjoininner) - { - /* - * We must compute the estimated number of output rows for the - * indexscan. This is less than rel->rows because of the additional - * selectivity of the join clauses. We make use of the selectivity - * estimated for the bitmap to do this; this isn't really quite right - * since there may be restriction conditions not included in the - * bitmap ... - */ - Cost indexTotalCost; - Selectivity indexSelectivity; - - cost_bitmap_tree_node(bitmapqual, &indexTotalCost, &indexSelectivity); - pathnode->rows = rel->tuples * indexSelectivity; - if (pathnode->rows > rel->rows) - pathnode->rows = rel->rows; - /* Like costsize.c, force estimate to be at least one row */ - pathnode->rows = clamp_row_est(pathnode->rows); - } - else - { - /* - * The number of rows is the same as the parent rel's estimate, since - * this isn't a join inner indexscan. - */ - pathnode->rows = rel->rows; - } - cost_bitmap_heap_scan(&pathnode->path, root, rel, bitmapqual, outer_rel); + cost_bitmap_heap_scan(&pathnode->path, root, rel, bitmapqual, loop_count); return pathnode; } @@ -589,13 +815,29 @@ create_bitmap_and_path(PlannerInfo *root, List *bitmapquals) { BitmapAndPath *pathnode = makeNode(BitmapAndPath); + ListCell *lc; pathnode->path.pathtype = T_BitmapAnd; pathnode->path.parent = rel; pathnode->path.pathkeys = NIL; /* always unordered */ + pathnode->path.required_outer = NULL; + pathnode->path.param_clauses = NIL; pathnode->bitmapquals = bitmapquals; + /* required_outer and param_clauses are the union of the inputs' values */ + foreach(lc, bitmapquals) + { + Path *bpath = (Path *) lfirst(lc); + + pathnode->path.required_outer = + bms_add_members(pathnode->path.required_outer, + bpath->required_outer); + pathnode->path.param_clauses = + list_concat(pathnode->path.param_clauses, + list_copy(bpath->param_clauses)); + } + /* this sets bitmapselectivity as well as the regular cost fields: */ cost_bitmap_and_node(pathnode, root); @@ -612,13 +854,29 @@ create_bitmap_or_path(PlannerInfo *root, List *bitmapquals) { BitmapOrPath *pathnode = makeNode(BitmapOrPath); + ListCell *lc; pathnode->path.pathtype = T_BitmapOr; pathnode->path.parent = rel; pathnode->path.pathkeys = NIL; /* always unordered */ + pathnode->path.required_outer = NULL; + pathnode->path.param_clauses = NIL; pathnode->bitmapquals = bitmapquals; + /* required_outer and param_clauses are the union of the inputs' values */ + foreach(lc, bitmapquals) + { + Path *bpath = (Path *) lfirst(lc); + + pathnode->path.required_outer = + bms_add_members(pathnode->path.required_outer, + bpath->required_outer); + pathnode->path.param_clauses = + list_concat(pathnode->path.param_clauses, + list_copy(bpath->param_clauses)); + } + /* this sets bitmapselectivity as well as the regular cost fields: */ cost_bitmap_or_node(pathnode, root); @@ -637,6 +895,8 @@ create_tidscan_path(PlannerInfo *root, RelOptInfo *rel, List *tidquals) pathnode->path.pathtype = T_TidScan; pathnode->path.parent = rel; pathnode->path.pathkeys = NIL; + pathnode->path.required_outer = NULL; + pathnode->path.param_clauses = NIL; pathnode->tidquals = tidquals; @@ -662,24 +922,46 @@ create_append_path(RelOptInfo *rel, List *subpaths) pathnode->path.parent = rel; pathnode->path.pathkeys = NIL; /* result is always considered * unsorted */ + pathnode->path.required_outer = NULL; /* updated below */ + pathnode->path.param_clauses = NIL; /* XXX see below */ pathnode->subpaths = subpaths; /* - * Compute cost as sum of subplan costs. We charge nothing extra for the - * Append itself, which perhaps is too optimistic, but since it doesn't do - * any selection or projection, it is a pretty cheap node. + * We don't bother with inventing a cost_append(), but just do it here. + * + * Compute rows and costs as sums of subplan rows and costs. We charge + * nothing extra for the Append itself, which perhaps is too optimistic, + * but since it doesn't do any selection or projection, it is a pretty + * cheap node. If you change this, see also make_append(). * - * If you change this, see also make_append(). + * We also compute the correct required_outer set, namely the union of + * the input paths' requirements. + * + * XXX We should also compute a proper param_clauses list, but that + * will require identifying which joinclauses are enforced by all the + * subplans, as well as locating the original parent RestrictInfo from + * which they were generated. For the moment we punt and leave the list + * as NIL. This will result in uselessly rechecking such joinclauses + * at the parameter-supplying nestloop join, which is slightly annoying, + * as well as overestimating the sizes of any intermediate joins, which + * is significantly more annoying. */ + pathnode->path.rows = 0; pathnode->path.startup_cost = 0; pathnode->path.total_cost = 0; foreach(l, subpaths) { Path *subpath = (Path *) lfirst(l); + pathnode->path.rows += subpath->rows; + if (l == list_head(subpaths)) /* first node? */ pathnode->path.startup_cost = subpath->startup_cost; pathnode->path.total_cost += subpath->total_cost; + + pathnode->path.required_outer = + bms_add_members(pathnode->path.required_outer, + subpath->required_outer); } return pathnode; @@ -704,6 +986,8 @@ create_merge_append_path(PlannerInfo *root, pathnode->path.pathtype = T_MergeAppend; pathnode->path.parent = rel; pathnode->path.pathkeys = pathkeys; + pathnode->path.required_outer = NULL; /* updated below */ + pathnode->path.param_clauses = NIL; /* XXX see below */ pathnode->subpaths = subpaths; /* @@ -735,13 +1019,22 @@ create_merge_append_path(PlannerInfo *root, } } - /* Add up all the costs of the input paths */ + /* + * Add up the sizes and costs of the input paths, and also compute the + * real required_outer value. + * + * XXX as in create_append_path(), we should compute param_clauses but + * it will require more work. + */ + pathnode->path.rows = 0; input_startup_cost = 0; input_total_cost = 0; foreach(l, subpaths) { Path *subpath = (Path *) lfirst(l); + pathnode->path.rows += subpath->rows; + if (pathkeys_contained_in(pathkeys, subpath->pathkeys)) { /* Subpath is adequately ordered, we won't need to sort it */ @@ -765,6 +1058,10 @@ create_merge_append_path(PlannerInfo *root, input_startup_cost += sort_path.startup_cost; input_total_cost += sort_path.total_cost; } + + pathnode->path.required_outer = + bms_add_members(pathnode->path.required_outer, + subpath->required_outer); } /* Now we can compute total costs of the MergeAppend */ @@ -789,9 +1086,12 @@ create_result_path(List *quals) pathnode->path.pathtype = T_Result; pathnode->path.parent = NULL; pathnode->path.pathkeys = NIL; + pathnode->path.required_outer = NULL; + pathnode->path.param_clauses = NIL; pathnode->quals = quals; - /* Ideally should define cost_result(), but I'm too lazy */ + /* Hardly worth defining a cost_result() function ... just do it */ + pathnode->path.rows = 1; pathnode->path.startup_cost = 0; pathnode->path.total_cost = cpu_tuple_cost; @@ -817,15 +1117,16 @@ create_material_path(RelOptInfo *rel, Path *subpath) pathnode->path.pathtype = T_Material; pathnode->path.parent = rel; - pathnode->path.pathkeys = subpath->pathkeys; + pathnode->path.required_outer = subpath->required_outer; + pathnode->path.param_clauses = subpath->param_clauses; pathnode->subpath = subpath; cost_material(&pathnode->path, subpath->startup_cost, subpath->total_cost, - rel->rows, + subpath->rows, rel->width); return pathnode; @@ -874,7 +1175,6 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath, /* * We must ensure path struct and subsidiary data are allocated in main * planning context; otherwise GEQO memory management causes trouble. - * (Compare best_inner_indexscan().) */ oldcontext = MemoryContextSwitchTo(root->planner_cxt); @@ -1032,6 +1332,8 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath, * to represent it. (This might get overridden below.) */ pathnode->path.pathkeys = NIL; + pathnode->path.required_outer = subpath->required_outer; + pathnode->path.param_clauses = subpath->param_clauses; pathnode->subpath = subpath; pathnode->in_operators = in_operators; @@ -1048,7 +1350,7 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath, uniq_exprs, in_operators)) { pathnode->umethod = UNIQUE_PATH_NOOP; - pathnode->rows = rel->rows; + pathnode->path.rows = rel->rows; pathnode->path.startup_cost = subpath->startup_cost; pathnode->path.total_cost = subpath->total_cost; pathnode->path.pathkeys = subpath->pathkeys; @@ -1081,7 +1383,7 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath, sub_tlist_colnos, in_operators)) { pathnode->umethod = UNIQUE_PATH_NOOP; - pathnode->rows = rel->rows; + pathnode->path.rows = rel->rows; pathnode->path.startup_cost = subpath->startup_cost; pathnode->path.total_cost = subpath->total_cost; pathnode->path.pathkeys = subpath->pathkeys; @@ -1095,7 +1397,7 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath, } /* Estimate number of output rows */ - pathnode->rows = estimate_num_groups(root, uniq_exprs, rel->rows); + pathnode->path.rows = estimate_num_groups(root, uniq_exprs, rel->rows); numCols = list_length(uniq_exprs); if (all_btree) @@ -1128,12 +1430,12 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath, */ int hashentrysize = rel->width + 64; - if (hashentrysize * pathnode->rows > work_mem * 1024L) + if (hashentrysize * pathnode->path.rows > work_mem * 1024L) all_hash = false; /* don't try to hash */ else cost_agg(&agg_path, root, AGG_HASHED, NULL, - numCols, pathnode->rows, + numCols, pathnode->path.rows, subpath->startup_cost, subpath->total_cost, rel->rows); @@ -1367,6 +1669,8 @@ create_subqueryscan_path(RelOptInfo *rel, List *pathkeys) pathnode->pathtype = T_SubqueryScan; pathnode->parent = rel; pathnode->pathkeys = pathkeys; + pathnode->required_outer = NULL; + pathnode->param_clauses = NIL; cost_subqueryscan(pathnode, rel); @@ -1386,6 +1690,8 @@ create_functionscan_path(PlannerInfo *root, RelOptInfo *rel) pathnode->pathtype = T_FunctionScan; pathnode->parent = rel; pathnode->pathkeys = NIL; /* for now, assume unordered result */ + pathnode->required_outer = NULL; + pathnode->param_clauses = NIL; cost_functionscan(pathnode, root, rel); @@ -1405,6 +1711,8 @@ create_valuesscan_path(PlannerInfo *root, RelOptInfo *rel) pathnode->pathtype = T_ValuesScan; pathnode->parent = rel; pathnode->pathkeys = NIL; /* result is always unordered */ + pathnode->required_outer = NULL; + pathnode->param_clauses = NIL; cost_valuesscan(pathnode, root, rel); @@ -1424,6 +1732,8 @@ create_ctescan_path(PlannerInfo *root, RelOptInfo *rel) pathnode->pathtype = T_CteScan; pathnode->parent = rel; pathnode->pathkeys = NIL; /* XXX for now, result is always unordered */ + pathnode->required_outer = NULL; + pathnode->param_clauses = NIL; cost_ctescan(pathnode, root, rel); @@ -1443,6 +1753,8 @@ create_worktablescan_path(PlannerInfo *root, RelOptInfo *rel) pathnode->pathtype = T_WorkTableScan; pathnode->parent = rel; pathnode->pathkeys = NIL; /* result is always unordered */ + pathnode->required_outer = NULL; + pathnode->param_clauses = NIL; /* Cost is the same as for a regular CTE scan */ cost_ctescan(pathnode, root, rel); @@ -1466,6 +1778,8 @@ create_foreignscan_path(PlannerInfo *root, RelOptInfo *rel) pathnode->path.pathtype = T_ForeignScan; pathnode->path.parent = rel; pathnode->path.pathkeys = NIL; /* result is always unordered */ + pathnode->path.required_outer = NULL; + pathnode->path.param_clauses = NIL; /* Get FDW's callback info */ rte = planner_rt_fetch(rel->relid, root); @@ -1479,12 +1793,68 @@ create_foreignscan_path(PlannerInfo *root, RelOptInfo *rel) pathnode->fdwplan = fdwplan; /* use costs estimated by FDW */ + pathnode->path.rows = rel->rows; pathnode->path.startup_cost = fdwplan->startup_cost; pathnode->path.total_cost = fdwplan->total_cost; return pathnode; } +/* + * calc_nestloop_required_outer + * Compute the required_outer set for a nestloop join path + * + * Note: result must not share storage with either input + */ +Relids +calc_nestloop_required_outer(Path *outer_path, Path *inner_path) +{ + Relids required_outer; + + /* inner_path can require rels from outer path, but not vice versa */ + Assert(!bms_overlap(outer_path->required_outer, + inner_path->parent->relids)); + /* easy case if inner path is not parameterized */ + if (!inner_path->required_outer) + return bms_copy(outer_path->required_outer); + /* else, form the union ... */ + required_outer = bms_union(outer_path->required_outer, + inner_path->required_outer); + /* ... and remove any mention of now-satisfied outer rels */ + required_outer = bms_del_members(required_outer, + outer_path->parent->relids); + /* maintain invariant that required_outer is exactly NULL if empty */ + if (bms_is_empty(required_outer)) + { + bms_free(required_outer); + required_outer = NULL; + } + return required_outer; +} + +/* + * calc_non_nestloop_required_outer + * Compute the required_outer set for a merge or hash join path + * + * Note: result must not share storage with either input + */ +Relids +calc_non_nestloop_required_outer(Path *outer_path, Path *inner_path) +{ + Relids required_outer; + + /* neither path can require rels from the other */ + Assert(!bms_overlap(outer_path->required_outer, + inner_path->parent->relids)); + Assert(!bms_overlap(inner_path->required_outer, + outer_path->parent->relids)); + /* form the union ... */ + required_outer = bms_union(outer_path->required_outer, + inner_path->required_outer); + /* we do not need an explicit test for empty; bms_union gets it right */ + return required_outer; +} + /* * create_nestloop_path * Creates a pathnode corresponding to a nestloop join between two @@ -1492,11 +1862,14 @@ create_foreignscan_path(PlannerInfo *root, RelOptInfo *rel) * * 'joinrel' is the join relation. * 'jointype' is the type of join required + * 'workspace' is the result from initial_cost_nestloop * 'sjinfo' is extra info about the join for selectivity estimation + * 'semifactors' contains valid data if jointype is SEMI or ANTI * 'outer_path' is the outer path * 'inner_path' is the inner path * 'restrict_clauses' are the RestrictInfo nodes to apply at the join * 'pathkeys' are the path keys of the new join path + * 'required_outer' is the set of required outer rels * * Returns the resulting path node. */ @@ -1504,23 +1877,46 @@ NestPath * create_nestloop_path(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype, + JoinCostWorkspace *workspace, SpecialJoinInfo *sjinfo, + SemiAntiJoinFactors *semifactors, Path *outer_path, Path *inner_path, List *restrict_clauses, - List *pathkeys) + List *pathkeys, + Relids required_outer) { NestPath *pathnode = makeNode(NestPath); pathnode->path.pathtype = T_NestLoop; pathnode->path.parent = joinrel; + pathnode->path.pathkeys = pathkeys; + pathnode->path.required_outer = required_outer; + if (pathnode->path.required_outer) + { + /* Identify parameter clauses not yet applied here */ + List *jclauses; + ListCell *lc; + + /* LHS clauses could not be satisfied here */ + jclauses = list_copy(outer_path->param_clauses); + foreach(lc, inner_path->param_clauses) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + + if (!bms_is_subset(rinfo->clause_relids, joinrel->relids)) + jclauses = lappend(jclauses, rinfo); + } + pathnode->path.param_clauses = jclauses; + } + else + pathnode->path.param_clauses = NIL; pathnode->jointype = jointype; pathnode->outerjoinpath = outer_path; pathnode->innerjoinpath = inner_path; pathnode->joinrestrictinfo = restrict_clauses; - pathnode->path.pathkeys = pathkeys; - cost_nestloop(pathnode, root, sjinfo); + final_cost_nestloop(root, pathnode, workspace, sjinfo, semifactors); return pathnode; } @@ -1532,11 +1928,13 @@ create_nestloop_path(PlannerInfo *root, * * 'joinrel' is the join relation * 'jointype' is the type of join required + * 'workspace' is the result from initial_cost_mergejoin * 'sjinfo' is extra info about the join for selectivity estimation * 'outer_path' is the outer path * 'inner_path' is the inner path * 'restrict_clauses' are the RestrictInfo nodes to apply at the join * 'pathkeys' are the path keys of the new join path + * 'required_outer' is the set of required outer rels * 'mergeclauses' are the RestrictInfo nodes to use as merge clauses * (this should be a subset of the restrict_clauses list) * 'outersortkeys' are the sort varkeys for the outer relation @@ -1546,41 +1944,36 @@ MergePath * create_mergejoin_path(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype, + JoinCostWorkspace *workspace, SpecialJoinInfo *sjinfo, Path *outer_path, Path *inner_path, List *restrict_clauses, List *pathkeys, + Relids required_outer, List *mergeclauses, List *outersortkeys, List *innersortkeys) { MergePath *pathnode = makeNode(MergePath); - /* - * If the given paths are already well enough ordered, we can skip doing - * an explicit sort. - */ - if (outersortkeys && - pathkeys_contained_in(outersortkeys, outer_path->pathkeys)) - outersortkeys = NIL; - if (innersortkeys && - pathkeys_contained_in(innersortkeys, inner_path->pathkeys)) - innersortkeys = NIL; - pathnode->jpath.path.pathtype = T_MergeJoin; pathnode->jpath.path.parent = joinrel; + pathnode->jpath.path.pathkeys = pathkeys; + pathnode->jpath.path.required_outer = required_outer; + pathnode->jpath.path.param_clauses = + list_concat(list_copy(outer_path->param_clauses), + inner_path->param_clauses); pathnode->jpath.jointype = jointype; pathnode->jpath.outerjoinpath = outer_path; pathnode->jpath.innerjoinpath = inner_path; pathnode->jpath.joinrestrictinfo = restrict_clauses; - pathnode->jpath.path.pathkeys = pathkeys; pathnode->path_mergeclauses = mergeclauses; pathnode->outersortkeys = outersortkeys; pathnode->innersortkeys = innersortkeys; - /* pathnode->materialize_inner will be set by cost_mergejoin */ + /* pathnode->materialize_inner will be set by final_cost_mergejoin */ - cost_mergejoin(pathnode, root, sjinfo); + final_cost_mergejoin(root, pathnode, workspace, sjinfo); return pathnode; } @@ -1591,10 +1984,13 @@ create_mergejoin_path(PlannerInfo *root, * * 'joinrel' is the join relation * 'jointype' is the type of join required + * 'workspace' is the result from initial_cost_hashjoin * 'sjinfo' is extra info about the join for selectivity estimation + * 'semifactors' contains valid data if jointype is SEMI or ANTI * 'outer_path' is the cheapest outer path * 'inner_path' is the cheapest inner path * 'restrict_clauses' are the RestrictInfo nodes to apply at the join + * 'required_outer' is the set of required outer rels * 'hashclauses' are the RestrictInfo nodes to use as hash clauses * (this should be a subset of the restrict_clauses list) */ @@ -1602,20 +1998,19 @@ HashPath * create_hashjoin_path(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype, + JoinCostWorkspace *workspace, SpecialJoinInfo *sjinfo, + SemiAntiJoinFactors *semifactors, Path *outer_path, Path *inner_path, List *restrict_clauses, + Relids required_outer, List *hashclauses) { HashPath *pathnode = makeNode(HashPath); pathnode->jpath.path.pathtype = T_HashJoin; pathnode->jpath.path.parent = joinrel; - pathnode->jpath.jointype = jointype; - pathnode->jpath.outerjoinpath = outer_path; - pathnode->jpath.innerjoinpath = inner_path; - pathnode->jpath.joinrestrictinfo = restrict_clauses; /* * A hashjoin never has pathkeys, since its output ordering is @@ -1629,10 +2024,18 @@ create_hashjoin_path(PlannerInfo *root, * outer rel than it does now.) */ pathnode->jpath.path.pathkeys = NIL; + pathnode->jpath.path.required_outer = required_outer; + pathnode->jpath.path.param_clauses = + list_concat(list_copy(outer_path->param_clauses), + inner_path->param_clauses); + pathnode->jpath.jointype = jointype; + pathnode->jpath.outerjoinpath = outer_path; + pathnode->jpath.innerjoinpath = inner_path; + pathnode->jpath.joinrestrictinfo = restrict_clauses; pathnode->path_hashclauses = hashclauses; - /* cost_hashjoin will fill in pathnode->num_batches */ + /* final_cost_hashjoin will fill in pathnode->num_batches */ - cost_hashjoin(pathnode, root, sjinfo); + final_cost_hashjoin(root, pathnode, workspace, sjinfo, semifactors); return pathnode; } diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index e9155efb3433a189419fbd1967ae5e55dd895f39..0cdf638c1ddb1c614eb8ef3cd2ddea4571248ff6 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -103,6 +103,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind) rel->cheapest_startup_path = NULL; rel->cheapest_total_path = NULL; rel->cheapest_unique_path = NULL; + rel->cheapest_parameterized_paths = NIL; rel->relid = relid; rel->rtekind = rte->rtekind; /* min_attr, max_attr, attr_needed, attr_widths are set below */ @@ -117,8 +118,6 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind) rel->baserestrictcost.per_tuple = 0; rel->joininfo = NIL; rel->has_eclass_joins = false; - rel->index_outer_relids = NULL; - rel->index_inner_paths = NIL; /* Check type of rtable entry */ switch (rte->rtekind) @@ -354,6 +353,7 @@ build_join_rel(PlannerInfo *root, joinrel->cheapest_startup_path = NULL; joinrel->cheapest_total_path = NULL; joinrel->cheapest_unique_path = NULL; + joinrel->cheapest_parameterized_paths = NIL; joinrel->relid = 0; /* indicates not a baserel */ joinrel->rtekind = RTE_JOIN; joinrel->min_attr = 0; @@ -371,8 +371,6 @@ build_join_rel(PlannerInfo *root, joinrel->baserestrictcost.per_tuple = 0; joinrel->joininfo = NIL; joinrel->has_eclass_joins = false; - joinrel->index_outer_relids = NULL; - joinrel->index_inner_paths = NIL; /* * Create a new tlist containing just the vars that need to be output from diff --git a/src/backend/optimizer/util/restrictinfo.c b/src/backend/optimizer/util/restrictinfo.c index d9c96020f14d650987f2b470da397dfa2ac17cb1..7d03d91f5d3b6f90de576021542fea9b21ca4122 100644 --- a/src/backend/optimizer/util/restrictinfo.c +++ b/src/backend/optimizer/util/restrictinfo.c @@ -33,8 +33,6 @@ static Expr *make_sub_restrictinfos(Expr *clause, bool pseudoconstant, Relids required_relids, Relids nullable_relids); -static List *select_nonredundant_join_list(List *restrictinfo_list, - List *reference_list); static bool join_clause_is_redundant(RestrictInfo *rinfo, List *reference_list); @@ -623,11 +621,14 @@ extract_actual_join_clauses(List *restrictinfo_list, /* * select_nonredundant_join_clauses + * Select the members of restrictinfo_list that are not redundant with + * any member of reference_list. * * Given a list of RestrictInfo clauses that are to be applied in a join, - * select the ones that are not redundant with any clause that's enforced - * by the inner_path. This is used for nestloop joins, wherein any clause - * being used in an inner indexscan need not be checked again at the join. + * select the ones that are not redundant with any clause that's listed in + * reference_list. This is used, for example, to avoid checking joinclauses + * again at a nestloop join when they've already been enforced by a + * parameterized inner path. * * "Redundant" means either equal() or derived from the same EquivalenceClass. * We have to check the latter because indxpath.c may select different derived @@ -637,78 +638,16 @@ extract_actual_join_clauses(List *restrictinfo_list, * restrictinfo_list; that should have been handled elsewhere. */ List * -select_nonredundant_join_clauses(PlannerInfo *root, - List *restrictinfo_list, - Path *inner_path) -{ - if (IsA(inner_path, IndexPath)) - { - /* - * Check the index quals to see if any of them are join clauses. - * - * We can skip this if the index path is an ordinary indexpath and not - * a special innerjoin path, since it then wouldn't be using any join - * clauses. - */ - IndexPath *innerpath = (IndexPath *) inner_path; - - if (innerpath->isjoininner) - restrictinfo_list = - select_nonredundant_join_list(restrictinfo_list, - innerpath->indexclauses); - } - else if (IsA(inner_path, BitmapHeapPath)) - { - /* - * Same deal for bitmapped index scans. - * - * Note: both here and above, we ignore any implicit index - * restrictions associated with the use of partial indexes. This is - * OK because we're only trying to prove we can dispense with some - * join quals; failing to prove that doesn't result in an incorrect - * plan. It's quite unlikely that a join qual could be proven - * redundant by an index predicate anyway. (Also, if we did manage to - * prove it, we'd have to have a special case for update targets; see - * notes about EvalPlanQual testing in create_indexscan_plan().) - */ - BitmapHeapPath *innerpath = (BitmapHeapPath *) inner_path; - - if (innerpath->isjoininner) - { - List *bitmapclauses; - - bitmapclauses = - make_restrictinfo_from_bitmapqual(innerpath->bitmapqual, - true, - false); - restrictinfo_list = - select_nonredundant_join_list(restrictinfo_list, - bitmapclauses); - } - } - - /* - * XXX the inner path of a nestloop could also be an append relation whose - * elements use join quals. However, they might each use different quals; - * we could only remove join quals that are enforced by all the appendrel - * members. For the moment we don't bother to try. - */ - - return restrictinfo_list; -} - -/* - * select_nonredundant_join_list - * Select the members of restrictinfo_list that are not redundant with - * any member of reference_list. See above for more info. - */ -static List * -select_nonredundant_join_list(List *restrictinfo_list, - List *reference_list) +select_nonredundant_join_clauses(List *restrictinfo_list, + List *reference_list) { List *result = NIL; ListCell *item; + /* Quick out if nothing could be removed */ + if (reference_list == NIL) + return restrictinfo_list; + foreach(item, restrictinfo_list) { RestrictInfo *rinfo = (RestrictInfo *) lfirst(item); diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c index da638f885aa5c381220f0e53688dd48d52cd86bf..09c9240490218b0525389465d6f4db6d46498cff 100644 --- a/src/backend/utils/adt/selfuncs.c +++ b/src/backend/utils/adt/selfuncs.c @@ -5971,7 +5971,7 @@ string_to_bytea_const(const char *str, size_t str_len) static void genericcostestimate(PlannerInfo *root, IndexPath *path, - RelOptInfo *outer_rel, + double loop_count, double numIndexTuples, Cost *indexStartupCost, Cost *indexTotalCost, @@ -6119,16 +6119,8 @@ genericcostestimate(PlannerInfo *root, * Note that we are counting pages not tuples anymore, so we take N = T = * index size, as if there were one "tuple" per page. */ - if (outer_rel != NULL && outer_rel->rows > 1) - { - num_outer_scans = outer_rel->rows; - num_scans = num_sa_scans * num_outer_scans; - } - else - { - num_outer_scans = 1; - num_scans = num_sa_scans; - } + num_outer_scans = loop_count; + num_scans = num_sa_scans * num_outer_scans; if (num_scans > 1) { @@ -6234,7 +6226,7 @@ btcostestimate(PG_FUNCTION_ARGS) { PlannerInfo *root = (PlannerInfo *) PG_GETARG_POINTER(0); IndexPath *path = (IndexPath *) PG_GETARG_POINTER(1); - RelOptInfo *outer_rel = (RelOptInfo *) PG_GETARG_POINTER(2); + double loop_count = PG_GETARG_FLOAT8(2); Cost *indexStartupCost = (Cost *) PG_GETARG_POINTER(3); Cost *indexTotalCost = (Cost *) PG_GETARG_POINTER(4); Selectivity *indexSelectivity = (Selectivity *) PG_GETARG_POINTER(5); @@ -6410,7 +6402,7 @@ btcostestimate(PG_FUNCTION_ARGS) numIndexTuples = rint(numIndexTuples / num_sa_scans); } - genericcostestimate(root, path, outer_rel, + genericcostestimate(root, path, loop_count, numIndexTuples, indexStartupCost, indexTotalCost, indexSelectivity, indexCorrelation); @@ -6527,13 +6519,13 @@ hashcostestimate(PG_FUNCTION_ARGS) { PlannerInfo *root = (PlannerInfo *) PG_GETARG_POINTER(0); IndexPath *path = (IndexPath *) PG_GETARG_POINTER(1); - RelOptInfo *outer_rel = (RelOptInfo *) PG_GETARG_POINTER(2); + double loop_count = PG_GETARG_FLOAT8(2); Cost *indexStartupCost = (Cost *) PG_GETARG_POINTER(3); Cost *indexTotalCost = (Cost *) PG_GETARG_POINTER(4); Selectivity *indexSelectivity = (Selectivity *) PG_GETARG_POINTER(5); double *indexCorrelation = (double *) PG_GETARG_POINTER(6); - genericcostestimate(root, path, outer_rel, 0.0, + genericcostestimate(root, path, loop_count, 0.0, indexStartupCost, indexTotalCost, indexSelectivity, indexCorrelation); @@ -6545,13 +6537,13 @@ gistcostestimate(PG_FUNCTION_ARGS) { PlannerInfo *root = (PlannerInfo *) PG_GETARG_POINTER(0); IndexPath *path = (IndexPath *) PG_GETARG_POINTER(1); - RelOptInfo *outer_rel = (RelOptInfo *) PG_GETARG_POINTER(2); + double loop_count = PG_GETARG_FLOAT8(2); Cost *indexStartupCost = (Cost *) PG_GETARG_POINTER(3); Cost *indexTotalCost = (Cost *) PG_GETARG_POINTER(4); Selectivity *indexSelectivity = (Selectivity *) PG_GETARG_POINTER(5); double *indexCorrelation = (double *) PG_GETARG_POINTER(6); - genericcostestimate(root, path, outer_rel, 0.0, + genericcostestimate(root, path, loop_count, 0.0, indexStartupCost, indexTotalCost, indexSelectivity, indexCorrelation); @@ -6563,13 +6555,13 @@ spgcostestimate(PG_FUNCTION_ARGS) { PlannerInfo *root = (PlannerInfo *) PG_GETARG_POINTER(0); IndexPath *path = (IndexPath *) PG_GETARG_POINTER(1); - RelOptInfo *outer_rel = (RelOptInfo *) PG_GETARG_POINTER(2); + double loop_count = PG_GETARG_FLOAT8(2); Cost *indexStartupCost = (Cost *) PG_GETARG_POINTER(3); Cost *indexTotalCost = (Cost *) PG_GETARG_POINTER(4); Selectivity *indexSelectivity = (Selectivity *) PG_GETARG_POINTER(5); double *indexCorrelation = (double *) PG_GETARG_POINTER(6); - genericcostestimate(root, path, outer_rel, 0.0, + genericcostestimate(root, path, loop_count, 0.0, indexStartupCost, indexTotalCost, indexSelectivity, indexCorrelation); @@ -6884,7 +6876,7 @@ gincostestimate(PG_FUNCTION_ARGS) { PlannerInfo *root = (PlannerInfo *) PG_GETARG_POINTER(0); IndexPath *path = (IndexPath *) PG_GETARG_POINTER(1); - RelOptInfo *outer_rel = (RelOptInfo *) PG_GETARG_POINTER(2); + double loop_count = PG_GETARG_FLOAT8(2); Cost *indexStartupCost = (Cost *) PG_GETARG_POINTER(3); Cost *indexTotalCost = (Cost *) PG_GETARG_POINTER(4); Selectivity *indexSelectivity = (Selectivity *) PG_GETARG_POINTER(5); @@ -7051,10 +7043,7 @@ gincostestimate(PG_FUNCTION_ARGS) } /* Will we have more than one iteration of a nestloop scan? */ - if (outer_rel != NULL && outer_rel->rows > 1) - outer_scans = outer_rel->rows; - else - outer_scans = 1; + outer_scans = loop_count; /* * Compute cost to begin scan, first of all, pay attention to pending list. diff --git a/src/include/nodes/bitmapset.h b/src/include/nodes/bitmapset.h index 8fdbd72d3525ddde33d24051e228005be4ec9527..bcca70d2b9817ca0d05f2a90e0c898ff0c48b1b6 100644 --- a/src/include/nodes/bitmapset.h +++ b/src/include/nodes/bitmapset.h @@ -36,6 +36,15 @@ typedef struct Bitmapset } Bitmapset; /* VARIABLE LENGTH STRUCT */ +/* result of bms_subset_compare */ +typedef enum +{ + BMS_EQUAL, /* sets are equal */ + BMS_SUBSET1, /* first set is a subset of the second */ + BMS_SUBSET2, /* second set is a subset of the first */ + BMS_DIFFERENT /* neither set is a subset of the other */ +} BMS_Comparison; + /* result of bms_membership */ typedef enum { @@ -58,6 +67,7 @@ extern Bitmapset *bms_union(const Bitmapset *a, const Bitmapset *b); extern Bitmapset *bms_intersect(const Bitmapset *a, const Bitmapset *b); extern Bitmapset *bms_difference(const Bitmapset *a, const Bitmapset *b); extern bool bms_is_subset(const Bitmapset *a, const Bitmapset *b); +extern BMS_Comparison bms_subset_compare(const Bitmapset *a, const Bitmapset *b); extern bool bms_is_member(int x, const Bitmapset *a); extern bool bms_overlap(const Bitmapset *a, const Bitmapset *b); extern bool bms_nonempty_difference(const Bitmapset *a, const Bitmapset *b); diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index b1168086e917c2cbe58c1b4f1f48be24ebd399c9..0e7d184a0d8b8ed83fad3142186a1e0e3e349509 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -232,7 +232,6 @@ typedef enum NodeTag T_EquivalenceMember, T_PathKey, T_RestrictInfo, - T_InnerIndexscanInfo, T_PlaceHolderVar, T_SpecialJoinInfo, T_AppendRelInfo, diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 94657d2aaa1aacc4ee04160833298c7af7e585fa..6ba920a479ef65a22deb4110bc90f7c9c1231295 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -145,6 +145,13 @@ typedef struct PlannerInfo */ RangeTblEntry **simple_rte_array; /* rangetable as an array */ + /* + * all_baserels is a Relids set of all base relids (but not "other" + * relids) in the query; that is, the Relids identifier of the final + * join we need to form. + */ + Relids all_baserels; + /* * join_rel_list is a list of all join-relation RelOptInfos we have * considered in this planning run. For small problems we just scan the @@ -298,11 +305,16 @@ typedef struct PlannerInfo * pathlist - List of Path nodes, one for each potentially useful * method of generating the relation * cheapest_startup_path - the pathlist member with lowest startup cost - * (regardless of its ordering) + * (regardless of its ordering; but must be + * unparameterized) * cheapest_total_path - the pathlist member with lowest total cost - * (regardless of its ordering) + * (regardless of its ordering; but must be + * unparameterized) * cheapest_unique_path - for caching cheapest path to produce unique * (no duplicates) output from relation + * cheapest_parameterized_paths - paths with cheapest total costs for + * their parameterizations; always includes + * cheapest_total_path * * If the relation is a base relation it will have these fields set: * @@ -343,11 +355,6 @@ typedef struct PlannerInfo * note this excludes clauses that might be derivable from * EquivalenceClasses) * has_eclass_joins - flag that EquivalenceClass joins are possible - * index_outer_relids - only used for base rels; set of outer relids - * that participate in indexable joinclauses for this rel - * index_inner_paths - only used for base rels; list of InnerIndexscanInfo - * nodes showing best indexpaths for various subsets of - * index_outer_relids. * * Note: Keeping a restrictinfo list in the RelOptInfo is useful only for * base rels, because for a join rel the set of clauses that are treated as @@ -393,6 +400,7 @@ typedef struct RelOptInfo struct Path *cheapest_startup_path; struct Path *cheapest_total_path; struct Path *cheapest_unique_path; + List *cheapest_parameterized_paths; /* information about a base rel (not set for join rels!) */ Index relid; @@ -416,18 +424,6 @@ typedef struct RelOptInfo List *joininfo; /* RestrictInfo structures for join clauses * involving this rel */ bool has_eclass_joins; /* T means joininfo is incomplete */ - - /* cached info about inner indexscan paths for relation: */ - Relids index_outer_relids; /* other relids in indexable join - * clauses */ - List *index_inner_paths; /* InnerIndexscanInfo nodes */ - - /* - * Inner indexscans are not in the main pathlist because they are not - * usable except in specific join contexts. We use the index_inner_paths - * list just to avoid recomputing the best inner indexscan repeatedly for - * similar outer relations. See comments for InnerIndexscanInfo. - */ } RelOptInfo; /* @@ -609,7 +605,6 @@ typedef struct EquivalenceMember * BTGreaterStrategyNumber (for DESC). We assume that all ordering-capable * index types will use btree-compatible strategy numbers. */ - typedef struct PathKey { NodeTag type; @@ -625,12 +620,31 @@ typedef struct PathKey * simple plan types that we don't need any extra information in the path for. * For other path types it is the first component of a larger struct. * - * Note: "pathtype" is the NodeTag of the Plan node we could build from this - * Path. It is partially redundant with the Path's NodeTag, but allows us - * to use the same Path type for multiple Plan types where there is no need - * to distinguish the Plan type during path processing. + * "pathtype" is the NodeTag of the Plan node we could build from this Path. + * It is partially redundant with the Path's NodeTag, but allows us to use + * the same Path type for multiple Plan types when there is no need to + * distinguish the Plan type during path processing. + * + * "rows" is the same as parent->rows in simple paths, but in parameterized + * paths and UniquePaths it can be less than parent->rows, reflecting the + * fact that we've filtered by extra join conditions or removed duplicates. + * + * "pathkeys" is a List of PathKey nodes (see above), describing the sort + * ordering of the path's output rows. + * + * "required_outer", if not NULL, contains the relids of one or more relations + * that must provide parameter values to each scan of this path, because the + * path relies on join clauses using those rels. That means this path can only + * be joined to those rels by means of nestloop joins with this path on the + * inside. Note: for a normal unparameterized path, required_outer must be + * NULL, not an empty-but-not-null Bitmapset. + * + * "param_clauses" is a List of RestrictInfo nodes, containing the join + * clauses used by a parameterized path. Ideally param_clauses should be NIL + * if and only if required_outer is NULL. XXX for the moment, however, we do + * not compute param_clauses for Append and MergeAppend paths, so the list + * is inaccurate in those paths and possibly paths above them. */ - typedef struct Path { NodeTag type; @@ -639,12 +653,15 @@ typedef struct Path RelOptInfo *parent; /* the relation this path can build */ - /* estimated execution costs for path (see costsize.c for more info) */ + /* estimated size/costs for path (see costsize.c for more info) */ + double rows; /* estimated number of result tuples */ Cost startup_cost; /* cost expended before fetching any tuples */ Cost total_cost; /* total cost (assuming all tuples fetched) */ List *pathkeys; /* sort ordering of path's output */ - /* pathkeys is a List of PathKey nodes; see above */ + + Relids required_outer; /* rels supplying parameters used by path */ + List *param_clauses; /* join clauses that use such parameters */ } Path; /*---------- @@ -685,12 +702,6 @@ typedef struct Path * ORDER BY expression is meant to be used with. (There is no restriction * on which index column each ORDER BY can be used with.) * - * 'isjoininner' is TRUE if the path is a nestloop inner scan (that is, - * some of the index conditions are join rather than restriction clauses). - * Note that the path costs will be calculated differently from a plain - * indexscan in this case, and in addition there's a special 'rows' value - * different from the parent RelOptInfo's (see below). - * * 'indexscandir' is one of: * ForwardScanDirection: forward scan of an ordered index * BackwardScanDirection: backward scan of an ordered index @@ -703,12 +714,6 @@ typedef struct Path * we need not recompute them when considering using the same index in a * bitmap index/heap scan (see BitmapHeapPath). The costs of the IndexPath * itself represent the costs of an IndexScan or IndexOnlyScan plan type. - * - * 'rows' is the estimated result tuple count for the indexscan. This - * is the same as path.parent->rows for a simple indexscan, but it is - * different for a nestloop inner scan, because the additional indexquals - * coming from join clauses make the scan more selective than the parent - * rel's restrict clauses alone would do. *---------- */ typedef struct IndexPath @@ -720,11 +725,9 @@ typedef struct IndexPath List *indexqualcols; List *indexorderbys; List *indexorderbycols; - bool isjoininner; ScanDirection indexscandir; Cost indextotalcost; Selectivity indexselectivity; - double rows; /* estimated number of result tuples */ } IndexPath; /* @@ -743,16 +746,11 @@ typedef struct IndexPath * always represent the costs to use it as a regular (or index-only) * IndexScan. The costs of a BitmapIndexScan can be computed using the * IndexPath's indextotalcost and indexselectivity. - * - * BitmapHeapPaths can be nestloop inner indexscans. The isjoininner and - * rows fields serve the same purpose as for plain IndexPaths. */ typedef struct BitmapHeapPath { Path path; Path *bitmapqual; /* IndexPath, BitmapAndPath, BitmapOrPath */ - bool isjoininner; /* T if it's a nestloop inner scan */ - double rows; /* estimated number of result tuples */ } BitmapHeapPath; /* @@ -822,6 +820,11 @@ typedef struct AppendPath #define IS_DUMMY_PATH(p) \ (IsA((p), AppendPath) && ((AppendPath *) (p))->subpaths == NIL) +/* A relation that's been proven empty will have one path that is dummy */ +#define IS_DUMMY_REL(r) \ + ((r)->cheapest_total_path != NULL && \ + IS_DUMMY_PATH((r)->cheapest_total_path)) + /* * MergeAppendPath represents a MergeAppend plan, ie, the merging of sorted * results from several member plans to produce similarly-sorted output. @@ -885,7 +888,6 @@ typedef struct UniquePath UniquePathMethod umethod; List *in_operators; /* equality operators of the IN clause */ List *uniq_exprs; /* expressions to be made unique */ - double rows; /* estimated number of result tuples */ } UniquePath; /* @@ -1172,42 +1174,6 @@ typedef struct MergeScanSelCache Selectivity rightendsel; /* last-join fraction for clause right side */ } MergeScanSelCache; -/* - * Inner indexscan info. - * - * An inner indexscan is one that uses one or more joinclauses as index - * conditions (perhaps in addition to plain restriction clauses). So it - * can only be used as the inner path of a nestloop join where the outer - * relation includes all other relids appearing in those joinclauses. - * The set of usable joinclauses, and thus the best inner indexscan, - * thus varies depending on which outer relation we consider; so we have - * to recompute the best such paths for every join. To avoid lots of - * redundant computation, we cache the results of such searches. For - * each relation we compute the set of possible otherrelids (all relids - * appearing in joinquals that could become indexquals for this table). - * Two outer relations whose relids have the same intersection with this - * set will have the same set of available joinclauses and thus the same - * best inner indexscans for the inner relation. By taking the intersection - * before scanning the cache, we avoid recomputing when considering - * join rels that differ only by the inclusion of irrelevant other rels. - * - * The search key also includes a bool showing whether the join being - * considered is an outer join. Since we constrain the join order for - * outer joins, I believe that this bool can only have one possible value - * for any particular lookup key; but store it anyway to avoid confusion. - */ - -typedef struct InnerIndexscanInfo -{ - NodeTag type; - /* The lookup key: */ - Relids other_relids; /* a set of relevant other relids */ - bool isouterjoin; /* true if join is outer */ - /* Best paths for this lookup key (NULL if no available indexscans): */ - Path *cheapest_startup_innerpath; /* cheapest startup cost */ - Path *cheapest_total_innerpath; /* cheapest total cost */ -} InnerIndexscanInfo; - /* * Placeholder node for an expression to be evaluated below the top level * of a plan tree. This is used during planning to represent the contained @@ -1490,4 +1456,64 @@ typedef struct PlannerParamItem Index abslevel; /* its absolute query level */ } PlannerParamItem; +/* + * When making cost estimates for a SEMI or ANTI join, there are some + * correction factors that are needed in both nestloop and hash joins + * to account for the fact that the executor can stop scanning inner rows + * as soon as it finds a match to the current outer row. These numbers + * depend only on the selected outer and inner join relations, not on the + * particular paths used for them, so it's worthwhile to calculate them + * just once per relation pair not once per considered path. This struct + * is filled by compute_semi_anti_join_factors and must be passed along + * to the join cost estimation functions. + * + * outer_match_frac is the fraction of the outer tuples that are + * expected to have at least one match. + * match_count is the average number of matches expected for + * outer tuples that have at least one match. + */ +typedef struct SemiAntiJoinFactors +{ + Selectivity outer_match_frac; + Selectivity match_count; +} SemiAntiJoinFactors; + +/* + * For speed reasons, cost estimation for join paths is performed in two + * phases: the first phase tries to quickly derive a lower bound for the + * join cost, and then we check if that's sufficient to reject the path. + * If not, we come back for a more refined cost estimate. The first phase + * fills a JoinCostWorkspace struct with its preliminary cost estimates + * and possibly additional intermediate values. The second phase takes + * these values as inputs to avoid repeating work. + * + * (Ideally we'd declare this in cost.h, but it's also needed in pathnode.h, + * so seems best to put it here.) + */ +typedef struct JoinCostWorkspace +{ + /* Preliminary cost estimates --- must not be larger than final ones! */ + Cost startup_cost; /* cost expended before fetching any tuples */ + Cost total_cost; /* total cost (assuming all tuples fetched) */ + + /* Fields below here should be treated as private to costsize.c */ + Cost run_cost; /* non-startup cost components */ + + /* private for cost_nestloop code */ + Cost inner_rescan_run_cost; + double outer_matched_rows; + Selectivity inner_scan_frac; + + /* private for cost_mergejoin code */ + Cost inner_run_cost; + double outer_rows; + double inner_rows; + double outer_skip_rows; + double inner_skip_rows; + + /* private for cost_hashjoin code */ + int numbuckets; + int numbatches; +} JoinCostWorkspace; + #endif /* RELATION_H */ diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h index 1786d5e93607846acf70aab80093163442700a13..b1e664265b25d2c3118fc82c2147e4d1c7b7e3ef 100644 --- a/src/include/optimizer/cost.h +++ b/src/include/optimizer/cost.h @@ -68,9 +68,9 @@ extern double index_pages_fetched(double tuples_fetched, BlockNumber pages, double index_pages, PlannerInfo *root); extern void cost_seqscan(Path *path, PlannerInfo *root, RelOptInfo *baserel); extern void cost_index(IndexPath *path, PlannerInfo *root, - RelOptInfo *outer_rel); + double loop_count); extern void cost_bitmap_heap_scan(Path *path, PlannerInfo *root, RelOptInfo *baserel, - Path *bitmapqual, RelOptInfo *outer_rel); + Path *bitmapqual, double loop_count); extern void cost_bitmap_and_node(BitmapAndPath *path, PlannerInfo *root); extern void cost_bitmap_or_node(BitmapOrPath *path, PlannerInfo *root); extern void cost_bitmap_tree_node(Path *path, Cost *cost, Selectivity *selec); @@ -107,15 +107,47 @@ extern void cost_group(Path *path, PlannerInfo *root, int numGroupCols, double numGroups, Cost input_startup_cost, Cost input_total_cost, double input_tuples); -extern void cost_nestloop(NestPath *path, PlannerInfo *root, - SpecialJoinInfo *sjinfo); -extern void cost_mergejoin(MergePath *path, PlannerInfo *root, - SpecialJoinInfo *sjinfo); -extern void cost_hashjoin(HashPath *path, PlannerInfo *root, - SpecialJoinInfo *sjinfo); +extern void initial_cost_nestloop(PlannerInfo *root, + JoinCostWorkspace *workspace, + JoinType jointype, + Path *outer_path, Path *inner_path, + SpecialJoinInfo *sjinfo, + SemiAntiJoinFactors *semifactors); +extern void final_cost_nestloop(PlannerInfo *root, NestPath *path, + JoinCostWorkspace *workspace, + SpecialJoinInfo *sjinfo, + SemiAntiJoinFactors *semifactors); +extern void initial_cost_mergejoin(PlannerInfo *root, + JoinCostWorkspace *workspace, + JoinType jointype, + List *mergeclauses, + Path *outer_path, Path *inner_path, + List *outersortkeys, List *innersortkeys, + SpecialJoinInfo *sjinfo); +extern void final_cost_mergejoin(PlannerInfo *root, MergePath *path, + JoinCostWorkspace *workspace, + SpecialJoinInfo *sjinfo); +extern void initial_cost_hashjoin(PlannerInfo *root, + JoinCostWorkspace *workspace, + JoinType jointype, + List *hashclauses, + Path *outer_path, Path *inner_path, + SpecialJoinInfo *sjinfo, + SemiAntiJoinFactors *semifactors); +extern void final_cost_hashjoin(PlannerInfo *root, HashPath *path, + JoinCostWorkspace *workspace, + SpecialJoinInfo *sjinfo, + SemiAntiJoinFactors *semifactors); extern void cost_subplan(PlannerInfo *root, SubPlan *subplan, Plan *plan); extern void cost_qual_eval(QualCost *cost, List *quals, PlannerInfo *root); extern void cost_qual_eval_node(QualCost *cost, Node *qual, PlannerInfo *root); +extern void compute_semi_anti_join_factors(PlannerInfo *root, + RelOptInfo *outerrel, + RelOptInfo *innerrel, + JoinType jointype, + SpecialJoinInfo *sjinfo, + List *restrictlist, + SemiAntiJoinFactors *semifactors); extern void set_baserel_size_estimates(PlannerInfo *root, RelOptInfo *rel); extern void set_joinrel_size_estimates(PlannerInfo *root, RelOptInfo *rel, RelOptInfo *outer_rel, diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index 3bc1b27384c0c6d9d06e13068f8be085b7abb18c..1cf34171f4fce1d68bec190c9c2d4dbeb850e976 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -26,6 +26,9 @@ extern int compare_fractional_path_costs(Path *path1, Path *path2, double fraction); extern void set_cheapest(RelOptInfo *parent_rel); extern void add_path(RelOptInfo *parent_rel, Path *new_path); +extern bool add_path_precheck(RelOptInfo *parent_rel, + Cost startup_cost, Cost total_cost, + List *pathkeys, Relids required_outer); extern Path *create_seqscan_path(PlannerInfo *root, RelOptInfo *rel); extern IndexPath *create_index_path(PlannerInfo *root, @@ -37,11 +40,12 @@ extern IndexPath *create_index_path(PlannerInfo *root, List *pathkeys, ScanDirection indexscandir, bool indexonly, - RelOptInfo *outer_rel); + Relids required_outer, + double loop_count); extern BitmapHeapPath *create_bitmap_heap_path(PlannerInfo *root, RelOptInfo *rel, Path *bitmapqual, - RelOptInfo *outer_rel); + double loop_count); extern BitmapAndPath *create_bitmap_and_path(PlannerInfo *root, RelOptInfo *rel, List *bitmapquals); @@ -66,23 +70,31 @@ extern Path *create_ctescan_path(PlannerInfo *root, RelOptInfo *rel); extern Path *create_worktablescan_path(PlannerInfo *root, RelOptInfo *rel); extern ForeignPath *create_foreignscan_path(PlannerInfo *root, RelOptInfo *rel); +extern Relids calc_nestloop_required_outer(Path *outer_path, Path *inner_path); +extern Relids calc_non_nestloop_required_outer(Path *outer_path, Path *inner_path); + extern NestPath *create_nestloop_path(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype, + JoinCostWorkspace *workspace, SpecialJoinInfo *sjinfo, + SemiAntiJoinFactors *semifactors, Path *outer_path, Path *inner_path, List *restrict_clauses, - List *pathkeys); + List *pathkeys, + Relids required_outer); extern MergePath *create_mergejoin_path(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype, + JoinCostWorkspace *workspace, SpecialJoinInfo *sjinfo, Path *outer_path, Path *inner_path, List *restrict_clauses, List *pathkeys, + Relids required_outer, List *mergeclauses, List *outersortkeys, List *innersortkeys); @@ -90,10 +102,13 @@ extern MergePath *create_mergejoin_path(PlannerInfo *root, extern HashPath *create_hashjoin_path(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype, + JoinCostWorkspace *workspace, SpecialJoinInfo *sjinfo, + SemiAntiJoinFactors *semifactors, Path *outer_path, Path *inner_path, List *restrict_clauses, + Relids required_outer, List *hashclauses); /* diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index 76d7b77fc1d782f8812d7308baa1e357db9ca5c6..082b54e120f3c4e6ae0f91cacc24bd1bcda98323 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -44,17 +44,14 @@ extern void debug_print_rel(PlannerInfo *root, RelOptInfo *rel); */ extern void create_index_paths(PlannerInfo *root, RelOptInfo *rel); extern List *generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel, - List *clauses, List *outer_clauses, - RelOptInfo *outer_rel); -extern void best_inner_indexscan(PlannerInfo *root, RelOptInfo *rel, - RelOptInfo *outer_rel, JoinType jointype, - Path **cheapest_startup, Path **cheapest_total); + List *clauses, List *other_clauses, + bool restriction_only); extern bool relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel, List *restrictlist, List *exprlist, List *oprlist); -extern bool eclass_matches_any_index(EquivalenceClass *ec, - EquivalenceMember *em, - RelOptInfo *rel); +extern bool eclass_member_matches_indexcol(EquivalenceClass *ec, + EquivalenceMember *em, + IndexOptInfo *index, int indexcol); extern bool match_index_to_operand(Node *operand, int indexcol, IndexOptInfo *index); extern void expand_indexqual_conditions(IndexOptInfo *index, @@ -127,9 +124,9 @@ extern void add_child_rel_equivalences(PlannerInfo *root, extern void mutate_eclass_expressions(PlannerInfo *root, Node *(*mutator) (), void *context); -extern List *find_eclass_clauses_for_index_join(PlannerInfo *root, - RelOptInfo *rel, - Relids outer_relids); +extern List *generate_implied_equalities_for_indexcol(PlannerInfo *root, + IndexOptInfo *index, + int indexcol); extern bool have_relevant_eclass_joinclause(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2); extern bool has_relevant_eclass_joinclause(PlannerInfo *root, @@ -153,9 +150,11 @@ extern List *canonicalize_pathkeys(PlannerInfo *root, List *pathkeys); extern PathKeysComparison compare_pathkeys(List *keys1, List *keys2); extern bool pathkeys_contained_in(List *keys1, List *keys2); extern Path *get_cheapest_path_for_pathkeys(List *paths, List *pathkeys, + Relids required_outer, CostSelector cost_criterion); extern Path *get_cheapest_fractional_path_for_pathkeys(List *paths, List *pathkeys, + Relids required_outer, double fraction); extern List *build_index_pathkeys(PlannerInfo *root, IndexOptInfo *index, ScanDirection scandir); diff --git a/src/include/optimizer/restrictinfo.h b/src/include/optimizer/restrictinfo.h index 157f58e5fa8a4a3d3956750a9fd4e509a5084dfb..34977f9b95cbf5b96502100b5c1b695e74b3a58a 100644 --- a/src/include/optimizer/restrictinfo.h +++ b/src/include/optimizer/restrictinfo.h @@ -40,8 +40,7 @@ extern List *extract_actual_clauses(List *restrictinfo_list, extern void extract_actual_join_clauses(List *restrictinfo_list, List **joinquals, List **otherquals); -extern List *select_nonredundant_join_clauses(PlannerInfo *root, - List *restrictinfo_list, - Path *inner_path); +extern List *select_nonredundant_join_clauses(List *restrictinfo_list, + List *reference_list); #endif /* RESTRICTINFO_H */