On Mon, 2005-06-27 at 01:41 +0100, Simon Riggs wrote: > I enclose a fully working implementation of Constraint Exclusion, a very > basic form of Partitioning. Initial review is requested, to allow us all > to assess what further work is required on this prior to Beta freeze. > > Patch against current cvstip; passes make check and all special tests.
It has been pointed out to me that the patch has a minor, yet basic error in the planning. This has now been corrected in this patch. It appears that my midnight-patch submission was not such a good idea after all. My full and sincere apologies to anybody that tried and failed on that patch. To err is human... I have also now filled the gap for when all relations are excluded, mentioned as question 1 in my original post. I have now re-tested the patch against cvstip... The special tests files are unchanged. Performance tests and comments welcome. Best Regards, Simon Riggs
Index: src/backend/commands/explain.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/backend/commands/explain.c,v retrieving revision 1.137 diff -c -c -r1.137 explain.c *** src/backend/commands/explain.c 4 Jun 2005 02:07:09 -0000 1.137 --- src/backend/commands/explain.c 18 Jul 2005 12:32:11 -0000 *************** *** 58,63 **** --- 58,67 ---- const char *outer_name, int outer_varno, Plan *outer_plan, const char *inner_name, int inner_varno, Plan *inner_plan, StringInfo str, int indent, ExplainState *es); + static void show_result_qual(List *qual, const char *qlabel, + const char *outer_name, int outer_varno, Plan *outer_plan, + const char *inner_name, int inner_varno, Plan *inner_plan, + StringInfo str, int indent, ExplainState *es); static void show_sort_keys(List *tlist, int nkeys, AttrNumber *keycols, const char *qlabel, StringInfo str, int indent, ExplainState *es); *************** *** 786,792 **** str, indent, es); break; case T_Result: ! show_upper_qual((List *) ((Result *) plan)->resconstantqual, "One-Time Filter", "subplan", OUTER, outerPlan(plan), "", 0, NULL, --- 790,796 ---- str, indent, es); break; case T_Result: ! show_result_qual((List *) ((Result *) plan)->resconstantqual, "One-Time Filter", "subplan", OUTER, outerPlan(plan), "", 0, NULL, *************** *** 1088,1093 **** --- 1092,1127 ---- } /* + * Show a qualifier expression for the special case of a Result node + */ + static void + show_result_qual(List *qual, const char *qlabel, + const char *outer_name, int outer_varno, Plan *outer_plan, + const char *inner_name, int inner_varno, Plan *inner_plan, + StringInfo str, int indent, ExplainState *es) + { + int i; + + /* + * If the qual is NIL, then Result node will treat that as false. + * Bypasses an eval step when we have passed a known-false qual + * through to the Result node at planning time. + */ + if (qual == NIL) + { + /* And add to str */ + for (i = 0; i < indent; i++) + appendStringInfo(str, " "); + appendStringInfo(str, " %s: false\n", qlabel); + } + else + show_upper_qual(qual, qlabel, + outer_name, outer_varno, outer_plan, + inner_name, inner_varno, inner_plan, + str, indent, es); + } + + /* * Show the sort keys for a Sort node. */ static void Index: src/backend/executor/nodeResult.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/backend/executor/nodeResult.c,v retrieving revision 1.31 diff -c -c -r1.31 nodeResult.c *** src/backend/executor/nodeResult.c 24 Apr 2005 15:32:07 -0000 1.31 --- src/backend/executor/nodeResult.c 18 Jul 2005 12:32:11 -0000 *************** *** 79,85 **** */ if (node->rs_checkqual) { ! bool qualResult = ExecQual((List *) node->resconstantqual, econtext, false); --- 79,88 ---- */ if (node->rs_checkqual) { ! bool qualResult = false; ! ! if (node->resconstantqual != NULL) ! qualResult = ExecQual((List *) node->resconstantqual, econtext, false); Index: src/backend/optimizer/path/allpaths.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/backend/optimizer/path/allpaths.c,v retrieving revision 1.134 diff -c -c -r1.134 allpaths.c *** src/backend/optimizer/path/allpaths.c 10 Jun 2005 03:32:21 -0000 1.134 --- src/backend/optimizer/path/allpaths.c 18 Jul 2005 12:32:13 -0000 *************** *** 18,30 **** --- 18,33 ---- #ifdef OPTIMIZER_DEBUG #include "nodes/print.h" #endif + #include "access/heapam.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/geqo.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/plancat.h" + #include "optimizer/planmain.h" #include "optimizer/planner.h" + #include "optimizer/predtest.h" #include "optimizer/prep.h" #include "optimizer/var.h" #include "parser/parsetree.h" *************** *** 35,49 **** /* These parameters are set by GUC */ bool enable_geqo = false; /* just in case GUC doesn't set it */ int geqo_threshold; - static void set_base_rel_pathlists(PlannerInfo *root); static void set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static void set_inherited_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte, List *inheritlist); static void set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); static void set_function_pathlist(PlannerInfo *root, RelOptInfo *rel, --- 38,55 ---- /* These parameters are set by GUC */ bool enable_geqo = false; /* just in case GUC doesn't set it */ + bool enable_constraint_exclusion = false; int geqo_threshold; static void set_base_rel_pathlists(PlannerInfo *root); static void set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static void set_inherited_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte, List *inheritlist); + static void propagate_rel_size( RelOptInfo *rel, RelOptInfo *childrel); + static bool RelationExclude(RelOptInfo *rel, Oid childOID); + static List *prepConstrQual(char *ConstrString); static void set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); static void set_function_pathlist(PlannerInfo *root, RelOptInfo *rel, *************** *** 250,255 **** --- 256,268 ---- Oid parentOID = rte->relid; List *subpaths = NIL; ListCell *il; + bool makeChildPlan = false; + + bool saveFirstPlan = false; + RelOptInfo *saverel; + int num_rels = 0; + int num_rels_included = 0; + RelOptInfo *childrel; /* * XXX for now, can't handle inherited expansion of FOR UPDATE/SHARE; *************** *** 275,283 **** int childRTindex = lfirst_int(il); RangeTblEntry *childrte; Oid childOID; ! RelOptInfo *childrel; ! ListCell *parentvars; ! ListCell *childvars; childrte = rt_fetch(childRTindex, root->parse->rtable); childOID = childrte->relid; --- 288,295 ---- int childRTindex = lfirst_int(il); RangeTblEntry *childrte; Oid childOID; ! ! num_rels++; childrte = rt_fetch(childRTindex, root->parse->rtable); childOID = childrte->relid; *************** *** 291,297 **** /* * Copy the parent's targetlist and restriction quals to the ! * child, with attribute-number adjustment as needed. We don't * bother to copy the join quals, since we can't do any joining of * the individual tables. Also, we just zap attr_needed rather * than trying to adjust it; it won't be looked at in the child. --- 303,309 ---- /* * Copy the parent's targetlist and restriction quals to the ! * child. targetlist has attribute-number adjustment if needed. We don't * bother to copy the join quals, since we can't do any joining of * the individual tables. Also, we just zap attr_needed rather * than trying to adjust it; it won't be looked at in the child. *************** *** 303,356 **** childRTindex, childOID); childrel->attr_needed = NULL; ! childrel->baserestrictinfo = (List *) ! adjust_inherited_attrs((Node *) rel->baserestrictinfo, parentRTindex, parentOID, childRTindex, childOID); ! /* ! * Now compute child access paths, and save the cheapest. ! */ ! set_plain_rel_pathlist(root, childrel, childrte); ! subpaths = lappend(subpaths, childrel->cheapest_total_path); ! /* ! * Propagate size information from the child back to the parent. ! * For simplicity, we use the largest widths from any child as the ! * parent estimates. ! */ ! rel->rows += childrel->rows; ! if (childrel->width > rel->width) ! rel->width = childrel->width; ! forboth(parentvars, rel->reltargetlist, ! childvars, childrel->reltargetlist) ! { ! Var *parentvar = (Var *) lfirst(parentvars); ! Var *childvar = (Var *) lfirst(childvars); ! if (IsA(parentvar, Var) &&IsA(childvar, Var)) ! { ! int pndx = parentvar->varattno - rel->min_attr; ! int cndx = childvar->varattno - childrel->min_attr; ! if (childrel->attr_widths[cndx] > rel->attr_widths[pndx]) ! rel->attr_widths[pndx] = childrel->attr_widths[cndx]; ! } } } /* ! * Finally, build Append path and install it as the only access path ! * for the parent rel. */ ! add_path(rel, (Path *) create_append_path(rel, subpaths)); ! /* Select cheapest path (pretty easy in this case...) */ ! set_cheapest(rel); } /* quick-and-dirty test to see if any joining is needed */ --- 315,552 ---- childRTindex, childOID); childrel->attr_needed = NULL; ! ! /* ! * Don't adjust the attr numbers yet, they are needed for ! * comparison in RelationExclude ! */ ! childrel->baserestrictinfo = rel->baserestrictinfo; ! ! /* ! * At this point we have a child RelOptInfo and the restrictinfo ! * we need to decide whether to eliminate the partition ! * completely at planning time ! */ ! makeChildPlan = !RelationExclude(childrel, childOID); ! ! if (!makeChildPlan && num_rels == 1) ! saveFirstPlan = true; ! ! if (makeChildPlan || saveFirstPlan) ! { ! /* ! * Now that we have compared Restriction quals with constraint ! * predicates we can adjust the childs attribute-numbers if needed. ! */ ! childrel->baserestrictinfo = (List *) ! adjust_inherited_attrs((Node *) rel->baserestrictinfo, parentRTindex, parentOID, childRTindex, childOID); ! /* ! * Now compute child access paths, and save the cheapest. ! */ ! set_plain_rel_pathlist(root, childrel, childrte); ! ! if (saveFirstPlan) ! { ! saverel = childrel; ! saveFirstPlan = false; ! } ! else ! { ! num_rels_included++; ! subpaths = lappend(subpaths, childrel->cheapest_total_path); ! propagate_rel_size(rel, childrel); ! } ! } ! } ! ! /* ! * If all rels excluded, then put in a Result node. Use the subpath to ! * first (parent) relation so that we always have at least one relation ! * for the query ! */ ! if (num_rels_included == 0) ! { ! /* ! * We pass through a NIL list, which the Result node interprets as ! * false at execution time. This is easier than setting up a ! * qual node, passing it through and then have it evaluate to false ! */ ! List *falsequal = NIL; ! ! /* ! * Reset rel's size information which we zeroed earlier to avoid ! * double counting. We know we will never execute this path ! * but best not to leave strange values in the plan, which ! * somebody may need later... ! */ ! propagate_rel_size(rel, saverel); ! ! add_path(rel, (Path *) create_result_path(rel, ! saverel->cheapest_total_path, falsequal)); ! } ! else ! { ! /* ! * Build an Append path and use it as the only access path ! * for the parent rel ! */ ! add_path(rel, (Path *) create_append_path(rel, subpaths)); ! } ! /* Select cheapest path (pretty easy in this case...) */ ! set_cheapest(rel); ! } ! /* ! * Propagate size information from the child back to the parent. ! * For simplicity, we use the largest widths from any child as the ! * parent estimates. ! */ ! static void ! propagate_rel_size( RelOptInfo *rel, RelOptInfo *childrel) ! { ! ListCell *parentvars; ! ListCell *childvars; ! rel->rows += childrel->rows; ! if (childrel->width > rel->width) ! rel->width = childrel->width; ! ! forboth(parentvars, rel->reltargetlist, ! childvars, childrel->reltargetlist) ! { ! Var *parentvar = (Var *) lfirst(parentvars); ! Var *childvar = (Var *) lfirst(childvars); ! ! if (IsA(parentvar, Var) &&IsA(childvar, Var)) ! { ! int pndx = parentvar->varattno - rel->min_attr; ! int cndx = childvar->varattno - childrel->min_attr; ! if (childrel->attr_widths[cndx] > rel->attr_widths[pndx]) ! rel->attr_widths[pndx] = childrel->attr_widths[cndx]; } } + } + + + static bool + RelationExclude(RelOptInfo *rel, Oid childOID) + { + List *restrictinfo_list = rel->baserestrictinfo; + List *constraint_pred; + bool exclude = false; + + Relation relation; + + uint16 num_check = 0; + ConstrCheck *check; /* array */ + uint16 i; + + /* If we aren't enabled to exclude relations, get out of here */ + if (!enable_constraint_exclusion) + return false; + + /* + * Get Constraints info from relcache + * This could have been done when the RelOptInfo was originally built + * though that would mean storing that information for all queries + * rather than just this specialised case + */ + relation = heap_open(childOID, AccessShareLock); + + if (relation->rd_att->constr != NULL) + { + num_check = relation->rd_att->constr->num_check; + if (num_check > 0) + { + check = (ConstrCheck *) + palloc(num_check * sizeof(ConstrCheck)); + + for (i = 0; i < num_check; i++) + { + check[i].ccbin = + MemoryContextStrdup(CurrentMemoryContext, + relation->rd_att->constr->check[i].ccbin); + check[i].ccname = + MemoryContextStrdup(CurrentMemoryContext, + relation->rd_att->constr->check[i].ccname); + } + } + } + heap_close(relation, AccessShareLock); + + /* If the relation has no constraints, we cannot exclude it */ + if (num_check == 0) + return false; + + /* Loop over the constraints, checking them against the query */ + for (i = 0; i < num_check; i++) + { + /* prepare the constraint for comparison */ + constraint_pred = prepConstrQual(check[i].ccbin); + + /* + * Constraints must be ALL true for rows in the relation, so we + * treat refutation the same way we would treat the constraints + * if they were ANDed together to make a single predicate... + * any provable refutation is sufficient to exclude the relation + * from the query. + */ + if (predicate_refuted_by(constraint_pred, restrictinfo_list)) + { + exclude = true; + break; + } + } + + pfree(check); + + return exclude; + } + + /* + * Prepare the Constraint for use in comparison to query qual clauses + * + * Currently identical to code in relcache.c:RelationGetIndexPredicate + */ + static List * + prepConstrQual(char *ConstrString) + { + List *result; + + result = (List *) stringToNode(ConstrString); + + /* + * Run the expression through canonicalize_qual and + * eval_const_expressions. This is not just an optimization, but is + * necessary, because the planner will be comparing it to + * similarly-processed qual clauses, and may fail to detect valid + * matches without this. + */ + result = (List *) canonicalize_qual((Expr *) result); + + result = (List *) eval_const_expressions((Node *) result); /* ! * Also mark any coercion format fields as "don't care", so that the ! * planner can match to both explicit and implicit coercions. */ ! set_coercionform_dontcare((Node *) result); ! /* Also convert to implicit-AND format */ ! result = make_ands_implicit((Expr *) result); ! ! /* May as well fix opfuncids too */ ! fix_opfuncids((Node *) result); ! ! return result; } /* quick-and-dirty test to see if any joining is needed */ Index: src/backend/optimizer/util/predtest.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/backend/optimizer/util/predtest.c,v retrieving revision 1.1 diff -c -c -r1.1 predtest.c *** src/backend/optimizer/util/predtest.c 10 Jun 2005 22:25:36 -0000 1.1 --- src/backend/optimizer/util/predtest.c 18 Jul 2005 12:32:14 -0000 *************** *** 27,34 **** --- 27,40 ---- static bool predicate_implied_by_recurse(Node *clause, Node *predicate); + static ThreeVLProof predicate_refuted_by_recurse(Node *clause, Node *predicate); + static bool predicate_implied_by_simple_clause(Expr *predicate, Node *clause); + static ThreeVLProof + predicate_simple_proof(Expr *predicate, Node *clause, + RequiredProof reqd_proof); + /* * predicate_implied_by *************** *** 70,75 **** --- 76,126 ---- return true; } + /* + * predicate_refuted_by + * Recursively checks whether the clauses in restrictinfo_list refute + * the given predicate. + * + * This is NOT the same as !(predicate_implied_by), though is similar in + * the technique and structure of the code. + * + * The top-level List structure of each list corresponds to an AND list. + * + * We assume that eval_const_expressions() has been applied and so there + * are no un-flattened ANDs or ORs (e.g., no AND immediately within an AND, + * including AND just below the top-level List structure). + * + * **** If this is not true we will report a refutation when we should not + * have done so, be careful to ensure this is done correctly. + */ + bool + predicate_refuted_by(List *predicate_list, List *restrictinfo_list) + { + ListCell *item; + + if (predicate_list == NIL) + return false; /* no predicate: no refutation is possible */ + if (restrictinfo_list == NIL) + return false; /* no restriction: refutation must fail */ + + /* + * In all cases where the predicate is an AND-clause, + * predicate_refuted_by_recurse() will prefer to iterate over the + * predicate's components. So we can just do that to start with here, + * and eliminate the need for predicate_implied_by_recurse() to handle + * a bare List on the predicate side. + * + * Logic is: restriction could imply any of the AND'ed predicate items. + * + */ + foreach(item, predicate_list) + { + if (predicate_refuted_by_recurse((Node *) restrictinfo_list, + lfirst(item)) == PROVABLY_FALSE) + return true; + } + return false; + } /*---------- * predicate_implied_by_recurse *************** *** 240,248 **** } } /* ! * Define an "operator implication table" for btree operators ("strategies"). * * The strategy numbers defined by btree indexes (see access/skey.h) are: * (1) < (2) <= (3) = (4) >= (5) > --- 291,508 ---- } } + /*---------- + * predicate_refuted_by_recurse + * Does the predicate refutation test for non-NULL restriction and + * predicate clauses. + * + * Uses three valued logic (ThreeVL), but otherwise very similar in structure + * to predicate_implied_by_recurse() + * + * Refutation is only possible for fairly simple predicates such as + * Range partitioning + * A1 [AND A2 [AND A3 [...]]] + * List partitioning + * A1 [OR A2 [OR A3 [...]]] i.e. an IN list + * Mixed Range and List partitioning + * A1 [AND A2 [AND A3 [...]]] OR B1 [OR B2 [OR B3 [OR B4 [...]]]] + * + * This restriction is by design because of the overhead of refutation for + * large predicates increases dramatically. We must search the whole clause for + * refutation to allow partitioning to work for complex queries. + *---------- + */ + static ThreeVLProof + predicate_refuted_by_recurse(Node *clause, Node *predicate) + { + ListCell *item; + bool refuted = false; + bool proven = false; + int noinfer = 0; + + Assert(clause != NULL); + /* skip through RestrictInfo */ + if (IsA(clause, RestrictInfo)) + { + clause = (Node *) ((RestrictInfo *) clause)->clause; + Assert(clause != NULL); + Assert(!IsA(clause, RestrictInfo)); + } + Assert(predicate != NULL); + + /* + * Since a restriction List clause is handled the same as an AND clause, + * we can avoid duplicate code like this: + */ + if (and_clause(clause)) + clause = (Node *) ((BoolExpr *) clause)->args; + + if (IsA(clause, List)) + { + if (or_clause(predicate)) + { + /* OR-clause R=> atom if all of A's items refutes B */ + /* 3 valued truth table: + * + * PROVABLY_TRUE NO_INFERENCE PROVABLY_FALSE + * PROVABLY_TRUE PROVABLY_TRUE PROVABLY_TRUE PROVABLY_TRUE + * NO_INFERENCE PROVABLY_TRUE NO_INFERENCE NO_INFERENCE + * PROVABLY_FALSE PROVABLY_TRUE NO_INFERENCE PROVABLY_FALSE + */ + foreach(item, ((BoolExpr *) predicate)->args) + { + switch (predicate_refuted_by_recurse(clause,lfirst(item))) + { + case PROVABLY_TRUE: + return PROVABLY_TRUE; + case PROVABLY_FALSE: + break; + case NO_INFERENCE: + noinfer++; + break; + default: + /* Shouldn't happen */ + break; + } + } + return (noinfer == 0) ? PROVABLY_FALSE : NO_INFERENCE; + } + else + { + /* AND-clause R=> atom if any of A's items refutes B */ + /* 3 valued truth table: + * + * PROVABLY_TRUE NO_INFERENCE PROVABLY_FALSE + * PROVABLY_TRUE PROVABLY_TRUE PROVABLY_TRUE PROVABLY_FALSE + * NO_INFERENCE PROVABLY_TRUE NO_INFERENCE PROVABLY_FALSE + * PROVABLY_FALSE PROVABLY_FALSE PROVABLY_FALSE PROVABLY_FALSE + */ + foreach(item, (List *) clause) + { + switch (predicate_refuted_by_recurse(lfirst(item),predicate)) + { + case PROVABLY_TRUE: + proven = true; + break; + case PROVABLY_FALSE: + return PROVABLY_FALSE; + case NO_INFERENCE: + break; + default: + /* Shouldn't happen */ + break; + } + } + return (proven) ? PROVABLY_TRUE : NO_INFERENCE; + } + } + else if (or_clause(clause)) + { + if (or_clause(predicate)) + { + /* + * OR-clause R=> OR-clause if all of A's items refutes all of + * B's items. So we use the AND truth table + * 3 valued truth table: + * + * PROVABLY_TRUE NO_INFERENCE PROVABLY_FALSE + * PROVABLY_TRUE PROVABLY_TRUE PROVABLY_TRUE PROVABLY_FALSE + * NO_INFERENCE PROVABLY_TRUE NO_INFERENCE PROVABLY_FALSE + * PROVABLY_FALSE PROVABLY_FALSE PROVABLY_FALSE PROVABLY_FALSE + */ + foreach(item, ((BoolExpr *) clause)->args) + { + Node *citem = lfirst(item); + ListCell *item2; + + foreach(item2, ((BoolExpr *) predicate)->args) + { + switch (predicate_refuted_by_recurse(citem, lfirst(item2))) + { + case PROVABLY_TRUE: + proven = true; + break; + case PROVABLY_FALSE: + return PROVABLY_FALSE; + case NO_INFERENCE: + break; + default: + break; + } + } + } + return (proven) ? PROVABLY_TRUE : NO_INFERENCE; + } + else + { + /* OR-clause R=> atom if all of A's items refutes B */ + /* 3 valued truth table: + * + * PROVABLY_TRUE NO_INFERENCE PROVABLY_FALSE + * PROVABLY_TRUE PROVABLY_TRUE PROVABLY_TRUE PROVABLY_TRUE + * NO_INFERENCE PROVABLY_TRUE NO_INFERENCE NO_INFERENCE + * PROVABLY_FALSE PROVABLY_TRUE NO_INFERENCE PROVABLY_FALSE + */ + foreach(item, ((BoolExpr *) clause)->args) + { + switch (predicate_refuted_by_recurse(lfirst(item),predicate)) + { + case PROVABLY_TRUE: + return PROVABLY_TRUE; + case PROVABLY_FALSE: + break; + case NO_INFERENCE: + noinfer++; + break; + default: + /* Shouldn't happen */ + break; + } + } + return (noinfer == 0) ? PROVABLY_FALSE : NO_INFERENCE; + } + } + else + { + if (or_clause(predicate)) + { + /* atom R=> OR-clause if A refutes all of B's items */ + /* 3 valued truth table: + * + * PROVABLY_TRUE NO_INFERENCE PROVABLY_FALSE + * PROVABLY_TRUE PROVABLY_TRUE PROVABLY_TRUE PROVABLY_TRUE + * NO_INFERENCE PROVABLY_TRUE NO_INFERENCE PROVABLY_FALSE + * PROVABLY_FALSE PROVABLY_TRUE PROVABLY_FALSE PROVABLY_FALSE + */ + foreach(item, ((BoolExpr *) predicate)->args) + { + switch (predicate_refuted_by_recurse(clause, lfirst(item))) + { + case PROVABLY_TRUE: + return PROVABLY_TRUE; + case PROVABLY_FALSE: + refuted = true; + break; + case NO_INFERENCE: + break; + default: + /* Shouldn't happen */ + break; + } + } + return (refuted) ? PROVABLY_FALSE : NO_INFERENCE; + } + else + { + /* atom R=> atom is the base case */ + return predicate_simple_proof((Expr *) predicate, clause, REFUTE); + } + } + } /* ! * Define an "operator implication table" for btree operators ("strategies"), ! * and a similar table for refutation also. * * The strategy numbers defined by btree indexes (see access/skey.h) are: * (1) < (2) <= (3) = (4) >= (5) > *************** *** 252,268 **** * * The interpretation of: * ! * test_op = BT_implic_table[given_op-1][target_op-1] * * where test_op, given_op and target_op are strategy numbers (from 1 to 6) * of btree operators, is as follows: * * If you know, for some ATTR, that "ATTR given_op CONST1" is true, and you * want to determine whether "ATTR target_op CONST2" must also be true, then * you can use "CONST2 test_op CONST1" as a test. If this test returns true, * then the target expression must be true; if the test returns false, then * the target expression may be false. * * An entry where test_op == 0 means the implication cannot be determined, * i.e., this test should always be considered false. */ --- 512,544 ---- * * The interpretation of: * ! * test_op = BT_[implic|refute]_table[given_op-1][target_op-1] * * where test_op, given_op and target_op are strategy numbers (from 1 to 6) * of btree operators, is as follows: * + * implic * If you know, for some ATTR, that "ATTR given_op CONST1" is true, and you * want to determine whether "ATTR target_op CONST2" must also be true, then * you can use "CONST2 test_op CONST1" as a test. If this test returns true, * then the target expression must be true; if the test returns false, then * the target expression may be false. * + * e.g. if clause is Quantity > 10 + * and pred is Quantity > 5 + * then we test "5 <= 10" which evals to true, so clause => pred + * + * refute + * If you know, for some ATTR, that "ATTR given_op CONST1" is true, and you + * want to determine whether "ATTR target_op CONST2" can be refuted, then + * you can use "CONST2 test_op CONST1" as a test. If this test returns true, + * then the target expression can be refuted; if the test returns false, then + * the target expression cannot be refuted. + * + * e.g. if clause is Quantity > 10 + * and pred is Quantity < 5 + * then we test "5 <= 10" which evals to true, so clause refutes pred + * * An entry where test_op == 0 means the implication cannot be determined, * i.e., this test should always be considered false. */ *************** *** 279,297 **** /* * The target operator: * ! * LT LE EQ GE GT NE */ ! {BTGE, BTGE, 0, 0, 0, BTGE}, /* LT */ ! {BTGT, BTGE, 0, 0, 0, BTGT}, /* LE */ {BTGT, BTGE, BTEQ, BTLE, BTLT, BTNE}, /* EQ */ ! {0, 0, 0, BTLE, BTLT, BTLT}, /* GE */ ! {0, 0, 0, BTLE, BTLE, BTLE}, /* GT */ ! {0, 0, 0, 0, 0, BTEQ} /* NE */ }; /*---------- ! * predicate_implied_by_simple_clause * Does the predicate implication test for a "simple clause" predicate * and a "simple clause" restriction. * --- 555,587 ---- /* * The target operator: * ! * LT LE EQ GE GT NE */ ! {BTGE, BTGE, 0 , 0 , 0 , BTGE}, /* LT */ ! {BTGT, BTGE, 0 , 0 , 0 , BTGT}, /* LE */ {BTGT, BTGE, BTEQ, BTLE, BTLT, BTNE}, /* EQ */ ! {0 , 0 , 0 , BTLE, BTLT, BTLT}, /* GE */ ! {0 , 0 , 0 , BTLE, BTLE, BTLE}, /* GT */ ! {0 , 0 , 0 , 0 , 0 , BTEQ} /* NE */ }; + static const StrategyNumber + BT_refute_table[6][6] = { + /* + * The target operator: + * + * LT LE EQ GE GT NE + */ + {0 , 0 , BTGE, BTGE, BTGE, 0 }, /* LT */ + {0 , 0 , BTGT, BTGT, BTGE, 0 }, /* LE */ + {BTLE, BTLT, BTNE, BTGT, BTGE, BTEQ}, /* EQ */ + {BTLE, BTLT, BTLT, 0 , 0 , 0 }, /* GE */ + {BTLE, BTLE, BTLE, 0 , 0 , 0 }, /* GT */ + {0 , 0 , BTEQ, 0 , 0 , 0 } /* NE */ + }; /*---------- ! * predicate_simple_proof_clause * Does the predicate implication test for a "simple clause" predicate * and a "simple clause" restriction. * *************** *** 324,331 **** * appropriate "RT_implic_table" array. *---------- */ ! static bool ! predicate_implied_by_simple_clause(Expr *predicate, Node *clause) { Node *leftop, *rightop; --- 614,622 ---- * appropriate "RT_implic_table" array. *---------- */ ! static ThreeVLProof ! predicate_simple_proof(Expr *predicate, Node *clause, ! RequiredProof reqd_proof) { Node *leftop, *rightop; *************** *** 358,380 **** /* First try the equal() test */ if (equal((Node *) predicate, clause)) ! return true; ! /* Next try the IS NOT NULL case */ ! if (predicate && IsA(predicate, NullTest) && ! ((NullTest *) predicate)->nulltesttype == IS_NOT_NULL) ! { Expr *nonnullarg = ((NullTest *) predicate)->arg; ! ! if (is_opclause(clause) && ! list_member(((OpExpr *) clause)->args, nonnullarg) && ! op_strict(((OpExpr *) clause)->opno)) ! return true; ! if (is_funcclause(clause) && ! list_member(((FuncExpr *) clause)->args, nonnullarg) && ! func_strict(((FuncExpr *) clause)->funcid)) ! return true; ! return false; /* we can't succeed below... */ } /* --- 649,683 ---- /* First try the equal() test */ if (equal((Node *) predicate, clause)) ! return PROVABLY_TRUE; ! /* Next try the case for IS NOT NULL or IS NULL */ ! if (predicate && IsA(predicate, NullTest)) ! { Expr *nonnullarg = ((NullTest *) predicate)->arg; ! if (((NullTest *) predicate)->nulltesttype == IS_NOT_NULL) ! { ! if (is_opclause(clause) && ! list_member(((OpExpr *) clause)->args, nonnullarg) && ! op_strict(((OpExpr *) clause)->opno)) ! return PROVABLY_TRUE; ! if (is_funcclause(clause) && ! list_member(((FuncExpr *) clause)->args, nonnullarg) && ! func_strict(((FuncExpr *) clause)->funcid)) ! return PROVABLY_TRUE; ! return NO_INFERENCE; /* we can't succeed below... */ ! } else ! { ! if (is_opclause(clause) && ! list_member(((OpExpr *) clause)->args, nonnullarg) && ! op_strict(((OpExpr *) clause)->opno)) ! return PROVABLY_FALSE; ! if (is_funcclause(clause) && ! list_member(((FuncExpr *) clause)->args, nonnullarg) && ! func_strict(((FuncExpr *) clause)->funcid)) ! return PROVABLY_FALSE; ! return NO_INFERENCE; /* we can't succeed below... */ ! } } /* *************** *** 387,397 **** * the test operator will always be strict. */ if (!is_opclause(predicate)) ! return false; leftop = get_leftop(predicate); rightop = get_rightop(predicate); if (rightop == NULL) ! return false; /* not a binary opclause */ if (IsA(rightop, Const)) { pred_var = leftop; --- 690,700 ---- * the test operator will always be strict. */ if (!is_opclause(predicate)) ! return NO_INFERENCE; leftop = get_leftop(predicate); rightop = get_rightop(predicate); if (rightop == NULL) ! return NO_INFERENCE; /* not a binary opclause */ if (IsA(rightop, Const)) { pred_var = leftop; *************** *** 405,420 **** pred_var_on_left = false; } else ! return false; /* no Const to be found */ if (pred_const->constisnull) ! return false; if (!is_opclause(clause)) ! return false; leftop = get_leftop((Expr *) clause); rightop = get_rightop((Expr *) clause); if (rightop == NULL) ! return false; /* not a binary opclause */ if (IsA(rightop, Const)) { clause_var = leftop; --- 708,723 ---- pred_var_on_left = false; } else ! return NO_INFERENCE; /* no Const to be found */ if (pred_const->constisnull) ! return NO_INFERENCE; if (!is_opclause(clause)) ! return NO_INFERENCE; leftop = get_leftop((Expr *) clause); rightop = get_rightop((Expr *) clause); if (rightop == NULL) ! return NO_INFERENCE; /* not a binary opclause */ if (IsA(rightop, Const)) { clause_var = leftop; *************** *** 428,436 **** clause_var_on_left = false; } else ! return false; /* no Const to be found */ if (clause_const->constisnull) ! return false; /* * Check for matching subexpressions on the non-Const sides. We used --- 731,739 ---- clause_var_on_left = false; } else ! return NO_INFERENCE; /* no Const to be found */ if (clause_const->constisnull) ! return NO_INFERENCE; /* * Check for matching subexpressions on the non-Const sides. We used *************** *** 440,446 **** * should yield identical results. */ if (!equal(pred_var, clause_var)) ! return false; /* * Okay, get the operators in the two clauses we're comparing. Commute --- 743,749 ---- * should yield identical results. */ if (!equal(pred_var, clause_var)) ! return NO_INFERENCE; /* * Okay, get the operators in the two clauses we're comparing. Commute *************** *** 451,457 **** { pred_op = get_commutator(pred_op); if (!OidIsValid(pred_op)) ! return false; } clause_op = ((OpExpr *) clause)->opno; --- 754,760 ---- { pred_op = get_commutator(pred_op); if (!OidIsValid(pred_op)) ! return NO_INFERENCE; } clause_op = ((OpExpr *) clause)->opno; *************** *** 459,465 **** { clause_op = get_commutator(clause_op); if (!OidIsValid(clause_op)) ! return false; } /* --- 762,768 ---- { clause_op = get_commutator(clause_op); if (!OidIsValid(clause_op)) ! return NO_INFERENCE; } /* *************** *** 579,594 **** /* * Look up the "test" strategy number in the implication table */ ! test_strategy = BT_implic_table[clause_strategy - 1][pred_strategy - 1]; if (test_strategy == 0) { ! /* Can't determine implication using this interpretation */ ! continue; } /* * See if opclass has an operator for the test strategy and the ! * clause datatype. */ if (test_strategy == BTNE) { --- 882,909 ---- /* * Look up the "test" strategy number in the implication table */ ! switch (reqd_proof) ! { ! case IMPLY: ! test_strategy = BT_implic_table[clause_strategy - 1][pred_strategy - 1]; ! break; ! case REFUTE: ! test_strategy = BT_refute_table[clause_strategy - 1][pred_strategy - 1]; ! break; ! default: ! /* Shouldn't happen */ ! break; ! } ! if (test_strategy == 0) { ! /* Can't determine implication using this interpretation */ ! continue; } /* * See if opclass has an operator for the test strategy and the ! * clause datatype.range */ if (test_strategy == BTNE) { *************** *** 607,626 **** /* * Last check: test_op must be immutable. * ! * Note that we require only the test_op to be immutable, not the ! * original clause_op. (pred_op must be immutable, else it ! * would not be allowed in an index predicate.) Essentially ! * we are assuming that the opclass is consistent even if it ! * contains operators that are merely stable. ! * ! * XXX the above reasoning doesn't hold anymore if this routine ! * is used to prove things that are not index predicates ... */ ! if (op_volatile(test_op) == PROVOLATILE_IMMUTABLE) ! { ! found = true; ! break; ! } } } --- 922,941 ---- /* * Last check: test_op must be immutable. * ! * If this routine is used to prove things that are index preds ! * then we require only the test_op to be immutable, not the ! * original clause_op. In general, we must test clause_op also. ! * (pred_op must be immutable, else it would not be allowed in a ! * predicate.) Essentially we are assuming that the opclass is ! * consistent even if it contains operators that are merely stable. */ ! if (op_volatile(test_op) == PROVOLATILE_IMMUTABLE && ! ( reqd_proof == IMPLY || ! op_volatile(clause_op) == PROVOLATILE_IMMUTABLE)) ! { ! found = true; ! break; ! } } } *************** *** 628,635 **** if (!found) { ! /* couldn't find a btree opclass to interpret the operators */ ! return false; } /* --- 943,950 ---- if (!found) { ! /* couldn't find a btree opclass to interpret the operators */ ! return NO_INFERENCE; } /* *************** *** 665,671 **** { /* Treat a null result as false ... but it's a tad fishy ... */ elog(DEBUG2, "null predicate test result"); ! return false; } ! return DatumGetBool(test_result); } --- 980,1004 ---- { /* Treat a null result as false ... but it's a tad fishy ... */ elog(DEBUG2, "null predicate test result"); ! return NO_INFERENCE; } ! ! if (reqd_proof == REFUTE) ! if (DatumGetBool(test_result)) ! return PROVABLY_FALSE; ! else ! return PROVABLY_TRUE; ! else ! if (DatumGetBool(test_result)) ! return PROVABLY_TRUE; ! else ! return PROVABLY_FALSE; ! } ! ! static bool ! predicate_implied_by_simple_clause(Expr *predicate, Node *clause) ! { ! /* Interpret the 3VL logic as to whether we have proven implication */ ! return (predicate_simple_proof(predicate, clause, IMPLY) ! == PROVABLY_TRUE) ? true : false; } Index: src/backend/utils/misc/guc.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/backend/utils/misc/guc.c,v retrieving revision 1.274 diff -c -c -r1.274 guc.c *** src/backend/utils/misc/guc.c 14 Jul 2005 05:13:42 -0000 1.274 --- src/backend/utils/misc/guc.c 18 Jul 2005 12:32:19 -0000 *************** *** 436,441 **** --- 436,451 ---- true, NULL, NULL }, { + {"enable_constraint_exclusion", PGC_USERSET, QUERY_TUNING_METHOD, + gettext_noop("Enables the planner's use of constraints in queries."), + gettext_noop("Constraints on tables will be used to exclude relations" + "that can be proven not to be required to produce" + "a correct result for the query.") + }, + &enable_constraint_exclusion, + false, NULL, NULL + }, + { {"geqo", PGC_USERSET, QUERY_TUNING_GEQO, gettext_noop("Enables genetic query optimization."), gettext_noop("This algorithm attempts to do planning without " Index: src/include/optimizer/cost.h =================================================================== RCS file: /projects/cvsroot/pgsql/src/include/optimizer/cost.h,v retrieving revision 1.68 diff -c -c -r1.68 cost.h *** src/include/optimizer/cost.h 5 Jun 2005 22:32:58 -0000 1.68 --- src/include/optimizer/cost.h 18 Jul 2005 12:32:21 -0000 *************** *** 49,54 **** --- 49,56 ---- extern bool enable_nestloop; extern bool enable_mergejoin; extern bool enable_hashjoin; + extern bool enable_constraint_exclusion; + extern double clamp_row_est(double nrows); extern void cost_seqscan(Path *path, PlannerInfo *root, RelOptInfo *baserel); Index: src/include/optimizer/predtest.h =================================================================== RCS file: /projects/cvsroot/pgsql/src/include/optimizer/predtest.h,v retrieving revision 1.1 diff -c -c -r1.1 predtest.h *** src/include/optimizer/predtest.h 10 Jun 2005 22:25:37 -0000 1.1 --- src/include/optimizer/predtest.h 18 Jul 2005 12:32:21 -0000 *************** *** 19,23 **** --- 19,38 ---- extern bool predicate_implied_by(List *predicate_list, List *restrictinfo_list); + extern bool predicate_refuted_by(List *predicate_list, + List *restrictinfo_list); + + typedef enum ThreeVLProof + { + PROVABLY_TRUE, + PROVABLY_FALSE, + NO_INFERENCE + } ThreeVLProof; + + typedef enum RequiredProof + { + IMPLY, + REFUTE + } RequiredProof; #endif /* PREDTEST_H */ Index: src/test/regress/expected/rangefuncs.out =================================================================== RCS file: /projects/cvsroot/pgsql/src/test/regress/expected/rangefuncs.out,v retrieving revision 1.11 diff -c -c -r1.11 rangefuncs.out *** src/test/regress/expected/rangefuncs.out 21 Apr 2005 19:18:13 -0000 1.11 --- src/test/regress/expected/rangefuncs.out 18 Jul 2005 12:32:22 -0000 *************** *** 1,16 **** SELECT name, setting FROM pg_settings WHERE name LIKE 'enable%'; ! name | setting ! -------------------+--------- ! enable_bitmapscan | on ! enable_hashagg | on ! enable_hashjoin | on ! enable_indexscan | on ! enable_mergejoin | on ! enable_nestloop | on ! enable_seqscan | on ! enable_sort | on ! enable_tidscan | on ! (9 rows) CREATE TABLE foo2(fooid int, f2 int); INSERT INTO foo2 VALUES(1, 11); --- 1,17 ---- SELECT name, setting FROM pg_settings WHERE name LIKE 'enable%'; ! name | setting ! -----------------------------+--------- ! enable_bitmapscan | on ! enable_constraint_exclusion | off ! enable_hashagg | on ! enable_hashjoin | on ! enable_indexscan | on ! enable_mergejoin | on ! enable_nestloop | on ! enable_seqscan | on ! enable_sort | on ! enable_tidscan | on ! (10 rows) CREATE TABLE foo2(fooid int, f2 int); INSERT INTO foo2 VALUES(1, 11);
---------------------------(end of broadcast)--------------------------- TIP 3: Have you checked our extensive FAQ? http://www.postgresql.org/docs/faq