diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c
index a1f26d9fc1ddb97e97097e1646625a621484a061..139c95c5911bb22a99be411a41720a09535b157e 100644
--- a/src/backend/executor/execAmi.c
+++ b/src/backend/executor/execAmi.c
@@ -5,7 +5,7 @@
  *
  * Copyright (c) 1994, Regents of the University of California
  *
- *  $Id: execAmi.c,v 1.28 1998/12/14 08:11:02 scrappy Exp $
+ *  $Id: execAmi.c,v 1.29 1999/01/18 00:09:45 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -39,6 +39,9 @@
 #include "executor/nodeNestloop.h"
 #include "executor/nodeHashjoin.h"
 #include "executor/nodeHash.h"
+/***S*I***/
+#include "executor/nodeGroup.h"
+
 #include "executor/nodeAgg.h"
 #include "executor/nodeGroup.h"
 #include "executor/nodeResult.h"
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index 0eed756319a1889d53d88a6fbeae6b17149a889a..dbb86bb0f7dcaaf27a067fc67ae891c84e641c04 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -491,7 +491,10 @@ ExecAgg(Agg *node)
 		 * As long as the retrieved group does not match the
 		 * qualifications it is ignored and the next group is fetched
 		 */
-		qual_result = ExecQual(fix_opids(node->plan.qual), econtext);
+		if(node->plan.qual != NULL){
+		  qual_result = ExecQual(fix_opids(node->plan.qual), econtext);
+		}
+		
 		if (oneTuple)
 			pfree(oneTuple);
 	}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 9d6d3e148ab62e36beda99045339eeffb89cb52c..5577bbb290c234c4d9510f93a88c1ec0ef918e14 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -5,7 +5,7 @@
  *
  * Copyright (c) 1994, Regents of the University of California
  *
- *  $Id: outfuncs.c,v 1.58 1998/12/20 07:13:36 scrappy Exp $
+ *  $Id: outfuncs.c,v 1.59 1999/01/18 00:09:45 momjian Exp $
  *
  * NOTES
  *	  Every (plan) node in POSTGRES has an associated "out" routine which
@@ -227,6 +227,9 @@ _outQuery(StringInfo str, Query *node)
 			node->hasSubLinks ? "true" : "false");
 	_outNode(str, node->unionClause);
 
+	appendStringInfo(str, " :intersectClause ");
+	_outNode(str, node->intersectClause);
+
 	appendStringInfo(str, " :limitOffset ");
 	_outNode(str, node->limitOffset);
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index a6650efecf1c2925aa45902e757431b54f351190..edcfdd9d261b2eebe654992b5ad13651c494a87e 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -7,7 +7,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.40 1998/12/14 00:01:47 thomas Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.41 1999/01/18 00:09:46 momjian Exp $
  *
  * NOTES
  *	  Most of the read functions for plan nodes are tested. (In fact, they
@@ -163,6 +163,11 @@ _readQuery()
 	token = lsptok(NULL, &length);		/* skip :unionClause */
 	local_node->unionClause = nodeRead(true);
 
+	/***S*I***/
+ 	token = lsptok(NULL, &length);		/* skip :intersectClause */
+ 	local_node->intersectClause = nodeRead(true);
+ 
+
 	token = lsptok(NULL, &length);		/* skip :limitOffset */
 	local_node->limitOffset = nodeRead(true);
 
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 9cf0746145a9065d9918664bdf5fe10b3efc7fb7..ec4597961a0ac220d31a5b139639a1ed9b540273 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -7,7 +7,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planner.c,v 1.35 1998/09/09 03:48:01 vadim Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planner.c,v 1.36 1999/01/18 00:09:47 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -95,20 +95,26 @@ Plan *
 union_planner(Query *parse)
 {
 	List	   *tlist = parse->targetList;
-	int			tlist_len = length(tlist);
+
+	/***S*H***/
+	/* copy the original tlist, we will need the original one 
+	 * for the AGG node later on */
+	List    *new_tlist = new_unsorted_tlist(tlist);
+		
 	List	   *rangetable = parse->rtable;
+
 	Plan	   *result_plan = (Plan *) NULL;
-	Index		rt_index;
 
+	Index		rt_index;
 
 	if (parse->unionClause)
 	{
-		result_plan = (Plan *) plan_union_queries(parse);
-		/* XXX do we need to do this? bjm 12/19/97 */
-		tlist = preprocess_targetlist(tlist,
-									  parse->commandType,
-									  parse->resultRelation,
-									  parse->rtable);
+	  result_plan = (Plan *) plan_union_queries(parse);
+	  /* XXX do we need to do this? bjm 12/19/97 */	  	  
+	  tlist = preprocess_targetlist(tlist,
+					parse->commandType,
+					parse->resultRelation,
+					parse->rtable);
 	}
 	else if ((rt_index =
 			  first_inherit_rt_entry(rangetable)) != -1)
@@ -116,47 +122,65 @@ union_planner(Query *parse)
 		result_plan = (Plan *) plan_inherit_queries(parse, rt_index);
 		/* XXX do we need to do this? bjm 12/19/97 */
 		tlist = preprocess_targetlist(tlist,
-									  parse->commandType,
-									  parse->resultRelation,
-									  parse->rtable);
+					      parse->commandType,
+					      parse->resultRelation,
+					      parse->rtable);
 	}
 	else
 	{
-		List	  **vpm = NULL;
-
-		/*
-		 * check_having_qual_for_vars takes the havingQual and the tlist
-		 * as arguments and recursively scans the havingQual for VAR nodes 
-		 * that are not contained in tlist yet. If so, it creates a new entry 
-		 * and attaches it to the tlist. Latter, we use tlist_len to 
-		 * truncate tlist - ie restore actual tlist...
-		 */
-		if (parse->hasAggs)
+	  List  **vpm = NULL;
+	  
+	  /***S*H***/
+	  /* This is only necessary if aggregates are in use in queries like:
+	   * SELECT sid 
+	   * FROM part
+	   * GROUP BY sid
+	   * HAVING MIN(pid) > 1;  (pid is used but never selected for!!!)
+	   * because the function 'query_planner' creates the plan for the lefttree
+	   * of the 'GROUP' node and returns only those attributes contained in 'tlist'.
+	   * The original 'tlist' contains only 'sid' here and that's why we have to
+	   * to extend it to attributes which are not selected but are used in the 
+	   * havingQual. */
+	  	  
+	  /* 'check_having_qual_for_vars' takes the havingQual and the actual 'tlist'
+	   * as arguments and recursively scans the havingQual for attributes 
+	   * (VAR nodes) that are not contained in 'tlist' yet. If so, it creates
+	   * a new entry and attaches it to the list 'new_tlist' (consisting of the 
+	   * VAR node and the RESDOM node as usual with tlists :-)  ) */
+	  if (parse->hasAggs)
+	    {
+	      if (parse->havingQual != NULL)
 		{
-			if (parse->havingQual != NULL)
-				tlist = check_having_qual_for_vars(parse->havingQual, tlist);
+		  new_tlist = check_having_qual_for_vars(parse->havingQual,new_tlist);
 		}
-
-		tlist = preprocess_targetlist(tlist,
-									  parse->commandType,
-									  parse->resultRelation,
-									  parse->rtable);
-
-		if (parse->rtable != NULL)
-		{
-			vpm = (List **) palloc(length(parse->rtable) * sizeof(List *));
-			memset(vpm, 0, length(parse->rtable) * sizeof(List *));
-		}
-		PlannerVarParam = lcons(vpm, PlannerVarParam);
-		result_plan = query_planner(parse,
-									parse->commandType,
-									tlist,
-									(List *) parse->qual);
-		PlannerVarParam = lnext(PlannerVarParam);
-		if (vpm != NULL)
-			pfree(vpm);
+	    }
+	  
+	  new_tlist = preprocess_targetlist(new_tlist,
+					    parse->commandType,
+					    parse->resultRelation,
+					    parse->rtable);
+	  
+	  /* Here starts the original (pre having) code */
+	  tlist = preprocess_targetlist(tlist,
+					parse->commandType,
+					parse->resultRelation,
+					parse->rtable);
+	  
+	  if (parse->rtable != NULL)
+	    {
+	      vpm = (List **) palloc(length(parse->rtable) * sizeof(List *));
+	      memset(vpm, 0, length(parse->rtable) * sizeof(List *));
+	    }
+	  PlannerVarParam = lcons(vpm, PlannerVarParam);
+	  result_plan = query_planner(parse,
+				      parse->commandType,
+				      new_tlist,
+				      (List *) parse->qual);
+	  PlannerVarParam = lnext(PlannerVarParam);
+	  if (vpm != NULL)
+	    pfree(vpm);		 
 	}
-
+	
 	/*
 	 * If we have a GROUP BY clause, insert a group node (with the
 	 * appropriate sort node.)
@@ -173,8 +197,10 @@ union_planner(Query *parse)
 		 */
 		tuplePerGroup = parse->hasAggs;
 
+		/***S*H***/
+		/* Use 'new_tlist' instead of 'tlist' */
 		result_plan =
