/* Copyright (C) 2002 MySQL AB

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */

#ifdef __GNUC__
#pragma implementation
#endif

#include "mysql_priv.h"
#include "sql_acl.h"
#include "sp_head.h"
#include "sp.h"
#include "sp_pcontext.h"
#include "sp_rcontext.h"

Item_result
sp_map_result_type(enum enum_field_types type)
{
  switch (type)
  {
  case MYSQL_TYPE_TINY:
  case MYSQL_TYPE_SHORT:
  case MYSQL_TYPE_LONG:
  case MYSQL_TYPE_LONGLONG:
  case MYSQL_TYPE_INT24:
    return INT_RESULT;
  case MYSQL_TYPE_DECIMAL:
  case MYSQL_TYPE_FLOAT:
  case MYSQL_TYPE_DOUBLE:
    return REAL_RESULT;
  default:
    return STRING_RESULT;
  }
}

/*
 * Returns TRUE if the 'cmd' is a command that might result in
 * multiple result sets being sent back.
 * Note: This does not include SQLCOM_SELECT which is treated
 *       separately in sql_yacc.yy.
 */
bool
sp_multi_results_command(enum enum_sql_command cmd)
{
  switch (cmd) {
  case SQLCOM_ANALYZE:
  case SQLCOM_HA_READ:
  case SQLCOM_SHOW_BINLOGS:
  case SQLCOM_SHOW_BINLOG_EVENTS:
  case SQLCOM_SHOW_CHARSETS:
  case SQLCOM_SHOW_COLLATIONS:
  case SQLCOM_SHOW_COLUMN_TYPES:
  case SQLCOM_SHOW_CREATE:
  case SQLCOM_SHOW_CREATE_DB:
  case SQLCOM_SHOW_CREATE_FUNC:
  case SQLCOM_SHOW_CREATE_PROC:
  case SQLCOM_SHOW_DATABASES:
  case SQLCOM_SHOW_ERRORS:
  case SQLCOM_SHOW_FIELDS:
  case SQLCOM_SHOW_GRANTS:
  case SQLCOM_SHOW_INNODB_STATUS:
  case SQLCOM_SHOW_KEYS:
  case SQLCOM_SHOW_LOGS:
  case SQLCOM_SHOW_MASTER_STAT:
  case SQLCOM_SHOW_NEW_MASTER:
  case SQLCOM_SHOW_OPEN_TABLES:
  case SQLCOM_SHOW_PRIVILEGES:
  case SQLCOM_SHOW_PROCESSLIST:
  case SQLCOM_SHOW_SLAVE_HOSTS:
  case SQLCOM_SHOW_SLAVE_STAT:
  case SQLCOM_SHOW_STATUS:
  case SQLCOM_SHOW_STATUS_FUNC:
  case SQLCOM_SHOW_STATUS_PROC:
  case SQLCOM_SHOW_STORAGE_ENGINES:
  case SQLCOM_SHOW_TABLES:
  case SQLCOM_SHOW_VARIABLES:
  case SQLCOM_SHOW_WARNS:
    return TRUE;
  default:
    return FALSE;
  }
}

/* Evaluate a (presumed) func item. Always returns an item, the parameter
** if nothing else.
*/
Item *
sp_eval_func_item(THD *thd, Item *it, enum enum_field_types type)
{
  DBUG_ENTER("sp_eval_func_item");
  it= it->this_item();
  DBUG_PRINT("info", ("type: %d", type));

  if (!it->fixed && it->fix_fields(thd, 0, &it))
  {
    DBUG_PRINT("info", ("fix_fields() failed"));
    DBUG_RETURN(NULL);
  }

  /* QQ How do we do this? Is there some better way? */
  if (type == MYSQL_TYPE_NULL)
    it= new Item_null();
  else
  {
    switch (sp_map_result_type(type)) {
    case INT_RESULT:
      {
	longlong i= it->val_int();

	if (it->null_value)
	{
	  DBUG_PRINT("info", ("INT_RESULT: null"));
	  it= new Item_null();
	}
	else
	{
	  DBUG_PRINT("info", ("INT_RESULT: %d", i));
	  it= new Item_int(it->val_int());
	}
	break;
      }
    case REAL_RESULT:
      {
	double d= it->val();

	if (it->null_value)
	{
	  DBUG_PRINT("info", ("REAL_RESULT: null"));
	  it= new Item_null();
	}
	else
	{
	  /* There's some difference between Item::new_item() and the
	   * constructor; the former crashes, the latter works... weird. */
	  uint8 decimals= it->decimals;
	  uint32 max_length= it->max_length;
	  DBUG_PRINT("info", ("REAL_RESULT: %g", d));
	  it= new Item_real(it->val());
	  it->decimals= decimals;
	  it->max_length= max_length;
	}
	break;
      }
    default:
      {
	char buffer[MAX_FIELD_WIDTH];
	String tmp(buffer, sizeof(buffer), it->collation.collation);
	String *s= it->val_str(&tmp);

	if (it->null_value)
	{
	  DBUG_PRINT("info", ("default result: null"));
	  it= new Item_null();
	}
	else
	{
	  DBUG_PRINT("info",("default result: %*s",s->length(),s->c_ptr_quick()));
	  it= new Item_string(thd->strmake(s->c_ptr_quick(), s->length()),
			      s->length(), it->collation.collation);
	}
	break;
      }
    }
  }

  DBUG_RETURN(it);
}


/*
 *
 *  sp_name
 *
 */

void
sp_name::init_qname(THD *thd)
{
  m_qname.length= m_db.length+m_name.length+1;
  m_qname.str= alloc_root(&thd->mem_root, m_qname.length+1);
  sprintf(m_qname.str, "%*s.%*s",
	  m_db.length, (m_db.length ? m_db.str : ""),
	  m_name.length, m_name.str);
}

sp_name *
sp_name_current_db_new(THD *thd, LEX_STRING name)
{
  sp_name *qname;

  if (! thd->db)
    qname= new sp_name(name);
  else
  {
    LEX_STRING db;

    db.length= strlen(thd->db);
    db.str= thd->strmake(thd->db, db.length);
    qname= new sp_name(db, name);
  }
  qname->init_qname(thd);
  return qname;
}


/* ------------------------------------------------------------------ */


