diff --git a/mysql-test/r/ps.result b/mysql-test/r/ps.result
new file mode 100644
index 0000000000000000000000000000000000000000..14af3c32292d0a84350a753d4ff5b00ae2b416c0
--- /dev/null
+++ b/mysql-test/r/ps.result
@@ -0,0 +1,86 @@
+drop table if exists t1,t2;
+create table t1
+(
+a int primary key,
+b char(10),
+);
+insert into t1 values (1,'one');
+insert into t1 values (2,'two');
+insert into t1 values (3,'three');
+insert into t1 values (4,'four');
+set @a=2;
+prepare stmt1 from 'select * from t1 where a <= ?';
+execute stmt1 using @a;
+a	b
+1	one
+2	two
+set @a=3;
+execute stmt1 using @a;
+a	b
+1	one
+2	two
+3	three
+deallocate prepare no_such_statement;
+ERROR HY000: Undefined prepared statement
+execute stmt1;
+ERROR HY000: Wrong arguments to mysql_execute
+prepare stmt2 from 'prepare nested_stmt from "select 1"';
+ERROR 42000: You have an error in your SQL syntax.  Check the manual that corresponds to your MySQL server version for the right syntax to use near '"select 1"' at line 1
+prepare stmt2 from 'execute stmt1';
+ERROR 42000: You have an error in your SQL syntax.  Check the manual that corresponds to your MySQL server version for the right syntax to use near 'stmt1' at line 1
+prepare stmt2 from 'deallocate prepare z';
+ERROR 42000: You have an error in your SQL syntax.  Check the manual that corresponds to your MySQL server version for the right syntax to use near 'z' at line 1
+prepare stmt3 from 'insert into t1 values (?,?)';
+set @arg1=5, @arg2='five';
+execute stmt3 using @arg1, @arg2;
+select * from t1 where a>3;
+a	b
+4	four
+5	five
+prepare stmt4 from 'update t1 set a=? where b=?';
+set @arg1=55, @arg2='five';
+execute stmt4 using @arg1, @arg2;
+select * from t1 where a>3;
+a	b
+4	four
+55	five
+prepare stmt4 from 'create table t2 (a int)';
+execute stmt4;
+prepare stmt4 from 'drop table t2';
+execute stmt4;
+execute stmt4;
+ERROR 42S02: Unknown table 't2'
+prepare stmt5 from 'select ? + a from t1';
+set @a=1;
+execute stmt5 using @a;
+? + a
+2
+3
+4
+5
+56
+execute stmt5 using @no_such_var;
+? + a
+NULL
+NULL
+NULL
+NULL
+NULL
+set @nullvar=1;
+set @nullvar=NULL;
+execute stmt5 using @nullvar;
+? + a
+NULL
+NULL
+NULL
+NULL
+NULL
+set @nullvar2=NULL;
+execute stmt5 using @nullvar2;
+? + a
+NULL
+NULL
+NULL
+NULL
+NULL
+drop table t1;
diff --git a/mysql-test/t/ps.test b/mysql-test/t/ps.test
new file mode 100644
index 0000000000000000000000000000000000000000..ab698174161aaa91c8bdeec8d10ea2224a465455
--- /dev/null
+++ b/mysql-test/t/ps.test
@@ -0,0 +1,79 @@
+#
+# SQL Syntax for Prepared Statements test
+#
+--disable_warnings
+drop table if exists t1,t2;
+--enable_warnings
+
+create table t1
+(
+  a int primary key,
+  b char(10),
+);
+insert into t1 values (1,'one');
+insert into t1 values (2,'two');
+insert into t1 values (3,'three');
+insert into t1 values (4,'four');
+
+# basic functionality
+set @a=2;
+prepare stmt1 from 'select * from t1 where a <= ?';
+execute stmt1 using @a;
+set @a=3;
+execute stmt1 using @a;
+
+# non-existant statement
+--error 1243
+deallocate prepare no_such_statement;
+
+--error 1210
+execute stmt1;
+
+# Nesting ps commands is not allowed: 
+--error 1064
+prepare stmt2 from 'prepare nested_stmt from "select 1"';
+
+--error 1064
+prepare stmt2 from 'execute stmt1';
+
+--error 1064
+prepare stmt2 from 'deallocate prepare z';
+
+# PS insert 
+prepare stmt3 from 'insert into t1 values (?,?)';
+set @arg1=5, @arg2='five';
+execute stmt3 using @arg1, @arg2;
+select * from t1 where a>3;
+
+# PS update 
+prepare stmt4 from 'update t1 set a=? where b=?';
+set @arg1=55, @arg2='five';
+execute stmt4 using @arg1, @arg2;
+select * from t1 where a>3;
+
+# PS create/delete
+prepare stmt4 from 'create table t2 (a int)';
+execute stmt4;
+prepare stmt4 from 'drop table t2';
+execute stmt4;
+
+# Do something that will cause error
+--error 1051
+execute stmt4;
+
+# placeholders in result field names.
+prepare stmt5 from 'select ? + a from t1';
+set @a=1;
+execute stmt5 using @a;
+
+execute stmt5 using @no_such_var;
+
+set @nullvar=1;
+set @nullvar=NULL;
+execute stmt5 using @nullvar;
+
+set @nullvar2=NULL;
+execute stmt5 using @nullvar2;
+
+drop table t1;
+
diff --git a/sql/item.cc b/sql/item.cc
index 2584c1cafb3a8cb083a00f70b0a4a125335dbbd5..bb8cf9a33cf5e06cc0e8a829b38125a13b318b04 100644
--- a/sql/item.cc
+++ b/sql/item.cc
@@ -635,16 +635,21 @@ void Item_param::set_double(double value)
 }
 
 
