diff --git a/mysql-test/r/sp.result b/mysql-test/r/sp.result
index 29200a439756d97d169a755ad936e90b93b1844b..c08d252cfe30cdc50d7152b5fa8cedbe7c171a21 100644
--- a/mysql-test/r/sp.result
+++ b/mysql-test/r/sp.result
@@ -95,6 +95,23 @@ delete from t1;
 drop procedure zip;
 drop procedure zap;
 drop procedure bar;
+create procedure c1(x int)
+call c2("c", x);
+create procedure c2(s char(16), x int)
+call c3(x, s);
+create procedure c3(x int, s char(16))
+call c4("level", x, s);
+create procedure c4(l char(8), x int, s char(16))
+insert into t1 values (concat(l,s), x);
+call c1(42);
+select * from t1;
+id	data
+levelc	42
+delete from t1;
+drop procedure c1;
+drop procedure c2;
+drop procedure c3;
+drop procedure c4;
 create procedure iotest(x1 char(16), x2 char(16), y int)
 begin
 call inc2(x2, y);
diff --git a/mysql-test/t/sp.test b/mysql-test/t/sp.test
index c501501b82fc7da8843c3ed13801751800211419..61519c529290d4699eb56c83f72541689eb47184 100644
--- a/mysql-test/t/sp.test
+++ b/mysql-test/t/sp.test
@@ -126,6 +126,24 @@ drop procedure zap|
 drop procedure bar|
 
 
+# "Deep" calls...
+create procedure c1(x int)
+  call c2("c", x)|
+create procedure c2(s char(16), x int)
+  call c3(x, s)|
+create procedure c3(x int, s char(16))
+  call c4("level", x, s)|
+create procedure c4(l char(8), x int, s char(16))
+  insert into t1 values (concat(l,s), x)|
+
+call c1(42)|
+select * from t1|
+delete from t1|
+drop procedure c1|
+drop procedure c2|
+drop procedure c3|
+drop procedure c4|
+
 # INOUT test
 create procedure iotest(x1 char(16), x2 char(16), y int)
 begin
diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h
index 64b84858282a6b34260fea89c0c94ee13cbc4b2d..57a7fd0f55351e061140f8bf4f6526cb64927abc 100644
--- a/sql/mysql_priv.h
+++ b/sql/mysql_priv.h
@@ -353,7 +353,7 @@ bool is_update_query(enum enum_sql_command command);
 void free_items(Item *item);
 bool alloc_query(THD *thd, char *packet, ulong packet_length);
 void mysql_init_select(LEX *lex);
-void mysql_init_query(THD *thd);
+void mysql_init_query(THD *thd, bool lexonly=0);
 bool mysql_new_select(LEX *lex, bool move_down);
 void create_select_for_variable(const char *var_name);
 void mysql_init_multi_delete(LEX *lex);
diff --git a/sql/sp.cc b/sql/sp.cc
index b1b73f308119cbbf27503cc7069555f553ad716a..5c87488bcda1eca9f7ed9b41115d4850a0796c80 100644
--- a/sql/sp.cc
+++ b/sql/sp.cc
@@ -88,7 +88,6 @@ db_find_routine(THD *thd, int type, char *name, uint namelen, sp_head **sphp)
   DBUG_ENTER("db_find_routine");
   DBUG_PRINT("enter", ("type: %d name: %*s", type, namelen, name));
   extern int yyparse(void *thd);
-  LEX *tmplex;
   TABLE *table;
   const char *defstr;
   int ret;
@@ -146,15 +145,29 @@ db_find_routine(THD *thd, int type, char *name, uint namelen, sp_head **sphp)
     table= NULL;
   }
 
