/*
 *  Routine takes the 'union' parsetree, messes around with it, 
 *  strips the union relations, and returns
 *  a list of orthogonal plans.
 *
 *  NOTE: Current implementation of versions disallows schema changes 
 *        between versions.  Thus we may assume that the attributes
 *        remain the same between versions and their deltas.
 *
 * $Header: /private/postgres/src/planner/prep/RCS/handleunion.c,v 1.12 1991/11/18 17:29:14 mer Exp $
 */

#include "tmp/c.h"

#include "nodes/nodes.h"
#include "nodes/pg_lisp.h"
#include "nodes/execnodes.h"
#include "nodes/plannodes.h"
#include "nodes/plannodes.a.h"
#include "nodes/primnodes.h"
#include "nodes/primnodes.a.h"
#include "nodes/relation.h"
#include "nodes/relation.a.h"

#include "parser/parse.h"
#include "parser/parsetree.h"
#include "tmp/utilities.h"
#include "utils/log.h"
#include "utils/lsyscache.h"

#include "planner/internal.h"
#include "planner/plancat.h"
#include "planner/planner.h"
#include "planner/prepunion.h"
#include "planner/clauses.h"
#include "planner/handleunion.h"
#include "planner/semanopt.h"
/* #include "planner/cfi.h" */

/* #define LispRemove remove  */

List
handleunion (root,rangetable, tlist, qual)
     List root, rangetable, tlist, qual;
{

  List new_root = LispNil;
  List temp_tlist = LispNil;
  List temp_qual = LispNil;
  List i = LispNil;
  List tlist_qual = LispNil;
  List rt_entry = LispNil;
  List temp = LispNil;
  List new_parsetree = LispNil;
  List union_plans = LispNil;
  int planno = 0;
  List tmp_uplans = LispNil;
  List firstplan = LispNil;
  List secondplan = LispNil;
  int num_plans = 0;

  /*
   * SplitTlistQual returns a list of the form:
   * ((tlist1 qual1) (tlist2 qual2) ...) 
   */

  /*
   *  First remove the union flag from the rangetable entries
   *  since we are processing them now.
   */

  foreach(i,rangetable) {
    rt_entry = CAR(i);
    if (member(lispAtom("union"),rt_flags(rt_entry))) {
      rt_flags(rt_entry) = LispRemove(lispAtom("union"),
				      rt_flags(rt_entry));
    }
  }

  tlist_qual = SplitTlistQual (root,rangetable,tlist,qual);

#ifdef VERBOSE
  num_plans = length(tlist_qual);
  printf("####################\n");
  printf("number of plans generated: %d\n", num_plans);
  printf("####################\n");
#endif
  
  foreach (i, tlist_qual) {
    temp = CAR(i);
    new_parsetree = lispCons(root,lispCons(CAR(temp),
					   lispCons(CADR(temp),
						    LispNil) ));
    union_plans = nappend1(union_plans,
			   (LispValue)planner(new_parsetree));

  }

  /*
   * testing:
   */
  
/*  union_plans = CDR(union_plans);  */

#ifdef SWAP_PLANS
  firstplan = CAR(union_plans);
  secondplan = CADR(union_plans);

  CAR(union_plans) = secondplan;
  CADR(union_plans) = firstplan;
#endif
/*
  foreach(i, union_plans) {
    planno += 1;
    tmp_uplans = nappend1(tmp_uplans, CAR(i));
  }
*/

return(union_plans);  

}


/*
 *  Given a 'union'ed tlist and qual, this routine splits them
 *  up into separate othrogonal (tlist qual) pairs.
 *  For example, 
 *  tlist:  (emp | emp1).name
 *  qual:   (emp | emp1). salary < 5000
 *  will be split into:
 *  ( ((emp.name) (emp.salary < 5000)) ((emp1.name) (emp1.salary < 5000)) )
 */