-			make_groupPlan(&tlist,
+			make_groupPlan(&new_tlist,
 						   tuplePerGroup,
 						   parse->groupClause,
 						   result_plan);
@@ -185,6 +211,11 @@ union_planner(Query *parse)
 	 */
 	if (parse->hasAggs)
 	{
+	        int old_length=0, new_length=0;
+		
+		/* Create the AGG node but use 'tlist' not 'new_tlist' as target list because we
+		 * don't want the additional attributes (only used for the havingQual, see above)
+		 * to show up in the result */
 		result_plan = (Plan *) make_agg(tlist, result_plan);
 
 		/*
@@ -192,78 +223,74 @@ union_planner(Query *parse)
 		 * the result tuple of the subplans.
 		 */
 		((Agg *) result_plan)->aggs =
-			set_agg_tlist_references((Agg *) result_plan);
+		  set_agg_tlist_references((Agg *) result_plan); 
 
 
-		if (parse->havingQual != NULL)
-		{
-			List	   *clause;
-			List	  **vpm = NULL;
-
-			/* 
-			 * Restore target list: get rid of Vars added for havingQual.
-			 * Assumption: tlist_len > 0...
-			 */
-			{
-				List   *l;
-				int		tlen = 0;
-			
-				foreach (l, ((Agg *) result_plan)->plan.targetlist)
-				{
-					if (++tlen == tlist_len)
-						break;
-				}
-				lnext(l) = NIL;
-			}
-			
-			/*
-			 * stuff copied from above to handle the use of attributes
-			 * from outside in subselects
-			 */
-
-			if (parse->rtable != NULL)
-			{
-				vpm = (List **) palloc(length(parse->rtable) * sizeof(List *));
-				memset(vpm, 0, length(parse->rtable) * sizeof(List *));
-			}
-			PlannerVarParam = lcons(vpm, PlannerVarParam);
-
-			/*
-			 * There is a subselect in the havingQual, so we have to
-			 * process it using the same function as for a subselect in
-			 * 'where'
-			 */
-			if (parse->hasSubLinks)
-				parse->havingQual = SS_process_sublinks((Node *) parse->havingQual);
-
-			/* convert the havingQual to conjunctive normal form (cnf) */
-			parse->havingQual = (Node *) cnfify((Expr *) (Node *) parse->havingQual, true);
-
-			/*
-			 * Calculate the opfids from the opnos (=select the correct
-			 * functions for the used VAR datatypes)
-			 */
-			parse->havingQual = (Node *) fix_opids((List *) parse->havingQual);
-
-			((Agg *) result_plan)->plan.qual = (List *) parse->havingQual;
-
-			/*
-			 * Check every clause of the havingQual for aggregates used
-			 * and append them to result_plan->aggs
-			 */
-			foreach(clause, ((Agg *) result_plan)->plan.qual)
-			{
-				((Agg *) result_plan)->aggs = nconc(((Agg *) result_plan)->aggs,
-					  check_having_qual_for_aggs((Node *) lfirst(clause),
-						((Agg *) result_plan)->plan.lefttree->targetlist,
-										 ((List *) parse->groupClause)));
-			}
-			PlannerVarParam = lnext(PlannerVarParam);
-			if (vpm != NULL)
-				pfree(vpm);
-		}
-	}
+		/***S*H***/
+		if(parse->havingQual!=NULL) 
+		  {
+		    List	   *clause;
+		    List	  **vpm = NULL;
+		    
+		    
+		    /* stuff copied from above to handle the use of attributes from outside
+		     * in subselects */
 
+		    if (parse->rtable != NULL)
+		      {
+			vpm = (List **) palloc(length(parse->rtable) * sizeof(List *));
+			memset(vpm, 0, length(parse->rtable) * sizeof(List *));
+		      }
+		    PlannerVarParam = lcons(vpm, PlannerVarParam);
+		    
+
+		    /* convert the havingQual to conjunctive normal form (cnf) */
+		    (List *) parse->havingQual=cnfify((Expr *)(Node *) parse->havingQual,true);
+
+		    /* There is a subselect in the havingQual, so we have to process it
+                     * using the same function as for a subselect in 'where' */
+		    if (parse->hasSubLinks)
+		      {
+			(List *) parse->havingQual = 
+			  (List *) SS_process_sublinks((Node *) parse->havingQual);
+		      }
+		    		    
+		    
+		    /* Calculate the opfids from the opnos (=select the correct functions for
+		     * the used VAR datatypes) */
+		    (List *) parse->havingQual=fix_opids((List *) parse->havingQual);
+		    
+		    ((Agg *) result_plan)->plan.qual=(List *) parse->havingQual;
+
+		    /* Check every clause of the havingQual for aggregates used and append
+		     * them to result_plan->aggs */
+		    foreach(clause, ((Agg *) result_plan)->plan.qual)
+		      {
+			/* Make sure there are aggregates in the havingQual 
+			 * if so, the list must be longer after check_having_qual_for_aggs */
+			old_length=length(((Agg *) result_plan)->aggs);			
+			
+			((Agg *) result_plan)->aggs = nconc(((Agg *) result_plan)->aggs,
+			    check_having_qual_for_aggs((Node *) lfirst(clause),
+				       ((Agg *) result_plan)->plan.lefttree->targetlist,
+				       ((List *) parse->groupClause)));
+
+			/* Have a look at the length of the returned list. If there is no
+			 * difference, no aggregates have been found and that means, that
+			 * the Qual belongs to the where clause */
+			if (((new_length=length(((Agg *) result_plan)->aggs)) == old_length) ||
+			    (new_length == 0))
+			  {
+			    elog(ERROR,"This could have been done in a where clause!!");
+			    return (Plan *)NIL;
+			  }
+		      }
+		    PlannerVarParam = lnext(PlannerVarParam);
+		    if (vpm != NULL)
+		      pfree(vpm);		
+		  }
+	}		  
+		
 	/*
 	 * For now, before we hand back the plan, check to see if there is a
 	 * user-specified sort that needs to be done.  Eventually, this will
@@ -277,14 +304,14 @@ union_planner(Query *parse)
 	{
 		Plan	   *sortplan = make_sortplan(tlist, parse->sortClause, result_plan);
 
-		return (Plan *) make_unique(tlist, sortplan, parse->uniqueFlag);
+		return ((Plan *) make_unique(tlist, sortplan, parse->uniqueFlag));
 	}
 	else
 	{
 		if (parse->sortClause)
-			return make_sortplan(tlist, parse->sortClause, result_plan);
+			return (make_sortplan(tlist, parse->sortClause, result_plan));
 		else
-			return (Plan *) result_plan;
+			return ((Plan *) result_plan);
 	}
 
 }
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 3d3ad51c3199d7a8745cd9a3062f4da0a56c06fa..dc04e3c5c3d50cac9569c2945abf5f30480e1c86 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -7,7 +7,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/setrefs.c,v 1.29 1998/12/14 00:02:10 thomas Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/setrefs.c,v 1.30 1999/01/18 00:09:48 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -923,173 +923,195 @@ del_agg_clause(Node *clause)
 	return NULL;
 }
 
-
+/***S*H***/ 
 /* check_having_qual_for_vars takes the the havingQual and the actual targetlist as arguments
  * and recursively scans the havingQual for attributes that are not included in the targetlist
  * yet. Attributes contained in the havingQual but not in the targetlist show up with queries
- * like:
- * SELECT sid
+ * like: 
+ * SELECT sid 
  * FROM part
  * GROUP BY sid
- * HAVING MIN(pid) > 1;  (pid is used but never selected for!!!).
+ * HAVING MIN(pid) > 1;  (pid is used but never selected for!!!). 
  * To be able to handle queries like that correctly we have to extend the actual targetlist
- *	(which will be the one used for the GROUP node later on) by these attributes. */
+ *  (which will be the one used for the GROUP node later on) by these attributes. */
 List *
 check_having_qual_for_vars(Node *clause, List *targetlist_so_far)
 {
-	List	   *t;
+  List	   *t;
 
 
-	if (IsA(clause, Var))
-	{
-		RelOptInfo	tmp_rel;
+  if (IsA(clause, Var))
+    {
+      RelOptInfo         tmp_rel;
+      
 
+      tmp_rel.targetlist = targetlist_so_far;
+      
+      /* 
+       * Ha! A Var node!
+       */
 
-		tmp_rel.targetlist = targetlist_so_far;
-
-		/*
-		 * Ha! A Var node!
-		 */
-
-		/* Check if the VAR is already contained in the targetlist */
-		if (tlist_member((Var *) clause, (List *) targetlist_so_far) == NULL)
-			add_tl_element(&tmp_rel, (Var *) clause);
-
-		return tmp_rel.targetlist;
-	}
-
-	else if (is_funcclause(clause) || not_clause(clause) ||
-			 or_clause(clause) || and_clause(clause))
+      /* Check if the VAR is already contained in the targetlist */
+      if (tlist_member((Var *)clause, (List *)targetlist_so_far) == NULL)
 	{
-
-		/*
-		 * This is a function. Recursively call this routine for its
-		 * arguments...
-		 */
-		foreach(t, ((Expr *) clause)->args)
-			targetlist_so_far = check_having_qual_for_vars(lfirst(t), targetlist_so_far);
-		return targetlist_so_far;
-	}
-	else if (IsA(clause, Aggreg))
+	  add_tl_element(&tmp_rel, (Var *)clause); 
+	} 
+	    
+      return tmp_rel.targetlist;
+    }
+  
+  else if (is_funcclause(clause) || not_clause(clause) || 
+	   or_clause(clause) || and_clause(clause))
+    {
+      
+      /*
+       * This is a function. Recursively call this routine for its
+       * arguments...
+       */
+      foreach(t, ((Expr *) clause)->args)
 	{
-		targetlist_so_far =
-			check_having_qual_for_vars(((Aggreg *) clause)->target, targetlist_so_far);
-		return targetlist_so_far;
+	  targetlist_so_far = check_having_qual_for_vars(lfirst(t), targetlist_so_far);
 	}
-	else if (IsA(clause, ArrayRef))
+      return targetlist_so_far;
+    }
+  else if (IsA(clause, Aggreg))
+    {
+	  targetlist_so_far = 
+	    check_having_qual_for_vars(((Aggreg *) clause)->target, targetlist_so_far);
+	  return targetlist_so_far;
+    }
+  else if (IsA(clause, ArrayRef))
+    {
+      ArrayRef   *aref = (ArrayRef *) clause;
+      
+      /*
+       * This is an arrayref. Recursively call this routine for its
+       * expression and its index expression...
+       */
+      foreach(t, aref->refupperindexpr)
 	{
-		ArrayRef   *aref = (ArrayRef *) clause;
-
-		/*
-		 * This is an arrayref. Recursively call this routine for its
-		 * expression and its index expression...
-		 */
-		foreach(t, aref->refupperindexpr)
-			targetlist_so_far = check_having_qual_for_vars(lfirst(t), targetlist_so_far);
-		foreach(t, aref->reflowerindexpr)
-			targetlist_so_far = check_having_qual_for_vars(lfirst(t), targetlist_so_far);
-		targetlist_so_far = check_having_qual_for_vars(aref->refexpr, targetlist_so_far);
-		targetlist_so_far = check_having_qual_for_vars(aref->refassgnexpr, targetlist_so_far);
-
-		return targetlist_so_far;
-	}
-	else if (is_opclause(clause))
-	{
-
-		/*
-		 * This is an operator. Recursively call this routine for both its
-		 * left and right operands
-		 */
-		Node	   *left = (Node *) get_leftop((Expr *) clause);
-		Node	   *right = (Node *) get_rightop((Expr *) clause);
-
-		if (left != (Node *) NULL)
-			targetlist_so_far = check_having_qual_for_vars(left, targetlist_so_far);
-		if (right != (Node *) NULL)
-			targetlist_so_far = check_having_qual_for_vars(right, targetlist_so_far);
-
-		return targetlist_so_far;
+	  targetlist_so_far = check_having_qual_for_vars(lfirst(t), targetlist_so_far);
 	}
-	else if (IsA(clause, Param) ||IsA(clause, Const))
+      foreach(t, aref->reflowerindexpr)
 	{
-		/* do nothing! */
-		return targetlist_so_far;
+	  targetlist_so_far = check_having_qual_for_vars(lfirst(t), targetlist_so_far);
 	}
-
-	/*
-	 * If we get to a sublink, then we only have to check the lefthand
-	 * side of the expression to see if there are any additional VARs
-	 */
-	else if (IsA(clause, SubLink))
+      targetlist_so_far = check_having_qual_for_vars(aref->refexpr, targetlist_so_far);
+      targetlist_so_far = check_having_qual_for_vars(aref->refassgnexpr, targetlist_so_far);
+      
+      return targetlist_so_far;
+    }
+  else if (is_opclause(clause))
+    {
+      
+      /*
+       * This is an operator. Recursively call this routine for both its
+       * left and right operands
+       */
+      Node	   *left = (Node *) get_leftop((Expr *) clause);
+      Node	   *right = (Node *) get_rightop((Expr *) clause);
+      
+      if (left != (Node *) NULL)
+	targetlist_so_far = check_having_qual_for_vars(left, targetlist_so_far);
+      if (right != (Node *) NULL)
+	targetlist_so_far = check_having_qual_for_vars(right, targetlist_so_far);
+      
+      return targetlist_so_far;
+    }
+  else if (IsA(clause, Param) || IsA(clause, Const))
+    {
+      /* do nothing! */
+      return targetlist_so_far;
+    }
+  /* If we get to a sublink, then we only have to check the lefthand side of the expression
+   * to see if there are any additional VARs */
+  else if (IsA(clause, SubLink))
+    {
+      foreach(t,((List *)((SubLink *)clause)->lefthand))
 	{
-		foreach(t, ((List *) ((SubLink *) clause)->lefthand))
-			targetlist_so_far = check_having_qual_for_vars(lfirst(t), targetlist_so_far);
-		return targetlist_so_far;
-	}
-	else
-	{
-
-		/*
-		 * Ooops! we can not handle that!
-		 */
-		elog(ERROR, "check_having_qual_for_vars: Can not handle this having_qual! %d\n",
-			 nodeTag(clause));
-		return NIL;
+	  targetlist_so_far = check_having_qual_for_vars(lfirst(t), targetlist_so_far);
 	}
+      return targetlist_so_far;
+    }
+  else
+    {
+      /*
+       * Ooops! we can not handle that!
+       */
+      elog(ERROR, "check_having_qual_for_vars: Can not handle this having_qual! %d\n",
+	   nodeTag(clause));
+      return NIL;
+    }
 }
 
