/*
 *      FILE
 *     	planner
 *     
 *      DESCRIPTION
 *     	The query optimizer external interface.
 *     
 */

/* RcsId ("$Header: /private/postgres/src/planner/plan/RCS/planner.c,v 1.32 1992/08/10 21:44:13 mer Exp $");  */

/*     
 *      EXPORTS
 *     		planner
 */

#include "tmp/c.h"

#include "nodes/pg_lisp.h"
#include "nodes/relation.h"
#include "nodes/relation.a.h"

#include "parser/parse.h"

#include "planner/internal.h"
#include "planner/planner.h"

#include "catalog/pg_type.h"	/* for pg_checkretval() */

#include "utils/log.h"

/* ----------------
 *	Existential creator declaration
 * ----------------
 */
extern Existential RMakeExistential();

/*    
 *    	*** Module loading ***
 *    
 */

#include "planner/cfi.h"   
/*   C function interface routines */
/* #include "pppp.h" */
/*   POSTGRES plan pretty printer */

/* #include "storeplan.h" */

/*   plan i/o routines */

/*    
 *    	PLANNER INITIALIZATION
 *    
 */

/*     ...Query tree preprocessing
 */
#include "planner/prepqual.h"

/*   for rule queries */
#include "planner/preptlist.h"

/*   normal targetlist preprocessing */
#include "planner/prepunion.h"

/*   for union (archive, inheritance, ...) queries */

/*    
 *    	PLAN CREATION
 *    
 */

/*     ...Planner driver routines
 */
#include "planner/planmain.h"

/*     ...Subplanner initialization
 */
#include "planner/initsplan.h"

/*     ...Query plan generation
 */
#include "planner/createplan.h"

/*   routines to create optimal subplan */
#include "planner/setrefs.h"

/*   routines to set vars to reference other nodes */
/* #include "planner/sortresult.h" */

/*   routines to manipulate sort keys */

/*    
 *    	SUBPLAN GENERATION
 *    
 */
#include "planner/allpaths.h"

/*   main routines to generate subplans */
#include "planner/indxpath.h"

/*   main routines to generate index paths */
#include "planner/orindxpath.h"
#include "planner/prune.h" 

/*   routines to prune the path space */
#include "planner/joinpath.h"

/*   main routines to create join paths */
#include "planner/joinrels.h"

/*   routines to determine which relations to join */
#include "planner/joinutils.h"

/*   generic join method key/clause routines */
#include "planner/mergeutils.h"

/*   routines to deal with merge keys and clauses */
#include "planner/hashutils.h"

/*   routines to deal with hash keys and clauses */

/*    
 *    	SUBPLAN SELECTION - utilities for planner, create-plan, join routines
 *    
 */
#include "planner/clausesel.h"

/*   routines to compute clause selectivities */
#include "planner/costsize.h"

/*   routines to compute costs and sizes */

/*    
 *    	DATA STRUCTURE CREATION/MANIPULATION ROUTINES
 *    
 */
#include "nodes/relation.h"
#include "planner/clauseinfo.h"
#include "planner/indexnode.h"
#include "planner/joininfo.h"
#include "planner/keys.h"
#include "planner/ordering.h"
#include "planner/pathnode.h"
#include "planner/clause.h"
#include "planner/relnode.h"
#include "planner/tlist.h"
#include "planner/var.h"

extern bool DebugPrintPlan;

/*    
 *    	*** Query optimizer entry point ***
 *    
 */

/*    
 *    	planner
 *    
 *    	Main query optimizer routine.
 *    
 *    	Invokes the planner on union queries if there are any left,
 *    	recursing if necessary to get them all, then processes normal plans.
 *    
 *    	Returns a query plan.
 *    
 */

/*  .. make-rule-plans, plan-union-query, process-rules    */