List
SplitTlistQual(root,rangetable,tlist, qual)
     List root,rangetable,tlist, qual;
{
  List i = LispNil;
  List tqlist = LispNil;
  List  temp = LispNil;
  List tl_list = LispNil;
  List qual_list = LispNil;
  List unionlist = LispNil;
  List mod_qual = LispNil;
  List varlist = LispNil;
  List is_redundent = LispNil;

  unionlist = collect_union_sets(tlist,qual);
#ifdef VERBOSE
  printf("##########################\n");
  printf("Union sets are: \n");
  lispDisplay(unionlist,0);
  printf("\n");
  fflush(stdout);
  printf("##########################\n");
#endif
  /*
   *  If query is a delete, the tlist is null.
   */
  if (CAtom(root_command_type_atom(root)) == DELETE) 
    foreach(i,unionlist) 
      foreach(temp,CAR(i))
	tl_list = nappend1(tl_list, LispNil);
  else {
    tl_list = lispCons(tlist, LispNil);
    foreach(i, unionlist)   /*  This loop effectively handles union joins */
      tl_list = SplitTlist(CAR(i),tl_list);
  }
  qual_list = lispCons(qual,LispNil);
  foreach(i,unionlist) {
    qual_list = SplitQual(CAR(i),qual_list); 
  }

/*
 *  The assumption here is that both the tl_list and the qual_list, 
 *  must be of the same length, if not, something funky has happened.
 *
 *  XXX I think this is not true in the case of 'DELETEs' ! sp.
 *  XXX In this case, 'tl_list' is nil.
 */
  
  if (CAtom(root_command_type_atom(root)) != DELETE)
      if (length (tl_list) != length(qual_list))
	elog (WARN, "UNION:  tlist with missing qual, or vice versa");

    if(null(tl_list)) {
      /*
       * this is a special case, where tl_list is null (i.e. this
       * is a DELETE command.
       * DISCLAIMER: I don't know what this code is doing, I don't
       * know what it is supposed to do, all I know is that before it
       * didn't work in queries like:
       * "delete foobar from f in (foo | bar) where foobar.a = 1"
       * and now it works (provided that you made a couple of prayers
       * and/or sacrifices to the gods....).
       * sp._
       */
      temp = NULL;
      varlist = find_allvars(root,rangetable, temp, CAR(qual_list));
      is_redundent = LispNil;
      mod_qual = SemantOpt(varlist,rangetable,CAR(qual_list),&is_redundent,1);
      if (is_redundent != LispTrue) {
        mod_qual = SemantOpt2(rangetable,mod_qual,mod_qual,temp);
        tqlist = nappend1(tqlist,
      		    lispCons(temp, 
      			     lispCons(mod_qual,
      				      LispNil)));
      }
      qual_list = CDR(qual_list);
    } else {
      foreach(i, tl_list) {
	temp = CAR(i);

	/*
	 *  Varlist contains the list of varnos of relations that participate
	 *  in the current query.  It is used to detect existential clauses.
	 *  Initially, varlist contains the list of target relations (varnos).
	 *  This should only be done once for each query.
	 */

	varlist = find_allvars(root,rangetable, temp, CAR(qual_list));
	is_redundent = LispNil;
	mod_qual = SemantOpt(varlist,rangetable,CAR(qual_list),&is_redundent,1);
	if (is_redundent != LispTrue) {
	  mod_qual = SemantOpt2(rangetable,mod_qual,mod_qual,temp);
	  tqlist = nappend1(tqlist,
			    lispCons(temp, 
				     lispCons(mod_qual,
					      LispNil)));
	}
      qual_list = CDR(qual_list);
      }
    }

  return (tqlist);

}



/*
 * SplitTlist
 * returns a list of the form (tlist1 tlist2 ...)
 */