-  tmplex= lex_start(thd, (uchar*)defstr, strlen(defstr));
-  if (yyparse(thd) || thd->is_fatal_error || tmplex->sphead == NULL)
-    ret= SP_PARSE_ERROR;
-  else
   {
-    *sphp= tmplex->sphead;
-    (*sphp)->sp_set_info((char *) creator, (uint) strlen(creator),
-			 created, modified, suid, 
-			 ptr, length);
+    LEX *oldlex= thd->lex;
+    enum enum_sql_command oldcmd= thd->lex->sql_command;
+
+    lex_start(thd, (uchar*)defstr, strlen(defstr));
+    if (yyparse(thd) || thd->is_fatal_error || thd->lex->sphead == NULL)
+    {
+      if (thd->lex->sphead)
+      {
+	if (oldlex != thd->lex)
+	  thd->lex->sphead->restore_lex(thd);
+	thd->lex->sphead->destroy();
+      }
+      ret= SP_PARSE_ERROR;
+    }
+    else
+    {
+      *sphp= thd->lex->sphead;
+      (*sphp)->sp_set_info((char *) creator, (uint) strlen(creator),
+			   created, modified, suid, 
+			   ptr, length);
+    }
+    thd->lex->sql_command= oldcmd;
   }
 
  done:
diff --git a/sql/sp_head.cc b/sql/sp_head.cc
index 15a85173f8f1e7720a2360a048fdcdb2a652e8c5..96624430307b4562c8638b0ccc961ec91aa28a33 100644
--- a/sql/sp_head.cc
+++ b/sql/sp_head.cc
@@ -365,54 +365,44 @@ sp_head::execute_procedure(THD *thd, List<Item> *args)
 void
 sp_head::reset_lex(THD *thd)
 {
-  memcpy(&m_lex, thd->lex, sizeof(LEX)); // Save old one
+  DBUG_ENTER("sp_head::reset_lex");
+  LEX *sublex;
+
+  m_lex= thd->lex;
+  thd->lex= sublex= new st_lex;
+  sublex->yylineno= m_lex->yylineno;
   /* Reset most stuff. The length arguments doesn't matter here. */
-  lex_start(thd, m_lex.buf, m_lex.end_of_query - m_lex.ptr);
+  lex_start(thd, m_lex->buf, m_lex->end_of_query - m_lex->ptr);
   /* We must reset ptr and end_of_query again */
-  thd->lex->ptr= m_lex.ptr;
-  thd->lex->end_of_query= m_lex.end_of_query;
+  sublex->ptr= m_lex->ptr;
+  sublex->end_of_query= m_lex->end_of_query;
   /* And keep the SP stuff too */
-  thd->lex->sphead = m_lex.sphead;
-  thd->lex->spcont = m_lex.spcont;
-  /* Clear all lists. (QQ Why isn't this reset by lex_start()?).
-     We may be overdoing this, but we know for sure that value_list must
-     be cleared at least. */
-  thd->lex->col_list.empty();
-  thd->lex->ref_list.empty();
-  thd->lex->drop_list.empty();
-  thd->lex->alter_list.empty();
-  thd->lex->interval_list.empty();
-  thd->lex->users_list.empty();
-  thd->lex->columns.empty();
-  thd->lex->key_list.empty();
-  thd->lex->create_list.empty();
-  thd->lex->insert_list= NULL;
-  thd->lex->field_list.empty();
-  thd->lex->value_list.empty();
-  thd->lex->many_values.empty();
-  thd->lex->var_list.empty();
-  thd->lex->param_list.empty();
-  thd->lex->proc_list.empty();
-  thd->lex->auxilliary_table_list.empty();
+  sublex->sphead= m_lex->sphead;
+  sublex->spcont= m_lex->spcont;
+  mysql_init_query(thd, true);	// Only init lex
+  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;
+
   // Update some state in the old one first
-  m_lex.ptr= thd->lex->ptr;
-  m_lex.next_state= thd->lex->next_state;
+  m_lex->ptr= sublex->ptr;
+  m_lex->next_state= sublex->next_state;
 
   // Collect some data from the sub statement lex.
-  sp_merge_funs(&m_lex, thd->lex);
+  sp_merge_funs(m_lex, sublex);
 #if 0
   // QQ We're not using this at the moment.
-  if (thd->lex.sql_command == SQLCOM_CALL)
+  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= thd->lex.udf.name.str;
+    char *proc= sublex.udf.name.str;
 
     List_iterator_fast<char *> li(m_calls);
     char **it;
@@ -428,7 +418,7 @@ sp_head::restore_lex(THD *thd)
   // 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 (SELECT_LEX *sl= thd->lex.all_selects_list ;
+  for (SELECT_LEX *sl= sublex.all_selects_list ;
        sl ;
        sl= sl->next_select())
   {
@@ -448,7 +438,8 @@ sp_head::restore_lex(THD *thd)
   }
 #endif
 
-  memcpy(thd->lex, &m_lex, sizeof(LEX)); // Restore lex
+  thd->lex= m_lex;
+  DBUG_VOID_RETURN;
 }
 
 void
