#include "utils/builtins.h"
#include "utils/memutils.h"
#include "optimizer/cost.h"
+#include "optimizer/joininfo.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/paths.h"
#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
static HashEntry *search_ent(TidList *tidlist);
#endif
+static join_search_hook_type org_join_search = NULL;
+
+static RelOptInfo *my_join_search(PlannerInfo *root, int levels_needed,
+ List *initial_rels);
+static void build_join_hints(PlannerInfo *root, int level, List *initial_rels);
+static void my_join_search_one_level(PlannerInfo *root, int level);
+
PG_FUNCTION_INFO_V1(pg_add_hint);
PG_FUNCTION_INFO_V1(pg_clear_hint);
org_cost_hook = cost_hook;
cost_hook = my_cost_hook;
#endif
+ org_join_search = join_search_hook;
+ join_search_hook = my_join_search;
for (i = 0 ; i < HASH_ENTRIES ; i++)
hashent[i] = NULL;
#ifdef NOT_USED
cost_hook = org_cost_hook;
#endif
+ join_search_hook = org_join_search;
for (i = 0 ; i < HASH_ENTRIES ; i++)
{
PG_RETURN_CSTRING(cstring_to_text(buf));
}
+
+/*
+ * pg_add_hint()で登録したヒントから、今回のクエリで使用するもののみ抽出し、
+ * 使用しやすい構造に変換する。
+ */
+static void
+build_join_hints(PlannerInfo *root, int level, List *initial_rels)
+{
+}
+
+/*
+ * src/backend/optimizer/path/joinrels.c
+ * export make_join_rel() をラップする関数
+ *
+ * ヒントにしたがって、enabele_* パラメータを変更した上で、make_join_rel()を
+ * 呼び出す。
+ */
+static RelOptInfo *
+my_make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2)
+{
+ return make_join_rel(root, rel1, rel2);
+}
+
+/*
+ * PostgreSQL 本体から流用した関数
+ */
+
+/*
+ * src/backend/optimizer/path/allpaths.c
+ * export standard_join_search() を流用
+ *
+ * 変更箇所
+ * build_join_hints() の呼び出しを追加
+ */
+/*
+ * standard_join_search
+ * Find possible joinpaths for a query by successively finding ways
+ * to join component relations into join relations.
+ *
+ * 'levels_needed' is the number of iterations needed, ie, the number of
+ * independent jointree items in the query. This is > 1.
+ *
+ * 'initial_rels' is a list of RelOptInfo nodes for each independent
+ * jointree item. These are the components to be joined together.
+ * Note that levels_needed == list_length(initial_rels).
+ *
+ * Returns the final level of join relations, i.e., the relation that is
+ * the result of joining all the original relations together.
+ * At least one implementation path must be provided for this relation and
+ * all required sub-relations.
+ *
+ * To support loadable plugins that modify planner behavior by changing the
+ * join searching algorithm, we provide a hook variable that lets a plugin
+ * replace or supplement this function. Any such hook must return the same
+ * final join relation as the standard code would, but it might have a
+ * different set of implementation paths attached, and only the sub-joinrels
+ * needed for these paths need have been instantiated.
+ *
+ * Note to plugin authors: the functions invoked during standard_join_search()
+ * modify root->join_rel_list and root->join_rel_hash. If you want to do more
+ * than one join-order search, you'll probably need to save and restore the
+ * original states of those data structures. See geqo_eval() for an example.
+ */
+static RelOptInfo *
+my_join_search(PlannerInfo *root, int levels_needed, List *initial_rels)
+{
+ int lev;
+ RelOptInfo *rel;
+
+ /*
+ * This function cannot be invoked recursively within any one planning
+ * problem, so join_rel_level[] can't be in use already.
+ */
+ Assert(root->join_rel_level == NULL);
+
+ /*
+ * We employ a simple "dynamic programming" algorithm: we first find all
+ * ways to build joins of two jointree items, then all ways to build joins
+ * of three items (from two-item joins and single items), then four-item
+ * joins, and so on until we have considered all ways to join all the
+ * items into one rel.
+ *
+ * root->join_rel_level[j] is a list of all the j-item rels. Initially we
+ * set root->join_rel_level[1] to represent all the single-jointree-item
+ * relations.
+ */
+ root->join_rel_level = (List **) palloc0((levels_needed + 1) * sizeof(List *));
+
+ root->join_rel_level[1] = initial_rels;
+
+ build_join_hints(root, levels_needed, initial_rels);
+
+ for (lev = 2; lev <= levels_needed; lev++)
+ {
+ ListCell *lc;
+
+ /*
+ * Determine all possible pairs of relations to be joined at this
+ * level, and build paths for making each one from every available
+ * pair of lower-level relations.
+ */
+ my_join_search_one_level(root, lev);
+
+ /*
+ * Do cleanup work on each just-processed rel.
+ */
+ foreach(lc, root->join_rel_level[lev])
+ {
+ rel = (RelOptInfo *) lfirst(lc);
+
+ /* Find and save the cheapest paths for this rel */
+ set_cheapest(rel);
+
+#ifdef OPTIMIZER_DEBUG
+ debug_print_rel(root, rel);
+#endif
+ }
+ }
+
+ /*
+ * We should have a single rel at the final level.
+ */
+ if (root->join_rel_level[levels_needed] == NIL)
+ elog(ERROR, "failed to build any %d-way joins", levels_needed);
+ Assert(list_length(root->join_rel_level[levels_needed]) == 1);
+
+ rel = (RelOptInfo *) linitial(root->join_rel_level[levels_needed]);
+
+ root->join_rel_level = NULL;
+
+ return rel;
+}
+
+/*
+ * src/backend/optimizer/path/joinrels.c
+ * static join_search_one_level() を流用
+ *
+ * 変更箇所
+ * make_join_rel() の呼び出しをラップする、my_make_join_rel()の呼び出しに変更
+ */
+/*
+ * join_search_one_level
+ * Consider ways to produce join relations containing exactly 'level'
+ * jointree items. (This is one step of the dynamic-programming method
+ * embodied in standard_join_search.) Join rel nodes for each feasible
+ * combination of lower-level rels are created and returned in a list.
+ * Implementation paths are created for each such joinrel, too.
+ *
+ * level: level of rels we want to make this time
+ * root->join_rel_level[j], 1 <= j < level, is a list of rels containing j items
+ *
+ * The result is returned in root->join_rel_level[level].
+ */
+static void
+my_join_search_one_level(PlannerInfo *root, int level)
+{
+ List **joinrels = root->join_rel_level;
+ ListCell *r;
+ int k;
+
+ Assert(joinrels[level] == NIL);
+
+ /* Set join_cur_level so that new joinrels are added to proper list */
+ root->join_cur_level = level;
+
+ /*
+ * First, consider left-sided and right-sided plans, in which rels of
+ * exactly level-1 member relations are joined against initial relations.
+ * We prefer to join using join clauses, but if we find a rel of level-1
+ * members that has no join clauses, we will generate Cartesian-product
+ * joins against all initial rels not already contained in it.
+ *
+ * In the first pass (level == 2), we try to join each initial rel to each
+ * initial rel that appears later in joinrels[1]. (The mirror-image joins
+ * are handled automatically by make_join_rel.) In later passes, we try
+ * to join rels of size level-1 from joinrels[level-1] to each initial rel
+ * in joinrels[1].
+ */
+ foreach(r, joinrels[level - 1])
+ {
+ RelOptInfo *old_rel = (RelOptInfo *) lfirst(r);
+ ListCell *other_rels;
+
+ if (level == 2)
+ other_rels = lnext(r); /* only consider remaining initial
+ * rels */
+ else
+ other_rels = list_head(joinrels[1]); /* consider all initial
+ * rels */
+
+ if (old_rel->joininfo != NIL || old_rel->has_eclass_joins ||
+ has_join_restriction(root, old_rel))
+ {
+ /*
+ * Note that if all available join clauses for this rel require
+ * more than one other rel, we will fail to make any joins against
+ * it here. In most cases that's OK; it'll be considered by
+ * "bushy plan" join code in a higher-level pass where we have
+ * those other rels collected into a join rel.
+ *
+ * See also the last-ditch case below.
+ */
+ make_rels_by_clause_joins(root,
+ old_rel,
+ other_rels);
+ }
+ else
+ {
+ /*
+ * Oops, we have a relation that is not joined to any other
+ * relation, either directly or by join-order restrictions.
+ * Cartesian product time.
+ */
+ make_rels_by_clauseless_joins(root,
+ old_rel,
+ other_rels);
+ }
+ }
+
+ /*
+ * Now, consider "bushy plans" in which relations of k initial rels are
+ * joined to relations of level-k initial rels, for 2 <= k <= level-2.
+ *
+ * We only consider bushy-plan joins for pairs of rels where there is a
+ * suitable join clause (or join order restriction), in order to avoid
+ * unreasonable growth of planning time.
+ */
+ for (k = 2;; k++)
+ {
+ int other_level = level - k;
+
+ /*
+ * Since make_join_rel(x, y) handles both x,y and y,x cases, we only
+ * need to go as far as the halfway point.
+ */
+ if (k > other_level)
+ break;
+
+ foreach(r, joinrels[k])
+ {
+ RelOptInfo *old_rel = (RelOptInfo *) lfirst(r);
+ ListCell *other_rels;
+ ListCell *r2;
+
+ /*
+ * We can ignore clauseless joins here, *except* when they
+ * participate in join-order restrictions --- then we might have
+ * to force a bushy join plan.
+ */
+ if (old_rel->joininfo == NIL && !old_rel->has_eclass_joins &&
+ !has_join_restriction(root, old_rel))
+ continue;
+
+ if (k == other_level)
+ other_rels = lnext(r); /* only consider remaining rels */
+ else
+ other_rels = list_head(joinrels[other_level]);
+
+ for_each_cell(r2, other_rels)
+ {
+ RelOptInfo *new_rel = (RelOptInfo *) lfirst(r2);
+
+ if (!bms_overlap(old_rel->relids, new_rel->relids))
+ {
+ /*
+ * OK, we can build a rel of the right level from this
+ * pair of rels. Do so if there is at least one usable
+ * join clause or a relevant join restriction.
+ */
+ if (have_relevant_joinclause(root, old_rel, new_rel) ||
+ have_join_order_restriction(root, old_rel, new_rel))
+ {
+ (void) my_make_join_rel(root, old_rel, new_rel);
+ }
+ }
+ }
+ }
+ }
+
+ /*
+ * Last-ditch effort: if we failed to find any usable joins so far, force
+ * a set of cartesian-product joins to be generated. This handles the
+ * special case where all the available rels have join clauses but we
+ * cannot use any of those clauses yet. An example is
+ *
+ * SELECT * FROM a,b,c WHERE (a.f1 + b.f2 + c.f3) = 0;
+ *
+ * The join clause will be usable at level 3, but at level 2 we have no
+ * choice but to make cartesian joins. We consider only left-sided and
+ * right-sided cartesian joins in this case (no bushy).
+ */
+ if (joinrels[level] == NIL)
+ {
+ /*
+ * This loop is just like the first one, except we always call
+ * make_rels_by_clauseless_joins().
+ */
+ foreach(r, joinrels[level - 1])
+ {
+ RelOptInfo *old_rel = (RelOptInfo *) lfirst(r);
+ ListCell *other_rels;
+
+ if (level == 2)
+ other_rels = lnext(r); /* only consider remaining initial
+ * rels */
+ else
+ other_rels = list_head(joinrels[1]); /* consider all initial
+ * rels */
+
+ make_rels_by_clauseless_joins(root,
+ old_rel,
+ other_rels);
+ }
+
+ /*----------
+ * When special joins are involved, there may be no legal way
+ * to make an N-way join for some values of N. For example consider
+ *
+ * SELECT ... FROM t1 WHERE
+ * x IN (SELECT ... FROM t2,t3 WHERE ...) AND
+ * y IN (SELECT ... FROM t4,t5 WHERE ...)
+ *
+ * We will flatten this query to a 5-way join problem, but there are
+ * no 4-way joins that join_is_legal() will consider legal. We have
+ * to accept failure at level 4 and go on to discover a workable
+ * bushy plan at level 5.
+ *
+ * However, if there are no special joins then join_is_legal() should
+ * never fail, and so the following sanity check is useful.
+ *----------
+ */
+ if (joinrels[level] == NIL && root->join_info_list == NIL)
+ elog(ERROR, "failed to build any %d-way joins", level);
+ }
+}
+
+/*
+ * src/backend/optimizer/path/joinrels.c
+ * static make_rels_by_clause_joins() を流用
+ *
+ * 変更箇所
+ * make_join_rel() の呼び出しをラップする、my_make_join_rel()の呼び出しに変更
+ */
+/*
+ * make_rels_by_clause_joins
+ * Build joins between the given relation 'old_rel' and other relations
+ * that participate in join clauses that 'old_rel' also participates in
+ * (or participate in join-order restrictions with it).
+ * The join rels are returned in root->join_rel_level[join_cur_level].
+ *
+ * Note: at levels above 2 we will generate the same joined relation in
+ * multiple ways --- for example (a join b) join c is the same RelOptInfo as
+ * (b join c) join a, though the second case will add a different set of Paths
+ * to it. This is the reason for using the join_rel_level mechanism, which
+ * automatically ensures that each new joinrel is only added to the list once.
+ *
+ * 'old_rel' is the relation entry for the relation to be joined
+ * 'other_rels': the first cell in a linked list containing the other
+ * rels to be considered for joining
+ *
+ * Currently, this is only used with initial rels in other_rels, but it
+ * will work for joining to joinrels too.
+ */
+static void
+make_rels_by_clause_joins(PlannerInfo *root,
+ RelOptInfo *old_rel,
+ ListCell *other_rels)
+{
+ ListCell *l;
+
+ for_each_cell(l, other_rels)
+ {
+ RelOptInfo *other_rel = (RelOptInfo *) lfirst(l);
+
+ if (!bms_overlap(old_rel->relids, other_rel->relids) &&
+ (have_relevant_joinclause(root, old_rel, other_rel) ||
+ have_join_order_restriction(root, old_rel, other_rel)))
+ {
+ (void) my_make_join_rel(root, old_rel, other_rel);
+ }
+ }
+}
+
+/*
+ * src/backend/optimizer/path/joinrels.c
+ * static make_rels_by_clauseless_joins() を流用
+ *
+ * 変更箇所
+ * make_join_rel() の呼び出しをラップする、my_make_join_rel()の呼び出しに変更
+ */
+/*
+ * make_rels_by_clauseless_joins
+ * Given a relation 'old_rel' and a list of other relations
+ * 'other_rels', create a join relation between 'old_rel' and each
+ * member of 'other_rels' that isn't already included in 'old_rel'.
+ * The join rels are returned in root->join_rel_level[join_cur_level].
+ *
+ * 'old_rel' is the relation entry for the relation to be joined
+ * 'other_rels': the first cell of a linked list containing the
+ * other rels to be considered for joining
+ *
+ * Currently, this is only used with initial rels in other_rels, but it would
+ * work for joining to joinrels too.
+ */
+static void
+make_rels_by_clauseless_joins(PlannerInfo *root,
+ RelOptInfo *old_rel,
+ ListCell *other_rels)
+{
+ ListCell *l;
+
+ for_each_cell(l, other_rels)
+ {
+ RelOptInfo *other_rel = (RelOptInfo *) lfirst(l);
+
+ if (!bms_overlap(other_rel->relids, old_rel->relids))
+ {
+ (void) my_make_join_rel(root, old_rel, other_rel);
+ }
+ }
+}
+
+/*
+ * src/backend/optimizer/path/joinrels.c
+ * static has_join_restriction() を流用
+ *
+ * 変更箇所
+ * なし
+ */
+/*
+ * has_join_restriction
+ * Detect whether the specified relation has join-order restrictions
+ * due to being inside an outer join or an IN (sub-SELECT).
+ *
+ * Essentially, this tests whether have_join_order_restriction() could
+ * succeed with this rel and some other one. It's OK if we sometimes
+ * say "true" incorrectly. (Therefore, we don't bother with the relatively
+ * expensive has_legal_joinclause test.)
+ */
+static bool
+has_join_restriction(PlannerInfo *root, RelOptInfo *rel)
+{
+ ListCell *l;
+
+ foreach(l, root->join_info_list)
+ {
+ SpecialJoinInfo *sjinfo = (SpecialJoinInfo *) lfirst(l);
+
+ /* ignore full joins --- other mechanisms preserve their ordering */
+ if (sjinfo->jointype == JOIN_FULL)
+ continue;
+
+ /* ignore if SJ is already contained in rel */
+ if (bms_is_subset(sjinfo->min_lefthand, rel->relids) &&
+ bms_is_subset(sjinfo->min_righthand, rel->relids))
+ continue;
+
+ /* restricted if it overlaps LHS or RHS, but doesn't contain SJ */
+ if (bms_overlap(sjinfo->min_lefthand, rel->relids) ||
+ bms_overlap(sjinfo->min_righthand, rel->relids))
+ return true;
+ }
+
+ return false;
+}