List
SplitTlist (unionlist,tlists)
     List unionlist;
     List tlists;
{
  List i = LispNil;
  List j = LispNil;
  List x = LispNil;
  List tlist = LispNil;
  List t = LispNil;
  List varnum = LispNil;
  List new_tlist ;
  List tle = LispNil;
  List varlist = LispNil;
  Var varnode = (Var)NULL;
  List tl_list = LispNil;
  List flatten_list = LispNil;

  foreach(t, tlists) {
    tlist = CAR(t);
    foreach (i, unionlist) {
      varnum = CAR(i);
      new_tlist = LispNil;
      new_tlist = copy_seq_tree(tlist);

      foreach (x, new_tlist) {
	tle = CAR(x);
	varlist = tl_expr(tle);
	if (IsA(varlist,Const) || IsA(varlist,Var))
	  flatten_list = LispNil;
	else
	  if (is_clause(varlist)) 
	    split_tlexpr(CDR(varlist),varnum);
	  else
	    if (CAtom(CAR(varlist)) == UNION) {
	      flatten_list = flatten_union_list(CDR(varlist));
	      foreach (j, flatten_list) {
		varnode = (Var)CAR(j);
		/*
		 * Find matching entry, and change the tlist to it.
		 */

		if (CInteger(varnum) == get_varno(varnode)) {
		  tl_expr(tle) = (List)varnode;
		  break;
		}
	      }
	    } /* for, varlist */
	    
      } /* for, new_tlist*/
      
      /*
       *  At this point, the new_tlist is an orthogonal targetlist, without
       *  any union relations.
       */
    
      if (new_tlist == NULL) 
	elog(WARN, "UNION: resulting tlist is empty");
    
      tl_list = nappend1(tl_list,new_tlist);
    }
  } /*tlists */
  return(tl_list);

}

/*
 * This routine is called to split the tlist whenever
 *  there is an expression in the targetlist.
 * Routine returns nothing as it modifies the tlist in place.
 */

void
split_tlexpr(clauses,varnum)
     List clauses;
     List varnum;
{
  List x = LispNil;
  List ulist = LispNil;
  List clause = LispNil;
  List flat_list = LispNil;
  Var varnode = (Var)NULL;
  List i = LispNil;

  foreach (x,clauses) {
    clause = CAR(x);
    if (IsA(clause,Const) || 
	IsA(clause,Var) )
      flat_list = LispNil;
    else
      if (is_clause(clause))
	split_tlexpr(CDR(clause), varnum);
      else
	if (consp(clause) &&
	    CAtom(CAR(clause)) == UNION) {
	  flat_list = flatten_union_list(CDR(clause));
	  foreach(i,flat_list) {
	    varnode = (Var)CAR(i);
	    if (CInteger(varnum) == get_varno(varnode)) {
	      CAR(clause) = (List)varnode;
	      break;
	    }
	  }
	}
  }
}

/*
 *  collect_union_sets
 *  runs through the tlist and qual, and forms a list of 
 *  unions sets in the query.  
 *  If there is > 1 union set, we are dealing with a union join.
 */

List 
collect_union_sets(tlist,qual)
     List tlist;
     List qual;
{
  List i = LispNil;
  List x = LispNil;
  List j = LispNil;
  int varno = 0;
  List tle = LispNil;
  List varlist = LispNil;
  List current_union_set = LispNil;
  List union_sets = LispNil;
  List qual_union_sets = LispNil;
  List flattened_ulist = LispNil;

/*
 *  First we run through the targetlist and collect all union sets
 *  there.
 */

  foreach(i, tlist) {
    tle = CAR(i);
    varlist = tl_expr(tle);
    current_union_set = LispNil;

    if (is_clause(varlist)) { /* if it is an expression */
      flattened_ulist = collect_tlist_uset(CDR(varlist));
      foreach (x, flattened_ulist) 
	current_union_set = nappend1(current_union_set,
				     lispInteger(get_varno((Var)CAR(x))) );
    } else
      if (consp(varlist) &&
	  CAtom(CAR(varlist)) == UNION) {
	flattened_ulist = flatten_union_list(CDR(varlist));
	foreach (x, flattened_ulist) 
	  current_union_set = nappend1(current_union_set,
				       lispInteger(get_varno((Var)CAR(x))) );

      }
    union_sets = nappend1(union_sets,
			  current_union_set);

  }
  union_sets = remove_subsets(union_sets);

/*
 *  Now we run through the qualifications to collect 
 *  union sets.
 */
  
  qual_union_sets = find_qual_union_sets(qual);
  
  if (qual_union_sets != LispNil)
    foreach(i, qual_union_sets)
      union_sets = nappend1(union_sets, CAR(i));

  if (length(union_sets) > 1)
    union_sets = remove_subsets(union_sets);

  return(union_sets);
}