@@ -490,14 +481,14 @@ int
 sp_instr_stmt::execute(THD *thd, uint *nextp)
 {
   DBUG_ENTER("sp_instr_stmt::execute");
-  DBUG_PRINT("info", ("command: %d", m_lex.sql_command));
-  LEX olex;			// The other lex
+  DBUG_PRINT("info", ("command: %d", m_lex->sql_command));
+  LEX *olex;			// The other lex
   int res;
 
-  memcpy(&olex, thd->lex, sizeof(LEX)); // Save the other lex
-
-  memcpy(thd->lex, &m_lex, sizeof(LEX)); // Use my own lex
-  thd->lex->thd = thd;
+  olex= thd->lex;		// Save the other lex
+  thd->lex= m_lex;		// Use my own lex
+  thd->lex->thd = thd;		// QQ Not reentrant!
+  thd->lex->unit.thd= thd;	// QQ Not reentrant
 
   res= mysql_execute_command(thd);
   if (thd->lock || thd->open_tables || thd->derived_tables)
@@ -506,7 +497,7 @@ sp_instr_stmt::execute(THD *thd, uint *nextp)
     close_thread_tables(thd);			/* Free tables */
   }
 
-  memcpy(thd->lex, &olex, sizeof(LEX)); // Restore the other lex
+  thd->lex= olex;		// Restore the other lex
 
   *nextp = m_ip+1;
   DBUG_RETURN(res);
diff --git a/sql/sp_head.h b/sql/sp_head.h
index d5bf7138785580ac914d43fea779729a5e824ec2..f25e141cd1850afdce29207a97714800b59be2b3 100644
--- a/sql/sp_head.h
+++ b/sql/sp_head.h
@@ -136,7 +136,7 @@ class sp_head : public Sql_alloc
   bool m_suid;
 
   sp_pcontext *m_pcont;		// Parse context
-  LEX m_lex;			// Temp. store for the other lex
+  LEX *m_lex;			// Temp. store for the other lex
   DYNAMIC_ARRAY m_instr;	// The "instructions"
   typedef struct
   {
@@ -222,18 +222,18 @@ class sp_instr_stmt : public sp_instr
   inline void
   set_lex(LEX *lex)
   {
-    memcpy(&m_lex, lex, sizeof(LEX));
+    m_lex= lex;
   }
 
   inline LEX *
   get_lex()
   {
-    return &m_lex;
+    return m_lex;
   }
 
 private:
 
-  LEX m_lex;			// My own lex
+  LEX *m_lex;			// My own lex
 
 }; // class sp_instr_stmt : public sp_instr
 