/*
 *
 *  sp_head
 *
 */

void *
sp_head::operator new(size_t size)
{
  DBUG_ENTER("sp_head::operator new");
  MEM_ROOT own_root;
  sp_head *sp;

  bzero((char *)&own_root, sizeof(own_root));
  init_alloc_root(&own_root, MEM_ROOT_BLOCK_SIZE, MEM_ROOT_PREALLOC);
  sp= (sp_head *)alloc_root(&own_root, size);
  sp->mem_root= own_root;
  DBUG_PRINT("info", ("mem_root 0x%lx", (ulong) &sp->mem_root));
  DBUG_RETURN(sp);
}

void 
sp_head::operator delete(void *ptr, size_t size)
{
  DBUG_ENTER("sp_head::operator delete");
  MEM_ROOT own_root;
  sp_head *sp= (sp_head *)ptr;

  memcpy(&own_root, (const void *)&sp->mem_root, sizeof(MEM_ROOT));
  DBUG_PRINT("info", ("mem_root 0x%lx moved to 0x%lx",
                      (ulong) &sp->mem_root, (ulong) &own_root));
  free_root(&own_root, MYF(0));

  DBUG_VOID_RETURN;
}


sp_head::sp_head()
  :Item_arena((bool)FALSE), m_returns_cs(NULL), m_has_return(FALSE),
   m_simple_case(FALSE), m_multi_results(FALSE), m_in_handler(FALSE)
{
  DBUG_ENTER("sp_head::sp_head");

  m_backpatch.empty();
  m_lex.empty();
  DBUG_VOID_RETURN;
}


void
sp_head::init(LEX *lex)
{
  DBUG_ENTER("sp_head::init");

  lex->spcont= m_pcont= new sp_pcontext(NULL);
  my_init_dynamic_array(&m_instr, sizeof(sp_instr *), 16, 8);
  m_param_begin= m_param_end= m_returns_begin= m_returns_end= m_body_begin= 0;
  m_qname.str= m_db.str= m_name.str= m_params.str= m_retstr.str=
    m_body.str= m_defstr.str= 0;
  m_qname.length= m_db.length= m_name.length= m_params.length=
    m_retstr.length= m_body.length= m_defstr.length= 0;
  m_returns_cs= NULL;
  DBUG_VOID_RETURN;
}

void
sp_head::init_strings(THD *thd, LEX *lex, sp_name *name)
{
  DBUG_ENTER("sp_head::init_strings");
  /* During parsing, we must use thd->mem_root */
  MEM_ROOT *root= &thd->mem_root;

  DBUG_PRINT("info", ("name: %*.s%*s",
		      name->m_db.length, name->m_db.str,
		      name->m_name.length, name->m_name.str));
  /* We have to copy strings to get them into the right memroot */
  if (name->m_db.length == 0)
  {
    m_db.length= (thd->db ? strlen(thd->db) : 0);
    m_db.str= strmake_root(root, (thd->db ? thd->db : ""), m_db.length);
  }
  else
  {
    m_db.length= name->m_db.length;
    m_db.str= strmake_root(root, name->m_db.str, name->m_db.length);
  }
  m_name.length= name->m_name.length;
  m_name.str= strmake_root(root, name->m_name.str, name->m_name.length);

  if (name->m_qname.length == 0)
    name->init_qname(thd);
  m_qname.length= name->m_qname.length;
  m_qname.str= strmake_root(root, name->m_qname.str, m_qname.length);

  m_params.length= m_param_end- m_param_begin;
  m_params.str= strmake_root(root,
			     (char *)m_param_begin, m_params.length);
  if (m_returns_begin && m_returns_end)
  {
    /* QQ KLUDGE: We can't seem to cut out just the type in the parser
       (without the RETURNS), so we'll have to do it here. :-(
       Furthermore, if there's a character type as well, it's not include
       (beyond the m_returns_end pointer), in which case we need
       m_returns_cs. */
    char *p= (char *)m_returns_begin+strspn((char *)m_returns_begin,"\t\n\r ");
    p+= strcspn(p, "\t\n\r ");
    p+= strspn(p, "\t\n\r ");
    if (p < (char *)m_returns_end)
      m_returns_begin= (uchar *)p;
    /* While we're at it, trim the end too. */
    p= (char *)m_returns_end-1;
    while (p > (char *)m_returns_begin &&
	   (*p == '\t' || *p == '\n' || *p == '\r' || *p == ' '))
      p-= 1;
    m_returns_end= (uchar *)p+1;
    if (m_returns_cs)
    {
      String s((char *)m_returns_begin, m_returns_end - m_returns_begin,
	       system_charset_info);

      s.append(' ');
      s.append(m_returns_cs->csname);
      m_retstr.length= s.length();
      m_retstr.str= strmake_root(root, s.ptr(), m_retstr.length);
    }
    else
    {
      m_retstr.length= m_returns_end - m_returns_begin;
      m_retstr.str= strmake_root(root,
				 (char *)m_returns_begin, m_retstr.length);
    }
  }
  m_body.length= lex->end_of_query - m_body_begin;
  m_body.str= strmake_root(root, (char *)m_body_begin, m_body.length);
  m_defstr.length= lex->end_of_query - lex->buf;
  m_defstr.str= strmake_root(root, (char *)lex->buf, m_defstr.length);
  DBUG_VOID_RETURN;
}

int
sp_head::create(THD *thd)
{
  DBUG_ENTER("sp_head::create");
  int ret;

  DBUG_PRINT("info", ("type: %d name: %s params: %s body: %s",
		      m_type, m_name.str, m_params.str, m_body.str));

#ifndef DBUG_OFF
  optimize();
  {
    String s;
    sp_instr *i;
    uint ip= 0;
    while ((i = get_instr(ip)))
    {
      char buf[8];

      sprintf(buf, "%4u: ", ip);
      s.append(buf);
      i->print(&s);
      s.append('\n');
      ip+= 1;
    }
    s.append('\0');
    DBUG_PRINT("info", ("Code %s\n%s", m_qname.str, s.ptr()));
  }
#endif

  if (m_type == TYPE_ENUM_FUNCTION)
    ret= sp_create_function(thd, this);
  else
    ret= sp_create_procedure(thd, this);

  DBUG_RETURN(ret);
}