-void Item_param::set_value(const char *str, uint length)
+void Item_param::set_value(const char *str, uint length, CHARSET_INFO *ci)
 {
   DBUG_ENTER("Item_param::set_value");
-  str_value.copy(str,length,default_charset());
+  str_value.copy(str,length,ci);
   item_type= STRING_ITEM;
   value_is_set= 1;
   DBUG_PRINT("info", ("string: %s", str_value.ptr()));
   DBUG_VOID_RETURN;
 }
 
+void Item_param::set_value(const char *str, uint length)
+{
+  set_value(str, length, default_charset());
+}
+
 
 void Item_param::set_time(TIME *tm, timestamp_type type)
 { 
@@ -1493,7 +1498,7 @@ bool Item::send(Protocol *protocol, String *buffer)
   }
   case MYSQL_TYPE_TINY:
   {
-    longlong nr;
+    longlong nr;  
     nr= val_int();
     if (!null_value)
       result= protocol->store_tiny(nr);
diff --git a/sql/item.h b/sql/item.h
index 0f2927b0d99755b8772b3f2df6ceb662b804ea33..25b8bbe06a68c4f82d5584c26a875067ed0e286d 100644
--- a/sql/item.h
+++ b/sql/item.h
@@ -413,6 +413,7 @@ public:
   void set_int(longlong i);
   void set_double(double i);
   void set_value(const char *str, uint length);
+  void set_value(const char *str, uint length, CHARSET_INFO *ci);
   void set_long_str(const char *str, ulong length);
   void set_long_binary(const char *str, ulong length);
   void set_longdata(const char *str, ulong length);
diff --git a/sql/lex.h b/sql/lex.h
index 94ea0295f056d279fa28d9ecbd273538b2f38532..3274d544744c2de6ad21f2c570943af4d159c337 100644
--- a/sql/lex.h
+++ b/sql/lex.h
@@ -133,6 +133,7 @@ static SYMBOL symbols[] = {
   { "DAY_MICROSECOND",	SYM(DAY_MICROSECOND_SYM)},
   { "DAY_MINUTE",	SYM(DAY_MINUTE_SYM)},
   { "DAY_SECOND",	SYM(DAY_SECOND_SYM)},
+  { "DEALLOCATE",       SYM(DEALLOCATE_SYM)},     
   { "DEC",		SYM(DECIMAL_SYM)},
   { "DECIMAL",		SYM(DECIMAL_SYM)},
   { "DEFAULT",		SYM(DEFAULT)},
@@ -322,6 +323,7 @@ static SYMBOL symbols[] = {
   { "POINT",		SYM(POINT_SYM)},
   { "POLYGON",		SYM(POLYGON)},
   { "PRECISION",	SYM(PRECISION)},
+  { "PREPARE",          SYM(PREPARE_SYM)},
   { "PREV",		SYM(PREV_SYM)},
   { "PRIMARY",		SYM(PRIMARY_SYM)},
   { "PRIVILEGES",	SYM(PRIVILEGES)},
diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h
index 1175b93b9ba52e2c2a80307b5eeafd5661dddde6..479b1d29be5657bb8a90b07a118cebbb9cba585a 100644
--- a/sql/mysql_priv.h
+++ b/sql/mysql_priv.h
@@ -634,8 +634,10 @@ int mysqld_show_column_types(THD *thd);
 int mysqld_help (THD *thd, const char *text);
 
 /* sql_prepare.cc */
-void mysql_stmt_prepare(THD *thd, char *packet, uint packet_length);
+int mysql_stmt_prepare(THD *thd, char *packet, uint packet_length, 
+                       LEX_STRING *name=NULL);
 void mysql_stmt_execute(THD *thd, char *packet, uint packet_length);
+void mysql_sql_stmt_execute(THD *thd, LEX_STRING *stmt_name);
 void mysql_stmt_free(THD *thd, char *packet);
 void mysql_stmt_reset(THD *thd, char *packet);
 void mysql_stmt_get_longdata(THD *thd, char *pos, ulong packet_length);
diff --git a/sql/mysqld.cc b/sql/mysqld.cc
index 5176ee33a17821a7a85bbebae7995cf7ba5c9bdb..3db8101366ea86bf7db5232662eaeedfb9e3fad3 100644
--- a/sql/mysqld.cc
+++ b/sql/mysqld.cc
@@ -4809,6 +4809,9 @@ struct show_var_st status_vars[]= {
   {"Com_unlock_tables",	       (char*) (com_stat+(uint) SQLCOM_UNLOCK_TABLES),SHOW_LONG},
   {"Com_update",	       (char*) (com_stat+(uint) SQLCOM_UPDATE),SHOW_LONG},
   {"Com_update_multi",	       (char*) (com_stat+(uint) SQLCOM_UPDATE_MULTI),SHOW_LONG},
+  {"Com_prepare_sql",          (char*) (com_stat+(uint) SQLCOM_PREPARE), SHOW_LONG}, 
+  {"Com_execute_sql",          (char*) (com_stat+(uint) SQLCOM_EXECUTE), SHOW_LONG},
+  {"Com_dealloc_sql",          (char*) (com_stat+(uint) SQLCOM_DEALLOCATE_PREPARE), SHOW_LONG},
   {"Connections",              (char*) &thread_id,              SHOW_LONG_CONST},
   {"Created_tmp_disk_tables",  (char*) &created_tmp_disk_tables,SHOW_LONG},
   {"Created_tmp_files",	       (char*) &my_tmp_file_created,	SHOW_LONG},
diff --git a/sql/sql_class.cc b/sql/sql_class.cc
index 1b4c8bec416cc17c09b5e17e0118204bf54896cb..87b6c49a4b72c61df429b3c7e2faaeb90a84cd0a 100644
--- a/sql/sql_class.cc
+++ b/sql/sql_class.cc
@@ -78,7 +78,6 @@ extern "C" void free_user_var(user_var_entry *entry)
   my_free((char*) entry,MYF(0));
 }
 
-
 /****************************************************************************
 ** Thread specific functions
 ****************************************************************************/
@@ -160,7 +159,7 @@ THD::THD():user_time(0), current_statement(0), is_fatal_error(0),
 			  16);
   else
     bzero((char*) &user_var_events, sizeof(user_var_events));
-
+  
   /* Protocol */
   protocol= &protocol_simple;			// Default protocol
   protocol_simple.init(this);
@@ -1199,6 +1198,7 @@ Statement::Statement(THD *thd)
   query_length(0),
   free_list(0)
 {
+  name.str= NULL;
   init_sql_alloc(&mem_root,
                  thd->variables.query_alloc_block_size,
                  thd->variables.query_prealloc_size);
@@ -1282,17 +1282,52 @@ static void delete_statement_as_hash_key(void *key)
   delete (Statement *) key;
 }
 
+byte *get_stmt_name_hash_key(Statement *entry, uint *length,
+		             my_bool not_used __attribute__((unused)))
+{
+  *length=(uint) entry->name.length;
+  return (byte*) entry->name.str;
+}
+
 C_MODE_END
 
 Statement_map::Statement_map() :
   last_found_statement(0)
 {
-  enum { START_HASH_SIZE = 16 };
-  hash_init(&st_hash, default_charset_info, START_HASH_SIZE, 0, 0,
+  enum
+  {
+    START_STMT_HASH_SIZE = 16,
+    START_NAME_HASH_SIZE = 16
+  };
+  hash_init(&st_hash, default_charset_info, START_STMT_HASH_SIZE, 0, 0,
             get_statement_id_as_hash_key,
             delete_statement_as_hash_key, MYF(0));
+  hash_init(&names_hash, &my_charset_bin, START_NAME_HASH_SIZE, 0, 0,
+	    (hash_get_key) get_stmt_name_hash_key,
+	    NULL,MYF(0));
 }
 
+int Statement_map::insert(Statement *statement)
+{
+  int rc= my_hash_insert(&st_hash, (byte *) statement);
+  if (rc == 0)
+    last_found_statement= statement;
+  if (statement->name.str)
+  {
+    /*
+      If there is a statement with the same name, remove it. It is ok to 
+      remove old and fail to insert new one at the same time.
+    */
+    Statement *old_stmt;
+    if ((old_stmt= find_by_name(&statement->name)))
+      erase(old_stmt); 
+    if ((rc= my_hash_insert(&names_hash, (byte*)statement)))
+      hash_delete(&st_hash, (byte*)statement);
+  }
+  return rc;
+}
+
+
 bool select_dumpvar::send_data(List<Item> &items)
 {
   List_iterator_fast<Item_func_set_user_var> li(vars);
diff --git a/sql/sql_class.h b/sql/sql_class.h
index aa526d5e4749428415dd4c708b469e4821ebec3c..8ccfe3cddd51886242914c38557ebe8e0a926faf 100644
--- a/sql/sql_class.h
+++ b/sql/sql_class.h
@@ -456,6 +456,7 @@ public:
   */
   bool allow_sum_func;
 
+  LEX_STRING name; /* name for named prepared statements */
   LEX *lex;                                     // parse tree descriptor
   /*
     Points to the query associated with this statement. It's const, but
@@ -522,8 +523,14 @@ public:
 
 
 /*
-  Used to seek all existing statements in the connection
-  Deletes all statements in destructor.
+  Container for all statements created/used in a connection.
+  Statements in Statement_map have unique Statement::id (guaranteed by id
+  assignment in Statement::Statement)
+  Non-empty statement names are unique too: attempt to insert a new statement
+  with duplicate name causes older statement to be deleted
+  
+  Statements are auto-deleted when they are removed from the map and when the
+  map is deleted.
 */
 
 class Statement_map
@@ -531,12 +538,14 @@ class Statement_map
 public:
   Statement_map();
   
-  int insert(Statement *statement)
+  int insert(Statement *statement);
+
+  Statement *find_by_name(LEX_STRING *name)
   {
-    int rc= my_hash_insert(&st_hash, (byte *) statement);
-    if (rc == 0)
-      last_found_statement= statement;
-    return rc;
+    Statement *stmt;
+    stmt= (Statement*)hash_search(&names_hash, (byte*)name->str,
+                                  name->length);
+    return stmt;
   }
 
   Statement *find(ulong id)
@@ -550,15 +559,21 @@ public:
   {
     if (statement == last_found_statement)
       last_found_statement= 0;
+    if (statement->name.str)
+    {
+      hash_delete(&names_hash, (byte *) statement);  
+    }
     hash_delete(&st_hash, (byte *) statement);
   }
 
   ~Statement_map()
   {
     hash_free(&st_hash);
+    hash_free(&names_hash);
   }
 private:
   HASH st_hash;
+  HASH names_hash;
   Statement *last_found_statement;
 };
 
diff --git a/sql/sql_lex.h b/sql/sql_lex.h
index ea1accabf37eb2664c9001e9a5bbef047232be97..2f1f7e64ac4553d32e38d1889043eaf065ff0713 100644
--- a/sql/sql_lex.h
+++ b/sql/sql_lex.h
@@ -76,6 +76,7 @@ enum enum_sql_command {
   SQLCOM_SHOW_COLUMN_TYPES, SQLCOM_SHOW_STORAGE_ENGINES, SQLCOM_SHOW_PRIVILEGES,
   SQLCOM_HELP, SQLCOM_DROP_USER, SQLCOM_REVOKE_ALL, SQLCOM_CHECKSUM,
 
+  SQLCOM_PREPARE, SQLCOM_EXECUTE, SQLCOM_DEALLOCATE_PREPARE,
   /* This should be the last !!! */
   SQLCOM_END
 };
@@ -592,6 +593,11 @@ typedef struct st_lex
   bool in_comment, ignore_space, verbose, simple_alter, no_write_to_binlog;
   bool derived_tables;
   bool safe_to_cache_query;
+  /* Prepared statements SQL syntax:*/
+  LEX_STRING prepared_stmt_name; /* Statement name (in all queries) */
+  LEX_STRING prepared_stmt_code; /* Statement query (in PREPARE )*/
+  /* Names of user variables holding parameters (in EXECUTE) */
+  List<LEX_STRING> prepared_stmt_params; 
   st_lex() {}
   inline void uncacheable(uint8 cause)
   {
diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc
index 1e3beeca8a915c245c3a086f9f004c9f62fb66df..64fa398e5f4639609e6d2808b83941ba4e6bfb6d 100644
--- a/sql/sql_parse.cc
+++ b/sql/sql_parse.cc
@@ -1957,7 +1957,44 @@ mysql_execute_command(THD *thd)
     }
     break;
   }
-
+  case SQLCOM_PREPARE:
+  {   
+    DBUG_PRINT("info", ("PREPARE: %.*s FROM '%.*s' \n", 
+                        lex->prepared_stmt_name.length,
+                        lex->prepared_stmt_name.str,
+                        lex->prepared_stmt_code.length,
+                        lex->prepared_stmt_code.str));
+    thd->command= COM_PREPARE;
+    if (!mysql_stmt_prepare(thd, lex->prepared_stmt_code.str,
+                            lex->prepared_stmt_code.length + 1, 
+                            &lex->prepared_stmt_name))    
+      send_ok(thd, 0L, 0L, "Statement prepared");
+    break;
+  }
+  case SQLCOM_EXECUTE:
+  {
+    DBUG_PRINT("info", ("EXECUTE: %.*s\n", 
+                        lex->prepared_stmt_name.length,
+                        lex->prepared_stmt_name.str));
+    mysql_sql_stmt_execute(thd, &lex->prepared_stmt_name);
+    lex->prepared_stmt_params.empty();
+    break;
+  }
+  case SQLCOM_DEALLOCATE_PREPARE:
+  {
+    Statement* stmt;
+    DBUG_PRINT("info", ("DEALLOCATE PREPARE: %.*s\n", 
+                        lex->prepared_stmt_name.length,
+                        lex->prepared_stmt_name.str));
+    if ((stmt= thd->stmt_map.find_by_name(&lex->prepared_stmt_name)))
+    {
+      thd->stmt_map.erase(stmt);
+      send_ok(thd);
+    }
+    else
+      send_error(thd,ER_UNKNOWN_STMT_HANDLER,"Undefined prepared statement");
+    break;
+  }
   case SQLCOM_DO:
     if (tables && ((res= check_table_access(thd, SELECT_ACL, tables,0)) ||
 		   (res= open_and_lock_tables(thd,tables))))
diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc
index 69c5be69210b4ba17bb9ca9e7a720293453bd1df..e578dc988f8e64b384a1c7e6cd1412073d24b7a4 100644
--- a/sql/sql_prepare.cc
+++ b/sql/sql_prepare.cc
@@ -99,12 +99,15 @@ public:
 #else
   bool (*set_params_data)(Prepared_statement *st);
 #endif
+  bool (*set_params_from_vars)(Prepared_statement *stmt, 
+                               List<LEX_STRING>& varnames);
 public:
   Prepared_statement(THD *thd_arg);
   virtual ~Prepared_statement();
   virtual Statement::Type type() const;
 };
 
+static void execute_stmt(THD *thd, Prepared_statement *stmt);
 
 /******************************************************************************
   Implementation
@@ -632,6 +635,116 @@ static bool emb_insert_params_withlog(Prepared_statement *stmt)
 #endif /*!EMBEDDED_LIBRARY*/
 
 
+/* 
+  Set prepared statement parameters from user variables.
+  SYNOPSIS
+    insert_params_from_vars()
+      stmt      Statement
+      varnames  List of variables. Caller must ensure that number of variables
+                in the list is equal to number of statement parameters
+
+*/
+
+static bool insert_params_from_vars(Prepared_statement *stmt, 
+                                    List<LEX_STRING>& varnames)
+{
+  Item_param **begin= stmt->param_array;
+  Item_param **end= begin + stmt->param_count;
+  user_var_entry *entry;
+  LEX_STRING *varname;
+  DBUG_ENTER("insert_params_from_vars"); 
+
+  List_iterator<LEX_STRING> var_it(varnames);
+  for (Item_param **it= begin; it < end; ++it)
+  {
+    Item_param *param= *it;
+    varname= var_it++;
+    if ((entry= (user_var_entry*)hash_search(&stmt->thd->user_vars, 
+                                             (byte*) varname->str,
+                                             varname->length))
+        && entry->value)
+    {
+      param->item_result_type= entry->type;
+      switch (entry->type)
+      {
+        case REAL_RESULT:
+          param->set_double(*(double*)entry->value);
+          break;
+        case INT_RESULT:
+          param->set_int(*(longlong*)entry->value);
+          break;
+        case STRING_RESULT:
+          param->set_value(entry->value, entry->length, 
+                           entry->collation.collation);
+          break;
+        default:
+          DBUG_ASSERT(0);
+      }
+    }
+    else
+      param->maybe_null= param->null_value= param->value_is_set= 1;
+  }
+  DBUG_RETURN(0);
+}
+
+static bool insert_params_from_vars_with_log(Prepared_statement *stmt,
+                                             List<LEX_STRING>& varnames)
+{
+  Item_param **begin= stmt->param_array;
+  Item_param **end= begin + stmt->param_count;
+  user_var_entry *entry;
+  LEX_STRING *varname;
+  DBUG_ENTER("insert_params_from_vars"); 
+
+  List_iterator<LEX_STRING> var_it(varnames);
+  String str, query;
+  const String *res;
+  uint32 length= 0;
+  if (query.copy(stmt->query, stmt->query_length, default_charset_info))
+    DBUG_RETURN(1);
+
+  for (Item_param **it= begin; it < end; ++it)
+  {
+    Item_param *param= *it;
+    varname= var_it++;
+    if ((entry= (user_var_entry*)hash_search(&stmt->thd->user_vars, 
+                                             (byte*) varname->str,
+                                             varname->length))
+        && entry->value)
+    {
+      param->item_result_type= entry->type;
+      switch (entry->type)
+      {
+        case REAL_RESULT:
+          param->set_double(*(double*)entry->value);
+          break;
+        case INT_RESULT:
+          param->set_int(*(longlong*)entry->value);
+          break;
+        case STRING_RESULT:
+          param->set_value(entry->value, entry->length, 
+                           entry->collation.collation);
+          break;
+        default:
+          DBUG_ASSERT(0);
+      }
+      res= param->query_val_str(&str);
+    }
+    else
+    {
+      param->maybe_null= param->null_value= param->value_is_set= 1;
+      res= &my_null_string;
+    }
+
+    if (query.replace(param->pos_in_query+length, 1, *res))
+      DBUG_RETURN(1);
+    length+= res->length()-1;
+  }
+  if (alloc_query(stmt->thd, (char *) query.ptr(), query.length()+1))
+    DBUG_RETURN(1);
+  DBUG_RETURN(0);
+}
+
 /*
   Validate INSERT statement: 
 
@@ -828,7 +941,7 @@ static int mysql_test_delete(Prepared_statement *stmt,
 */
 
 static int mysql_test_select(Prepared_statement *stmt,
-			     TABLE_LIST *tables)
+			     TABLE_LIST *tables, bool text_protocol)
 {
   THD *thd= stmt->thd;
   LEX *lex= stmt->lex;
@@ -860,7 +973,7 @@ static int mysql_test_select(Prepared_statement *stmt,
 
   if (lex->describe)
   {
-    if (send_prep_stmt(stmt, 0))
+    if (!text_protocol && send_prep_stmt(stmt, 0))
       goto err;
   }
   else
@@ -874,14 +987,16 @@ static int mysql_test_select(Prepared_statement *stmt,
       goto err_prep;
     }
 
-    if (send_prep_stmt(stmt, lex->select_lex.item_list.elements) ||
+    if (!text_protocol)
+    {
+      if (send_prep_stmt(stmt, lex->select_lex.item_list.elements) ||
         thd->protocol_simple.send_fields(&lex->select_lex.item_list, 0)
 #ifndef EMBEDDED_LIBRARY
-        || net_flush(&thd->net)
+          || net_flush(&thd->net)
 #endif
-       )
-      goto err_prep;
-
+         )
+        goto err_prep;
+    }
     unit->cleanup();
   }
   thd->free_temporary_memory_pool_for_ps_preparing();
@@ -1159,7 +1274,7 @@ static int mysql_test_insert_select(Prepared_statement *stmt,
     0   success
     1   error, sent to client
 */
-static int send_prepare_results(Prepared_statement *stmt)
+static int send_prepare_results(Prepared_statement *stmt, bool text_protocol)
 {   
   THD *thd= stmt->thd;
   LEX *lex= stmt->lex;
@@ -1196,7 +1311,7 @@ static int send_prepare_results(Prepared_statement *stmt)
     break;
 
   case SQLCOM_SELECT:
-    if ((res= mysql_test_select(stmt, tables)))
+    if ((res= mysql_test_select(stmt, tables, text_protocol)))
       goto error;
     /* Statement and field info has already been sent */
     DBUG_RETURN(0);
@@ -1254,7 +1369,7 @@ static int send_prepare_results(Prepared_statement *stmt)
     goto error;
   }
   if (res == 0)
-    DBUG_RETURN(send_prep_stmt(stmt, 0));
+    DBUG_RETURN(text_protocol?0:send_prep_stmt(stmt, 0));
 error:
   if (res < 0)
     send_error(thd, thd->killed ? ER_SERVER_SHUTDOWN : 0);
@@ -1295,6 +1410,14 @@ static bool init_param_array(Prepared_statement *stmt)
 
 
 /*
+  SYNOPSIS
+    mysql_stmt_prepare()
+      packet         Prepared query 
+      packet_length  query length, with ignored trailing NULL or quote char.
+      name           NULL or statement name. For unnamed statements binary PS
+                     protocol is used, for named statmenents text protocol is 
+                     used.
+
   Parse the query and send the total number of parameters 
   and resultset metadata information back to client (if any), 
   without executing the query i.e. without any log/disk 
@@ -1306,9 +1429,11 @@ static bool init_param_array(Prepared_statement *stmt)
   list in lex->param_array, so that a fast and direct
   retrieval can be made without going through all field
   items.
+   
 */
 
-void mysql_stmt_prepare(THD *thd, char *packet, uint packet_length)
+int mysql_stmt_prepare(THD *thd, char *packet, uint packet_length,
+                       LEX_STRING *name)
 {
   LEX *lex;
   Prepared_statement *stmt= new Prepared_statement(thd);
@@ -1320,14 +1445,26 @@ void mysql_stmt_prepare(THD *thd, char *packet, uint packet_length)
   if (stmt == 0)
   {
     send_error(thd, ER_OUT_OF_RESOURCES);
-    DBUG_VOID_RETURN;
+    DBUG_RETURN(1);
+  }
+
+  if (name)
+  {
+    stmt->name.length= name->length;
+    if (!(stmt->name.str= my_memdup((byte*)name->str, name->length, 
+                                    MYF(MY_WME))))
+    {
+      delete stmt;
+      send_error(thd, ER_OUT_OF_RESOURCES);
+      DBUG_RETURN(1);
+    }
   }
 
   if (thd->stmt_map.insert(stmt))
   {
     delete stmt;
     send_error(thd, ER_OUT_OF_RESOURCES);
-    DBUG_VOID_RETURN;
+    DBUG_RETURN(1);
   }
 
   thd->stmt_backup.set_statement(thd);
@@ -1344,7 +1481,7 @@ void mysql_stmt_prepare(THD *thd, char *packet, uint packet_length)
     /* Statement map deletes statement on erase */
     thd->stmt_map.erase(stmt);
     send_error(thd, ER_OUT_OF_RESOURCES);
-    DBUG_VOID_RETURN;
+    DBUG_RETURN(1);
   }
 
   mysql_log.write(thd, COM_PREPARE, "%s", packet);
@@ -1356,7 +1493,7 @@ void mysql_stmt_prepare(THD *thd, char *packet, uint packet_length)
 
   error= yyparse((void *)thd) || thd->is_fatal_error ||
          init_param_array(stmt) ||
-         send_prepare_results(stmt);
+         send_prepare_results(stmt, test(name));
 
   /* restore to WAIT_PRIOR: QUERY_PRIOR is set inside alloc_query */
   if (!(specialflag & SPECIAL_NO_PRIOR))
@@ -1372,6 +1509,7 @@ void mysql_stmt_prepare(THD *thd, char *packet, uint packet_length)
   {
     /* Statement map deletes statement on erase */
     thd->stmt_map.erase(stmt);
+    stmt= NULL;
     /* error is sent inside yyparse/send_prepare_results */
   }
   else
@@ -1386,7 +1524,7 @@ void mysql_stmt_prepare(THD *thd, char *packet, uint packet_length)
       sl->prep_where= sl->where;
     }
   }