Plan
planner (parse)
     LispValue parse ;
{
    LispValue root = parse_root (parse);
    LispValue tlist = parse_targetlist (parse);
    LispValue qual = parse_qualification (parse);
    LispValue rangetable = root_rangetable (root);
    LispValue commandType = root_command_type_atom (root);
    LispValue uniqueflag = root_uniqueflag(root);
    LispValue sortclause = root_sortclause(root);
    LispValue sortkeys = LispNil;
    LispValue sortops = LispNil;
    Plan special_plans = (Plan)NULL;
    Plan regular_plans = (Plan)NULL;
    LispValue flag = LispNil;
    List plan_list = LispNil;

    plan_list = lispCons(lispAtom("inherits"),
		   lispCons(lispAtom("union"),
			    lispCons(lispAtom("archive"),LispNil)));
    foreach (flag,plan_list) {
	Index rt_index = first_matching_rt_entry (rangetable,CAR(flag));
	if ( rt_index != -1 )
	  special_plans = (Plan)plan_union_queries (rt_index,
						    CAtom(CAR(flag)),
						    root,
						    tlist,
						    qual,rangetable);
    }

    /*
     *  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 be moved into the guts of the planner s.t. user specified 
     *  sorts will be considered as part of the planning process. 
     *  Since we can only make use of user-specified sorts in
     *  special cases, we can do the optimization step later.
     *   
     *  sortkeys:    a list of resdom nodes corresponding to the attributes in
     *               the tlist that are to be sorted.
     *  sortops:     a corresponding list of sort operators.               
     */

    if (uniqueflag || sortclause) {
	sortkeys = CADR(sortclause);
	sortops = CADDR(sortclause);
    }

    if (special_plans) {
	if (uniqueflag) {
	    Plan sortplan = make_sortplan(tlist,sortkeys,
					  sortops,special_plans);
	    return((Plan)make_unique(tlist,sortplan));
	}
	else 
	  if (sortclause) 
	      return(make_sortplan(tlist,sortkeys,sortops,special_plans));
	  else 
	    return((Plan)special_plans);
    }
    else {  
	regular_plans = init_query_planner(root,tlist,qual);
	
	if (uniqueflag) {
	    Plan sortplan = make_sortplan(tlist,sortkeys,
					  sortops,regular_plans);
	    return((Plan)make_unique(tlist,sortplan));
	}
	else
	  if (sortclause)
	    return(make_sortplan(tlist,sortkeys,sortops,regular_plans)); 
	  else
	    return(regular_plans);
    }
    
} /* function end  */

/*
 *      make_sortplan
 *   
 *      Returns a sortplan which is basically a SORT node attached to the
 *      top of the plan returned from the planner.  It also adds the 
 *      cost of sorting into the plan.
 *      
 *      sortkeys: ( resdom1 resdom2 resdom3 ...)
 *      sortops:  (sortop1 sortop2 sortop3 ...)
 */

Plan
make_sortplan(tlist,sortkeys,sortops,plannode)
     List tlist;
     List sortkeys;
     List sortops;
     Plan plannode;

{
  Plan sortplan = (Plan)NULL;
  List temp_tlist = LispNil;
  LispValue i = LispNil;
  Resdom resnode = (Resdom)NULL;
  Resdom resdom = (Resdom)NULL;
  int keyno =1;

  /*  First make a copy of the tlist so that we don't corrupt the 
   *  the original .
   */
  
  temp_tlist = new_unsorted_tlist(tlist);
  
  foreach (i, sortkeys) {
      
      resnode = (Resdom)CAR(i);
      resdom = tlist_resdom(temp_tlist,resnode);

      /*    Order the resdom keys and replace the operator OID for each 
       *    key with the regproc OID. 
       */
      
      set_reskey (resdom,keyno);
      set_reskeyop (resdom,(OperatorTupleForm)get_opcode (CInteger(CAR(sortops))));      

      keyno += 1;
      sortops = CDR(sortops);
  }

  sortplan = (Plan) make_sort(temp_tlist,
			      _TEMP_RELATION_ID_,
			      (Plan)plannode,
			      length(sortkeys));

  /*
   *  XXX Assuming that an internal sort has no. cost. 
   *      This is wrong, but given that at this point, we don't
   *      know the no. of tuples returned, etc, we can't do
   *      better than to add a constant cost.
   *      This will be fixed once we move the sort further into the planner,
   *      but for now ... functionality....
   */

  set_cost(sortplan, get_cost(plannode));
  
  return(sortplan);
}