/*
 *  This routine is called whenever there is a 
 *  an expression in the targetlist.
 *  Returns a flattened ulist if found.
 */

List collect_tlist_uset(args)
     List args;
{
  List retlist = LispNil;
  List x = LispNil;
  List i = LispNil;
  List current_clause = LispNil;
  List ulist = LispNil;
  
  foreach (x, args) {
    current_clause = CAR(x);
    if (IsA(current_clause,Const))
      retlist = LispNil;
    else
      if (is_clause(current_clause))
	retlist = collect_tlist_uset(CDR(current_clause));
      else
	if (consp(current_clause) &&
	    CAtom(CAR(current_clause)) == UNION) {
	  retlist = flatten_union_list(CDR(current_clause));
	}
	else
	  retlist = LispNil;   /* If it is just a varnode. */

    foreach(i,retlist)
      if (CAR(i) != LispNil)
	ulist = nappend1(ulist,CAR(i));
  }

  return(ulist);
}
List
find_qual_union_sets(qual)
     List qual;
{
  List leftop = LispNil;
  List rightop = LispNil;
  List i = LispNil;
  List j = LispNil;
  List union_sets = LispNil;
  List current_union_set = LispNil;
  List qual_uset = LispNil;
  List flattened_ulist = LispNil;

  if (null(qual))
    return(LispNil);
  else
    if (is_clause(qual)) {
      leftop = (List) get_leftop(qual);
      rightop = (List) get_rightop(qual);

      if (consp(leftop) && CAtom(CAR(leftop)) == UNION) {
	current_union_set = LispNil;
	flattened_ulist = flatten_union_list(CDR(leftop));
	foreach(i, flattened_ulist) 
	  current_union_set = nappend1(current_union_set,
				       lispInteger(get_varno((Var)CAR(i))) );
	union_sets = nappend1(union_sets,
			      current_union_set);
      }    
      if (consp(rightop) && CAtom(CAR(rightop)) == UNION) {
	current_union_set = LispNil;
	flattened_ulist = flatten_union_list(CDR(rightop));
	foreach(i, flattened_ulist) 
	  current_union_set = nappend1(current_union_set,
				       lispInteger(get_varno((Var)CAR(i))) );
	union_sets = nappend1(union_sets,
			      current_union_set);
      }
    } else
      if (and_clause(qual)) {
	foreach(j,get_andclauseargs(qual)) {
	  qual_uset = find_qual_union_sets(CAR(j));
	  if (qual_uset != LispNil) 
	    foreach(i,qual_uset) 
	      union_sets = nappend1(union_sets,
				    CAR(i));
	}
      } else
	if (or_clause(qual)) {
	  foreach(j,get_orclauseargs(qual)) {
	    qual_uset = find_qual_union_sets(CAR(j));
	    if (qual_uset != LispNil)
	      foreach(i,qual_uset)
		union_sets = nappend1(union_sets,
				      CAR(i));
	  }
	} else
	  if (not_clause(qual)) {
	    qual_uset = find_qual_union_sets(CDR(qual));
	    if (qual_uset != LispNil)
	      foreach(i,qual_uset)
		union_sets = nappend1(union_sets,
				      CAR(i));
	  }

  return(union_sets);

}