sp_head::~sp_head()
{
  destroy();
  if (m_thd)
    restore_thd_mem_root(m_thd);
}

void
sp_head::destroy()
{
  DBUG_ENTER("sp_head::destroy");
  DBUG_PRINT("info", ("name: %s", m_name.str));
  sp_instr *i;
  LEX *lex;

  for (uint ip = 0 ; (i = get_instr(ip)) ; ip++)
    delete i;
  delete_dynamic(&m_instr);
  m_pcont->destroy();
  free_items(free_list);
  while ((lex= (LEX *)m_lex.pop()))
  {
    if (lex != &m_thd->main_lex) // We got interrupted and have lex'es left
      delete lex;
  }
  DBUG_VOID_RETURN;
}

int
sp_head::execute(THD *thd)
{
  DBUG_ENTER("sp_head::execute");
  char olddb[128];
  bool dbchanged;
  sp_rcontext *ctx;
  int ret= 0;
  uint ip= 0;
  Item_arena *old_arena;


#ifndef EMBEDDED_LIBRARY
  if (check_stack_overrun(thd, olddb))
  {
    DBUG_RETURN(-1);
  }
#endif

  dbchanged= FALSE;
  if ((ret= sp_use_new_db(thd, m_db.str, olddb, sizeof(olddb), 0, &dbchanged)))
    goto done;

  if ((ctx= thd->spcont))
    ctx->clear_handler();
  thd->query_error= 0;
  old_arena= thd->current_arena;
  thd->current_arena= this;

  do
  {
    sp_instr *i;
    uint hip;			// Handler ip

    i = get_instr(ip);	// Returns NULL when we're done.
    if (i == NULL)
      break;
    DBUG_PRINT("execute", ("Instruction %u", ip));
    ret= i->execute(thd, &ip);
    if (i->free_list)
      cleanup_items(i->free_list);
    // Check if an exception has occurred and a handler has been found
    // Note: We havo to check even if ret==0, since warnings (and some
    //       errors don't return a non-zero value.
    if (!thd->killed && ctx)
    {
      uint hf;

      switch (ctx->found_handler(&hip, &hf))
      {
      case SP_HANDLER_NONE:
	break;
      case SP_HANDLER_CONTINUE:
	ctx->save_variables(hf);
	ctx->push_hstack(ip);
	// Fall through
      default:
	ip= hip;
	ret= 0;
	ctx->clear_handler();
	continue;
      }
    }
  } while (ret == 0 && !thd->killed && !thd->query_error);

  if (thd->current_arena)
    cleanup_items(thd->current_arena->free_list);
  thd->current_arena= old_arena;

 done:
  DBUG_PRINT("info", ("ret=%d killed=%d query_error=%d",
		      ret, thd->killed, thd->query_error));

  if (thd->killed || thd->query_error)
    ret= -1;
  /* If the DB has changed, the pointer has changed too, but the
     original thd->db will then have been freed */
  if (dbchanged)
  {
    if (! thd->killed)
      ret= sp_change_db(thd, olddb, 0);
  }
  DBUG_RETURN(ret);
}


int
sp_head::execute_function(THD *thd, Item **argp, uint argcount, Item **resp)
{
  DBUG_ENTER("sp_head::execute_function");
  DBUG_PRINT("info", ("function %s", m_name.str));
  uint csize = m_pcont->max_pvars();
  uint params = m_pcont->current_pvars();
  uint hmax = m_pcont->max_handlers();
  uint cmax = m_pcont->max_cursors();
  sp_rcontext *octx = thd->spcont;
  sp_rcontext *nctx = NULL;
  uint i;
  int ret;

  if (argcount != params)
  {
    // Need to use my_printf_error here, or it will not terminate the
    // invoking query properly.
    my_printf_error(ER_SP_WRONG_NO_OF_ARGS, ER(ER_SP_WRONG_NO_OF_ARGS), MYF(0),
		    "FUNCTION", m_name.str, params, argcount);
    DBUG_RETURN(-1);
  }

  // QQ Should have some error checking here? (types, etc...)
  nctx= new sp_rcontext(csize, hmax, cmax);
  for (i= 0 ; i < params && i < argcount ; i++)
  {
    sp_pvar_t *pvar = m_pcont->find_pvar(i);
    Item *it= sp_eval_func_item(thd, *argp++, pvar->type);

    if (it)
      nctx->push_item(it);
    else
    {
      DBUG_RETURN(-1);
    }
  }
#ifdef NOT_WORKING
  /*
    Close tables opened for subselect in argument list
    This can't be done as this will close all other tables used
    by the query.
  */
  close_thread_tables(thd);
#endif
  // The rest of the frame are local variables which are all IN.
  // Default all variables to null (those with default clauses will
  // be set by an set instruction).
  {
    Item_null *nit= NULL;	// Re-use this, and only create if needed
    for (; i < csize ; i++)
    {
      if (! nit)
	nit= new Item_null();
      nctx->push_item(nit);
    }
  }
  thd->spcont= nctx;

  ret= execute(thd);
  if (ret == 0)
  {
    Item *it= nctx->get_result();

    if (it)
      *resp= it;
    else
    {
      my_printf_error(ER_SP_NORETURNEND, ER(ER_SP_NORETURNEND), MYF(0),
		      m_name.str);
      ret= -1;
    }
  }

  nctx->pop_all_cursors();	// To avoid memory leaks after an error
  thd->spcont= octx;
  DBUG_RETURN(ret);
}

