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

Reply via email to