-  DBUG_VOID_RETURN;
+  DBUG_RETURN(!stmt);
 }
 
 /* Reinit statement before execution */
@@ -1439,6 +1577,7 @@ static void reset_stmt_for_execute(Prepared_statement *stmt)
   }
 }
 
+
 /*
   Executes previously prepared query.
   If there is any parameters, then replace markers with the data supplied
@@ -1447,7 +1586,6 @@ static void reset_stmt_for_execute(Prepared_statement *stmt)
     mysql_stmt_execute()
 */
 
-
 void mysql_stmt_execute(THD *thd, char *packet, uint packet_length)
 {
   ulong stmt_id= uint4korr(packet);
@@ -1471,11 +1609,6 @@ void mysql_stmt_execute(THD *thd, char *packet, uint packet_length)
     DBUG_VOID_RETURN;
   }
 
-  thd->stmt_backup.set_statement(thd);
-  thd->set_statement(stmt);
-
-  reset_stmt_for_execute(stmt);
-
 #ifndef EMBEDDED_LIBRARY
   if (stmt->param_count)
   {
@@ -1493,21 +1626,69 @@ void mysql_stmt_execute(THD *thd, char *packet, uint packet_length)
   if (stmt->param_count && stmt->set_params_data(stmt))
     goto set_params_data_err;
 #endif
-
-  if (!(specialflag & SPECIAL_NO_PRIOR))
-    my_pthread_setprio(pthread_self(),QUERY_PRIOR);
- 
-  /*
-    TODO:
-    Also, have checks on basic executions such as mysql_insert(), 
-    mysql_delete(), mysql_update() and mysql_select() to not to 
-    have re-check on setup_* and other things ..
-  */
   thd->protocol= &thd->protocol_prep;           // Switch to binary protocol
-  mysql_execute_command(thd);
+  execute_stmt(thd, stmt);
   thd->lex->unit.cleanup();
   thd->protocol= &thd->protocol_simple;         // Use normal protocol
+  DBUG_VOID_RETURN;
+
+set_params_data_err:
+  my_error(ER_WRONG_ARGUMENTS, MYF(0), "mysql_execute");
+  send_error(thd);
+  DBUG_VOID_RETURN;
+}
+
+
+/*
+  Execute prepared statement using parameter values from 
+  lex->prepared_stmt_params and send result to the client using text protocol.
+*/
+
+void mysql_sql_stmt_execute(THD *thd, LEX_STRING *stmt_name)
+{
+  Prepared_statement *stmt;
+  DBUG_ENTER("mysql_stmt_execute");
+    
+  if (!(stmt= (Prepared_statement*)thd->stmt_map.find_by_name(stmt_name)))
+  {
+      send_error(thd, ER_UNKNOWN_STMT_HANDLER, 
+                 "Undefined prepared statement");
+      DBUG_VOID_RETURN;
+  }
+
+  if (stmt->param_count != thd->lex->prepared_stmt_params.elements)
+  {
+    my_error(ER_WRONG_ARGUMENTS, MYF(0), "mysql_execute");
+    send_error(thd);
+    DBUG_VOID_RETURN;
+  }
+  /* Item_param allows setting parameters in COM_EXECUTE only */
+  thd->command= COM_EXECUTE;
+
+  if (stmt->set_params_from_vars(stmt, thd->lex->prepared_stmt_params))
+  {
+    my_error(ER_WRONG_ARGUMENTS, MYF(0), "mysql_execute");
+    send_error(thd);
+  }
+
+  execute_stmt(thd, stmt);
+  DBUG_VOID_RETURN;
+}
+
+/*
+  Execute prepared statement.
+  Caller must set parameter values and thd::protocol.
+*/
+static void execute_stmt(THD *thd, Prepared_statement *stmt)
+{
+  DBUG_ENTER("execute_stmt");
+  thd->stmt_backup.set_statement(thd);
+  thd->set_statement(stmt);
+  reset_stmt_for_execute(stmt);
 
+  if (!(specialflag & SPECIAL_NO_PRIOR))
+    my_pthread_setprio(pthread_self(),QUERY_PRIOR);
+  mysql_execute_command(thd);
   if (!(specialflag & SPECIAL_NO_PRIOR))
     my_pthread_setprio(pthread_self(), WAIT_PRIOR);
 
@@ -1515,15 +1696,10 @@ void mysql_stmt_execute(THD *thd, char *packet, uint packet_length)
   close_thread_tables(thd); // to close derived tables
   thd->set_statement(&thd->stmt_backup);
   DBUG_VOID_RETURN;
-
-set_params_data_err:
-  thd->set_statement(&thd->stmt_backup);
-  my_error(ER_WRONG_ARGUMENTS, MYF(0), "mysql_execute");
-  send_error(thd);
-  DBUG_VOID_RETURN;
 }
 
 