int
sp_head::execute_procedure(THD *thd, List<Item> *args)
{
  DBUG_ENTER("sp_head::execute_procedure");
  DBUG_PRINT("info", ("procedure %s", m_name.str));
  int ret= 0;
  uint csize = m_pcont->max_pvars();
  uint params = m_pcont->current_pvars();
  uint hmax = m_pcont->max_handlers();
  uint cmax = m_pcont->max_cursors();
  sp_rcontext *octx = thd->spcont;
  sp_rcontext *nctx = NULL;
  my_bool tmp_octx = FALSE;	// True if we have allocated a temporary octx

  if (args->elements != params)
  {
    net_printf(thd, ER_SP_WRONG_NO_OF_ARGS, "PROCEDURE", m_name.str,
	       params, args->elements);
    DBUG_RETURN(-1);
  }

  if (csize > 0 || hmax > 0 || cmax > 0)
  {
    Item_null *nit= NULL;	// Re-use this, and only create if needed
    uint i;
    List_iterator_fast<Item> li(*args);
    Item *it;

    nctx= new sp_rcontext(csize, hmax, cmax);
    if (! octx)
    {				// Create a temporary old context
      octx= new sp_rcontext(csize, hmax, cmax);
      tmp_octx= TRUE;
    }
    // QQ: Should do type checking?
    for (i = 0 ; (it= li++) && i < params ; i++)
    {
      sp_pvar_t *pvar = m_pcont->find_pvar(i);

      if (! pvar)
	nctx->set_oindex(i, -1); // Shouldn't happen
      else
      {
	if (pvar->mode == sp_param_out)
	{
	  if (! nit)
	    nit= new Item_null();
	  nctx->push_item(nit); // OUT
	}
	else
	{
	  Item *it2= sp_eval_func_item(thd, it,pvar->type);

	  if (it2)
	    nctx->push_item(it2); // IN or INOUT
	  else
	  {
	    ret= -1;		// Eval failed
	    break;
	  }
	}
	// Note: If it's OUT or INOUT, it must be a variable.
	// QQ: We can check for global variables here, or should we do it
	//     while parsing?
	if (pvar->mode == sp_param_in)
	  nctx->set_oindex(i, -1); // IN
	else			// OUT or INOUT
	  nctx->set_oindex(i, static_cast<Item_splocal *>(it)->get_offset());
      }
    }
    // Close tables opened for subselect in argument list
    close_thread_tables(thd);

    // The rest of the frame are local variables which are all IN.
    // Default all variables to null (those with default clauses will
    // be set by an set instruction).
    for (; i < csize ; i++)
    {
      if (! nit)
	nit= new Item_null();
      nctx->push_item(nit);
    }
    thd->spcont= nctx;
  }

  if (! ret)
    ret= execute(thd);

  // Don't copy back OUT values if we got an error
  if (ret)
  {
    if (thd->net.report_error)
      send_error(thd, 0, NullS);
  }
  else if (csize > 0)
  {
    List_iterator_fast<Item> li(*args);
    Item *it;

    // Copy back all OUT or INOUT values to the previous frame, or
    // set global user variables
    for (uint i = 0 ; (it= li++) && i < params ; i++)
    {
      int oi = nctx->get_oindex(i);

      if (oi >= 0)
      {
	if (! tmp_octx)
	  octx->set_item(nctx->get_oindex(i), nctx->get_item(i));
	else
	{
	  // QQ Currently we just silently ignore non-user-variable arguments.
	  //    We should check this during parsing, when setting up the call
	  //    above
	  if (it->type() == Item::FUNC_ITEM)
	  {
	    Item_func *fi= static_cast<Item_func*>(it);

	    if (fi->functype() == Item_func::GUSERVAR_FUNC)
	    {			// A global user variable
	      Item *item= nctx->get_item(i);
	      Item_func_set_user_var *suv;
	      Item_func_get_user_var *guv=
		static_cast<Item_func_get_user_var*>(fi);

	      suv= new Item_func_set_user_var(guv->get_name(), item);
	      suv->fix_fields(thd, NULL, &item);
	      suv->fix_length_and_dec();
	      suv->check();
	      suv->update();
	    }
	  }
	}
      }
    }
  }

  if (tmp_octx)
    octx= NULL;
  if (nctx)
    nctx->pop_all_cursors();	// To avoid memory leaks after an error
  thd->spcont= octx;

  DBUG_RETURN(ret);
}


// Reset lex during parsing, before we parse a sub statement.
void
sp_head::reset_lex(THD *thd)
{
  DBUG_ENTER("sp_head::reset_lex");
  LEX *sublex;
  LEX *oldlex= thd->lex;

  (void)m_lex.push_front(oldlex);
  thd->lex= sublex= new st_lex;

  /* Reset most stuff. The length arguments doesn't matter here. */
  mysql_init_query(thd,oldlex->buf, oldlex->end_of_query - oldlex->ptr, TRUE);

  /* We must reset ptr and end_of_query again */
  sublex->ptr= oldlex->ptr;
  sublex->end_of_query= oldlex->end_of_query;
  sublex->tok_start= oldlex->tok_start;
  sublex->yylineno= oldlex->yylineno;
  /* And keep the SP stuff too */
  sublex->sphead= oldlex->sphead;
  sublex->spcont= oldlex->spcont;
  sublex->sp_lex_in_use= FALSE;
  DBUG_VOID_RETURN;
}

// Restore lex during parsing, after we have parsed a sub statement.
void
sp_head::restore_lex(THD *thd)
{
  DBUG_ENTER("sp_head::restore_lex");
  LEX *sublex= thd->lex;
  LEX *oldlex= (LEX *)m_lex.pop();

  if (! oldlex)
    return;			// Nothing to restore

  // Update some state in the old one first
  oldlex->ptr= sublex->ptr;
  oldlex->next_state= sublex->next_state;

  // Collect some data from the sub statement lex.
  sp_merge_funs(oldlex, sublex);
#ifdef NOT_USED_NOW
  // QQ We're not using this at the moment.
  if (sublex.sql_command == SQLCOM_CALL)
  {
    // It would be slightly faster to keep the list sorted, but we need
    // an "insert before" method to do that.
    char *proc= sublex.udf.name.str;

    List_iterator_fast<char *> li(m_calls);
    char **it;

    while ((it= li++))
      if (my_strcasecmp(system_charset_info, proc, *it) == 0)
	break;
    if (! it)
      m_calls.push_back(&proc);

  }
  // Merge used tables
  // QQ ...or just open tables in thd->open_tables?
  //    This is not entirerly clear at the moment, but for now, we collect
  //    tables here.
  for (sl= sublex.all_selects_list ;
       sl ;
       sl= sl->next_select())
  {
    for (TABLE_LIST *tables= sl->get_table_list() ;
	 tables ;
	 tables= tables->next)
    {
      List_iterator_fast<char *> li(m_tables);
      char **tb;

      while ((tb= li++))
	if (my_strcasecmp(system_charset_info, tables->real_name, *tb) == 0)
	  break;
      if (! tb)
	m_tables.push_back(&tables->real_name);
    }
  }
#endif
  if (! sublex->sp_lex_in_use)
    delete sublex;
  thd->lex= oldlex;
  DBUG_VOID_RETURN;
}