List
flatten_union_list(ulist)
     List ulist;
{
  List retlist = LispNil;
  List i = LispNil;
  List tmp_var = LispNil;
  List tmplist = LispNil;

  foreach(i,ulist) {
    tmp_var = CAR(i);
    if (consp(tmp_var) && CAtom(CAR(tmp_var)) == UNION)
      tmplist = flatten_union_list(CDR(tmp_var));
    else
      retlist = nappend1(retlist, tmp_var);
  }

  if (tmplist)
    foreach(i, tmplist)
      retlist= nappend1(retlist, CAR(i));

  return(retlist);
  
}

/*
 *  remove_subsets
 *  Routine finds and removes any subsets in the given list.
 *  Returns the modified list.
 *  Thus given ( (A B C) (B C) (D E F) (D F) ), routine returns:
 *  ( (A B C) (D E F) )
 */

List 
remove_subsets(usets)
     List usets;
{
  List retlist = LispNil;
  List i = LispNil;
  List j = LispNil;
  List k = LispNil;
  List uset1 = LispNil;
  List uset2 = LispNil;
  int is_subset = 1;

  foreach(i, usets) {
    uset1 = CAR(i);
    foreach(j, CDR(i)) {
      uset2 = CAR(j);
      if (uset2 == LispNil)
	break;


      if (length (uset1) >= length(uset2) &&
	  length(uset2) != 1) {
	foreach(k, uset2) 
	  if (!member(CAR(k),uset1)) {
	    is_subset = 0;
	    break;
	  }
	if (is_subset) 
	  CAR(j) = lispCons(lispInteger(1), LispNil);
      } /* uset1 > uset2 */
      else 
	if (length(uset1) < length(uset2) &&
	    length(uset1) != 1) {
	  foreach(k,uset1) 
	    if (!member(CAR(k), uset2)) {
	      is_subset = 0;
	      break;
	    }
	  if (is_subset) 
	    CAR(i)= lispCons(lispInteger(1),LispNil);
	}
      is_subset = 1;
    } /* inner loop usets */
  } /* outer loop usets */
  
  foreach (i, usets) 
    if (length (CAR(i)) > 1)
      retlist = nappend1(retlist, CAR(i) );

  return(retlist);

}

/*
 *  SplitQual:
 *
 *  Given a list of union rels, this routine splits the qualification
 *  up accordingly.  
 *  NOTE:  Only union rels in the qualification will be split.
 *  ( I believe that utimately, we want to split union rels by position. )
 *
 *  Input:  (uvar1 uvar2 ...) (unioned qual)
 *  Output: (qual1 qual2 ...)
 */

List 
SplitQual (ulist, uquals)
     List ulist;
     List uquals;
{
  List i = LispNil;
  List x = LispNil;
  List uqual = LispNil;
  List uvarno = LispNil;
  List qual_list = LispNil;
  List currentlist = LispNil;
  List new_qual = LispNil;

  foreach(x, uquals) {
    uqual = CAR(x);
    if (uqual == LispNil) {
      foreach(i, ulist) 
	qual_list = nappend1(qual_list, LispNil);
      return(qual_list);
    }
    foreach(i, ulist) {
      uvarno = CAR(i);
      currentlist = nappend1(currentlist, uvarno);
      new_qual = copy_seq_tree(uqual);
      new_qual = find_matching_union_qual(currentlist, new_qual);
      qual_list = nappend1(qual_list, new_qual);
      currentlist = LispNil;
    }
  }
  return(qual_list);

}

/*
 * find_matching_union_qual
 *
 * Given a union qual, and a union relation, returns 
 * the qualifications that apply to it.
 */