/*    
 *    	init-query-planner
 *    
 *    	Deals with all non-union preprocessing, including existential
 *    	qualifications and CNFifying the qualifications.
 *    
 *    	Returns a query plan.
 *	MODIFIES: tlist,qual
 *    
 */

/*  .. plan-union-queries, planner    */

Plan
init_query_planner (root,tlist,qual)
     LispValue root,tlist,qual ;
{
     LispValue primary_qual;
     LispValue existential_qual;
     Existential exist_plan;

     _query_max_level_ = root_numlevels (root);
     _query_command_type_ = (int) root_command_type (root);
     _query_result_relation_ = root_result_relation (root);
     _query_range_table_ = root_rangetable (root);
     
     tlist = preprocess_targetlist (tlist,
				    _query_command_type_,
				    _query_result_relation_,
				    _query_range_table_);

     qual = preprocess_qualification (qual,tlist);

     if ( DebugPrintPlan ) {
	 printf("after preprocessing, qual is :\n");
	 lispDisplay(qual);
	 printf("\n");
	 fflush(stdout);
     }

     if (qual) {
	 primary_qual = CAR(qual);
	 existential_qual = CADR(qual);
     } else {
	 primary_qual = LispNil;
	 existential_qual = LispNil;
     }

     if(lispNullp (existential_qual)) {
	 return(query_planner(_query_command_type_,
			      tlist,
			      primary_qual,
			      1,_query_max_level_));

     } else {

	 char *globals;
	 int temp = _query_command_type_;
	 Plan existential_plan;

	 /* with saved globals */
	 globals = save_globals();

	 _query_command_type_ = RETRIEVE;
	 existential_plan = query_planner(temp,LispNil,existential_qual,1,
					  _query_max_level_);
     
	 exist_plan = make_existential (existential_plan,
					query_planner (_query_command_type_,
						       tlist,primary_qual,
						       1,
						       _query_max_level_));

	 restore_globals(globals);
	 return((Plan)exist_plan);
}

 }  /* function end  */

/* 
 * 	make_existential
 *	( NB: no corresponding lisp code )
 *
 *	Instantiates an existential plan node and fills in 
 *	the left and right subtree slots.
 */

Existential
make_existential(left,right)
     Plan left,right;
{
    Existential node = RMakeExistential();

    set_lefttree(node,(PlanPtr)left);
    set_righttree(node,(PlanPtr)right);
    return(node);
}

/*
 *  pg_checkretval() -- check return value of a list of postquel parse
 *			trees.
 *
 *	The return value of a postquel function is the value returned by
 *	the final query in the function.  We do some ad-hoc define-time
 *	type checking here to be sure that the user is returning the
 *	type he claims.
 */