void
sp_head::push_backpatch(sp_instr *i, sp_label_t *lab)
{
  bp_t *bp= (bp_t *)sql_alloc(sizeof(bp_t));

  if (bp)
  {
    bp->lab= lab;
    bp->instr= i;
    (void)m_backpatch.push_front(bp);
  }
}

void
sp_head::backpatch(sp_label_t *lab)
{
  bp_t *bp;
  uint dest= instructions();
  List_iterator_fast<bp_t> li(m_backpatch);

  while ((bp= li++))
  {
    if (bp->lab == lab ||
	(bp->lab->type == SP_LAB_REF &&
	 my_strcasecmp(system_charset_info, bp->lab->name, lab->name) == 0))
    {
      if (bp->lab->type != SP_LAB_REF)
	bp->instr->backpatch(dest, lab->ctx);
      else
      {
	sp_label_t *dstlab= bp->lab->ctx->find_label(lab->name);

	if (dstlab)
	{
	  bp->lab= lab;
	  bp->instr->backpatch(dest, dstlab->ctx);
	}
      }
    }
  }
}

int
sp_head::check_backpatch(THD *thd)
{
  bp_t *bp;
  List_iterator_fast<bp_t> li(m_backpatch);

  while ((bp= li++))
  {
    if (bp->lab->type == SP_LAB_REF)
    {
      net_printf(thd, ER_SP_LILABEL_MISMATCH, "GOTO", bp->lab->name);
      return -1;
    }
  }
  return 0;
}

void
sp_head::set_info(char *definer, uint definerlen,
		  longlong created, longlong modified,
		  st_sp_chistics *chistics, ulong sql_mode)
{
  char *p= strchr(definer, '@');
  uint len;

  if (! p)
    p= definer;		// Weird...
  len= p-definer;
  m_definer_user.str= strmake_root(&mem_root, definer, len);
  m_definer_user.length= len;
  len= definerlen-len-1;
  m_definer_host.str= strmake_root(&mem_root, p+1, len);
  m_definer_host.length= len;
  m_created= created;
  m_modified= modified;
  m_chistics= (st_sp_chistics *)alloc_root(&mem_root, sizeof(st_sp_chistics));
  memcpy(m_chistics, chistics, sizeof(st_sp_chistics));
  if (m_chistics->comment.length == 0)
    m_chistics->comment.str= 0;
  else
    m_chistics->comment.str= strmake_root(&mem_root,
					  m_chistics->comment.str,
					  m_chistics->comment.length);
  m_sql_mode= sql_mode;
}

void
sp_head::reset_thd_mem_root(THD *thd)
{
  DBUG_ENTER("sp_head::reset_thd_mem_root");
  m_thd_root= thd->mem_root;
  thd->mem_root= mem_root;
  DBUG_PRINT("info", ("mem_root 0x%lx moved to thd mem root 0x%lx",
                      (ulong) &mem_root, (ulong) &thd->mem_root));
  free_list= thd->free_list; // Keep the old list
  thd->free_list= NULL;	// Start a new one
  /* Copy the db, since substatements will point to it */
  m_thd_db= thd->db;
  thd->db= strmake_root(&thd->mem_root, thd->db, thd->db_length);
  m_thd= thd;
  DBUG_VOID_RETURN;
}

void
sp_head::restore_thd_mem_root(THD *thd)
{
  DBUG_ENTER("sp_head::restore_thd_mem_root");
  Item *flist= free_list;	// The old list
  set_item_arena(thd);          // Get new fre_list and mem_root
  DBUG_PRINT("info", ("mem_root 0x%lx returned from thd mem root 0x%lx",
                      (ulong) &mem_root, (ulong) &thd->mem_root));
  thd->free_list= flist;	// Restore the old one
  thd->db= m_thd_db;		// Restore the original db pointer
  thd->mem_root= m_thd_root;
  m_thd= NULL;
  DBUG_VOID_RETURN;
}


int
sp_head::show_create_procedure(THD *thd)
{
  Protocol *protocol= thd->protocol;
  char buff[2048];
  String buffer(buff, sizeof(buff), system_charset_info);
  int res;
  List<Item> field_list;
  ulong old_sql_mode;
  sys_var *sql_mode_var;
  byte *sql_mode_str;
  ulong sql_mode_len;

  DBUG_ENTER("sp_head::show_create_procedure");
  DBUG_PRINT("info", ("procedure %s", m_name.str));
  LINT_INIT(sql_mode_str);
  LINT_INIT(sql_mode_len);
  
  old_sql_mode= thd->variables.sql_mode;
  thd->variables.sql_mode= m_sql_mode;
  sql_mode_var= find_sys_var("SQL_MODE", 8);
  if (sql_mode_var)
  {
    sql_mode_str= sql_mode_var->value_ptr(thd, OPT_SESSION, 0);
    sql_mode_len= strlen((char*) sql_mode_str);
  }

  field_list.push_back(new Item_empty_string("Procedure", NAME_LEN));
  if (sql_mode_var)
    field_list.push_back(new Item_empty_string("sql_mode", sql_mode_len));
  // 1024 is for not to confuse old clients
  field_list.push_back(new Item_empty_string("Create Procedure",
					     max(buffer.length(), 1024)));
  if (protocol->send_fields(&field_list, Protocol::SEND_NUM_ROWS |
                                         Protocol::SEND_EOF))
  {
    res= 1;
    goto done;
  }
  protocol->prepare_for_resend();
  protocol->store(m_name.str, m_name.length, system_charset_info);
  if (sql_mode_var)
    protocol->store((char*) sql_mode_str, sql_mode_len, system_charset_info);
  protocol->store(m_defstr.str, m_defstr.length, system_charset_info);
  res= protocol->write();
  send_eof(thd);

 done:
  thd->variables.sql_mode= old_sql_mode;
  DBUG_RETURN(res);
}


/*
  Add instruction to SP

  SYNOPSIS
    sp_head::add_instr()
    instr   Instruction
*/

void sp_head::add_instr(sp_instr *instr)
{
  instr->free_list= m_thd->free_list;
  m_thd->free_list= 0;
  insert_dynamic(&m_instr, (gptr)&instr);
}