-/* check_having_qual_for_aggs takes the havingQual, the targetlist and the groupClause
+/* check_having_qual_for_aggs takes the havingQual, the targetlist and the groupClause  
  * as arguments and scans the havingQual recursively for aggregates. If an aggregate is
- * found it is attached to a list and returned by the function. (All the returned lists
+ * found it is attached to a list and returned by the function. (All the returned lists 
  * are concenated to result_plan->aggs in planner.c:union_planner() */
 List *
 check_having_qual_for_aggs(Node *clause, List *subplanTargetList, List *groupClause)
 {
-	List	   *t,
-			   *l1;
+	List	   *t, *l1;
 	List	   *agg_list = NIL;
 
-	int			contained_in_group_clause = 0;
-
+	int contained_in_group_clause = 0;
+	
 
 	if (IsA(clause, Var))
 	{
-		TargetEntry *subplanVar;
-
-		/*
-		 * Ha! A Var node!
-		 */
-		subplanVar = match_varid((Var *) clause, subplanTargetList);
-
-		/*
-		 * Change the varno & varattno fields of the var node to point to
-		 * the resdom->resno fields of the subplan (lefttree)
-		 */
-		((Var *) clause)->varattno = subplanVar->resdom->resno;
-
-		return NIL;
+	  TargetEntry *subplanVar;
+	  
+	  /*
+	   * Ha! A Var node!
+	   */
+	  subplanVar = match_varid((Var *) clause, subplanTargetList);
+	  
+	  /*
+	   * Change the varno & varattno fields of the var node to point to the resdom->resno
+	   * fields of the subplan (lefttree) 
+	   */	  
+	  ((Var *) clause)->varattno = subplanVar->resdom->resno;
+
+	  return NIL;
 
 	}
-	else if (is_funcclause(clause) || not_clause(clause) ||
-			 or_clause(clause) || and_clause(clause))
+        /***S*H***/
+	else if (is_funcclause(clause) || not_clause(clause) || 
+		 or_clause(clause) || and_clause(clause))
 	{
+	  int new_length=0, old_length=0;
+	  
 		/*
 		 * This is a function. Recursively call this routine for its
 		 * arguments... (i.e. for AND, OR, ... clauses!)
 		 */
 		foreach(t, ((Expr *) clause)->args)
 		{
-			agg_list = nconc(agg_list,
-				 check_having_qual_for_aggs(lfirst(t), subplanTargetList,
-											groupClause));
+		  old_length=length((List *)agg_list);
+
+		  agg_list = nconc(agg_list,
+				   check_having_qual_for_aggs(lfirst(t), subplanTargetList,
+							      groupClause));
+
+		  /* The arguments of OR or AND clauses are comparisons or relations 
+		   * and because we are in the havingQual there must be at least one operand
+		   * using an aggregate function. If so, we will find it and the length of the
+		   * agg_list will be increased after the above call to 
+		   * check_having_qual_for_aggs. If there are no aggregates used, the query
+                   * could have been formulated using the 'where' clause */
+		  if(((new_length=length((List *)agg_list)) == old_length) || (new_length == 0))
+		    {
+		      elog(ERROR,"This could have been done in a where clause!!");
+		      return NIL;
+		    } 
 		}
 		return agg_list;
 	}
 	else if (IsA(clause, Aggreg))
 	{
 		return lcons(clause,
-					 check_having_qual_for_aggs(((Aggreg *) clause)->target, subplanTargetList,
-												groupClause));
+		    check_having_qual_for_aggs(((Aggreg *) clause)->target, subplanTargetList,
+					       groupClause));		
 	}
 	else if (IsA(clause, ArrayRef))
 	{
@@ -1102,21 +1124,21 @@ check_having_qual_for_aggs(Node *clause, List *subplanTargetList, List *groupCla
 		foreach(t, aref->refupperindexpr)
 		{
 			agg_list = nconc(agg_list,
-				 check_having_qual_for_aggs(lfirst(t), subplanTargetList,
-											groupClause));
+					 check_having_qual_for_aggs(lfirst(t), subplanTargetList,
+								    groupClause));
 		}
 		foreach(t, aref->reflowerindexpr)
 		{
 			agg_list = nconc(agg_list,
-				 check_having_qual_for_aggs(lfirst(t), subplanTargetList,
-											groupClause));
+					 check_having_qual_for_aggs(lfirst(t), subplanTargetList,
+								    groupClause));
 		}
 		agg_list = nconc(agg_list,
-			 check_having_qual_for_aggs(aref->refexpr, subplanTargetList,
-										groupClause));
+				 check_having_qual_for_aggs(aref->refexpr, subplanTargetList,
+							    groupClause));
 		agg_list = nconc(agg_list,
-		check_having_qual_for_aggs(aref->refassgnexpr, subplanTargetList,
-								   groupClause));
+				 check_having_qual_for_aggs(aref->refassgnexpr, subplanTargetList,
+							    groupClause));
 
 		return agg_list;
 	}
@@ -1132,85 +1154,92 @@ check_having_qual_for_aggs(Node *clause, List *subplanTargetList, List *groupCla
 
 		if (left != (Node *) NULL)
 			agg_list = nconc(agg_list,
-					  check_having_qual_for_aggs(left, subplanTargetList,
-												 groupClause));
+					 check_having_qual_for_aggs(left, subplanTargetList,
+								    groupClause));
 		if (right != (Node *) NULL)
 			agg_list = nconc(agg_list,
 					 check_having_qual_for_aggs(right, subplanTargetList,
-												groupClause));
+								    groupClause));
 
 		return agg_list;
 	}
-	else if (IsA(clause, Param) ||IsA(clause, Const))
+	else if (IsA(clause, Param) || IsA(clause, Const))
 	{
 		/* do nothing! */
 		return NIL;
 	}
-
-	/*
-	 * This is for Sublinks which show up as EXPR nodes. All the other
-	 * EXPR nodes (funcclauses, and_clauses, or_clauses) were caught above
-	 */
+	/* This is for Sublinks which show up as EXPR nodes. All the other EXPR nodes
+         * (funcclauses, and_clauses, or_clauses) were caught above */
 	else if (IsA(clause, Expr))
-	{
-
-		/*
-		 * Only the lefthand side of the sublink has to be checked for
-		 * aggregates to be attached to result_plan->aggs (see
-		 * planner.c:union_planner() )
-		 */
-		foreach(t, ((List *) ((SubLink *) ((SubPlan *)
-						   ((Expr *) clause)->oper)->sublink)->lefthand))
-		{
-			agg_list =
-				nconc(agg_list,
-					  check_having_qual_for_aggs(lfirst(t),
-										subplanTargetList, groupClause));
-		}
-
-
-		/*
-		 * All arguments to the Sublink node are attributes from outside
-		 * used within the sublink. Here we have to check that only
-		 * attributes that is grouped for are used!
-		 */
-		foreach(t, ((Expr *) clause)->args)
-		{
-			contained_in_group_clause = 0;
-
-			foreach(l1, groupClause)
-			{
-				if (tlist_member(lfirst(t), lcons(((GroupClause *) lfirst(l1))->entry, NIL)) !=
-					NULL)
-					contained_in_group_clause = 1;
-			}
-
-			/*
-			 * If the use of the attribute is allowed (i.e. it is in the
-			 * groupClause) we have to adjust the varnos and varattnos
-			 */
-			if (contained_in_group_clause)
-			{
-				agg_list =
-					nconc(agg_list,
-						  check_having_qual_for_aggs(lfirst(t),
-										subplanTargetList, groupClause));
-			}
-			else
-			{
-				elog(ERROR, "You must group by the attribute used from outside!");
-				return NIL;
-			}
+	  {
+	    /* Only the lefthand side of the sublink has to be checked for aggregates
+             * to be attached to result_plan->aggs (see planner.c:union_planner() )
+	     */	    
+	    foreach(t,((List *)((SubLink *)((SubPlan *)
+		       ((Expr *)clause)->oper)->sublink)->lefthand)) 
+	      {
+		agg_list = 
+		  nconc(agg_list,
+			check_having_qual_for_aggs(lfirst(t), 
+						   subplanTargetList, groupClause));
+	      }
+
+            /* The first argument of ...->oper has also to be checked */
+	    {	      
+	      List *tmp_ptr;
+	      
+	      foreach(tmp_ptr, ((SubLink *)((SubPlan *)
+					    ((Expr *)clause)->oper)->sublink)->oper)
+		{		
+  		  agg_list = 
+		    nconc(agg_list,
+			  check_having_qual_for_aggs((Node *)lfirst(((Expr *)
+								     lfirst(tmp_ptr))->args), 
+						     subplanTargetList, groupClause));
 		}
-		return agg_list;
-	}
+	    }
+	      		
+	    /* All arguments to the Sublink node are attributes from outside used within
+	     * the sublink. Here we have to check that only attributes that is grouped for
+	     * are used! */
+	    foreach(t,((Expr *)clause)->args) 
+	      {	
+		contained_in_group_clause = 0;
+
+		foreach(l1,groupClause)
+		  {
+		    if (tlist_member(lfirst(t),lcons(((GroupClause *)lfirst(l1))->entry,NIL)) != 
+			NULL)
+		      {
+			contained_in_group_clause=1;
+		      }
+		  }
+		
+		/* If the use of the attribute is allowed (i.e. it is in the groupClause)
+		 * we have to adjust the varnos and varattnos */
+		if (contained_in_group_clause)
+		  {
+		    agg_list = 
+		      nconc(agg_list,
+			    check_having_qual_for_aggs(lfirst(t), 
+						       subplanTargetList, groupClause));
+		  }
+		else
+		  {
+		    elog(ERROR,"You must group by the attribute used from outside!");
+		    return NIL;
+		  }		
+	      }
+	    return agg_list;
+	  }
 	else
-	{
-		/*
-		 * Ooops! we can not handle that!
-		 */
-		elog(ERROR, "check_having_qual_for_aggs: Can not handle this having_qual! %d\n",
-			 nodeTag(clause));
-		return NIL;
-	}
+	  {
+	    /*
+	     * Ooops! we can not handle that!
+	     */
+	    elog(ERROR, "check_having_qual_for_aggs: Can not handle this having_qual! %d\n",
+		 nodeTag(clause));
+	    return NIL;
+	  }
 }
+/***S*H***/ /* End */
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index a1b4cd22cce8fde7d7797e0ba1dbf430403853f3..b16203d9f7b7b23f56bd936a4a4c58a5e331d853 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -405,20 +405,6 @@ SS_process_sublinks(Node *expr)
 			SS_process_sublinks((Node *) ((Expr *) expr)->args);
 	else if (IsA(expr, SubLink))/* got it! */
 	{
-
-		/*
-		 * Hack to make sure expr->oper->args points to the same VAR node
-		 * as expr->lefthand does. Needed for subselects in the havingQual
-		 * when used on views. Otherwise aggregate functions will fail
-		 * later on (at execution time!) Reason: The rewite System makes
-		 * several copies of the VAR nodes and in this case it should not
-		 * do so :-(
-		 */
-		if (((SubLink *) expr)->lefthand != NULL)
-		{
-			lfirst(((Expr *) lfirst(((SubLink *) expr)->oper))->args) =
-				lfirst(((SubLink *) expr)->lefthand);
-		}
 		expr = _make_subplan((SubLink *) expr);
 	}
 
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 3dc0cad8165c0e1a0dbb81b9c0931f09afff0e27..ea9cb5fa8c2df9dee969a7519efe334a32eefa23 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -5,7 +5,7 @@
  *
  * Copyright (c) 1994, Regents of the University of California
  *
- *  $Id: analyze.c,v 1.91 1998/12/14 06:50:32 scrappy Exp $
+ *  $Id: analyze.c,v 1.92 1999/01/18 00:09:49 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -26,6 +26,11 @@
 #include "parser/parse_node.h"
 #include "parser/parse_relation.h"
 #include "parser/parse_target.h"
+/***S*I***/
+#include "parser/parse_expr.h"
+#include "catalog/pg_type.h"
+#include "parse.h"
+
 #include "utils/builtins.h"
 #include "utils/mcxt.h"
 