diff --git a/sql/sql_lex.h b/sql/sql_lex.h
index ecf04c1575c033da1d2a854b141894e3dc9ed289..09cbe107ffaaf75e1d949807f48d734b471ca100 100644
--- a/sql/sql_lex.h
+++ b/sql/sql_lex.h
@@ -320,7 +320,7 @@ class st_select_lex_unit: public st_select_lex_node {
   int exec();
   int cleanup();
   
-  friend void mysql_init_query(THD *thd);
+  friend void mysql_init_query(THD *thd, bool lexonly);
   friend int subselect_union_engine::exec();
 private:
   bool create_total_list_n_last_return(THD *thd, st_lex *lex,
@@ -408,7 +408,7 @@ class st_select_lex: public st_select_lex_node
     order_list.next= (byte**) &order_list.first;
   }
   
-  friend void mysql_init_query(THD *thd);
+  friend void mysql_init_query(THD *thd, bool lexonly);
   st_select_lex(struct st_lex *lex);
   st_select_lex() {}
   void make_empty_select(st_select_lex *last_select)
diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc
index d1b9fecee3518f3175c5871db5342fb3c62cadb4..e434524957e138896228cf34de2ccec9de96a497 100644
--- a/sql/sql_parse.cc
+++ b/sql/sql_parse.cc
@@ -3433,7 +3433,7 @@ bool my_yyoverflow(short **yyss, YYSTYPE **yyvs, int *yystacksize)
 ****************************************************************************/
 
 void
-mysql_init_query(THD *thd)
+mysql_init_query(THD *thd, bool lexonly)
 {
   DBUG_ENTER("mysql_init_query");
   LEX *lex=thd->lex;
@@ -3457,17 +3457,20 @@ mysql_init_query(THD *thd)
   lex->lock_option= TL_READ;
   lex->found_colon= 0;
   lex->safe_to_cache_query= 1;
-  thd->select_number= lex->select_lex.select_number= 1;
-  thd->free_list= 0;
-  thd->total_warn_count=0;			// Warnings for this query
-  thd->last_insert_id_used= thd->query_start_used= thd->insert_id_used=0;
-  thd->sent_row_count= thd->examined_row_count= 0;
-  thd->is_fatal_error= thd->rand_used= 0;
-  thd->server_status &= ~SERVER_MORE_RESULTS_EXISTS;
-  thd->tmp_table_used= 0;
-  if (opt_bin_log)
-    reset_dynamic(&thd->user_var_events);
-  thd->clear_error();
+  if (! lexonly)
+  {
+    thd->select_number= lex->select_lex.select_number= 1;
+    thd->free_list= 0;
+    thd->total_warn_count=0;			// Warnings for this query
+    thd->last_insert_id_used= thd->query_start_used= thd->insert_id_used=0;
+    thd->sent_row_count= thd->examined_row_count= 0;
+    thd->is_fatal_error= thd->rand_used= 0;
+    thd->server_status &= ~SERVER_MORE_RESULTS_EXISTS;
+    thd->tmp_table_used= 0;
+    if (opt_bin_log)
+      reset_dynamic(&thd->user_var_events);
+    thd->clear_error();
+  }
   DBUG_VOID_RETURN;
 }
 
@@ -3582,7 +3585,11 @@ mysql_parse(THD *thd, char *inBuf, uint length)
       else
       {
 	if (thd->net.report_error)
+	{
 	  send_error(thd, 0, NullS);
+	  if (thd->lex->sphead)
+	    thd->lex->sphead->destroy();
+	}
 	else
 	{
 	  mysql_execute_command(thd);
@@ -3598,8 +3605,12 @@ mysql_parse(THD *thd, char *inBuf, uint length)
 			 thd->is_fatal_error));
 #ifndef EMBEDDED_LIBRARY   /* TODO query cache in embedded library*/
       query_cache_abort(&thd->net);
+      if (thd->lex->sphead)
+	thd->lex->sphead->destroy();
 #endif
     }
+    if (thd->lex->sphead && lex != thd->lex)
+      thd->lex->sphead->restore_lex(thd);
     thd->proc_info="freeing items";
     free_items(thd->free_list);  /* Free strings used by items */
     lex_end(lex);
diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc
index 43376012d4c369a9a05970029eef3f2bcb965ba5..bd0a3a094222a9a586b07f6a4e905b339edececc 100644
--- a/sql/sql_prepare.cc
+++ b/sql/sql_prepare.cc
@@ -71,6 +71,7 @@ Long data handling:
 #include "sql_acl.h"
 #include "sql_select.h" // for JOIN
 #include <m_ctype.h>  // for isspace()
+#include "sp_head.h"
 
 #define IS_PARAM_NULL(pos, param_no) pos[param_no/8] & (1 << param_no & 7)
 
@@ -761,6 +762,8 @@ static bool parse_prepare_query(PREP_STMT *stmt,
   thd->lex->param_count= 0;
   if (!yyparse((void *)thd) && !thd->is_fatal_error) 
     error= send_prepare_results(stmt);
+  if (thd->lex->sphead && lex != thd->lex)
+    thd->lex->sphead->restore_lex(thd);
   lex_end(lex);
   DBUG_RETURN(error);
 }