int
sp_head::show_create_function(THD *thd)
{
  Protocol *protocol= thd->protocol;
  char buff[2048];
  String buffer(buff, sizeof(buff), system_charset_info);
  int res;
  List<Item> field_list;
  ulong old_sql_mode;
  sys_var *sql_mode_var;
  byte *sql_mode_str;
  ulong sql_mode_len;
  DBUG_ENTER("sp_head::show_create_function");
  DBUG_PRINT("info", ("procedure %s", m_name.str));
  LINT_INIT(sql_mode_str);
  LINT_INIT(sql_mode_len);

  old_sql_mode= thd->variables.sql_mode;
  thd->variables.sql_mode= m_sql_mode;
  sql_mode_var= find_sys_var("SQL_MODE", 8);
  if (sql_mode_var)
  {
    sql_mode_str= sql_mode_var->value_ptr(thd, OPT_SESSION, 0);
    sql_mode_len= strlen((char*) sql_mode_str);
  }

  field_list.push_back(new Item_empty_string("Function",NAME_LEN));
  if (sql_mode_var)
    field_list.push_back(new Item_empty_string("sql_mode", sql_mode_len));
  field_list.push_back(new Item_empty_string("Create Function",
					     max(buffer.length(),1024)));
  if (protocol->send_fields(&field_list,
                            Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
  {
    res= 1;
    goto done;
  }
  protocol->prepare_for_resend();
  protocol->store(m_name.str, m_name.length, system_charset_info);
  if (sql_mode_var)
    protocol->store((char*) sql_mode_str, sql_mode_len, system_charset_info);
  protocol->store(m_defstr.str, m_defstr.length, system_charset_info);
  res= protocol->write();
  send_eof(thd);

 done:
  thd->variables.sql_mode= old_sql_mode;
  DBUG_RETURN(res);
}

void
sp_head::optimize()
{
  List<sp_instr> bp;
  sp_instr *i;
  uint src, dst;

  opt_mark(0);

  bp.empty();
  src= dst= 0;
  while ((i= get_instr(src)))
  {
    if (! i->marked)
    {
      delete i;
      src+= 1;
    }
    else
    {
      if (src != dst)
      {
	sp_instr *ibp;
	List_iterator_fast<sp_instr> li(bp);

	set_dynamic(&m_instr, (gptr)&i, dst);
	while ((ibp= li++))
	{
	  sp_instr_jump *ji= static_cast<sp_instr_jump *>(ibp);
	  if (ji->m_dest == src)
	    ji->m_dest= dst;
	}
      }
      i->opt_move(dst, &bp);
      src+= 1;
      dst+= 1;
    }
  }
  m_instr.elements= dst;
  bp.empty();
}

void
sp_head::opt_mark(uint ip)
{
  sp_instr *i;

  while ((i= get_instr(ip)) && !i->marked)
    ip= i->opt_mark(this);
}

// ------------------------------------------------------------------

//
// sp_instr_stmt
//
sp_instr_stmt::~sp_instr_stmt()
{
  if (m_lex)
    delete m_lex;
}

int
sp_instr_stmt::execute(THD *thd, uint *nextp)
{
  DBUG_ENTER("sp_instr_stmt::execute");
  DBUG_PRINT("info", ("command: %d", m_lex->sql_command));
  int res= exec_stmt(thd, m_lex);
  *nextp = m_ip+1;
  DBUG_RETURN(res);
}

void
sp_instr_stmt::print(String *str)
{
  str->reserve(12);
  str->append("stmt ");
  str->qs_append((uint)m_lex->sql_command);
}


int
sp_instr_stmt::exec_stmt(THD *thd, LEX *lex)
{
  LEX *olex;			// The other lex
  int res;

  olex= thd->lex;		// Save the other lex
  thd->lex= lex;		// Use my own lex
  thd->lex->thd = thd;		// QQ Not reentrant!
  thd->lex->unit.thd= thd;	// QQ Not reentrant
  thd->free_list= NULL;

  VOID(pthread_mutex_lock(&LOCK_thread_count));
  thd->query_id= query_id++;
  VOID(pthread_mutex_unlock(&LOCK_thread_count));

  reset_stmt_for_execute(thd, lex);

  res= mysql_execute_command(thd);

  lex->unit.cleanup();
  if (thd->lock || thd->open_tables || thd->derived_tables)
  {
    thd->proc_info="closing tables";
    close_thread_tables(thd);			/* Free tables */
  }

  thd->lex= olex;		// Restore the other lex

  return res;
}

//
// sp_instr_set
//
int
sp_instr_set::execute(THD *thd, uint *nextp)
{
  DBUG_ENTER("sp_instr_set::execute");
  DBUG_PRINT("info", ("offset: %u", m_offset));
  Item *it;
  int res;

  if (tables &&
      ((res= check_table_access(thd, SELECT_ACL, tables, 0)) ||
       (res= open_and_lock_tables(thd, tables))))
    DBUG_RETURN(-1);

  it= sp_eval_func_item(thd, m_value, m_type);
  if (! it)
    res= -1;
  else
  {
    res= 0;
    thd->spcont->set_item(m_offset, it);
  }
  *nextp = m_ip+1;
  if (thd->lock || thd->open_tables || thd->derived_tables)
    close_thread_tables(thd);
  DBUG_RETURN(res);
}

void
sp_instr_set::print(String *str)
{
  str->reserve(12);
  str->append("set ");
  str->qs_append(m_offset);
  str->append(' ');
  m_value->print(str);
}

//
// sp_instr_jump
//
int
sp_instr_jump::execute(THD *thd, uint *nextp)
{
  DBUG_ENTER("sp_instr_jump::execute");
  DBUG_PRINT("info", ("destination: %u", m_dest));

  *nextp= m_dest;
  DBUG_RETURN(0);
}

void
sp_instr_jump::print(String *str)
{
  str->reserve(12);
  str->append("jump ");
  str->qs_append(m_dest);
}

uint
sp_instr_jump::opt_mark(sp_head *sp)
{
  m_dest= opt_shortcut_jump(sp, this);
  if (m_dest != m_ip+1)		/* Jumping to following instruction? */
    marked= 1;
  m_optdest= sp->get_instr(m_dest);
  return m_dest;
}

uint
sp_instr_jump::opt_shortcut_jump(sp_head *sp, sp_instr *start)
{
  uint dest= m_dest;
  sp_instr *i;

  while ((i= sp->get_instr(dest)))
  {
    uint ndest;

    if (start == i)
      break;
    ndest= i->opt_shortcut_jump(sp, start);
    if (ndest == dest)
      break;
    dest= ndest;
  }
  return dest;
}

void
sp_instr_jump::opt_move(uint dst, List<sp_instr> *bp)
{
  if (m_dest > m_ip)
    bp->push_back(this);	// Forward
  else if (m_optdest)
    m_dest= m_optdest->m_ip;	// Backward
  m_ip= dst;
}

//
// sp_instr_jump_if
//
int
sp_instr_jump_if::execute(THD *thd, uint *nextp)
{
  DBUG_ENTER("sp_instr_jump_if::execute");
  DBUG_PRINT("info", ("destination: %u", m_dest));
  Item *it;
  int res;

  if (tables &&
      ((res= check_table_access(thd, SELECT_ACL, tables, 0)) ||
       (res= open_and_lock_tables(thd, tables))))
    DBUG_RETURN(-1);

  it= sp_eval_func_item(thd, m_expr, MYSQL_TYPE_TINY);
  if (!it)
    res= -1;
  else
  {
    res= 0;
    if (it->val_int())
      *nextp = m_dest;
    else
      *nextp = m_ip+1;
  }
  if (thd->lock || thd->open_tables || thd->derived_tables)
    close_thread_tables(thd);
  DBUG_RETURN(res);
}

void
sp_instr_jump_if::print(String *str)
{
  str->reserve(12);
  str->append("jump_if ");
  str->qs_append(m_dest);
  str->append(' ');
  m_expr->print(str);
}

uint
sp_instr_jump_if::opt_mark(sp_head *sp)
{
  sp_instr *i;

  marked= 1;
  if ((i= sp->get_instr(m_dest)))
  {
    m_dest= i->opt_shortcut_jump(sp, this);
    m_optdest= sp->get_instr(m_dest);
  }
  sp->opt_mark(m_dest);
  return m_ip+1;
}

//
// sp_instr_jump_if_not
//
int
sp_instr_jump_if_not::execute(THD *thd, uint *nextp)
{
  DBUG_ENTER("sp_instr_jump_if_not::execute");
  DBUG_PRINT("info", ("destination: %u", m_dest));
  Item *it;
  int res;

  if (tables &&
      ((res= check_table_access(thd, SELECT_ACL, tables, 0)) ||
       (res= open_and_lock_tables(thd, tables))))
    DBUG_RETURN(-1);

  it= sp_eval_func_item(thd, m_expr, MYSQL_TYPE_TINY);
  if (! it)
    res= -1;
  else
  {
    res= 0;
    if (! it->val_int())
      *nextp = m_dest;
    else
      *nextp = m_ip+1;
  }
  if (thd->lock || thd->open_tables || thd->derived_tables)
    close_thread_tables(thd);
  DBUG_RETURN(res);
}

void
sp_instr_jump_if_not::print(String *str)
{
  str->reserve(16);
  str->append("jump_if_not ");
  str->qs_append(m_dest);
  str->append(' ');
  m_expr->print(str);
}

uint
sp_instr_jump_if_not::opt_mark(sp_head *sp)
{
  sp_instr *i;

  marked= 1;
  if ((i= sp->get_instr(m_dest)))
  {
    m_dest= i->opt_shortcut_jump(sp, this);
    m_optdest= sp->get_instr(m_dest);
  }
  sp->opt_mark(m_dest);
  return m_ip+1;
}

//
// sp_instr_freturn
//
int
sp_instr_freturn::execute(THD *thd, uint *nextp)
{
  DBUG_ENTER("sp_instr_freturn::execute");
  Item *it;
  int res;

  if (tables &&
      ((res= check_table_access(thd, SELECT_ACL, tables, 0)) ||
       (res= open_and_lock_tables(thd, tables))))
    DBUG_RETURN(-1);

  it= sp_eval_func_item(thd, m_value, m_type);
  if (! it)
    res= -1;
  else
  {
    res= 0;
    thd->spcont->set_result(it);
  }
  *nextp= UINT_MAX;
  DBUG_RETURN(res);
}

void
sp_instr_freturn::print(String *str)
{
  str->reserve(12);
  str->append("freturn ");
  str->qs_append((uint)m_type);
  str->append(' ');
  m_value->print(str);
}

//
// sp_instr_hpush_jump
//
int
sp_instr_hpush_jump::execute(THD *thd, uint *nextp)
{
  DBUG_ENTER("sp_instr_hpush_jump::execute");
  List_iterator_fast<sp_cond_type_t> li(m_cond);
  sp_cond_type_t *p;

  while ((p= li++))
    thd->spcont->push_handler(p, m_handler, m_type, m_frame);

  *nextp= m_dest;
  DBUG_RETURN(0);
}

void
sp_instr_hpush_jump::print(String *str)
{
  str->reserve(32);
  str->append("hpush_jump ");
  str->qs_append(m_dest);
  str->append(" t=");
  str->qs_append(m_type);
  str->append(" f=");
  str->qs_append(m_frame);
  str->append(" h=");
  str->qs_append(m_handler);
}

uint
sp_instr_hpush_jump::opt_mark(sp_head *sp)
{
  sp_instr *i;

  marked= 1;
  if ((i= sp->get_instr(m_dest)))
  {
    m_dest= i->opt_shortcut_jump(sp, this);
    m_optdest= sp->get_instr(m_dest);
  }
  sp->opt_mark(m_dest);
  return m_ip+1;
}

//
// sp_instr_hpop
//
int
sp_instr_hpop::execute(THD *thd, uint *nextp)
{
  DBUG_ENTER("sp_instr_hpop::execute");
  thd->spcont->pop_handlers(m_count);
  *nextp= m_ip+1;
  DBUG_RETURN(0);
}

void
sp_instr_hpop::print(String *str)
{
  str->reserve(12);
  str->append("hpop ");
  str->qs_append(m_count);
}

void
sp_instr_hpop::backpatch(uint dest, sp_pcontext *dst_ctx)
{
  m_count= m_ctx->diff_handlers(dst_ctx);
}


//
// sp_instr_hreturn
//
int
sp_instr_hreturn::execute(THD *thd, uint *nextp)
{
  DBUG_ENTER("sp_instr_hreturn::execute");
  thd->spcont->restore_variables(m_frame);
  *nextp= thd->spcont->pop_hstack();
  DBUG_RETURN(0);
}

void
sp_instr_hreturn::print(String *str)
{
  str->reserve(12);
  str->append("hreturn ");
  str->qs_append(m_frame);
}

//
// sp_instr_cpush
//
int
sp_instr_cpush::execute(THD *thd, uint *nextp)
{
  DBUG_ENTER("sp_instr_cpush::execute");
  thd->spcont->push_cursor(m_lex);
  *nextp= m_ip+1;
  DBUG_RETURN(0);
}

sp_instr_cpush::~sp_instr_cpush()
{
  if (m_lex)
    delete m_lex;
}

void
sp_instr_cpush::print(String *str)
{
  str->append("cpush");
}

//
// sp_instr_cpop
//
int
sp_instr_cpop::execute(THD *thd, uint *nextp)
{
  DBUG_ENTER("sp_instr_cpop::execute");
  thd->spcont->pop_cursors(m_count);
  *nextp= m_ip+1;
  DBUG_RETURN(0);
}

void
sp_instr_cpop::print(String *str)
{
  str->reserve(12);
  str->append("cpop ");
  str->qs_append(m_count);
}

void
sp_instr_cpop::backpatch(uint dest, sp_pcontext *dst_ctx)
{
  m_count= m_ctx->diff_cursors(dst_ctx);
}

//
// sp_instr_copen
//
int
sp_instr_copen::execute(THD *thd, uint *nextp)
{
  sp_cursor *c= thd->spcont->get_cursor(m_cursor);
  int res;
  DBUG_ENTER("sp_instr_copen::execute");

  if (! c)
    res= -1;
  else
  {
    LEX *lex= c->pre_open(thd);

    if (! lex)
      res= -1;
    else
      res= exec_stmt(thd, lex);
    c->post_open(thd, (lex ? TRUE : FALSE));
  }

  *nextp= m_ip+1;
  DBUG_RETURN(res);
}

void
sp_instr_copen::print(String *str)
{
  str->reserve(12);
  str->append("copen ");
  str->qs_append(m_cursor);
}

//
// sp_instr_cclose
//
int
sp_instr_cclose::execute(THD *thd, uint *nextp)
{
  sp_cursor *c= thd->spcont->get_cursor(m_cursor);
  int res;
  DBUG_ENTER("sp_instr_cclose::execute");

  if (! c)
    res= -1;
  else
    res= c->close(thd);
  *nextp= m_ip+1;
  DBUG_RETURN(res);
}

void
sp_instr_cclose::print(String *str)
{
  str->reserve(12);
  str->append("cclose ");
  str->qs_append(m_cursor);
}

//
// sp_instr_cfetch
//
int
sp_instr_cfetch::execute(THD *thd, uint *nextp)
{
  sp_cursor *c= thd->spcont->get_cursor(m_cursor);
  int res;
  DBUG_ENTER("sp_instr_cfetch::execute");

  if (! c)
    res= -1;
  else
    res= c->fetch(thd, &m_varlist);
  *nextp= m_ip+1;
  DBUG_RETURN(res);
}

void
sp_instr_cfetch::print(String *str)
{
  List_iterator_fast<struct sp_pvar> li(m_varlist);
  sp_pvar_t *pv;

  str->reserve(12);
  str->append("cfetch ");
  str->qs_append(m_cursor);
  while ((pv= li++))
  {
    str->reserve(8);
    str->append(' ');
    str->qs_append(pv->offset);
  }
}

//
// sp_instr_error
//
int
sp_instr_error::execute(THD *thd, uint *nextp)
{
  DBUG_ENTER("sp_instr_error::execute");

  my_error(m_errcode, MYF(0));
  *nextp= m_ip+1;
  DBUG_RETURN(-1);
}

void
sp_instr_error::print(String *str)
{
  str->reserve(12);
  str->append("error ");
  str->qs_append(m_errcode);
}

/* ------------------------------------------------------------------ */


//
// Security context swapping
//

#ifndef NO_EMBEDDED_ACCESS_CHECKS
void
sp_change_security_context(THD *thd, sp_head *sp, st_sp_security_context *ctxp)
{
  ctxp->changed= (sp->m_chistics->suid != IS_NOT_SUID &&
		   (strcmp(sp->m_definer_user.str, thd->priv_user) ||
		    strcmp(sp->m_definer_host.str, thd->priv_host)));

  if (ctxp->changed)
  {
    ctxp->master_access= thd->master_access;
    ctxp->db_access= thd->db_access;
    ctxp->priv_user= thd->priv_user;
    strncpy(ctxp->priv_host, thd->priv_host, sizeof(ctxp->priv_host));
    ctxp->user= thd->user;
    ctxp->host= thd->host;
    ctxp->ip= thd->ip;

    /* Change thise just to do the acl_getroot_no_password */
    thd->user= sp->m_definer_user.str;
    thd->host= thd->ip = sp->m_definer_host.str;

    if (acl_getroot_no_password(thd))
    {			// Failed, run as invoker for now
      ctxp->changed= FALSE;
      thd->master_access= ctxp->master_access;
      thd->db_access= ctxp->db_access;
      thd->priv_user= ctxp->priv_user;
      strncpy(thd->priv_host, ctxp->priv_host, sizeof(thd->priv_host));
    }

    /* Restore these immiediately */
    thd->user= ctxp->user;
    thd->host= ctxp->host;
    thd->ip= ctxp->ip;
  }
}

void
sp_restore_security_context(THD *thd, sp_head *sp, st_sp_security_context *ctxp)
{
  if (ctxp->changed)
  {
    ctxp->changed= FALSE;
    thd->master_access= ctxp->master_access;
    thd->db_access= ctxp->db_access;
    thd->priv_user= ctxp->priv_user;
    strncpy(thd->priv_host, ctxp->priv_host, sizeof(thd->priv_host));
  }
}

#endif /* NO_EMBEDDED_ACCESS_CHECKS */