List 
find_matching_union_qual(ulist, qual)
     List ulist, qual;
{
  List leftop = LispNil;
  List rightop = LispNil;
  List op = LispNil;
  List matching_clauseargs = LispNil;
  List retqual = LispNil;
  List i = LispNil;
  List tmp_list = LispNil;

  if (null(qual))
    return(LispNil);
  else
    if (is_clause(qual)) {
      leftop = (List)get_leftop(qual);
      rightop = (List)get_rightop(qual);
      op = get_op(qual);
      leftop = find_matching_union_qual(ulist,leftop);
      rightop = find_matching_union_qual(ulist,rightop);
      match_union_clause(ulist, &leftop, &rightop);
      retqual = make_opclause((Oper)op,(Var)leftop,(Var)rightop);

      /*
       * if the clause does not belong to the current union relation,
       * remove it from the qualification.
       */

      /*
     if (matching_clauseargs == LispNil)
	remove(clause, clauses);  * NOTE: may have to mod. qual *
      else
	clauseargs = matching_clauseargs; 
      */
    }
  else
    if (and_clause (qual)) {
      foreach(i, get_andclauseargs(qual)) {
	tmp_list = nappend1(tmp_list, find_matching_union_qual(ulist, 
							     CAR(i)));
      }
      retqual = make_andclause(tmp_list);
    }
    else
      if (or_clause (qual)) {
	foreach(i, get_orclauseargs(qual)) {
	  tmp_list = nappend1(tmp_list, find_matching_union_qual(ulist, 
								 CAR(i)));
	}
	retqual = make_orclause(tmp_list);
      }
      else
	if (not_clause (qual)) {
	  retqual = find_matching_union_qual(ulist, 
					     get_notclausearg(qual));
	  retqual = make_notclause(retqual);
	}
	else
	  retqual = qual;
    
  return(retqual);
}
/*
 *  match_union_clauses:
 *
 *  Given a list of *2* clause args, (leftarg rightarg), 
 *  it picks out the clauses within unions that
 *  are relevant to the particular union relation being scanned.
 *
 *  returns nothing. It modifies the qual in place.
 */

void
match_union_clause (unionlist, leftarg, rightarg)
     List unionlist, *leftarg, *rightarg;
{
  List i = LispNil;
  List x = LispNil;
  List varlist = LispNil;
  Var uvarnode = (Var)NULL;
  List varlist2 = LispNil;

  /*
   *  If leftarg is a union var.
   */
  if (consp(*leftarg) && CAtom(CAR(*leftarg)) == UNION) {
    varlist = flatten_union_list(CDR(*leftarg));
/*    varlist = CDR(*leftarg); */
    foreach(i,varlist) {
      uvarnode = (Var)CAR(i);
      if (member(lispInteger(get_varno(uvarnode)), unionlist)) {
	*leftarg = (List)uvarnode;
	break;
      }
    }
  }
  
  /*
   *  If rightarg is a union var.
   */
  if (consp(*rightarg) && CAtom(CAR(*rightarg)) == UNION) {
    varlist = flatten_union_list(CDR(*rightarg));
/*    varlist = CDR(*rightarg); */
    foreach(i,varlist) {
      uvarnode = (Var)CAR(i);
      if (member(lispInteger(get_varno(uvarnode)), unionlist)) {
	*rightarg = (List)uvarnode;
	break;
      }
    }
  }

}
#ifdef NOT_USED
/*
 *  Routine is not used.
 *  find_union_vars
 *  runs through the rangetable, and forms a list of all the 
 *  relations that are unioned. It will also remove the union
 *  flag from the rangetable entries after it is done processing
 *  them.
 *  REturns a list of varnos.
 */

List
find_union_vars (rangetable)
     List rangetable;
{
  
  List i = LispNil;
  List unionlist = LispNil;
  List rt_entry = LispNil;
  Index uvarno = 0;

  /*
   * XXX what about the case when there are 2 emps in the rangetable, and
   * both are union relations?
   * Currently, both would appear in the unionlist.
   */

  foreach( i, rangetable) {
    rt_entry = CAR(i);
    uvarno += 1;
    if (member(lispAtom("union"),rt_flags(rt_entry))) {
      unionlist = nappend1(unionlist, lispInteger(uvarno));
      rt_flags(rt_entry) = LispRemove(lispAtom("union"), 
				  rt_flags(rt_entry)); 
    }     
  }
  return(unionlist);
}
#endif