+
 /*
     Reset a prepared statement, in case there was an error in send_longdata.
     Note: we don't send any reply to that command.
@@ -1665,6 +1841,7 @@ Prepared_statement::Prepared_statement(THD *thd_arg)
   if (mysql_bin_log.is_open())
   {
     log_full_query= 1;
+    set_params_from_vars= insert_params_from_vars_with_log;
 #ifndef EMBEDDED_LIBRARY
     set_params= insert_params_withlog;
 #else
@@ -1672,17 +1849,22 @@ Prepared_statement::Prepared_statement(THD *thd_arg)
 #endif
   }
   else
+  {
+    set_params_from_vars= insert_params_from_vars;
 #ifndef EMBEDDED_LIBRARY
     set_params= insert_params;
 #else
     set_params_data= emb_insert_params;
 #endif
+  }
 }
 
 
 Prepared_statement::~Prepared_statement()
 {
   free_items(free_list);
+  if (name.str)
+    my_free(name.str, MYF(0));
 }
 
 
diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy
index b87b0b29677c04e85944bc58604381749b522cdf..a25bd3f17bce025d1dad032de004424979b29dea 100644
--- a/sql/sql_yacc.yy
+++ b/sql/sql_yacc.yy
@@ -431,6 +431,8 @@ bool my_yyoverflow(short **a, YYSTYPE **b,int *yystacksize);
 %token	MEDIUMTEXT
 %token	NUMERIC_SYM
 %token	PRECISION
+%token  PREPARE_SYM
+%token  DEALLOCATE_SYM
 %token	QUICK
 %token	REAL
 %token	SIGNED_SYM
@@ -723,6 +725,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b,int *yystacksize);
 	precision subselect_start opt_and charset
 	subselect_end select_var_list select_var_list_init help opt_len
 	opt_extended_describe
+        prepare execute deallocate 
 END_OF_INPUT
 
 %type <NONE>
@@ -759,10 +762,12 @@ verb_clause:
 	| checksum
 	| commit
 	| create
+        | deallocate 
 	| delete
 	| describe
 	| do
 	| drop
+        | execute 
 	| flush
 	| grant
 	| handler
@@ -774,6 +779,7 @@ verb_clause:
 	| optimize
         | keycache
 	| preload
+        | prepare 
 	| purge
 	| rename
 	| repair
@@ -794,6 +800,72 @@ verb_clause:
 	| use
         ;
 
+deallocate:
+        DEALLOCATE_SYM PREPARE_SYM ident 
+        {
+          THD *thd=YYTHD;
+	  LEX *lex= thd->lex;
+          if (thd->command == COM_PREPARE)
+          {
+            yyerror(ER(ER_SYNTAX_ERROR));
+            YYABORT;
+          }
+	  lex->sql_command= SQLCOM_DEALLOCATE_PREPARE;
+          lex->prepared_stmt_name= $3;
+        };
+
+prepare:
+        PREPARE_SYM ident FROM TEXT_STRING_sys
+        {
+          THD *thd=YYTHD;
+	  LEX *lex= thd->lex;
+          if (thd->command == COM_PREPARE)
+          {
+            yyerror(ER(ER_SYNTAX_ERROR));
+            YYABORT;
+          }
+	  lex->sql_command= SQLCOM_PREPARE;
+          lex->prepared_stmt_name= $2;
+          lex->prepared_stmt_code= $4;
+        };
+        
+
+execute:
+        EXECUTE_SYM ident
+        {
+          THD *thd=YYTHD;
+	  LEX *lex= thd->lex;
+          if (thd->command == COM_PREPARE)
+          {
+            yyerror(ER(ER_SYNTAX_ERROR));
+            YYABORT;
+          }
+	  lex->sql_command= SQLCOM_EXECUTE;
+          lex->prepared_stmt_name= $2;
+        }
+        execute_using
+        {}
+        ;
+
+execute_using:
+        /* nothing */
+        | USING execute_var_list
+        ;
+
+execute_var_list:
+	execute_var_list ',' execute_var_ident
+	| execute_var_ident
+        ;
+
+execute_var_ident: '@' ident_or_text
+        {
+          LEX *lex=Lex;
+          LEX_STRING *lexstr= (LEX_STRING*)sql_memdup(&$2, sizeof(LEX_STRING));
+          if (!lexstr || lex->prepared_stmt_params.push_back(lexstr))
+              YYABORT;
+	}
+        ;
+
 /* help */
 
 help:
@@ -4899,6 +4971,7 @@ keyword:
 	| DATETIME		{}
 	| DATE_SYM		{}
 	| DAY_SYM		{}
+        | DEALLOCATE_SYM        {}
 	| DELAY_KEY_WRITE_SYM	{}
 	| DES_KEY_FILE		{}
 	| DIRECTORY_SYM		{}
@@ -4996,6 +5069,7 @@ keyword:
 	| PASSWORD		{}
 	| POINT_SYM		{}
 	| POLYGON		{}
+        | PREPARE_SYM           {}
 	| PREV_SYM		{}
 	| PROCESS		{}
 	| PROCESSLIST_SYM	{}