diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index de14ddd2dc..55bdfd17fd 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -9,7 +9,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/util/plancat.c,v 1.127 2006/10/04 00:29:55 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/util/plancat.c,v 1.127.2.1 2008/01/12 00:11:45 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -43,7 +43,8 @@ bool constraint_exclusion = false; static void estimate_rel_size(Relation rel, int32 *attr_widths, BlockNumber *pages, double *tuples); -static List *get_relation_constraints(Oid relationObjectId, RelOptInfo *rel); +static List *get_relation_constraints(Oid relationObjectId, RelOptInfo *rel, + bool include_notnull); /* @@ -391,12 +392,16 @@ estimate_rel_size(Relation rel, int32 *attr_widths, * indicated by rel->relid. This allows the expressions to be easily * compared to expressions taken from WHERE. * + * If include_notnull is true, "col IS NOT NULL" expressions are generated + * and added to the result for each column that's marked attnotnull. + * * Note: at present this is invoked at most once per relation per planner * run, and in many cases it won't be invoked at all, so there seems no * point in caching the data in RelOptInfo. */ static List * -get_relation_constraints(Oid relationObjectId, RelOptInfo *rel) +get_relation_constraints(Oid relationObjectId, RelOptInfo *rel, + bool include_notnull) { List *result = NIL; Index varno = rel->relid; @@ -451,6 +456,30 @@ get_relation_constraints(Oid relationObjectId, RelOptInfo *rel) result = list_concat(result, make_ands_implicit((Expr *) cexpr)); } + + /* Add NOT NULL constraints in expression form, if requested */ + if (include_notnull && constr->has_not_null) + { + int natts = relation->rd_att->natts; + + for (i = 1; i <= natts; i++) + { + Form_pg_attribute att = relation->rd_att->attrs[i - 1]; + + if (att->attnotnull && !att->attisdropped) + { + NullTest *ntest = makeNode(NullTest); + + ntest->arg = (Expr *) makeVar(varno, + i, + att->atttypid, + att->atttypmod, + 0); + ntest->nulltesttype = IS_NOT_NULL; + result = lappend(result, ntest); + } + } + } } heap_close(relation, NoLock); @@ -502,8 +531,11 @@ relation_excluded_by_constraints(RelOptInfo *rel, RangeTblEntry *rte) if (rte->rtekind != RTE_RELATION || rte->inh) return false; - /* OK to fetch the constraint expressions */ - constraint_pred = get_relation_constraints(rte->relid, rel); + /* + * OK to fetch the constraint expressions. Include "col IS NOT NULL" + * expressions for attnotnull columns, in case we can refute those. + */ + constraint_pred = get_relation_constraints(rte->relid, rel, true); /* * We do not currently enforce that CHECK constraints contain only diff --git a/src/backend/optimizer/util/predtest.c b/src/backend/optimizer/util/predtest.c index c7ab95a9f4..599826bba7 100644 --- a/src/backend/optimizer/util/predtest.c +++ b/src/backend/optimizer/util/predtest.c @@ -9,7 +9,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/util/predtest.c,v 1.10.2.2 2007/07/24 17:22:13 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/util/predtest.c,v 1.10.2.3 2008/01/12 00:11:45 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -82,7 +82,6 @@ static Node *arrayexpr_next_fn(PredIterInfo info); static void arrayexpr_cleanup_fn(PredIterInfo info); static bool predicate_implied_by_simple_clause(Expr *predicate, Node *clause); static bool predicate_refuted_by_simple_clause(Expr *predicate, Node *clause); -static bool is_null_contradicts(NullTest *ntest, Node *clause); static Node *extract_not_arg(Node *clause); static bool list_member_strip(List *list, Expr *datum); static bool btree_predicate_proof(Expr *predicate, Node *clause, @@ -129,6 +128,14 @@ predicate_implied_by(List *predicate_list, List *restrictinfo_list) * This is NOT the same as !(predicate_implied_by), though it is similar * in the technique and structure of the code. * + * An important fine point is that truth of the clauses must imply that + * the predicate returns FALSE, not that it does not return TRUE. This + * is normally used to try to refute CHECK constraints, and the only + * thing we can assume about a CHECK constraint is that it didn't return + * FALSE --- a NULL result isn't a violation per the SQL spec. (Someday + * perhaps this code should be extended to support both "strong" and + * "weak" refutation, but for now we only need "strong".) + * * 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, @@ -408,10 +415,11 @@ predicate_implied_by_recurse(Node *clause, Node *predicate) * * In addition, if the predicate is a NOT-clause then we can use * A R=> NOT B if: A => B - * while if the restriction clause is a NOT-clause then we can use - * NOT A R=> B if: B => A * This works for several different SQL constructs that assert the non-truth * of their argument, ie NOT, IS FALSE, IS NOT TRUE, IS UNKNOWN. + * Unfortunately we *cannot* use + * NOT A R=> B if: B => A + * because this type of reasoning fails to prove that B doesn't yield NULL. * * Other comments are as for predicate_implied_by_recurse(). *---------- @@ -595,13 +603,21 @@ predicate_refuted_by_recurse(Node *clause, Node *predicate) case CLASS_ATOM: +#ifdef NOT_USED /* * If A is a NOT-clause, A R=> B if B => A's arg + * + * Unfortunately not: this would only prove that B is not-TRUE, + * not that it's not NULL either. Keep this code as a comment + * because it would be useful if we ever had a need for the + * weak form of refutation. */ not_arg = extract_not_arg(clause); if (not_arg && predicate_implied_by_recurse(predicate, not_arg)) return true; +#endif + switch (pclass) { case CLASS_AND: @@ -989,9 +1005,11 @@ predicate_implied_by_simple_clause(Expr *predicate, Node *clause) * When the predicate is of the form "foo IS NULL", we can conclude that * the predicate is refuted if the clause is a strict operator or function * that has "foo" as an input (see notes for implication case), or if the - * clause is "foo IS NOT NULL". Conversely a clause "foo IS NULL" refutes - * predicates of those types. (The motivation for covering these cases is - * to support using IS NULL/IS NOT NULL as partition-defining constraints.) + * clause is "foo IS NOT NULL". A clause "foo IS NULL" refutes a predicate + * "foo IS NOT NULL", but unfortunately does not refute strict predicates, + * because we are looking for strong refutation. (The motivation for covering + * these cases is to support using IS NULL/IS NOT NULL as partition-defining + * constraints.) * * Finally, we may be able to deduce something using knowledge about btree * operator classes; this is encapsulated in btree_predicate_proof(). @@ -1009,8 +1027,28 @@ predicate_refuted_by_simple_clause(Expr *predicate, Node *clause) if (predicate && IsA(predicate, NullTest) && ((NullTest *) predicate)->nulltesttype == IS_NULL) { - if (is_null_contradicts((NullTest *) predicate, clause)) + Expr *isnullarg = ((NullTest *) predicate)->arg; + + /* row IS NULL does not act in the simple way we have in mind */ + if (type_is_rowtype(exprType((Node *) isnullarg))) + return false; + + /* Any strict op/func on foo refutes foo IS NULL */ + if (is_opclause(clause) && + list_member_strip(((OpExpr *) clause)->args, isnullarg) && + op_strict(((OpExpr *) clause)->opno)) return true; + if (is_funcclause(clause) && + list_member_strip(((FuncExpr *) clause)->args, isnullarg) && + func_strict(((FuncExpr *) clause)->funcid)) + return true; + + /* foo IS NOT NULL refutes foo IS NULL */ + if (clause && IsA(clause, NullTest) && + ((NullTest *) clause)->nulltesttype == IS_NOT_NULL && + equal(((NullTest *) clause)->arg, isnullarg)) + return true; + return false; /* we can't succeed below... */ } @@ -1018,8 +1056,18 @@ predicate_refuted_by_simple_clause(Expr *predicate, Node *clause) if (clause && IsA(clause, NullTest) && ((NullTest *) clause)->nulltesttype == IS_NULL) { - if (is_null_contradicts((NullTest *) clause, (Node *) predicate)) + Expr *isnullarg = ((NullTest *) clause)->arg; + + /* row IS NULL does not act in the simple way we have in mind */ + if (type_is_rowtype(exprType((Node *) isnullarg))) + return false; + + /* foo IS NULL refutes foo IS NOT NULL */ + if (predicate && IsA(predicate, NullTest) && + ((NullTest *) predicate)->nulltesttype == IS_NOT_NULL && + equal(((NullTest *) predicate)->arg, isnullarg)) return true; + return false; /* we can't succeed below... */ } @@ -1028,40 +1076,6 @@ predicate_refuted_by_simple_clause(Expr *predicate, Node *clause) } -/* - * Check whether a "foo IS NULL" test contradicts clause. (We say - * "contradicts" rather than "refutes" because the refutation goes - * both ways.) - */ -static bool -is_null_contradicts(NullTest *ntest, Node *clause) -{ - Expr *isnullarg = ntest->arg; - - /* row IS NULL does not act in the simple way we have in mind */ - if (type_is_rowtype(exprType((Node *) isnullarg))) - return false; - - /* foo IS NULL contradicts any strict op/func on foo */ - if (is_opclause(clause) && - list_member_strip(((OpExpr *) clause)->args, isnullarg) && - op_strict(((OpExpr *) clause)->opno)) - return true; - if (is_funcclause(clause) && - list_member_strip(((FuncExpr *) clause)->args, isnullarg) && - func_strict(((FuncExpr *) clause)->funcid)) - return true; - - /* foo IS NULL contradicts foo IS NOT NULL */ - if (clause && IsA(clause, NullTest) && - ((NullTest *) clause)->nulltesttype == IS_NOT_NULL && - equal(((NullTest *) clause)->arg, isnullarg)) - return true; - - return false; -} - - /* * If clause asserts the non-truth of a subclause, return that subclause; * otherwise return NULL.