diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c index c00fa30..d30d715 100644 --- a/src/backend/optimizer/path/costsize.c +++ b/src/backend/optimizer/path/costsize.c @@ -3732,611 +3732,248 @@ get_parameterized_joinrel_size(PlannerInfo *root, RelOptInfo *rel, } /* - * clause_is_fk_compatible - * Verify that the clause may be compatible with foreign keys (i.e. it's - * a simple operator clause with two Vars, referencing two different - * relations). + * find_best_match_foreign_key + * Returns a Bitmapset with bits set for each 0 based index of 'joinquals' + * which to a foreign key which is defined on fkrel. * - * This only checks form of the clause (and returns 'true' if it may match - * a foreign key), but does not cross-check the clause against existing foreign - * keys for example. + * Note: in the event that there's more than 1 foreign key between these two + * relations, the largest match, i.e. the bitmask for the one with the most + * matching 'joinquals' items, is returned. * - * It also extracts data from the clause - rangetable indexes and attnums for - * each Var (if the function returns 'false' then those values are undefined - * and may contain garbage). + * XXX it's possible to trick this code when the joinquals contain duplicate + * quals. Is this worth fixing? */ -static bool -clause_is_fk_compatible(RestrictInfo *rinfo, - Index *varnoa, AttrNumber *attnoa, - Index *varnob, AttrNumber *attnob, - Oid *opno) +static Bitmapset * +find_best_match_foreign_key(PlannerInfo *root, RelOptInfo *fkrel, + RelOptInfo *foreignrel, List *joinquals) { - OpExpr *clause; - Var *var0, *var1; - - /* We only expect restriction clauses here, with operator expression. */ - - if (! IsA(rinfo, RestrictInfo)) - return false; - - if (! IsA(rinfo->clause, OpExpr)) - return false; + Bitmapset *bestmatch; + Bitmapset *usefulquals; + ListCell *lc; + int lstidx; + Oid frelid; - clause = (OpExpr*)rinfo->clause; - - /* The clause has to use exactly two simple variables. */ - - if (list_length(clause->args) != 2) - return false; - - var0 = list_nth(clause->args, 0); - var1 = list_nth(clause->args, 1); - - if (! (IsA(var0, Var) && IsA(var1, Var))) - return false; + /* fast path out when there's no foreign keys on fkrel */ + if (fkrel->fkeylist == NIL) + return NULL; - /* The variables has to reference two different rangetable entries. */ - - if (var0->varno == var1->varno) - return false; + bestmatch = NULL; + lstidx = -1; + usefulquals = NULL; /* - * At this point we know the clause has the right structure, so extract - * the interesting info we'll need outside. We don't really track which - * relation is inner/outer, so we'll check both directions. + * First build a bitmapset with all possibly useful quals. This will save + * from having to do this for each foreign key later. The only quals that + * are useful to us are in the form "var op var", so here we'll ignore + * things like "function(var1, var2)". */ + foreach(lc, joinquals) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + OpExpr *clause; + Var *leftvar, *rightvar; - *varnoa = var0->varno; - *attnoa = var0->varattno; - - *varnob = var1->varno; - *attnob = var1->varattno; + /* + * Increment this at the start of the loop to save doing it before each + * continue statement. + */ + lstidx++; - *opno = clause->opno; + if (!IsA(rinfo, RestrictInfo)) + continue; - return true; -} + if (!IsA(rinfo->clause, OpExpr)) + continue; + clause = (OpExpr *) rinfo->clause; -/* - * fkey_is_matched_by_clauses - * Check whether the foreign key is "fully" matched by the clauses. - * - * This checks whether the foreign key is fully matched by clauses, i.e. if - * there's a clause matching perfectly all the parts of the foreign key. - */ -static bool -fkey_is_matched_by_clauses(PlannerInfo *root, ForeignKeyOptInfo *fkinfo, - List *clauses, int relid, int frelid) -{ - int i; - ListCell *lc; - bool r; - bool *matched; + if (list_length(clause->args) != 2) + continue; - /* we should only get multi-column foreign keys here */ - Assert(fkinfo->nkeys > 1); + leftvar = (Var *) get_leftop((Expr *) clause); + rightvar = (Var *) get_rightop((Expr *) clause); - /* flags for each part of the foreign key */ - matched = palloc0(fkinfo->nkeys); + /* Foreign keys only support Vars, so ignore anything more complex */ + if (!IsA(leftvar, Var) || !IsA(rightvar, Var)) + continue; - foreach (lc, clauses) - { - RestrictInfo *rinfo = (RestrictInfo*)lfirst(lc); + usefulquals = bms_add_member(usefulquals, lstidx); + } - /* info extracted from the clause (opno, varnos and varattnos) */ - Oid opno; - Index relida, relidb; - AttrNumber attnoa, attnob; + frelid = root->simple_rte_array[foreignrel->relid]->relid; - /* we'll look this up in the simple_rte_array table */ - Oid oida, oidb; + /* now check the matches for each foreign key defined on the fkrel */ + foreach(lc, fkrel->fkeylist) + { + ForeignKeyOptInfo *fkinfo = (ForeignKeyOptInfo *) lfirst(lc); + Bitmapset *matches; + int i; - /* - * If the clause has structure incompatible with foreign keys (not an - * operator clause with two Var nodes), just skip it. - */ - if (! clause_is_fk_compatible(rinfo, &relida, &attnoa, - &relidb, &attnob, &opno)) + /* skip any foreign keys which don't reference frelid */ + if (fkinfo->confrelid != frelid) continue; - /* lookup range table entries for the indexes */ - oida = root->simple_rte_array[relida]->relid; - oidb = root->simple_rte_array[relidb]->relid; + matches = NULL; /* - * Check if the clause matches any part of the foreign key. + * Loop over each column of the foreign key and build a bitmap index + * of each joinqual which matches. Note that we don't stop when we find + * the first match, as the expression could be duplicated in the + * joinquals, and we want to match as many as possible. */ for (i = 0; i < fkinfo->nkeys; i++) { - /* if the operator does not match, try next key */ - if (! fkinfo->conpfeqop[i] == opno) - continue; - - /* - * We don't know in what order the clause lists the Vars, so we'll check - * the foreign key in both directions (it does not really matter). - */ - if ((oida == fkinfo->conrelid) && (oidb == fkinfo->confrelid)) - { - if ((fkinfo->confkeys[i] == attnob) && - (fkinfo->conkeys[i] == attnoa)) - matched[i] = true; - } - - if ((oida == fkinfo->confrelid) && (oidb == fkinfo->conrelid)) - { - if ((fkinfo->confkeys[i] == attnoa) && - (fkinfo->conkeys[i] == attnob)) - matched[i] = true; - } - } - } - - /* return 'true' if all the parts of the foreign key were matched */ - r = true; - for (i = 0; i < fkinfo->nkeys; i++) - r &= matched[i]; - - pfree(matched); - - return r; -} - -/* - * find_satisfied_fkeys - * Searches for all foreign keys fully-satisfied by the join clauses. - * - * A join is fully-satisfied if all the parts are matched by at least one - * join condition (same operator and attnos). - * - * This returns a list of foreign keys of identified foreign keys (or NIL), - * and also selectivity for all the (matched) foreign keys. - */ -static List * -find_satisfied_fkeys(PlannerInfo *root, SpecialJoinInfo *sjinfo, List *joinquals, - Selectivity *sel) -{ - int inner, outer; - List *fkeys = NIL; - - /* - * We'll take all combinations of inner/outer relations, and check if - * there are foreign keys between them. If we found a foreign key for - * the pair of base relations, we'll try matching it to clauses. - * - * We don't know in which direction the foreign keys are created, so - * we'll check both directions (it's allways between inner and outer - * side of the join). - */ - - inner = -1; - while ((inner = bms_next_member(sjinfo->min_righthand, inner)) >= 0) - { - RelOptInfo *rel_inner = find_base_rel(root, inner); - RangeTblEntry *rt_inner = planner_rt_fetch(inner, root); - - Assert(rel_inner->reloptkind == RELOPT_BASEREL); - - outer = -1; - while ((outer = bms_next_member(sjinfo->min_lefthand, outer)) >= 0) - { - ListCell *lc; - RelOptInfo *rel_outer = find_base_rel(root, outer); - RangeTblEntry *rt_outer = planner_rt_fetch(outer, root); - - Assert(rel_outer->reloptkind == RELOPT_BASEREL); + ListCell *lc2; - /* - * Walk through foreign keys defined on the inner side, referencing - * relation on the outer side. - */ - foreach (lc, rel_inner->fkeylist) + lstidx = -1; + foreach(lc2, joinquals) { - ForeignKeyOptInfo *fkinfo = (ForeignKeyOptInfo *)lfirst(lc); + RestrictInfo *rinfo; + OpExpr *clause; + Var *leftvar; + Var *rightvar; - /* We only care about keys referencing the current outer relation */ - if (fkinfo->confrelid != rt_outer->relid) - continue; + lstidx++; - /* - * And we don't care about foreign keys with less than two columns - * (those clauses will be handled by the regular estimation, unless - * matched by some other key). - * - * XXX Maybe we should estimate even the single-column keys here, - * as it's really cheap. But it can't do any cross-table check - * of MCV lists or whatever clauselist_selectivity() does. - */ - if (fkinfo->nkeys < 2) + /* skip anything we didn't mark as useful above. */ + if (!bms_is_member(lstidx, usefulquals)) continue; /* - * Finally check if the foreign key is full matched by clauses, - * and update the selectivity (simply use 1/cardinality of the - * table referenced by the foreign key). - * - * XXX Notice we're using 'rel->tuples' here and not 'rows', - * because we need the cardinality (before applying clauses). + * Here we can safely assume that we have an OpExpr, in the + * from of "var op var" */ - if (fkey_is_matched_by_clauses(root, fkinfo, joinquals, inner, outer)) - { - fkeys = lappend(fkeys, fkinfo); - *sel *= (1.0 / rel_outer->tuples); - } - - } - - /* - * And now check foreign keys in the other direction (defined on - * outer relation, referencing inner). - * - * XXX This does exactly the same thing as the previous loop, so no - * comments. - * - * TODO Merge those two blocks into a single utility function to - * reduce the code duplication. - */ - foreach (lc, rel_outer->fkeylist) - { - ForeignKeyOptInfo *fkinfo = (ForeignKeyOptInfo *)lfirst(lc); + rinfo = (RestrictInfo *) lfirst(lc2); + clause = (OpExpr *) rinfo->clause; - if (fkinfo->confrelid != rt_inner->relid) + /* skip if the operator does not match */ + if (clause->opno != fkinfo->conpfeqop[i]) continue; - if (fkinfo->nkeys < 2) - continue; + leftvar = (Var *) get_leftop((Expr *) clause); + rightvar = (Var *) get_rightop((Expr *) clause); - if (fkey_is_matched_by_clauses(root, fkinfo, joinquals, outer, inner)) - { - fkeys = lappend(fkeys, fkinfo); - *sel *= (1.0 / rel_inner->tuples); - } + /* + * Check if the OpExpr matches the foreign key. Remember that + * this could be written with the Vars in either order, so we + * test both permutations of the expression. + */ + if (fkinfo->confkeys[i] == leftvar->varattno && + fkinfo->conkeys[i] == rightvar->varattno) + matches = bms_add_member(matches, lstidx); + else if (fkinfo->confkeys[i] == rightvar->varattno && + fkinfo->conkeys[i] == leftvar->varattno) + matches = bms_add_member(matches, lstidx); } + } + /* Is this match better than the current best match? */ + if (bms_num_members(matches) > bms_num_members(bestmatch)) + { + bms_free(bestmatch); + bestmatch = matches; } } - return fkeys; + return bestmatch; } /* - * filter_fk_join_clauses - * Remove the clauses that were used to match foreign keys (and will be - * estimated using the selectivity from keys). - * - * Once we identify the foreign keys matched by clauses, we need to remove the - * clauses so that we don't include them into the estimate twice. This method - * performs that - cross-checks the foreign keys and clauses and removes all - * clauses matching any of the foreign keys. - * - * If there are no foreign keys, this simply returns the original list of - * clauses. Otherwise it builds a new list (without modifying the source one). + * clauselist_join_selectivity + * Estimate selectivity of join clauses either by using foreign key info + * or by using the regular clauselist_selectivity(). + * + * Since selectivity estimates for each joinqual are multiplied together, this + * can cause significant underestimates on the number of join tuples in cases + * where there's more than 1 clause in the join condition. To help ease the + * pain here we make use of foreign keys, and we assume that 1 row will match + * when *all* of the foreign key columns are present in the join condition, any + * additional clauses are estimated using clauselist_selectivity(). + * + * XXX is it worth adding code to do something smart when we only get a partial + * foreign key match? */ -static List * -filter_fk_join_clauses(PlannerInfo *root, SpecialJoinInfo *sjinfo, List *fkeys, - List *joinquals) +static Selectivity +clauselist_join_selectivity(PlannerInfo *root, List *joinquals, + JoinType jointype, SpecialJoinInfo *sjinfo) { - ListCell *lc, - *lc2; - List *clauses = NIL; - - /* if there are no foreign keys, return the original list */ - if (list_length(fkeys) == 0) - return joinquals; + int outerrelid; + int innerrelid; - foreach (lc, joinquals) + /* + * Since foreign keys can only references a single relation, and each + * foreign key belongs to at most a single relation, we require that only + * a single relation be at either side of the join condition. We'll fall + * back on clauselist_selectivity() if this is not the case. + */ + if (bms_get_singleton_member(sjinfo->min_righthand, &innerrelid) && + bms_get_singleton_member(sjinfo->min_lefthand, &outerrelid)) { - Oid opno; - Index relida, relidb; - AttrNumber attnoa, attnob; - Oid oida, oidb; - - /* was the clause matched by at least one key? */ - bool matched = false; - - RestrictInfo *rinfo = (RestrictInfo*)lfirst(lc); + Bitmapset *outer2inner, *inner2outer; + RelOptInfo *innerrel = find_base_rel(root, innerrelid); + RelOptInfo *outerrel = find_base_rel(root, outerrelid); - /* if the clause is not compatible with foreign keys, just add it */ - if (! clause_is_fk_compatible(rinfo, &relida, &attnoa, - &relidb, &attnob, &opno)) - { - clauses = lappend(clauses, rinfo); - continue; - } + /* check which quals are matched by a foreign key referencing the innerrel */ + outer2inner = find_best_match_foreign_key(root, outerrel, innerrel, joinquals); - oida = root->simple_rte_array[relida]->relid; - oidb = root->simple_rte_array[relidb]->relid; + /* do the same again, but with relations swapped */ + inner2outer = find_best_match_foreign_key(root, innerrel, outerrel, joinquals); /* - * Walk through the matched foreign keys, and try to match the clause - * against each one. We don't know in what order are the Vars listed - * in the clause, so try both ways. + * did we find any matches at all? If so we need to see which one is + * the best/longest match */ - foreach (lc2, fkeys) + if (outer2inner || inner2outer) { - int i; - ForeignKeyOptInfo *fkinfo = (ForeignKeyOptInfo*)lfirst(lc2); + Selectivity sel; + ListCell *lc; + int lstidx; + List *nonfkeyclauses; - /* now check the keys - we can stop after finding the first match */ - for (i = 0; i < fkinfo->nkeys; i++) + /* either could be NULL, but bms_num_members will handle that */ + if (bms_num_members(outer2inner) < bms_num_members(inner2outer)) { - /* check the operator first */ - if (fkinfo->conpfeqop[i] != opno) - continue; + /* + * We can now disgard the lesser of the two and save the best + * match into one of the bitmaps. Let's just use outer2inner + */ + bms_free(outer2inner); + outer2inner = inner2outer; - /* now check the attnums */ - if ((fkinfo->conrelid == oida) && (fkinfo->confrelid == oidb)) - { - if ((fkinfo->confkeys[i] == attnob) && - (fkinfo->conkeys[i] == attnoa)) - { - matched = true; - break; - } - } - else if ((fkinfo->conrelid == oidb) && (fkinfo->confrelid == oida)) - { - if ((fkinfo->confkeys[i] == attnoa) && - (fkinfo->conkeys[i] == attnob)) - { - matched = true; - break; - } - } + if (jointype == JOIN_SEMI || jointype == JOIN_ANTI) + sel = 1.0; + else + sel = 1.0 / Max(outerrel->tuples, 1.0); } + else + { + bms_free(inner2outer); - /* no need to try more keys, single match is enough */ - if (matched) - break; - } - - /* if a clause was not matched by any foreign key, continue */ - if (! matched) - clauses = lappend(clauses, rinfo); - - } - - return clauses; -} - - - -/* - * Estimate selectivity of join clauses - either by using foreign key info or - * by using the regular clauselist_selectivity(). - * - * If there are multiple join clauses, we check whether the clauses match - * a foreign key between the tables - in that case we can use this information - * to derive a better estimate (otherwise we'd multiply the selectivities for - * each clause, which often causes significant underestimates). - * - * We only need to care about multi-clause join conditions and simply defer - * simple clauses to clauselist_selectivity(). - * - * Let's see a few examples of foreign-key joins, illustrating the estimation - * ideas here. - * - * CREATE TABLE a ( - * a1 INT, - * a2 INT, - * a3 INT, - * a4 INT, - * PRIMARY KEY (a1, a2, a3) - * ); - * - * CREATE TABLE b ( - * b1 INT, - * b2 INT, - * b3 INT, - * b4 INT, - * FOREIGN KEY (b1, b2, b3) REFERENCES (a1, a2, a3) - * ); - * - * clauses exactly match a foreign key - * ----------------------------------- - * - * SELECT * FROM a JOIN b ON (a1=b1 AND a2=b2 AND a3=b3); - * - * - trivial, just use 1/card(a) - * - * clauses match a foreign key, with additional conditions exist - * ------------------------------------------------------------- - * - * SELECT * FROM a JOIN b ON (a1=b1 AND a2=b2 AND a3=b3 AND a4=b4); - * - * - trivial, just use 1/card(a) * selectivity(remaining_clauses) - * - * incomplete foreign key match - * ---------------------------- - * - * SELECT * FROM a JOIN b ON (a1=b1 AND a2=b2); - * - * - not sure, we'd need to compensate for the "missing part" somehow (we know - * the row exists, but we don't know much many rows - it's likely more than - * unique) - * - * - one way would be to assume each clause is responsible for (1/card(a))^(1/n) - * where 'n' is number of clauses - this way 'multiplying the FK clauses' would - * gets us the 1/card(a) selectivity if we had all the clauses - * - * - another thing is we might use 1/card(a) as a lower boundary - we can't - * possibly get lower selectivity, we know the rows exist (also this is not - * based on assumptions like the previous idea) - * - * multiple distinct foreign keys matching - * --------------------------------------- - * - * CREATE TABLE a ( - * a1 INT, - * a2 INT, - * a3 INT, - * a4 INT, - * PRIMARY KEY (a1, a2), - * UNIQUE (a3, a4) - * ); - * - * CREATE TABLE b ( - * b1 INT, - * b2 INT, - * b3 INT, - * b4 INT, - * FOREIGN KEY (b1, b2) REFERENCES (a1, a2), - * FOREIGN KEY (b3, b4) REFERENCES (a3, a4) - * ); - * - * - simply just use 1/card(a) for each foreign key (assumes independence of the - * foreign keys, but well - we're assuming attribute independence so this is - * an improvement) - * - * multiple overlapping foreign keys matching - * ------------------------------------------ - * - * CREATE TABLE a ( - * a1 INT, - * a2 INT, - * a3 INT, - * PRIMARY KEY (a1, a2), - * UNIQUE (a2, a3) - * ); - * - * CREATE TABLE b ( - * b1 INT, - * b2 INT, - * b3 INT, - * b4 INT, - * FOREIGN KEY (b1, b2) REFERENCES (a1, a2), - * FOREIGN KEY (b3, b4) REFERENCES (a2, a3) - * ); - * - * - probably just use 1/card(a) for each foreign key, as in the previous - * example (assumes independence of the foreign keys, but well - we're - * assuming attribute independence so this is an improvement) - * - * There are strange cases with multiple foreign keys, where one FK implies - * the other FK. For example consider this: - * - * CREATE TABLE a ( - * a1 INT, - * a2 INT, - * a3 INT, - * UNIQUE (a1, a2, a3), - * UNIQUE (a1, a2) - * ); - * - * CREATE TABLE b ( - * b1 INT, - * b2 INT, - * b3 INT, - * FOREIGN KEY (b1, b2, b3) REFERENCES a (a1, a2, a3), - * FOREIGN KEY (b1, b2) REFERENCES a (a1, a2) - * ); - * - * Clearly the (b1,b2) is implied by (b1,b2,b3) - if the latter exists, then - * the former exists too. Not sure how to handle this (or if it's actually - * needed). - * - * Another slightly strange case is FK constraints in both directions (these - * statements don't work - the foreign keys need to be established using - * ALTER, but for illustration it's sufficient). - * - * CREATE TABLE a ( - * a1 INT, - * a2 INT, - * UNIQUE (a1, a2), - * FOREIGN KEY (a1, a2) REFERENCES a (b1, b2) - * ); - * - * CREATE TABLE b ( - * b1 INT, - * b2 INT, - * UNIQUE (b1, b2), - * FOREIGN KEY (b1, b2) REFERENCES a (a1, a2) - * ); - * - * which effectively establishes 1:1 relationship, or with distinct groups of - * columns for each direction - * - * CREATE TABLE a ( - * a1 INT, - * a2 INT, - * a3 INT, - * a4 INT, - * UNIQUE (a1, a2), - * FOREIGN KEY (a3, a4) REFERENCES a (b1, b2) - * ); - * - * CREATE TABLE b ( - * b1 INT, - * b2 INT, - * b3 INT, - * b4 INT, - * UNIQUE (b1, b2), - * FOREIGN KEY (b3, b4) REFERENCES a (a1, a2) - * ); - * - * which creates a cycle of foreign keys. - * - * In the first case the foreign keys should be counted only once into the - * selectivity, because it's effectively a 1:1 relationship - each row has - * to have one matching row in the other table (not matched by other) rows. - * - * In the other case, it's probably right to factor in both foreign keys. - */ -static Selectivity -clauselist_join_selectivity(PlannerInfo *root, List *joinquals, int varno, - JoinType jointype, SpecialJoinInfo *sjinfo) -{ - Selectivity sel = 1.0; - - List *fkeys; /* list of satisfied foreign keys */ - List *unmatched; /* clauses remaining after removing FK clauses */ - - Assert(list_length(joinquals) >= 0); - - /* - * If we only have a single clause, we don't need to mess with the foreign - * keys - that's only useful with multi-column clauses anyway. Just use the - * simple clauselist_selectivity. - */ - if (list_length(joinquals) <= 1) - return clauselist_selectivity(root, - joinquals, - 0, - jointype, - sjinfo); - - /* - * First we'll identify foreign keys that are fully matched by the join - * clauses, and we'll update the selectivity accordingly while doing so. - */ - fkeys = find_satisfied_fkeys(root, sjinfo, joinquals, &sel); - - /* - * Now that we have the foreign keys, we can get rid of the clauses - * matching any of them, and only keep the remaining clauses, so that - * we can estimate them using the regular selectivity estimation. - */ - unmatched = filter_fk_join_clauses(root, sjinfo, fkeys, joinquals); + if (jointype == JOIN_SEMI || jointype == JOIN_ANTI) + sel = 1.0; + else + sel = 1.0 / Max(innerrel->tuples, 1.0); + } - /* - * Estimate the remaining clauses (not matching any FK), if still have any. - */ - if (list_length(unmatched) > 0) - { - sel *= clauselist_selectivity(root, - unmatched, - 0, - jointype, - sjinfo); - - /* Only free the list if we actually found any foreign keys. */ - if (list_length(fkeys) > 0) - list_free(unmatched); + /* + * build a list of all non-fkey joinquals, we'll need to enlist + * clauselist_selectivity() to estimate these + */ + lstidx = 0; + nonfkeyclauses = NIL; + foreach (lc, joinquals) + { + if (!bms_is_member(lstidx, outer2inner)) + nonfkeyclauses = lappend(nonfkeyclauses, lfirst(lc)); + lstidx++; + } + return sel * clauselist_selectivity(root, nonfkeyclauses, 0, jointype, sjinfo); + } } - return sel; + /* perform normal estimation without the help of foreign keys */ + return clauselist_selectivity(root, joinquals, 0, jointype, sjinfo); } /* @@ -4387,7 +4024,6 @@ calc_joinrel_size_estimate(PlannerInfo *root, /* Get the separate selectivities */ jselec = clauselist_join_selectivity(root, joinquals, - 0, jointype, sjinfo); @@ -4405,7 +4041,6 @@ calc_joinrel_size_estimate(PlannerInfo *root, { jselec = clauselist_join_selectivity(root, restrictlist, - 0, jointype, sjinfo); pselec = 0.0; /* not used, keep compiler quiet */ diff --git a/src/backend/optimizer/plan/analyzejoins.c b/src/backend/optimizer/plan/analyzejoins.c index 05c8698..7912b15 100644 --- a/src/backend/optimizer/plan/analyzejoins.c +++ b/src/backend/optimizer/plan/analyzejoins.c @@ -562,6 +562,7 @@ remove_rel_from_joinlist(List *joinlist, int relid, int *nremoved) return result; } + /* * query_supports_distinctness - could the query possibly be proven distinct * on some set of output columns? diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index fbc5579..dbe6038 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -96,6 +96,8 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, bool hasindex; List *indexinfos = NIL; List *fkinfos = NIL; + List *fkoidlist; + ListCell *l; /* * We need not lock the relation since it was already locked, either by @@ -143,7 +145,6 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, if (hasindex) { List *indexoidlist; - ListCell *l; LOCKMODE lmode; indexoidlist = RelationGetIndexList(relation); @@ -384,86 +385,75 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, rel->indexlist = indexinfos; - /* - * TODO Can we do something like (hasindex) here? Is it necessary? The - * trouble with that is that we don't have a good place to reset that - * flag (relhasindex is reset by vacuum, but is has nothing to do with - * foreign keys at this point). - */ - if (true) - { - List *fkoidlist; - ListCell *l; - - fkoidlist = RelationGetFKeyList(relation); - - foreach(l, fkoidlist) - { - int i; - ArrayType *arr; - Datum adatum; - bool isnull; - int numkeys; - Oid fkoid = lfirst_oid(l); + /* load foreign keys */ + fkoidlist = RelationGetFKeyList(relation); - HeapTuple htup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(fkoid)); - Form_pg_constraint constraint = (Form_pg_constraint) GETSTRUCT(htup); + foreach(l, fkoidlist) + { + int i; + ArrayType *arr; + Datum adatum; + bool isnull; + int numkeys; + Oid fkoid = lfirst_oid(l); - ForeignKeyOptInfo *info; + HeapTuple htup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(fkoid)); + Form_pg_constraint constraint = (Form_pg_constraint) GETSTRUCT(htup); - Assert(constraint->contype == CONSTRAINT_FOREIGN); + ForeignKeyOptInfo *info; - info = makeNode(ForeignKeyOptInfo); + Assert(constraint->contype == CONSTRAINT_FOREIGN); - info->conrelid = constraint->conrelid; - info->confrelid = constraint->confrelid; + info = makeNode(ForeignKeyOptInfo); - /* conkey */ - adatum = SysCacheGetAttr(CONSTROID, htup, - Anum_pg_constraint_conkey, &isnull); - Assert(!isnull); + info->conrelid = constraint->conrelid; + info->confrelid = constraint->confrelid; - arr = DatumGetArrayTypeP(adatum); - numkeys = ARR_DIMS(arr)[0]; - info->conkeys = (int*)palloc0(numkeys * sizeof(int)); + /* conkey */ + adatum = SysCacheGetAttr(CONSTROID, htup, + Anum_pg_constraint_conkey, &isnull); + Assert(!isnull); - for (i = 0; i < numkeys; i++) - info->conkeys[i] = ((int16 *) ARR_DATA_PTR(arr))[i]; + arr = DatumGetArrayTypeP(adatum); + numkeys = ARR_DIMS(arr)[0]; + info->conkeys = (int*)palloc0(numkeys * sizeof(int)); - /* confkey */ - adatum = SysCacheGetAttr(CONSTROID, htup, - Anum_pg_constraint_confkey, &isnull); - Assert(!isnull); + for (i = 0; i < numkeys; i++) + info->conkeys[i] = ((int16 *) ARR_DATA_PTR(arr))[i]; - arr = DatumGetArrayTypeP(adatum); - numkeys = ARR_DIMS(arr)[0]; - info->confkeys = (int*)palloc0(numkeys * sizeof(int)); + /* confkey */ + adatum = SysCacheGetAttr(CONSTROID, htup, + Anum_pg_constraint_confkey, &isnull); + Assert(!isnull); - for (i = 0; i < numkeys; i++) - info->confkeys[i] = ((int16 *) ARR_DATA_PTR(arr))[i]; + arr = DatumGetArrayTypeP(adatum); + numkeys = ARR_DIMS(arr)[0]; + info->confkeys = (int*)palloc0(numkeys * sizeof(int)); - /* conpfeqop */ - adatum = SysCacheGetAttr(CONSTROID, htup, - Anum_pg_constraint_conpfeqop, &isnull); - Assert(!isnull); + for (i = 0; i < numkeys; i++) + info->confkeys[i] = ((int16 *) ARR_DATA_PTR(arr))[i]; - arr = DatumGetArrayTypeP(adatum); - numkeys = ARR_DIMS(arr)[0]; - info->conpfeqop = (Oid*)palloc0(numkeys * sizeof(Oid)); + /* conpfeqop */ + adatum = SysCacheGetAttr(CONSTROID, htup, + Anum_pg_constraint_conpfeqop, &isnull); + Assert(!isnull); - for (i = 0; i < numkeys; i++) - info->conpfeqop[i] = ((Oid *) ARR_DATA_PTR(arr))[i]; + arr = DatumGetArrayTypeP(adatum); + numkeys = ARR_DIMS(arr)[0]; + info->conpfeqop = (Oid*)palloc0(numkeys * sizeof(Oid)); - info->nkeys = numkeys; + for (i = 0; i < numkeys; i++) + info->conpfeqop[i] = ((Oid *) ARR_DATA_PTR(arr))[i]; - ReleaseSysCache(htup); + info->nkeys = numkeys; - fkinfos = lcons(info, fkinfos); - } + ReleaseSysCache(htup); - list_free(fkoidlist); + fkinfos = lcons(info, fkinfos); } + list_free(fkoidlist); + rel->fkeylist = fkinfos; /* Grab foreign-table info using the relcache, while we have it */