@@ -383,8 +388,13 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	 * The INSERT INTO ... SELECT ... could have a UNION in child, so
 	 * unionClause may be false
 	 */
-	qry->unionall = stmt->unionall;
-	qry->unionClause = transformUnionClause(stmt->unionClause, qry->targetList);
+  	qry->unionall = stmt->unionall;	
+
+ 	/***S*I***/
+ 	/* Just hand through the unionClause and intersectClause. 
+ 	 * We will handle it in the function Except_Intersect_Rewrite() */
+ 	qry->unionClause = stmt->unionClause;
+ 	qry->intersectClause = stmt->intersectClause;	
 
 	/*
 	 * If there is a havingQual but there are no aggregates, then there is
@@ -942,7 +952,12 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 	 * unionClause may be false
 	 */
 	qry->unionall = stmt->unionall;
-	qry->unionClause = transformUnionClause(stmt->unionClause, qry->targetList);
+
+ 	/***S*I***/
+ 	/* Just hand through the unionClause and intersectClause. 
+ 	 * We will handle it in the function Except_Intersect_Rewrite() */
+ 	qry->unionClause = stmt->unionClause;
+ 	qry->intersectClause = stmt->intersectClause;
 
 	/*
 	 * If there is a havingQual but there are no aggregates, then there is
@@ -1012,3 +1027,97 @@ transformCursorStmt(ParseState *pstate, SelectStmt *stmt)
 
 	return qry;
 }
+
+/***S*I***/
+/* This function steps through the tree
+ * built up by the select_w_o_sort rule
+ * and builds a list of all SelectStmt Nodes found
+ * The built up list is handed back in **select_list.
+ * If one of the SelectStmt Nodes has the 'unionall' flag
+ * set to true *unionall_present hands back 'true' */
+void 
+create_select_list(Node *ptr, List **select_list, bool *unionall_present)
+{
+  if(IsA(ptr, SelectStmt)) {
+    *select_list = lappend(*select_list, ptr);    
+    if(((SelectStmt *)ptr)->unionall == TRUE) *unionall_present = TRUE;    
+    return;    
+  }
+  
+  /* Recursively call for all arguments. A NOT expr has no lexpr! */
+  if (((A_Expr *)ptr)->lexpr != NULL) 
+     create_select_list(((A_Expr *)ptr)->lexpr, select_list, unionall_present);
+  create_select_list(((A_Expr *)ptr)->rexpr, select_list, unionall_present);
+}
+
+/* Changes the A_Expr Nodes to Expr Nodes and exchanges ANDs and ORs.
+ * The reason for the exchange is easy: We implement INTERSECTs and EXCEPTs 
+ * by rewriting these queries to semantically equivalent queries that use
+ * IN and NOT IN subselects. To be able to use all three operations 
+ * (UNIONs INTERSECTs and EXCEPTs) in one complex query we have to 
+ * translate the queries into Disjunctive Normal Form (DNF). Unfortunately
+ * there is no function 'dnfify' but there is a function 'cnfify'
+ * which produces DNF when we exchange ANDs and ORs before calling
+ * 'cnfify' and exchange them back in the result.
+ *
+ * If an EXCEPT or INTERSECT is present *intersect_present
+ * hands back 'true' */ 
+Node *A_Expr_to_Expr(Node *ptr, bool *intersect_present)
+{
+  Node *result;
+  
+  switch(nodeTag(ptr))
+    {
+    case T_A_Expr:
+      {
+	A_Expr *a = (A_Expr *)ptr;
+	
+	switch (a->oper)
+	  {
+	  case AND:
+	    {
+	      Expr *expr = makeNode(Expr);
+	      Node	   *lexpr = A_Expr_to_Expr(((A_Expr *)ptr)->lexpr, intersect_present);
+	      Node	   *rexpr = A_Expr_to_Expr(((A_Expr *)ptr)->rexpr, intersect_present);
+
+	      *intersect_present = TRUE;
+	      
+	      expr->typeOid = BOOLOID;
+	      expr->opType = OR_EXPR;
+	      expr->args = makeList(lexpr, rexpr, -1);
+	      result = (Node *) expr;
+	      break;	      
+	    }	  	  
+	  case OR:
+	    {
+	      Expr *expr = makeNode(Expr);
+	      Node	   *lexpr = A_Expr_to_Expr(((A_Expr *)ptr)->lexpr, intersect_present);
+	      Node	   *rexpr = A_Expr_to_Expr(((A_Expr *)ptr)->rexpr, intersect_present);
+
+	      expr->typeOid = BOOLOID;
+	      expr->opType = AND_EXPR;
+	      expr->args = makeList(lexpr, rexpr, -1);
+	      result = (Node *) expr;
+	      break;	      
+	    }
+	  case NOT:
+	    {
+	      Expr *expr = makeNode(Expr);
+	      Node	   *rexpr = A_Expr_to_Expr(((A_Expr *)ptr)->rexpr, intersect_present);
+
+	      expr->typeOid = BOOLOID;
+	      expr->opType = NOT_EXPR;
+	      expr->args = makeList(rexpr, -1);
+	      result = (Node *) expr;
+	      break;	      
+	    }
+	  }	
+	break;	
+      }
+    default:
+      {
+	result = ptr;
+      }      
+    }
+  return result;  
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 76e9dc1d7fde9d71fc4945996c379f27278f8a94..b3fb314a8f47285402e56bbdc64e653fae932aea 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10,7 +10,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.42 1999/01/05 15:46:25 vadim Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.43 1999/01/18 00:09:51 momjian Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -47,6 +47,7 @@
 #include "access/xact.h"
 #include "storage/lmgr.h"
 #include "utils/numeric.h"
+#include "parser/analyze.h"
 
 #ifdef MULTIBYTE
 #include "mb/pg_wchar.h"
@@ -128,9 +129,9 @@ Oid	param_type(int t); /* used in parse_expr.c */
 		ProcedureStmt, 	RecipeStmt, RemoveAggrStmt, RemoveOperStmt,
 		RemoveFuncStmt, RemoveStmt,
 		RenameStmt, RevokeStmt, RuleStmt, TransactionStmt, ViewStmt, LoadStmt,
-		CreatedbStmt, DestroydbStmt, VacuumStmt, CursorStmt, SubSelect, SubUnion,
-		UpdateStmt, InsertStmt, SelectStmt, NotifyStmt, DeleteStmt, ClusterStmt,
-		ExplainStmt, VariableSetStmt, VariableShowStmt, VariableResetStmt,
+		CreatedbStmt, DestroydbStmt, VacuumStmt, CursorStmt, SubSelect,
+		UpdateStmt, InsertStmt, select_w_o_sort, SelectStmt, NotifyStmt, DeleteStmt, 
+		ClusterStmt, ExplainStmt, VariableSetStmt, VariableShowStmt, VariableResetStmt,
 		CreateUserStmt, AlterUserStmt, DropUserStmt
 
 %type <str>	opt_database1, opt_database2, location, encoding
@@ -174,7 +175,7 @@ Oid	param_type(int t); /* used in parse_expr.c */
 
 %type <boolean>	TriggerForOpt, TriggerForType
 
-%type <list>	union_clause, select_list, for_update_clause
+%type <list>	for_update_clause
 %type <list>	join_list
 %type <joinusing>
 				join_using
@@ -271,11 +272,11 @@ Oid	param_type(int t); /* used in parse_expr.c */
 		CONSTRAINT, CREATE, CROSS, CURRENT, CURRENT_DATE, CURRENT_TIME, 
 		CURRENT_TIMESTAMP, CURRENT_USER, CURSOR,
 		DAY_P, DECIMAL, DECLARE, DEFAULT, DELETE, DESC, DISTINCT, DOUBLE, DROP,
-		ELSE, END_TRANS, EXECUTE, EXISTS, EXTRACT,
+		ELSE, END_TRANS, EXCEPT, EXECUTE, EXISTS, EXTRACT,
 		FALSE_P, FETCH, FLOAT, FOR, FOREIGN, FROM, FULL,
 		GRANT, GROUP, HAVING, HOUR_P,
-		IN, INNER_P, INSENSITIVE, INSERT, INTERVAL, INTO, IS, ISOLATION,
-		JOIN, KEY, LANGUAGE, LEADING, LEFT, LEVEL, LIKE, LOCAL,
+		IN, INNER_P, INSENSITIVE, INSERT, INTERSECT, INTERVAL, INTO, IS,
+		ISOLATION, JOIN, KEY, LANGUAGE, LEADING, LEFT, LEVEL, LIKE, LOCAL,
 		MATCH, MINUTE_P, MONTH_P, NAMES,
 		NATIONAL, NATURAL, NCHAR, NEXT, NO, NOT, NULLIF, NULL_P, NUMERIC,
 		OF, ON, ONLY, OPTION, OR, ORDER, OUTER_P,
@@ -343,7 +344,7 @@ Oid	param_type(int t); /* used in parse_expr.c */
 %left		'.'
 %left		'[' ']'
 %nonassoc	TYPECAST
-%left		UNION
+%left		UNION INTERSECT EXCEPT
 %%
 
 stmtblock:  stmtmulti
@@ -354,8 +355,13 @@ stmtblock:  stmtmulti
 
 stmtmulti:  stmtmulti stmt ';'
 				{ $$ = lappend($1, $2); }
-		| stmtmulti stmt
-				{ $$ = lappend($1, $2); }
+/***S*I***/
+/* We comment the next rule because it seems to be redundant
+ * and produces 16 shift/reduce conflicts with the new SelectStmt rule
+ * needed for EXCEPT and INTERSECTS. So far I did not notice any
+ * violations by removing the rule! */
+/* 		| stmtmulti stmt
+				{ $$ = lappend($1, $2); } */
 		| stmt ';'
 				{ $$ = lcons($1,NIL); }
 		;
@@ -2062,7 +2068,10 @@ RuleStmt:  CREATE RULE name AS
 OptStmtList:  NOTHING					{ $$ = NIL; }
 		| OptimizableStmt				{ $$ = lcons($1, NIL); }
 		| '[' OptStmtBlock ']'			{ $$ = $2; }
-		| '(' OptStmtBlock ')'			{ $$ = $2; }
+/***S*I*D***/
+/* We comment this out because it produces a shift / reduce conflict 
+ * with the select_w_o_sort rule */
+/*		| '(' OptStmtBlock ')'			{ $$ = $2; } */
 		;
 
 OptStmtBlock:  OptStmtMulti
@@ -2073,8 +2082,13 @@ OptStmtBlock:  OptStmtMulti
 
 OptStmtMulti:  OptStmtMulti OptimizableStmt ';'
 				{  $$ = lappend($1, $2); }
-		| OptStmtMulti OptimizableStmt
-				{  $$ = lappend($1, $2); }
+/***S*I***/
+/* We comment the next rule because it seems to be redundant
+ * and produces 16 shift/reduce conflicts with the new SelectStmt rule
+ * needed for EXCEPT and INTERSECT. So far I did not notice any
+ * violations by removing the rule! */
+/* 		| OptStmtMulti OptimizableStmt
+				{  $$ = lappend($1, $2); } */
 		| OptimizableStmt ';'
 				{ $$ = lcons($1, NIL); }
 		;
@@ -2426,17 +2440,23 @@ OptimizableStmt:  SelectStmt
  *
  *****************************************************************************/
 
-InsertStmt:  INSERT INTO relation_name opt_column_list insert_rest
+/***S*I***/
+/* This rule used 'opt_column_list' between 'relation_name' and 'insert_rest'
+ * originally. When the second rule of 'insert_rest' was changed to use
+ * the new 'SelectStmt' rule (for INTERSECT and EXCEPT) it produced a shift/reduce
+ * conflict. So I just changed the rules 'InsertStmt' and 'insert_rest' to accept
+ * the same statements without any shift/reduce conflicts */
+InsertStmt:  INSERT INTO relation_name  insert_rest
 				{
-					$5->relname = $3;
-					$5->cols = $4;
-					$$ = (Node *)$5;
+ 					$4->relname = $3;
+					$$ = (Node *)$4;
 				}
 		;
 
 insert_rest:  VALUES '(' res_target_list2 ')'
 				{
 					$$ = makeNode(InsertStmt);
+					$$->cols = NULL;
 					$$->unique = NULL;
 					$$->targetList = $3;
 					$$->fromClause = NIL;
@@ -2455,20 +2475,57 @@ insert_rest:  VALUES '(' res_target_list2 ')'
 					$$->groupClause = NIL;
 					$$->havingClause = NULL;
 					$$->unionClause = NIL;
+					/***S*I***/
+				 	$$->intersectClause = NIL;
 				}
-		| SELECT opt_unique res_target_list2
-			 from_clause where_clause
-			 group_clause having_clause
-			 union_clause
+		/***S*I***/
+		/* We want the full power of SelectStatements including INTERSECT and EXCEPT
+                 * for insertion */
+		| SelectStmt
 				{
+					SelectStmt *n;
+
+					n = (SelectStmt *)$1;
 					$$ = makeNode(InsertStmt);
-					$$->unique = $2;
-					$$->targetList = $3;
-					$$->fromClause = $4;
-					$$->whereClause = $5;
-					$$->groupClause = $6;
-					$$->havingClause = $7;
-					$$->unionClause = $8;
+					$$->cols = NULL;
+					$$->unique = n->unique;
+					$$->targetList = n->targetList;
+					$$->fromClause = n->fromClause;
+					$$->whereClause = n->whereClause;
+					$$->groupClause = n->groupClause;
+					$$->havingClause = n->havingClause;
+					$$->unionClause = n->unionClause;
+					$$->intersectClause = n->intersectClause;
+				}
+		| '(' columnList ')' VALUES '(' res_target_list2 ')'
+				{
+					$$ = makeNode(InsertStmt);
+					$$->cols = $2;
+					$$->unique = NULL;
+					$$->targetList = $6;
+					$$->fromClause = NIL;
+					$$->whereClause = NULL;
+					$$->groupClause = NIL;
+					$$->havingClause = NULL;
+					$$->unionClause = NIL; 
+					/***S*I***/
+				 	$$->intersectClause = NIL;
+				}
+		| '(' columnList ')' SelectStmt
+				{
+					SelectStmt *n;
+
+					n = (SelectStmt *)$4;
+					$$ = makeNode(InsertStmt);
+					$$->cols = $2;
+					$$->unique = n->unique;
+					$$->targetList = n->targetList;
+					$$->fromClause = n->fromClause;
+					$$->whereClause = n->whereClause;
+					$$->groupClause = n->groupClause;
+					$$->havingClause = n->havingClause;
+					$$->unionClause = n->unionClause;
+					$$->intersectClause = n->intersectClause;
 				}
 		;
 
@@ -2610,18 +2667,15 @@ UpdateStmt:  UPDATE relation_name
  *				CURSOR STATEMENTS
  *
  *****************************************************************************/
-CursorStmt:  DECLARE name opt_cursor CURSOR FOR
- 			 SELECT opt_unique res_target_list2
-			 from_clause where_clause
-			 group_clause having_clause
-			 union_clause sort_clause
-			 cursor_clause
-				{
-					SelectStmt *n = makeNode(SelectStmt);
-
-					/* from PORTAL name */
-					/*
-					 *	15 august 1991 -- since 3.0 postgres does locking
+/***S*I***/
+CursorStmt:  DECLARE name opt_cursor CURSOR FOR SelectStmt cursor_clause
+  				{
+ 					SelectStmt *n;
+  
+ 					n= (SelectStmt *)$6;
+  					/* from PORTAL name */
+  					/*
+  					 *	15 august 1991 -- since 3.0 postgres does locking
 					 *	right, we discovered that portals were violating
 					 *	locking protocol.  portal locks cannot span xacts.
 					 *	as a short-term fix, we installed the check here.
@@ -2632,14 +2686,6 @@ CursorStmt:  DECLARE name opt_cursor CURSOR FOR
 
 					n->portalname = $2;
 					n->binary = $3;
-					n->unique = $7;
-					n->targetList = $8;
-					n->fromClause = $9;
-					n->whereClause = $10;
-					n->groupClause = $11;
-					n->havingClause = $12;
-					n->unionClause = $13;
-					n->sortClause = $14;
 					$$ = (Node *)n;
 				}
 		;
@@ -2675,88 +2721,164 @@ opt_of:  OF columnList
  *				SELECT STATEMENTS
  *
  *****************************************************************************/
+/***S*I***/
+/* The new 'SelectStmt' rule adapted for the optional use of INTERSECT EXCEPT and UNION
+ * accepts the use of '(' and ')' to select an order of set operations.
+ * 
+ * The rule returns a SelectStmt Node having the set operations attached to 
+ * unionClause and intersectClause (NIL if no set operations were present) */ 
+SelectStmt:	  select_w_o_sort sort_clause for_update_clause
+			{
+				/* There were no set operations, so just attach the sortClause */
+				if IsA($1, SelectStmt)
+				{
+				  SelectStmt *n = (SelectStmt *)$1;
+  				  n->sortClause = $2;
+				  n->forUpdate = $3;
+				  $$ = (Node *)n;
+                }
+				/* There were set operations: The root of the operator tree
+				 * is delivered by $1 but we cannot hand back an A_Expr Node.
+				 * So we search for the leftmost 'SelectStmt' in the operator
+				 * tree $1 (which is the first Select Statement in the query 
+				 * typed in by the user or where ever it came from). 
+				 * 
+				 * Then we attach the whole operator tree to 'intersectClause', 
+				 * and a list of all 'SelectStmt' Nodes to 'unionClause' and 
+				 * hand back the leftmost 'SelectStmt' Node. (We do it this way
+				 * because the following functions (e.g. parse_analyze etc.)
+				 * excpect a SelectStmt node and not an operator tree! The whole
+				 * tree attached to 'intersectClause' won't be touched by 
+				 * parse_analyze() etc. until the function 
+				 * Except_Intersect_Rewrite() (in rewriteHandler.c) which performs
+				 * the necessary steps to be able create a plan!) */
+				else
+				{
+				  List *select_list = NIL;
+				  SelectStmt *first_select;
+				  Node *op = (Node *) $1;
+				  bool intersect_present = FALSE, unionall_present = FALSE;
+
+				  /* Take the operator tree as an argument and 
+				   * create a list of all SelectStmt Nodes found in the tree.
+				   *
+				   * If one of the SelectStmt Nodes has the 'unionall' flag
+				   * set to true the 'unionall_present' flag is also set to
+				   * true */
+				  create_select_list((Node *)op, &select_list, &unionall_present);
+
+				  /* Replace all the A_Expr Nodes in the operator tree by
+				   * Expr Nodes.
+				   *
+				   * If an INTERSECT or an EXCEPT is present, the 
+				   * 'intersect_present' flag is set to true */
+				  op = A_Expr_to_Expr(op, &intersect_present);
+
+				  /* If both flags are set to true we have a UNION ALL
+				   * statement mixed up with INTERSECT or EXCEPT 
+				   * which can not be handled at the moment */
+				  if (intersect_present && unionall_present)
+				  {
+				  	elog(ERROR,"UNION ALL not allowed in mixed set operations!");
+				  }
+
+				  /* Get the leftmost SeletStmt Node (which automatically
+				   * represents the first Select Statement of the query!) */
+				  first_select = (SelectStmt *)lfirst(select_list);
+
+				  /* Attach the list of all SeletStmt Nodes to unionClause */
+				  first_select->unionClause = select_list;
+
+				  /* Attach the whole operator tree to intersectClause */
+				  first_select->intersectClause = (List *) op;
+
+				  /* finally attach the sort clause */
+				  first_select->sortClause = $2;
+				  first_select>forUpdate = $3;
+				  $$ = (Node *)first_select;
+				}		
+				if ((SelectStmt *)$$)->forUpdate != NULL)
+				{
+					SelectStmt *n = (SelectStmt *)$1;
+
+					if (n->unionClause != NULL)
+						elog(ERROR, "SELECT FOR UPDATE is not allowed with UNION clause");
+					if (n->unique != NULL)
+						elog(ERROR, "SELECT FOR UPDATE is not allowed with DISTINCT clause");
+					if (n->groupClause != NULL)
+						elog(ERROR, "SELECT FOR UPDATE is not allowed with GROUP BY clause");
+					if (n->havingClause != NULL)
+						elog(ERROR, "SELECT FOR UPDATE is not allowed with HAVING clause");
+				}
+			}
+		;
+
+/***S*I***/ 
+/* This rule parses Select statements including UNION INTERSECT and EXCEPT.
+ * '(' and ')' can be used to specify the order of the operations 
+ * (UNION EXCEPT INTERSECT). Without the use of '(' and ')' we want the
+ * operations to be left associative.
+ *
+ *  The sort_clause is not handled here!
+ * 
+ * The rule builds up an operator tree using A_Expr Nodes. AND Nodes represent
+ * INTERSECTs OR Nodes represent UNIONs and AND NOT nodes represent EXCEPTs. 
+ * The SelectStatements to be connected are the left and right arguments to
+ * the A_Expr Nodes.
+ * If no set operations show up in the query the tree consists only of one
+ * SelectStmt Node */
+select_w_o_sort: '(' select_w_o_sort ')'
+			{
+				$$ = $2; 
+			}
+		| SubSelect
+			{
+				$$ = $1; 
+			}
+		| select_w_o_sort EXCEPT select_w_o_sort
+			{
+				$$ = (Node *)makeA_Expr(AND,NULL,$1,
+							makeA_Expr(NOT,NULL,NULL,$3));
+			}
+		| select_w_o_sort UNION opt_union select_w_o_sort
+			{	
+				if (IsA($4, SelectStmt))
+				  {
+				     SelectStmt *n = (SelectStmt *)$4;
+				     n->unionall = $3;
+				  }
+				$$ = (Node *)makeA_Expr(OR,NULL,$1,$4);
+			}
+		| select_w_o_sort INTERSECT select_w_o_sort
+			{
+				$$ = (Node *)makeA_Expr(AND,NULL,$1,$3);
+			}
+		; 
 
-SelectStmt:  SELECT opt_unique res_target_list2
+/***S*I***/
+SubSelect:	SELECT opt_unique res_target_list2
 			 result from_clause where_clause
 			 group_clause having_clause
-			 union_clause sort_clause for_update_clause
 				{
 					SelectStmt *n = makeNode(SelectStmt);
 					n->unique = $2;
+					n->unionall = FALSE;
 					n->targetList = $3;
+					/***S*I***/
+					/* This is new: Subselects support the INTO clause
+					 * which allows queries that are not part of the
+					 * SQL92 standard and should not be formulated!
+					 * We need it for INTERSECT and EXCEPT and I did not
+					 * want to create a new rule 'SubSelect1' including the
+					 * feature. If it makes troubles we will have to add 
+					 * a new rule and change this to prevent INTOs in 
+					 * Subselects again */ 
 					n->into = $4;
+
 					n->fromClause = $5;
 					n->whereClause = $6;
 					n->groupClause = $7;
 					n->havingClause = $8;
-					n->unionClause = $9;
-					n->sortClause = $10;
-					n->forUpdate = $11;
-					if (n->forUpdate != NULL)
-					{
-						if (n->unionClause != NULL)
-							elog(ERROR, "SELECT FOR UPDATE is not allowed with UNION clause");
-						if (n->unique != NULL)
-							elog(ERROR, "SELECT FOR UPDATE is not allowed with DISTINCT clause");
-						if (n->groupClause != NULL)
-							elog(ERROR, "SELECT FOR UPDATE is not allowed with GROUP BY clause");
-						if (n->havingClause != NULL)
-							elog(ERROR, "SELECT FOR UPDATE is not allowed with HAVING clause");
-					}
-					else
-						$$ = (Node *)n;
-				}
-		;
-
-SubSelect:  SELECT opt_unique res_target_list2
-			 from_clause where_clause
-			 group_clause having_clause
-			 union_clause
-				{
-					SelectStmt *n = makeNode(SelectStmt);
-					n->unique = $2;
-					n->targetList = $3;
-					n->fromClause = $4;
-					n->whereClause = $5;
-					n->groupClause = $6;
-					n->havingClause = $7;
-					n->unionClause = $8;
-					$$ = (Node *)n;
-				}
-		;
-
-union_clause:  UNION opt_union select_list
-				{
-					SelectStmt *n = (SelectStmt *)lfirst($3);
-					n->unionall = $2;
-					$$ = $3;
-				}
-		| /*EMPTY*/
-				{ $$ = NIL; }
-		;
-
-select_list:  select_list UNION opt_union SubUnion
-				{
-					SelectStmt *n = (SelectStmt *)$4;
-					n->unionall = $3;
-					$$ = lappend($1, $4);
-				}
-		| SubUnion
-				{ $$ = lcons($1, NIL); }
-		;
-
-SubUnion:	SELECT opt_unique res_target_list2
-			 from_clause where_clause
-			 group_clause having_clause
-				{
-					SelectStmt *n = makeNode(SelectStmt);
-					n->unique = $2;
-					n->unionall = FALSE;
-					n->targetList = $3;
-					n->fromClause = $4;
-					n->whereClause = $5;
-					n->groupClause = $6;
-					n->havingClause = $7;
 					$$ = (Node *)n;
 				}
 		;
diff --git a/src/backend/parser/keywords.c b/src/backend/parser/keywords.c
index e6da4f20a04441c03711bed2e191cc1ddc0bd79f..b7a9d003dfb1ea3160affbc938c02ed921ae0478 100644
--- a/src/backend/parser/keywords.c
+++ b/src/backend/parser/keywords.c
@@ -7,7 +7,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/parser/keywords.c,v 1.50 1998/12/18 09:10:34 vadim Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/parser/keywords.c,v 1.51 1999/01/18 00:09:53 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -93,6 +93,9 @@ static ScanKeyword ScanKeywords[] = {
 	{"else", ELSE},
 	{"encoding", ENCODING},
 	{"end", END_TRANS},
+	/***S*I***/
+	{"except", EXCEPT},
+
 	{"execute", EXECUTE},
 	{"exists", EXISTS},
 	{"explain", EXPLAIN},
@@ -120,6 +123,9 @@ static ScanKeyword ScanKeywords[] = {
 	{"insensitive", INSENSITIVE},
 	{"insert", INSERT},
 	{"instead", INSTEAD},
+	/***S*I***/
+	{"intersect", INTERSECT},
+
 	{"interval", INTERVAL},
 	{"into", INTO},
 	{"is", IS},
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index a8997d5a8ab33089bbd09a8726b9179eeffdf5d6..b931bc744fe816d2ce0a958f01cfaf6fc6632afb 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -6,7 +6,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteHandler.c,v 1.27 1998/12/14 00:02:16 thomas Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteHandler.c,v 1.28 1999/01/18 00:09:54 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -24,6 +24,13 @@
 #include "parser/parse_relation.h"
 #include "nodes/parsenodes.h"
 
+/***S*I***/
+#include "parser/parse_node.h"
+#include "parser/parse_target.h"
+
+#include "parser/analyze.h"
+#include "optimizer/prep.h"
+
 #include "rewrite/rewriteSupport.h"
 #include "rewrite/rewriteHandler.h"
 #include "rewrite/rewriteManip.h"
@@ -1661,7 +1668,8 @@ apply_RIR_view(Node **nodePtr, int rt_index, RangeTblEntry *rte, List *tlist, in
 
 		case T_SubLink:
 			{
-				SubLink	*sub = (SubLink *)node;
+				SubLink *sub = (SubLink *)node;
+				List *tmp_lefthand, *tmp_oper;
 
 				apply_RIR_view(
 						(Node **)(&(sub->lefthand)),
@@ -1678,6 +1686,15 @@ apply_RIR_view(Node **nodePtr, int rt_index, RangeTblEntry *rte, List *tlist, in
 						tlist,
 						modified,
 						sublevels_up + 1);
+
+				/***S*I***/
+				tmp_lefthand = sub->lefthand;				
+				foreach(tmp_oper, sub->oper)
+				  {				    
+				    lfirst(((Expr *) lfirst(tmp_oper))->args) = 
+				      lfirst(tmp_lefthand);
+				    tmp_lefthand = lnext(tmp_lefthand);
+				  }								
 			}
 			break;
 
@@ -2614,8 +2631,387 @@ QueryRewrite(Query *parsetree)
 		query = (Query *)lfirst(l);
 		results = lappend(results, fireRIRrules(query));
 	}
-
 	return results;
 }
+/***S*I***/
+/* This function takes two targetlists as arguments and checks if the targetlists are compatible
+ * (i.e. both select for the same number of attributes and the types are compatible 
+ */
+void check_targetlists_are_compatible(List *prev_target, List *current_target)
+{
+  List *next_target;
+  
+  if (length(prev_target) != 
+      length(current_target))
+    elog(ERROR,"Each UNION | EXCEPT | INTERSECT query must have the same number of columns.");		      
+  foreach(next_target, current_target)
+    {
+      Oid			itype;
+      Oid			otype;
+      
+      otype = ((TargetEntry *) lfirst(prev_target))->resdom->restype;
+      itype = ((TargetEntry *) lfirst(next_target))->resdom->restype;
+	      
+      /* one or both is a NULL column? then don't convert... */
+      if (otype == InvalidOid)
+	{
+	  /* propagate a known type forward, if available */
+	  if (itype != InvalidOid)
+	    ((TargetEntry *) lfirst(prev_target))->resdom->restype = itype;
+#if FALSE
+	  else
+	    {
+	      ((TargetEntry *) lfirst(prev_target))->resdom->restype = UNKNOWNOID;
+	      ((TargetEntry *) lfirst(next_target))->resdom->restype = UNKNOWNOID;
+	    }
+#endif
+	}
+      else if (itype == InvalidOid)
+	{
+	}
+      /* they don't match in type? then convert... */
+      else if (itype != otype)
+	{
+	  Node	   *expr;
+	  
+	  expr = ((TargetEntry *) lfirst(next_target))->expr;
+	  expr = CoerceTargetExpr(NULL, expr, itype, otype);
+	  if (expr == NULL)
+	    {
+	      elog(ERROR, "Unable to transform %s to %s"
+		   "\n\tEach UNION | EXCEPT | INTERSECT clause must have compatible target types",
+		   typeidTypeName(itype),
+		   typeidTypeName(otype));
+	    }
+	  ((TargetEntry *) lfirst(next_target))->expr = expr;
+	  ((TargetEntry *) lfirst(next_target))->resdom->restype = otype;
+	}
+	      
+      /* both are UNKNOWN? then evaluate as text... */
+      else if (itype == UNKNOWNOID)
+	{
+	  ((TargetEntry *) lfirst(next_target))->resdom->restype = TEXTOID;
+	  ((TargetEntry *) lfirst(prev_target))->resdom->restype = TEXTOID;
+	}
+      prev_target = lnext(prev_target);
+    }
+}
+
+/***S*I***/
+/* Rewrites UNION INTERSECT and EXCEPT queries to semantiacally equivalent
+ * queries that use IN and NOT IN subselects. 
+ * 
+ * The operator tree is attached to 'intersectClause' (see rule
+ * 'SelectStmt' in gram.y) of the 'parsetree' given as an
+ * argument. First we remember some clauses (the sortClause, the
+ * unique flag etc.)  Then we translate the operator tree to DNF
+ * (disjunctive normal form) by 'cnfify'. (Note that 'cnfify' produces
+ * CNF but as we exchanged ANDs with ORs in function A_Expr_to_Expr()
+ * earlier we get DNF after exchanging ANDs and ORs again in the
+ * result.) Now we create a new query by evaluating the new operator
+ * tree which is in DNF now. For every AND we create an entry in the
+ * union list and for every OR we create an IN subselect. (NOT IN
+ * subselects are created for OR NOT nodes). The first entry of the
+ * union list is handed back but before that the remembered clauses
+ * (sortClause etc) are attached to the new top Node (Note that the 
+ * new top Node can differ from the parsetree given as argument because of
+ * the translation to DNF. That's why we have to remember the sortClause or
+ * unique flag!) */
+Query *
+Except_Intersect_Rewrite (Query *parsetree)
+{
+ 
+  SubLink *n;
+  Query	  *result, *intersect_node;
+  List	  *elist, *intersect_list = NIL, *intersect, *intersectClause;
+  List	  *union_list = NIL, *sortClause;  
+  List	  *left_expr, *right_expr, *resnames = NIL;
+  char	  *op, *uniqueFlag, *into;
+  bool	  isBinary, isPortal;  
+  CmdType commandType = CMD_SELECT;
+  List	  *rtable_insert = NIL;  
+
+  List	  *prev_target = NIL;
+
+  /* Remember the Resnames of the given parsetree's targetlist
+   * (these are the resnames of the first Select Statement of 
+   * the query formulated by the user and he wants the columns
+   * named by these strings. The transformation to DNF can
+   * cause another Select Statment to be the top one which
+   * uses other names for its columns. Therefore we remeber
+   * the original names and attach them to the targetlist
+   * of the new topmost Node at the end of this function */
+  foreach(elist, parsetree->targetList)
+    {
+      TargetEntry *tent = (TargetEntry *)lfirst(elist);
+      
+      resnames = lappend(resnames, tent->resdom->resname);	
+    }
+  
+  /* If the Statement is an INSERT INTO ... (SELECT...) statement
+   * using UNIONs, INTERSECTs or EXCEPTs and the transformation
+   * to DNF makes another Node to the top node we have to transform
+   * the new top node to an INSERT node and the original INSERT node
+   * to a SELECT node */
+  if (parsetree->commandType == CMD_INSERT) 
+    {
+      parsetree->commandType = CMD_SELECT;
+      commandType = CMD_INSERT;
+      parsetree->resultRelation	 = 0;
+      
+      /* The result relation ( = the one to insert into) has to be
+       * attached to the rtable list of the new top node */
+      rtable_insert = nth(length(parsetree->rtable) - 1, parsetree->rtable);	 
+    }
+    
+  /* Save some items, to be able to attach them to the resulting top node
+   * at the end of the function */
+  sortClause = parsetree->sortClause;
+  uniqueFlag = parsetree->uniqueFlag;
+  into = parsetree->into;
+  isBinary = parsetree->isBinary;
+  isPortal = parsetree->isPortal;  
+  
+  /* The operator tree attached to parsetree->intersectClause is still 'raw'
+   * ( = the leaf nodes are still SelectStmt nodes instead of Query nodes)
+   * So step through the tree and transform the nodes using parse_analyze().
+   *
+   * The parsetree (given as an argument to
+   * Except_Intersect_Rewrite()) has already been transformed and
+   * transforming it again would cause troubles.  So we give the 'raw'
+   * version (of the cooked parsetree) to the function to
+   * prevent an additional transformation. Instead we hand back the
+   * 'cooked' version also given as an argument to
+   * intersect_tree_analyze() */
+  intersectClause = 
+    (List *)intersect_tree_analyze((Node *)parsetree->intersectClause, 
+				   (Node *)lfirst(parsetree->unionClause),
+				   (Node *)parsetree);
+  
+  /* intersectClause is no longer needed so set it to NIL */
+  parsetree->intersectClause = NIL;  
+  /* unionClause will be needed later on but the list it delivered
+   * is no longer needed, so set it to NIL */
+  parsetree->unionClause = NIL;	 
+  
+  /* Transform the operator tree to DNF (remember ANDs and ORs have been exchanged,
+   * that's why we get DNF by using cnfify) 
+   * 
+   * After the call, explicit ANDs are removed and all AND operands
+   * are simply items in the intersectClause list */
+  intersectClause = cnfify((Expr *)intersectClause, true);
+ 
+  /* For every entry of the intersectClause list we generate one entry in 
+   * the union_list */
+  foreach(intersect, intersectClause)
+    {	   
+      /* for every OR we create an IN subselect and for every OR NOT
+       * we create a NOT IN subselect, so first extract all the Select
+       * Query nodes from the tree (that contains only OR or OR NOTs
+       * any more because we did a transformation to DNF 
+       *
+       * There must be at least one node that is not negated
+       * (i.e. just OR and not OR NOT) and this node will be the first
+       * in the list returned */
+      intersect_list = NIL; 
+      create_list((Node *)lfirst(intersect), &intersect_list);
+      
+      /* This one will become the Select Query node, all other
+       * nodes are transformed into subselects under this node! */
+      intersect_node = (Query *)lfirst(intersect_list);
+      intersect_list = lnext(intersect_list);
+      
+      /* Check if all Select Statements use the same number of attributes and
+       * if all corresponding attributes are of the same type */
+      if (prev_target)
+	check_targetlists_are_compatible(prev_target, intersect_node->targetList);	     
+      prev_target = intersect_node->targetList;	 
+      /* End of check for corresponding targetlists */
+      
+      /* Transform all nodes remaining into subselects and add them to
+       * the qualifications of the Select Query node */
+      while(intersect_list != NIL) { 
+	
+	n = makeNode(SubLink);
+	
+	/* Here we got an OR so transform it to an IN subselect */
+	if(IsA(lfirst(intersect_list), Query)) 
+	  {	      
+	    /* Check if all Select Statements use the same number of attributes and
+	     * if all corresponding attributes are of the same type */
+	    check_targetlists_are_compatible(prev_target, 
+                                  ((Query *)lfirst(intersect_list))->targetList);  
+	    /* End of check for corresponding targetlists */
+	    
+	    n->subselect = lfirst(intersect_list);
+	    op = "=";	   
+	    n->subLinkType = ANY_SUBLINK;  
+	    n->useor = false;
+	  }
+	/* Here we got an OR NOT node so transform it to a NOT IN  subselect */
+	else 
+	  {
+	    /* Check if all Select Statements use the same number of attributes and
+	     * if all corresponding attributes are of the same type */
+	    check_targetlists_are_compatible(prev_target,
+                   ((Query *)lfirst(((Expr *)lfirst(intersect_list))->args))->targetList);
+	    /* End of check for corresponding targetlists */
+	    
+	    n->subselect = (Node *)lfirst(((Expr *)lfirst(intersect_list))->args);
+	    op = "<>";    
+	    n->subLinkType = ALL_SUBLINK;  
+	    n->useor = true;
+	  }
+	
+	/* Prepare the lefthand side of the Sublinks: All the entries of the
+	 * targetlist must be (IN) or must not be (NOT IN) the subselect */
+	foreach(elist, intersect_node->targetList)
+	  {
+	    Node	  *expr = lfirst(elist);
+	    TargetEntry *tent = (TargetEntry *)expr;
+	    
+	    n->lefthand = lappend(n->lefthand, tent->expr);	  
+	  }
+	
+	/* The first arguments of oper also have to be created for the
+	 * sublink (they are the same as the lefthand side!) */
+	left_expr = n->lefthand;
+	right_expr = ((Query *)(n->subselect))->targetList;
+	
+	foreach(elist, left_expr)
+	  {
+	    Node	   *lexpr = lfirst(elist);
+	    Node	   *rexpr = lfirst(right_expr);
+	    TargetEntry *tent = (TargetEntry *) rexpr;
+	    Expr	   *op_expr;
+	    
+	    op_expr = make_op(op, lexpr, tent->expr);
+	    
+	    n->oper = lappend(n->oper, op_expr);
+	    right_expr = lnext(right_expr);
+	  }
+	
+	/* If the Select Query node has aggregates in use
+	 * add all the subselects to the HAVING qual else to
+	 * the WHERE qual */
+	if(intersect_node->hasAggs == false) {    
+	  AddQual(intersect_node, (Node *)n);
+	}
+	else {
+	  AddHavingQual(intersect_node, (Node *)n);
+	} 
+	
+	/* Now we got sublinks */
+	intersect_node->hasSubLinks = true;	  
+	intersect_list = lnext(intersect_list);      
+      }      
+      intersect_node->intersectClause = NIL;
+      union_list = lappend(union_list, intersect_node);
+    }
+  
+  /* The first entry to union_list is our new top node */
+  result = (Query *)lfirst(union_list);
+  /* attach the rest to unionClause */
+  result->unionClause = lnext(union_list);  
+  /* Attach all the items remembered in the beginning of the function */
+  result->sortClause = sortClause;  
+  result->uniqueFlag = uniqueFlag;  
+  result->into = into;
+  result->isPortal = isPortal;
+  result->isBinary = isBinary;
+  /* The relation to insert into is attached to the range table
+   * of the new top node */
+  if (commandType == CMD_INSERT)  
+    {	   
+      result->rtable = lappend(result->rtable, rtable_insert);	
+      result->resultRelation = length(result->rtable);
+      result->commandType = commandType;  
+    }  
+  /* The resnames of the originally first SelectStatement are 
+   * attached to the new first SelectStatement */
+  foreach(elist, result->targetList)
+    {
+      TargetEntry *tent = (TargetEntry *)lfirst(elist);
+      
+      tent->resdom->resname = lfirst(resnames);
+      resnames = lnext(resnames);
+    }
+  return  result;  
+}
+
+/* Create a list of nodes that are either Query nodes of NOT Expr
+ * nodes followed by a Query node. The tree given in ptr contains at
+ * least one non negated Query node. This node is attached to the
+ * beginning of the list */
+
+void create_list(Node *ptr, List **intersect_list)
+{
+  List *arg;
+  
+  if(IsA(ptr,Query))
+    {
+      /* The non negated node is attached at the beginning (lcons) */
+      *intersect_list = lcons(ptr, *intersect_list);
+      return;	   
+    }
+  
+  if(IsA(ptr,Expr))
+    {
+      if(((Expr *)ptr)->opType == NOT_EXPR)
+	{
+	  /* negated nodes are appended to the end (lappend) */
+	  *intersect_list = lappend(*intersect_list, ptr);	  
+	  return;	  
+	}
+      else
+	{
+	  foreach(arg, ((Expr *)ptr)->args)
+	    {
+	      create_list(lfirst(arg), intersect_list);
+	    }	  
+	  return;	  
+	}
+      return;	   
+    }
+}
+
+/* The nodes given in 'tree' are still 'raw' so 'cook' them using parse_analyze().
+ * The node given in first_select has already been cooked, so don't transform
+ * it again but return a pointer to the previously cooked version given in 'parsetree' 
+ * instead. */
+Node *intersect_tree_analyze(Node *tree, Node *first_select, Node *parsetree)
+{
+  Node *result = (Node *)NIL;
+  List *arg;
+  
+  if(IsA(tree, SelectStmt))
+    {
+      QueryTreeList *qtree;
+      
+      /* If we get to the tree given in first_select return
+       * parsetree instead of performing parse_analyze() */
+      if(tree == first_select){
+	result = parsetree;
+      }
+      else {	
+	/* transform the 'raw' nodes to 'cooked' Query nodes */ 
+	qtree = parse_analyze(lcons(tree, NIL), NULL);
+	result = (Node *)qtree->qtrees[0];	
+      }
+      
+    }  
+  if(IsA(tree,Expr))
+    {
+      /* Call recursively for every argument of the node */
+      foreach(arg, ((Expr *)tree)->args)
+	{
+	  lfirst(arg) = intersect_tree_analyze(lfirst(arg), first_select, parsetree);
+	}
+      result = tree;	  
+    }
+  return result;  
+}
+
+
 
 
diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c
index 9d6d5e7d64dc79394e4a98a4d05ba5511b1fa89c..c958c7810f6333ff54b4bf99ad215a1fb446b8ef 100644
--- a/src/backend/rewrite/rewriteManip.c
+++ b/src/backend/rewrite/rewriteManip.c
@@ -6,7 +6,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteManip.c,v 1.23 1998/12/14 00:02:17 thomas Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteManip.c,v 1.24 1999/01/18 00:09:56 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -152,7 +152,10 @@ OffsetVarNodes(Node *node, int offset, int sublevels_up)
 		case T_SubLink:
 			{
 				SubLink	*sub = (SubLink *)node;
+				List *tmp_oper, *tmp_lefthand;
 
+				/* We also have to adapt the variables used in sub->lefthand
+				 * and sub->oper */
 				OffsetVarNodes(
 						(Node *)(sub->lefthand),
 						offset,
@@ -162,6 +165,19 @@ OffsetVarNodes(Node *node, int offset, int sublevels_up)
 						(Node *)(sub->subselect),
 						offset,
 						sublevels_up + 1);
+
+				/***S*I***/
+				/* Make sure the first argument of sub->oper points to the
+				 * same var as sub->lefthand does otherwise we will
+				 * run into troubles using aggregates (aggno will not be
+				 * set correctly) */
+				tmp_lefthand = sub->lefthand;				
+				foreach(tmp_oper, sub->oper)
+				  {				    
+				    lfirst(((Expr *) lfirst(tmp_oper))->args) = 
+				      lfirst(tmp_lefthand);
+				    tmp_lefthand = lnext(tmp_lefthand);
+				  }	
 			}
 			break;
 
@@ -364,7 +380,8 @@ ChangeVarNodes(Node *node, int rt_index, int new_index, int sublevels_up)
 		case T_SubLink:
 			{
 				SubLink	*sub = (SubLink *)node;
-
+				List *tmp_oper, *tmp_lefthand;
+				
 				ChangeVarNodes(
 						(Node *)(sub->lefthand),
 						rt_index,
@@ -376,6 +393,19 @@ ChangeVarNodes(Node *node, int rt_index, int new_index, int sublevels_up)
 						rt_index,
 						new_index,
 						sublevels_up + 1);
+				
+				/***S*I***/
+				/* Make sure the first argument of sub->oper points to the
+				 * same var as sub->lefthand does otherwise we will
+				 * run into troubles using aggregates (aggno will not be
+				 * set correctly) */
+				tmp_lefthand = sub->lefthand;
+				foreach(tmp_oper, sub->oper)
+				  {				    
+				    lfirst(((Expr *) lfirst(tmp_oper))->args) = 
+				      lfirst(tmp_lefthand);
+				    tmp_lefthand = lnext(tmp_lefthand);
+				  }	
 			}
 			break;
 
@@ -465,7 +495,10 @@ AddQual(Query *parsetree, Node *qual)
 	if (qual == NULL)
 		return;
 
-	copy = copyObject(qual);
+	/***S*I***/
+	/* copy = copyObject(qual); */
+	copy = qual;
+
 	old = parsetree->qual;
 	if (old == NULL)
 		parsetree->qual = copy;
@@ -485,7 +518,10 @@ AddHavingQual(Query *parsetree, Node *havingQual)
 	if (havingQual == NULL)
 		return;
 
-	copy = copyObject(havingQual);
+	/***S*I***/
+	copy = havingQual;
+	/* copy = copyObject(havingQual); */
+
 	old = parsetree->havingQual;
 	if (old == NULL)
 		parsetree->havingQual = copy;
@@ -494,6 +530,20 @@ AddHavingQual(Query *parsetree, Node *havingQual)
 			(Node *) make_andclause(makeList(parsetree->havingQual, copy, -1));
 }
 
+void
+AddNotHavingQual(Query *parsetree, Node *havingQual)
+{
+	Node	   *copy;
+
+	if (havingQual == NULL)
+		return;
+
+	/***S*I***/
+	/* copy = (Node *)make_notclause( (Expr *)copyObject(havingQual)); */
+	copy = (Node *) make_notclause((Expr *)havingQual);
+
+	AddHavingQual(parsetree, copy);
+}
 
 void
 AddNotQual(Query *parsetree, Node *qual)
@@ -503,7 +553,9 @@ AddNotQual(Query *parsetree, Node *qual)
 	if (qual == NULL)
 		return;
 
-	copy = (Node *) make_notclause(copyObject(qual));
+	/***S*I***/
+	/* copy = (Node *) make_notclause((Expr *)copyObject(qual)); */
+	copy = (Node *) make_notclause((Expr *)qual);
 
 	AddQual(parsetree, copy);
 }
@@ -835,7 +887,7 @@ HandleRIRAttributeRule(Query *parsetree,
 							   rt_index, attr_num, modified, badsql, 0);
 }
 
-
+#ifdef NOT_USED
 static void
 nodeHandleViewRule(Node **nodePtr,
 				   List *rtable,
@@ -976,10 +1028,19 @@ nodeHandleViewRule(Node **nodePtr,
 			{
 				SubLink    *sublink = (SubLink *) node;
 				Query	   *query = (Query *) sublink->subselect;
+				List *tmp_lefthand, *tmp_oper;
+				
 
 				nodeHandleViewRule((Node **) &(query->qual), rtable, targetlist,
 								   rt_index, modified, sublevels_up + 1);
 
+				/***S*H*D***/
+				nodeHandleViewRule((Node **) &(query->havingQual), rtable, targetlist,
+						   rt_index, modified, sublevels_up + 1);
+				nodeHandleViewRule((Node **) &(query->targetList), rtable, targetlist,
+						   rt_index, modified, sublevels_up + 1);
+
+
 				/*
 				 * We also have to adapt the variables used in
 				 * sublink->lefthand and sublink->oper
@@ -993,10 +1054,17 @@ nodeHandleViewRule(Node **nodePtr,
 				 * will run into troubles using aggregates (aggno will not
 				 * be set correctly
 				 */
-				pfree(lfirst(((Expr *) lfirst(sublink->oper))->args));
-				lfirst(((Expr *) lfirst(sublink->oper))->args) =
-					lfirst(sublink->lefthand);
-			}
+				/* pfree(lfirst(((Expr *) lfirst(sublink->oper))->args)); */
+
+				/***S*I***/
+ 				tmp_lefthand = sublink->lefthand;				
+ 				foreach(tmp_oper, sublink->oper)
+ 				  {				    
+ 				    lfirst(((Expr *) lfirst(tmp_oper))->args) = 
+ 				      lfirst(tmp_lefthand);
+ 				    tmp_lefthand = lnext(tmp_lefthand);
+ 				  }								
+  			}
 			break;
 		default:
 			/* ignore the others */
@@ -1004,7 +1072,6 @@ nodeHandleViewRule(Node **nodePtr,
 	}
 }
 
-#ifdef NOT_USED
 void
 HandleViewRule(Query *parsetree,
 			   List *rtable,
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index eeec7b02259306c07a0c92d7b48f74fd9587bd6a..f50dfee7d67ce491ee3dea386de51e43e453def1 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -7,7 +7,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/tcop/postgres.c,v 1.96 1999/01/17 06:18:42 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/tcop/postgres.c,v 1.97 1999/01/18 00:09:56 momjian Exp $
  *
  * NOTES
  *	  this is the "main" module of the postgres backend and
@@ -448,12 +448,20 @@ pg_parse_and_plan(char *query_string,	/* string to execute */
 
 		querytree = querytree_list->qtrees[i];
 
+ 		/***S*I***/
+ 		/* Rewrite Union, Intersect and Except Queries
+ 		 * to normal Union Queries using IN and NOT IN subselects */
+ 		if(querytree->intersectClause != NIL) 
+ 		  {	  
+ 		    querytree = Except_Intersect_Rewrite(querytree);
+ 		  }
+
 		if (DebugPrintQuery)
 		{
 			if (DebugPrintQuery > 3)
-			{
-				/* Print the query string as is if query debug level > 3 */
-				TPRINTF(TRACE_QUERY, "query: %s", query_string);
+			{			  
+			  /* Print the query string as is if query debug level > 3 */
+			  TPRINTF(TRACE_QUERY, "query: %s", query_string); 
 			}
 			else
 			{
@@ -1527,7 +1535,7 @@ PostgresMain(int argc, char *argv[], int real_argc, char *real_argv[])
 	if (!IsUnderPostmaster)
 	{
 		puts("\nPOSTGRES backend interactive interface ");
-		puts("$Revision: 1.96 $ $Date: 1999/01/17 06:18:42 $\n");
+		puts("$Revision: 1.97 $ $Date: 1999/01/18 00:09:56 $\n");
 	}
 
 	/* ----------------
diff --git a/src/include/executor/nodeGroup.h b/src/include/executor/nodeGroup.h
index 0c23aa02d33ff92daffe9bb3fb00241a0b563c15..e56995fecdf1c3f77519bf21f9fee33fa86cc901 100644
--- a/src/include/executor/nodeGroup.h
+++ b/src/include/executor/nodeGroup.h
@@ -6,7 +6,7 @@
  *
  * Copyright (c) 1994, Regents of the University of California
  *
- * $Id: nodeGroup.h,v 1.7 1998/09/01 04:35:56 momjian Exp $
+ * $Id: nodeGroup.h,v 1.8 1999/01/18 00:10:02 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -22,5 +22,8 @@ extern bool ExecInitGroup(Group *node, EState *estate, Plan *parent);
 extern int	ExecCountSlotsGroup(Group *node);
 extern void ExecEndGroup(Group *node);
 extern void ExecReScanGroup(Group *node, ExprContext *exprCtxt, Plan *parent);
+/***S*I***/
+extern void ExecReScanGroup(Group *node, ExprContext *exprCtxt, Plan *parent);
+
 
 #endif	 /* NODEGROUP_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 41730bf3463e8afab674746e956348ea67ffc8f9..d71d8c6b1e5187516c59547b95e54f4e275041bd 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -6,7 +6,7 @@
  *
  * Copyright (c) 1994, Regents of the University of California
  *
- * $Id: parsenodes.h,v 1.65 1999/01/05 15:45:49 vadim Exp $
+ * $Id: parsenodes.h,v 1.66 1999/01/18 00:10:06 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -58,6 +58,9 @@ typedef struct Query
 								 * BY */
 	Node	   *havingQual;		/* qualification of each group */
 
+        /***S*I***/
+	List	   *intersectClause;	
+
 	List	   *unionClause;	/* unions are linked under the previous
 								 * query */
 	Node	   *limitOffset;	/* # of result tuples to skip */
@@ -605,7 +608,9 @@ typedef struct InsertStmt
 	List	   *groupClause;	/* group by clause */
 	Node	   *havingClause;	/* having conditional-expression */
 	List	   *unionClause;	/* union subselect parameters */
-	bool		unionall;		/* union without unique sort */
+	bool	   unionall;		/* union without unique sort */
+        /***S*I***/
+        List       *intersectClause;  
 } InsertStmt;
 
 /* ----------------------
@@ -646,6 +651,10 @@ typedef struct SelectStmt
 	Node	   *whereClause;	/* qualifications */
 	List	   *groupClause;	/* group by clause */
 	Node	   *havingClause;	/* having conditional-expression */
+        /***S*I***/
+        List       *intersectClause;
+        List       *exceptClause;
+  
 	List	   *unionClause;	/* union subselect parameters */
 	List	   *sortClause;		/* sort clause (a list of SortGroupBy's) */
 	char	   *portalname;		/* the portal (cursor) to create */
diff --git a/src/include/parser/analyze.h b/src/include/parser/analyze.h
index 0c4f838d1355f74a1d8367c639fd524eb4431d0c..4be7637d744003a04489d19a09e7b47960972b1f 100644
--- a/src/include/parser/analyze.h
+++ b/src/include/parser/analyze.h
@@ -5,7 +5,7 @@
  *
  * Copyright (c) 1994, Regents of the University of California
  *
- * $Id: analyze.h,v 1.4 1998/09/01 04:37:25 momjian Exp $
+ * $Id: analyze.h,v 1.5 1999/01/18 00:10:11 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -15,5 +15,8 @@
 #include <parser/parse_node.h>
 
 extern QueryTreeList *parse_analyze(List *pl, ParseState *parentParseState);
+/***S*I***/
+extern void create_select_list(Node *ptr, List **select_list, bool *unionall_present);
+extern Node *A_Expr_to_Expr(Node *ptr, bool *intersect_present);
 
 #endif	 /* ANALYZE_H */
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index ecec766aece8142d87a13b42bce80588c81e13b3..0adf71baabc6a49e29bbd96a0103fc6c4db6ff97 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -6,7 +6,7 @@
  *
  * Copyright (c) 1994, Regents of the University of California
  *
- * $Id: rewriteHandler.h,v 1.6 1998/09/01 04:38:01 momjian Exp $
+ * $Id: rewriteHandler.h,v 1.7 1999/01/18 00:10:12 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -34,5 +34,9 @@ typedef struct _rewrite_meta_knowledge RewriteInfo;
 
 
 extern List *QueryRewrite(Query *parsetree);
-
+/***S*I***/
+extern Query *Except_Intersect_Rewrite(Query *parsetree);
+extern void create_list(Node *ptr, List **intersect_list);
+extern Node *intersect_tree_analyze(Node *tree, Node *first_select, Node *parsetree);
+extern void check_targetlists_are_compatible(List *prev_target, List *current_target);
 #endif	 /* REWRITEHANDLER_H */
diff --git a/src/include/rewrite/rewriteManip.h b/src/include/rewrite/rewriteManip.h
index e1b54829bbc97f9269f9a3074b72b416dc360cb3..dc3724515b5c7aa775bb5ea0b186b7aadc22a81f 100644
--- a/src/include/rewrite/rewriteManip.h
+++ b/src/include/rewrite/rewriteManip.h
@@ -6,7 +6,7 @@
  *
  * Copyright (c) 1994, Regents of the University of California
  *
- * $Id: rewriteManip.h,v 1.11 1998/10/21 16:21:29 momjian Exp $
+ * $Id: rewriteManip.h,v 1.12 1999/01/18 00:10:16 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -25,6 +25,8 @@ void		AddQual(Query *parsetree, Node *qual);
 void		AddHavingQual(Query *parsetree, Node *havingQual);
 
 void		AddNotQual(Query *parsetree, Node *qual);
+void            AddNotHavingQual(Query *parsetree, Node *havingQual);
+
 void		FixNew(RewriteInfo *info, Query *parsetree);
 
 void HandleRIRAttributeRule(Query *parsetree, List *rtable, List *targetlist,
diff --git a/src/include/utils/elog.h b/src/include/utils/elog.h
index 7d74644c620883a84530651fc258d96adfd5ac64..258ef01aa4806f408ae00e9f7c57ec5a2049601d 100644
--- a/src/include/utils/elog.h
+++ b/src/include/utils/elog.h
@@ -6,7 +6,7 @@
  *
  * Copyright (c) 1994, Regents of the University of California
  *
- * $Id: elog.h,v 1.8 1998/09/01 04:39:03 momjian Exp $
+ * $Id: elog.h,v 1.9 1999/01/18 00:10:17 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -29,7 +29,12 @@
 #define ABORTX	0x4000			/* abort process after logging */
 #endif
 
-#define ELOG_MAXLEN 4096
+/***S*I***/
+/* Increase this to be able to use postmaster -d 3 with complex
+ * view definitions (which are transformed to very, very large INSERT statements
+ * and if -d 3 is used the query string of these statements is printed using
+ * vsprintf which expects enough memory reserved! */
+#define ELOG_MAXLEN 12288
 
 
 /* uncomment the following if you want your elog's to be timestamped */