pathkeys.c
来自「PostgreSQL7.4.6 for Linux」· C语言 代码 · 共 1,266 行 · 第 1/3 页
C
1,266 行
/*------------------------------------------------------------------------- * * pathkeys.c * Utilities for matching and building path keys * * See src/backend/optimizer/README for a great deal of information about * the nature and use of path keys. * * * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION * $Header: /cvsroot/pgsql/src/backend/optimizer/path/pathkeys.c,v 1.53.4.1 2003/12/03 17:45:36 tgl Exp $ * *------------------------------------------------------------------------- */#include "postgres.h"#include "nodes/makefuncs.h"#include "optimizer/clauses.h"#include "optimizer/pathnode.h"#include "optimizer/paths.h"#include "optimizer/planmain.h"#include "optimizer/tlist.h"#include "optimizer/var.h"#include "parser/parsetree.h"#include "parser/parse_expr.h"#include "parser/parse_func.h"#include "utils/lsyscache.h"#include "utils/memutils.h"static PathKeyItem *makePathKeyItem(Node *key, Oid sortop, bool checkType);static List *make_canonical_pathkey(Query *root, PathKeyItem *item);static Var *find_indexkey_var(Query *root, RelOptInfo *rel, AttrNumber varattno);/* * makePathKeyItem * create a PathKeyItem node */static PathKeyItem *makePathKeyItem(Node *key, Oid sortop, bool checkType){ PathKeyItem *item = makeNode(PathKeyItem); /* * Some callers pass expressions that are not necessarily of the same * type as the sort operator expects as input (for example when dealing * with an index that uses binary-compatible operators). We must relabel * these with the correct type so that the key expressions will be seen * as equal() to expressions that have been correctly labeled. */ if (checkType) { Oid lefttype, righttype; op_input_types(sortop, &lefttype, &righttype); if (exprType(key) != lefttype) key = (Node *) makeRelabelType((Expr *) key, lefttype, -1, COERCE_DONTCARE); } item->key = key; item->sortop = sortop; return item;}/* * add_equijoined_keys * The given clause has a mergejoinable operator, so its two sides * can be considered equal after restriction clause application; in * particular, any pathkey mentioning one side (with the correct sortop) * can be expanded to include the other as well. Record the exprs and * associated sortops in the query's equi_key_list for future use. * * The query's equi_key_list field points to a list of sublists of PathKeyItem * nodes, where each sublist is a set of two or more exprs+sortops that have * been identified as logically equivalent (and, therefore, we may consider * any two in a set to be equal). As described above, we will subsequently * use direct pointers to one of these sublists to represent any pathkey * that involves an equijoined variable. */voidadd_equijoined_keys(Query *root, RestrictInfo *restrictinfo){ Expr *clause = restrictinfo->clause; PathKeyItem *item1 = makePathKeyItem(get_leftop(clause), restrictinfo->left_sortop, false); PathKeyItem *item2 = makePathKeyItem(get_rightop(clause), restrictinfo->right_sortop, false); List *newset, *cursetlink; /* We might see a clause X=X; don't make a single-element list from it */ if (equal(item1, item2)) return; /* * Our plan is to make a two-element set, then sweep through the * existing equijoin sets looking for matches to item1 or item2. When * we find one, we remove that set from equi_key_list and union it * into our new set. When done, we add the new set to the front of * equi_key_list. * * It may well be that the two items we're given are already known to be * equijoin-equivalent, in which case we don't need to change our data * structure. If we find both of them in the same equivalence set to * start with, we can quit immediately. * * This is a standard UNION-FIND problem, for which there exist better * data structures than simple lists. If this code ever proves to be * a bottleneck then it could be sped up --- but for now, simple is * beautiful. */ newset = NIL; /* cannot use foreach here because of possible lremove */ cursetlink = root->equi_key_list; while (cursetlink) { List *curset = lfirst(cursetlink); bool item1here = member(item1, curset); bool item2here = member(item2, curset); /* must advance cursetlink before lremove possibly pfree's it */ cursetlink = lnext(cursetlink); if (item1here || item2here) { /* * If find both in same equivalence set, no need to do any * more */ if (item1here && item2here) { /* Better not have seen only one in an earlier set... */ Assert(newset == NIL); return; } /* Build the new set only when we know we must */ if (newset == NIL) newset = makeList2(item1, item2); /* Found a set to merge into our new set */ newset = set_union(newset, curset); /* * Remove old set from equi_key_list. */ root->equi_key_list = lremove(curset, root->equi_key_list); freeList(curset); /* might as well recycle old cons cells */ } } /* Build the new set only when we know we must */ if (newset == NIL) newset = makeList2(item1, item2); root->equi_key_list = lcons(newset, root->equi_key_list);}/* * generate_implied_equalities * Scan the completed equi_key_list for the query, and generate explicit * qualifications (WHERE clauses) for all the pairwise equalities not * already mentioned in the quals; or remove qualifications found to be * redundant. * * Adding deduced equalities is useful because the additional clauses help * the selectivity-estimation code and may allow better joins to be chosen; * and in fact it's *necessary* to ensure that sort keys we think are * equivalent really are (see src/backend/optimizer/README for more info). * * If an equi_key_list set includes any constants then we adopt a different * strategy: we record all the "var = const" deductions we can make, and * actively remove all the "var = var" clauses that are implied by the set * (including the clauses that originally gave rise to the set!). The reason * is that given input like "a = b AND b = 42", once we have deduced "a = 42" * there is no longer any need to apply the clause "a = b"; not only is * it a waste of time to check it, but we will misestimate selectivity if the * clause is left in. So we must remove it. For this purpose, any pathkey * item that mentions no Vars of the current level can be taken as a constant. * (The only case where this would be risky is if the item contains volatile * functions; but we will never consider such an expression to be a pathkey * at all, because check_mergejoinable() will reject it.) * * This routine just walks the equi_key_list to find all pairwise equalities. * We call process_implied_equality (in plan/initsplan.c) to adjust the * restrictinfo datastructures for each pair. */voidgenerate_implied_equalities(Query *root){ List *cursetlink; foreach(cursetlink, root->equi_key_list) { List *curset = lfirst(cursetlink); int nitems = length(curset); Relids *relids; bool have_consts; List *ptr1; int i1; /* * A set containing only two items cannot imply any equalities * beyond the one that created the set, so we can skip it. */ if (nitems < 3) continue; /* * Collect info about relids mentioned in each item. For this * routine we only really care whether there are any at all in * each item, but process_implied_equality() needs the exact sets, * so we may as well pull them here. */ relids = (Relids *) palloc(nitems * sizeof(Relids)); have_consts = false; i1 = 0; foreach(ptr1, curset) { PathKeyItem *item1 = (PathKeyItem *) lfirst(ptr1); relids[i1] = pull_varnos(item1->key); if (bms_is_empty(relids[i1])) have_consts = true; i1++; } /* * Match each item in the set with all that appear after it (it's * sufficient to generate A=B, need not process B=A too). */ i1 = 0; foreach(ptr1, curset) { PathKeyItem *item1 = (PathKeyItem *) lfirst(ptr1); bool i1_is_variable = !bms_is_empty(relids[i1]); List *ptr2; int i2 = i1 + 1; foreach(ptr2, lnext(ptr1)) { PathKeyItem *item2 = (PathKeyItem *) lfirst(ptr2); bool i2_is_variable = !bms_is_empty(relids[i2]); /* * If it's "const = const" then just ignore it altogether. * There is no place in the restrictinfo structure to * store it. (If the two consts are in fact unequal, then * propagating the comparison to Vars will cause us to * produce zero rows out, as expected.) */ if (i1_is_variable || i2_is_variable) { /* * Tell process_implied_equality to delete the clause, * not add it, if it's "var = var" and we have * constants present in the list. */ bool delete_it = (have_consts && i1_is_variable && i2_is_variable); process_implied_equality(root, item1->key, item2->key, item1->sortop, item2->sortop, relids[i1], relids[i2], delete_it); } i2++; } i1++; } }}/* * exprs_known_equal * Detect whether two expressions are known equal due to equijoin clauses. * * Note: does not bother to check for "equal(item1, item2)"; caller must * check that case if it's possible to pass identical items. */boolexprs_known_equal(Query *root, Node *item1, Node *item2){ List *cursetlink; foreach(cursetlink, root->equi_key_list) { List *curset = lfirst(cursetlink); bool item1member = false; bool item2member = false; List *ptr; foreach(ptr, curset) { PathKeyItem *pitem = (PathKeyItem *) lfirst(ptr); if (equal(item1, pitem->key)) item1member = true; else if (equal(item2, pitem->key)) item2member = true; /* Exit as soon as equality is proven */ if (item1member && item2member) return true; } } return false;}/* * make_canonical_pathkey * Given a PathKeyItem, find the equi_key_list subset it is a member of, * if any. If so, return a pointer to that sublist, which is the * canonical representation (for this query) of that PathKeyItem's * equivalence set. If it is not found, add a singleton "equivalence set" * to the equi_key_list and return that --- see compare_pathkeys. * * Note that this function must not be used until after we have completed * scanning the WHERE clause for equijoin operators. */static List *make_canonical_pathkey(Query *root, PathKeyItem *item){ List *cursetlink; List *newset; foreach(cursetlink, root->equi_key_list) { List *curset = lfirst(cursetlink); if (member(item, curset)) return curset; } newset = makeList1(item); root->equi_key_list = lcons(newset, root->equi_key_list); return newset;}/* * canonicalize_pathkeys * Convert a not-necessarily-canonical pathkeys list to canonical form. * * Note that this function must not be used until after we have completed * scanning the WHERE clause for equijoin operators. */List *canonicalize_pathkeys(Query *root, List *pathkeys){ List *new_pathkeys = NIL; List *i; foreach(i, pathkeys) { List *pathkey = (List *) lfirst(i); PathKeyItem *item; List *cpathkey; /* * It's sufficient to look at the first entry in the sublist; if * there are more entries, they're already part of an equivalence * set by definition. */ Assert(pathkey != NIL); item = (PathKeyItem *) lfirst(pathkey); cpathkey = make_canonical_pathkey(root, item); /* * Eliminate redundant ordering requests --- ORDER BY A,A is the * same as ORDER BY A. We want to check this only after we have * canonicalized the keys, so that equivalent-key knowledge is * used when deciding if an item is redundant. */ if (!ptrMember(cpathkey, new_pathkeys)) new_pathkeys = lappend(new_pathkeys, cpathkey); } return new_pathkeys;}/* * count_canonical_peers * Given a PathKeyItem, find the equi_key_list subset it is a member of, * if any. If so, return the number of other members of the set. * If not, return 0 (without actually adding it to our equi_key_list). * * This is a hack to support the rather bogus heuristics in * build_subquery_pathkeys. */static intcount_canonical_peers(Query *root, PathKeyItem *item){ List *cursetlink; foreach(cursetlink, root->equi_key_list) { List *curset = lfirst(cursetlink); if (member(item, curset)) return length(curset) - 1; } return 0;}/**************************************************************************** * PATHKEY COMPARISONS ****************************************************************************//* * compare_pathkeys
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?