void
pg_checkretval(rettype, parselist)
    ObjectId rettype;
    List parselist;
{
    LispValue parse;
    LispValue tlist;
    LispValue tle;
    LispValue root;
    LispValue rt;
    LispValue rte;
    int cmd;
    char *typ;
    Resdom resnode;
    LispValue thenode;
    Relation reln;
    ObjectId relid;
    ObjectId tletype;
    int relnatts;
    int i;

    /* find the final query */
    while (CDR(parselist) != LispNil)
	parselist = CDR(parselist);

    parse = CAR(parselist);

    /*
     *  test 1:  if the last query is a utility invocation, then there
     *  had better not be a return value declared.
     */

    if (atom(CAR(parse))) {
	if (rettype == InvalidObjectId)
	    return;
	else
	    elog(WARN, "return type mismatch in function decl: final query is a catalog utility");
    }

    /* okay, it's an ordinary query */
    root = parse_root(parse);
    tlist = parse_targetlist(parse);
    rt = root_rangetable(root);
    cmd = (int) root_command_type(root);

    /*
     *  test 2:  if the function is declared to return no value, then the
     *  final query had better not be a retrieve.
     */

    if (rettype == InvalidObjectId) {
	if (cmd == RETRIEVE)
	    elog(WARN, "function declared with no return type, but final query is a retrieve");
	else
	    return;
    }

    /* by here, the function is declared to return some type */
    if ((typ = (char *) get_id_type(rettype)) == (char *) NULL)
	elog(WARN, "can't find return type %d for function\n", rettype);

    /*
     *  test 3:  if the function is declared to return a value, then the
     *  final query had better be a retrieve.
     */

    if (cmd != RETRIEVE)
	elog(WARN, "function declared to return type %s, but final query is not a retrieve", tname(typ));

    /*
     *  test 4:  for base type returns, the target list should have exactly
     *  one entry, and its type should agree with what the user declared.
     */

    if (get_typrelid(typ) == InvalidObjectId) {
	if (ExecTargetListLength(tlist) > 1)
	    elog(WARN, "function declared to return %s returns multiple values in final retrieve", tname(typ));

	resnode = (Resdom) CAR(CAR(tlist));
	if (get_restype(resnode) != rettype)
	    elog(WARN, "return type mismatch in function: declared to return %s, returns %s", tname(typ), tname(get_id_type(get_restype(resnode))));

	/* by here, base return types match */
	return;
    }

    /*
     *  If the target list is of length 1, and the type of the varnode
     *  in the target list is the same as the declared return type, this
     *  is okay.  This can happen, for example, where the body of the
     *  function is 'retrieve (x = func2())', where func2 has the same
     *  return type as the function that's calling it.
     */

    if (ExecTargetListLength(tlist) == 1) {
	resnode = (Resdom) CAR(CAR(tlist));
	if (get_restype(resnode) == rettype)
	    return;
    }

    /*
     *  By here, the procedure returns a (set of) tuples.  This part of
     *  the typechecking is a hack.  We look up the relation that is
     *  the declared return type, and be sure that attributes 1 .. n
     *  in the target list match the declared types.
     */

    reln = heap_open(get_typrelid(typ));

    if (!RelationIsValid(reln))
	elog(WARN, "cannot open relation relid %d", get_typrelid(typ));

    relid = reln->rd_id;
    relnatts = reln->rd_rel->relnatts;

    if (ExecTargetListLength(tlist) != relnatts)
	elog(WARN, "function declared to return type %s does not retrieve (%s.all)", tname(typ), tname(typ));

    /* expect attributes 1 .. n in order */
    for (i = 1; i <= relnatts; i++) {
	tle = CAR(tlist);
	tlist = CDR(tlist);
	thenode = CADR(tle);

	/* this is tedious */
	if (IsA(thenode,Var))
	    tletype = (ObjectId) get_vartype((Var)thenode);
	else if (IsA(thenode,Const))
	    tletype = (ObjectId) get_consttype((Const)thenode);
	else if (IsA(thenode,Param))
	    tletype = (ObjectId) get_paramtype((Param)thenode);
	else if (IsA(thenode,LispList)) {
	    thenode = CAR(thenode);
	    if (IsA(thenode,Oper))
		tletype = (ObjectId) get_opresulttype((Oper)thenode);
	    else if (IsA(thenode,Func))
		tletype = (ObjectId) get_functype((Func)thenode);
	    else
		elog(WARN, "function declared to return type %s does not retrieve (%s.all)", tname(typ), tname(typ));
	} else
	    elog(WARN, "function declared to return type %s does not retrieve (%s.all)", tname(typ), tname(typ));

	/* reach right in there, why don't you? */
	if (tletype != reln->rd_att.data[i-1]->atttypid)
	    elog(WARN, "function declared to return type %s does not retrieve (%s.all)", tname(typ), tname(typ));
    }

    heap_close(reln);

    /* success */
    return;
}
