#At file:///mnt/raid/alik/MySQL/devel/bug-11638/6.0-rt-wl4435/
2652 Alexander Nozdrin 2008-06-17
A draft patch for WL#4435: Support OUT-parameters in prepared statements.
modified:
include/mysql.h
include/mysql_com.h
libmysql/client_settings.h
libmysql/libmysql.c
libmysqld/lib_sql.cc
mysql-test/r/ps.result
mysql-test/t/ps.test
sql/item.cc
sql/item.h
sql/item_func.cc
sql/item_func.h
sql/mysql_priv.h
sql/protocol.cc
sql/protocol.h
sql/sp_head.cc
sql/sql_class.cc
sql/sql_class.h
sql/sql_prepare.cc
tests/mysql_client_test.c
per-file messages:
include/mysql.h
Add a prototype for new mysql_stmt_next_result().
include/mysql_com.h
Add CLIENT_PS_OUT_PARAMS -- a client capability indicating that the client supports OUT-parameters.
libmysql/libmysql.c
Add mysql_stmt_next_result() -- analogue of mysql_next_result() for binary protocol.
libmysqld/lib_sql.cc
Refactoring: change prepare_for_send() so that it accepts only what it really needs -- a number of elements in the list.
mysql-test/t/ps.test
A test case for an SQL-part of the problem.
sql/protocol.cc
Refactoring: change prepare_for_send() so that it accepts only what it really needs -- a number of elements in the list.
sql/protocol.h
Refactoring: change prepare_for_send() so that it accepts only what it really needs -- a number of elements in the list.
sql/sql_class.cc
Add a context (THD) attribute to get/indicate the current prepared statement (if any).
sql/sql_class.h
Add a context (THD) attribute to get/indicate the current prepared statement (if any).
tests/mysql_client_test.c
Add a test case for a binary part of the problem.
=== modified file 'include/mysql.h'
--- a/include/mysql.h 2007-11-26 19:11:48 +0000
+++ b/include/mysql.h 2008-06-17 08:07:59 +0000
@@ -714,6 +714,7 @@ my_bool STDCALL mysql_rollback(MYSQL * m
my_bool STDCALL mysql_autocommit(MYSQL * mysql, my_bool auto_mode);
my_bool STDCALL mysql_more_results(MYSQL *mysql);
int STDCALL mysql_next_result(MYSQL *mysql);
+int STDCALL mysql_stmt_next_result(MYSQL_STMT *stmt);
void STDCALL mysql_close(MYSQL *sock);
=== modified file 'include/mysql_com.h'
--- a/include/mysql_com.h 2008-05-21 10:17:29 +0000
+++ b/include/mysql_com.h 2008-06-17 08:07:59 +0000
@@ -154,6 +154,7 @@ enum enum_server_command
#define CLIENT_SECURE_CONNECTION 32768 /* New 4.1 authentication */
#define CLIENT_MULTI_STATEMENTS (1UL << 16) /* Enable/disable multi-stmt support */
#define CLIENT_MULTI_RESULTS (1UL << 17) /* Enable/disable multi-results */
+#define CLIENT_PS_OUT_PARAMS (1UL << 18) /* Output parameters in PS-protocol */
#define CLIENT_SSL_VERIFY_SERVER_CERT (1UL << 30)
#define CLIENT_REMEMBER_OPTIONS (1UL << 31)
@@ -177,6 +178,7 @@ enum enum_server_command
CLIENT_SECURE_CONNECTION | \
CLIENT_MULTI_STATEMENTS | \
CLIENT_MULTI_RESULTS | \
+ CLIENT_PS_OUT_PARAMS | \
CLIENT_SSL_VERIFY_SERVER_CERT | \
CLIENT_REMEMBER_OPTIONS)
=== modified file 'libmysql/client_settings.h'
--- a/libmysql/client_settings.h 2007-09-29 19:31:08 +0000
+++ b/libmysql/client_settings.h 2008-06-17 08:07:59 +0000
@@ -16,9 +16,12 @@
extern uint mysql_port;
extern char * mysql_unix_port;
-#define CLIENT_CAPABILITIES (CLIENT_LONG_PASSWORD | CLIENT_LONG_FLAG | \
+#define CLIENT_CAPABILITIES (CLIENT_LONG_PASSWORD | \
+ CLIENT_LONG_FLAG | \
CLIENT_TRANSACTIONS | \
- CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONNECTION)
+ CLIENT_PROTOCOL_41 | \
+ CLIENT_SECURE_CONNECTION | \
+ CLIENT_PS_OUT_PARAMS)
sig_handler my_pipe_sig_handler(int sig);
void read_user_name(char *name);
=== modified file 'libmysql/libmysql.c'
--- a/libmysql/libmysql.c 2008-05-21 10:17:29 +0000
+++ b/libmysql/libmysql.c 2008-06-17 08:07:59 +0000
@@ -4832,6 +4832,29 @@ int STDCALL mysql_next_result(MYSQL *mys
}
+int STDCALL mysql_stmt_next_result(MYSQL_STMT *stmt)
+{
+ int rc;
+ DBUG_ENTER("mysql_stmt_next_result");
+
+ rc= mysql_next_result(stmt->mysql);
+
+ if (rc)
+ DBUG_RETURN(rc);
+
+ if (!(stmt->mysql->server_status & SERVER_MORE_RESULTS_EXISTS))
+ DBUG_RETURN(-1);
+
+ stmt->state= MYSQL_STMT_EXECUTE_DONE;
+ stmt->mysql->unbuffered_fetch_owner= &stmt->unbuffered_fetch_cancelled;
+ stmt->unbuffered_fetch_cancelled= FALSE;
+ stmt->read_row_func= stmt_read_row_unbuffered;
+ stmt->mysql->status= MYSQL_STATUS_GET_RESULT;
+
+ DBUG_RETURN(0);
+}
+
+
MYSQL_RES * STDCALL mysql_use_result(MYSQL *mysql)
{
return (*mysql->methods->use_result)(mysql);
=== modified file 'libmysqld/lib_sql.cc'
--- a/libmysqld/lib_sql.cc 2008-05-21 10:17:29 +0000
+++ b/libmysqld/lib_sql.cc 2008-06-17 08:07:59 +0000
@@ -963,7 +963,7 @@ bool Protocol::send_fields(List<Item> *l
if (flags & SEND_EOF)
write_eof_packet(thd, thd->server_status, thd->total_warn_count);
- DBUG_RETURN(prepare_for_send(list));
+ DBUG_RETURN(prepare_for_send(list->elements));
err:
my_error(ER_OUT_OF_RESOURCES, MYF(0)); /* purecov: inspected */
DBUG_RETURN(1); /* purecov: inspected */
=== modified file 'mysql-test/r/ps.result'
--- a/mysql-test/r/ps.result 2008-05-21 10:17:29 +0000
+++ b/mysql-test/r/ps.result 2008-06-17 08:07:59 +0000
@@ -2920,4 +2920,33 @@ execute stmt;
Db Name Definer Time zone Type Execute at Interval value Interval field Starts Ends Status Originator character_set_client collation_connection Database Collation
drop table t1;
deallocate prepare stmt;
+
End of 5.1 tests.
+
+#
+# Bug#11638: Cannot prepare and execute a stored procedure with
+# OUT parameter.
+#
+
+DROP PROCEDURE IF EXISTS p1;
+CREATE PROCEDURE p1(OUT v INT) SET v = 1;
+PREPARE stmt FROM 'CALL p1(?)';
+
+SET @x1 = NULL;
+EXECUTE stmt USING @x1;
+SELECT @x1;
+@x1
+1
+
+SET @x2 = 0;
+EXECUTE stmt USING @x2;
+SELECT @x2;
+@x2
+1
+
+DEALLOCATE PREPARE stmt;
+DROP PROCEDURE p1;
+
+# End of Bug#11638.
+
+End of 6.0 tests.
=== modified file 'mysql-test/t/ps.test'
--- a/mysql-test/t/ps.test 2008-05-21 10:17:29 +0000
+++ b/mysql-test/t/ps.test 2008-06-17 08:07:59 +0000
@@ -2998,5 +2998,48 @@ execute stmt;
drop table t1;
deallocate prepare stmt;
+###########################################################################
+--echo
--echo End of 5.1 tests.
+
+###########################################################################
+
+--echo
+--echo #
+--echo # Bug#11638: Cannot prepare and execute a stored procedure with
+--echo # OUT parameter.
+--echo #
+--echo
+
+--disable_warnings
+DROP PROCEDURE IF EXISTS p1;
+--enable_warnings
+
+CREATE PROCEDURE p1(OUT v INT) SET v = 1;
+
+PREPARE stmt FROM 'CALL p1(?)';
+
+--echo
+SET @x1 = NULL;
+EXECUTE stmt USING @x1;
+SELECT @x1;
+
+--echo
+SET @x2 = 0;
+EXECUTE stmt USING @x2;
+SELECT @x2;
+
+--echo
+DEALLOCATE PREPARE stmt;
+DROP PROCEDURE p1;
+
+--echo
+--echo # End of Bug#11638.
+
+###########################################################################
+
+--echo
+--echo End of 6.0 tests.
+
+###########################################################################
=== modified file 'sql/item.cc'
--- a/sql/item.cc 2008-05-21 10:17:29 +0000
+++ b/sql/item.cc 2008-06-17 08:07:59 +0000
@@ -1140,7 +1140,10 @@ void Item_splocal::print(String *str, en
}
-bool Item_splocal::set_value(THD *thd, sp_rcontext *ctx, Item **it)
+bool Item_splocal::set_value(THD *thd,
+ sp_rcontext *ctx,
+ Item **it,
+ Out_param_info *)
{
return ctx->set_variable(thd, get_var_idx(), it);
}
@@ -2516,7 +2519,8 @@ Item_param::Item_param(uint pos_in_query
param_type(MYSQL_TYPE_VARCHAR),
pos_in_query(pos_in_query_arg),
set_param_func(default_set_param_func),
- limit_clause_param(FALSE)
+ limit_clause_param(FALSE),
+ m_user_var_name(NULL)
{
name= (char*) "?";
/*
@@ -2693,9 +2697,12 @@ bool Item_param::set_longdata(const char
1 Out of memory
*/
-bool Item_param::set_from_user_var(THD *thd, const user_var_entry *entry)
+bool Item_param::set_from_user_var(THD *thd,
+ const LEX_STRING *user_var_name,
+ const user_var_entry *entry)
{
DBUG_ENTER("Item_param::set_from_user_var");
+
if (entry && entry->value)
{
item_result_type= entry->type;
@@ -2761,6 +2768,8 @@ bool Item_param::set_from_user_var(THD *
else
set_null();
+ m_user_var_name= user_var_name;
+
DBUG_RETURN(0);
}
@@ -3156,6 +3165,37 @@ Item_param::eq(const Item *arg, bool bin
return FALSE;
}
+void ps_add_out_parameter(THD *thd,
+ Item_param *param_item,
+ Item *value_item,
+ Out_param_info *info);
+
+
+bool Item_param::set_value(THD *thd,
+ sp_rcontext *ctx,
+ Item **it,
+ Out_param_info *out_param_info)
+{
+ if (m_user_var_name)
+ {
+ Item_func_set_user_var *suv=
+ new Item_func_set_user_var(*m_user_var_name, *it);
+ /*
+ Item_func_set_user_var is not fixed after construction, call
+ fix_fields().
+ */
+ return !suv ||
+ suv->fix_fields(thd, it) ||
+ suv->check(0) ||
+ suv->update();
+ }
+ else
+ {
+ ps_add_out_parameter(thd, this, *it, out_param_info);
+ return FALSE;
+ }
+}
+
/* End of Item_param related */
void Item_param::print(String *str, enum_query_type query_type)
@@ -6388,7 +6428,10 @@ void Item_trigger_field::set_required_pr
}
-bool Item_trigger_field::set_value(THD *thd, sp_rcontext * /*ctx*/, Item **it)
+bool Item_trigger_field::set_value(THD *thd,
+ sp_rcontext * /*ctx*/,
+ Item **it,
+ Out_param_info *)
{
Item *item= sp_prepare_func_item(thd, it);
=== modified file 'sql/item.h'
--- a/sql/item.h 2008-05-21 10:17:29 +0000
+++ b/sql/item.h 2008-06-17 08:07:59 +0000
@@ -19,6 +19,7 @@
#endif
class Protocol;
+class Out_param_info;
struct TABLE_LIST;
void item_init(void); /* Init item functions */
class Item_field;
@@ -442,7 +443,10 @@ public:
FALSE if parameter value has been set,
TRUE if error has occured.
*/
- virtual bool set_value(THD *thd, sp_rcontext *ctx, Item **it)= 0;
+ virtual bool set_value(THD *thd,
+ sp_rcontext *ctx,
+ Item **it,
+ Out_param_info *)= 0;
};
@@ -1195,7 +1199,7 @@ public:
inline enum_field_types field_type() const { return m_field_type; }
private:
- bool set_value(THD *thd, sp_rcontext *ctx, Item **it);
+ bool set_value(THD *thd, sp_rcontext *ctx, Item **it, Out_param_info *);
public:
Settable_routine_parameter *get_settable_routine_parameter()
@@ -1603,7 +1607,8 @@ public:
/* Item represents one placeholder ('?') of prepared statement */
-class Item_param :public Item
+class Item_param :public Item,
+ private Settable_routine_parameter
{
char cnvbuf[MAX_FIELD_WIDTH];
String cnvstr;
@@ -1694,7 +1699,9 @@ public:
bool set_str(const char *str, ulong length);
bool set_longdata(const char *str, ulong length);
void set_time(MYSQL_TIME *tm, timestamp_type type, uint32 max_length_arg);
- bool set_from_user_var(THD *thd, const user_var_entry *entry);
+ bool set_from_user_var(THD *thd,
+ const LEX_STRING *user_var_name,
+ const user_var_entry *entry);
void reset();
/*
Assign placeholder value from bind data.
@@ -1740,6 +1747,20 @@ public:
/** Item is a argument to a limit clause. */
bool limit_clause_param;
void set_param_type_and_swap_value(Item_param *from);
+
+ virtual inline Settable_routine_parameter *
+ get_settable_routine_parameter()
+ {
+ return this;
+ }
+
+ virtual bool set_value(THD *thd,
+ sp_rcontext *ctx,
+ Item **it,
+ Out_param_info *out_param_info);
+
+private:
+ const LEX_STRING *m_user_var_name;
};
@@ -2741,7 +2762,7 @@ public:
private:
void set_required_privilege(bool rw);
- bool set_value(THD *thd, sp_rcontext *ctx, Item **it);
+ bool set_value(THD *thd, sp_rcontext *ctx, Item **it, Out_param_info *);
public:
Settable_routine_parameter *get_settable_routine_parameter()
@@ -2751,7 +2772,7 @@ public:
bool set_value(THD *thd, Item **it)
{
- return set_value(thd, NULL, it);
+ return set_value(thd, NULL, it, NULL);
}
private:
=== modified file 'sql/item_func.cc'
--- a/sql/item_func.cc 2008-04-17 14:31:44 +0000
+++ b/sql/item_func.cc 2008-06-17 08:07:59 +0000
@@ -4763,7 +4763,9 @@ bool Item_func_get_user_var::eq(const It
bool Item_func_get_user_var::set_value(THD *thd,
- sp_rcontext * /*ctx*/, Item **it)
+ sp_rcontext * /*ctx*/,
+ Item **it,
+ Out_param_info *)
{
Item_func_set_user_var *suv= new Item_func_set_user_var(get_name(), *it);
/*
=== modified file 'sql/item_func.h'
--- a/sql/item_func.h 2008-03-27 19:02:15 +0000
+++ b/sql/item_func.h 2008-06-17 08:07:59 +0000
@@ -1371,7 +1371,7 @@ public:
{ return const_item() ? 0 : RAND_TABLE_BIT; }
bool eq(const Item *item, bool binary_cmp) const;
private:
- bool set_value(THD *thd, sp_rcontext *ctx, Item **it);
+ bool set_value(THD *thd, sp_rcontext *ctx, Item **it, struct Out_param_info *);
public:
Settable_routine_parameter *get_settable_routine_parameter()
=== modified file 'sql/mysql_priv.h'
--- a/sql/mysql_priv.h 2008-05-21 10:17:29 +0000
+++ b/sql/mysql_priv.h 2008-06-17 08:07:59 +0000
@@ -2568,6 +2568,13 @@ bool load_collation(MEM_ROOT *mem_root,
CHARSET_INFO *dflt_cl,
CHARSET_INFO **cl);
+struct Out_param_info
+{
+ LEX_STRING db_name;
+ LEX_STRING sp_name;
+ LEX_STRING var_name;
+};
+
#endif /* MYSQL_SERVER */
#endif /* MYSQL_CLIENT */
=== modified file 'sql/protocol.cc'
--- a/sql/protocol.cc 2008-05-08 16:01:15 +0000
+++ b/sql/protocol.cc 2008-06-17 08:07:59 +0000
@@ -702,7 +702,7 @@ bool Protocol::send_fields(List<Item> *l
*/
write_eof_packet(thd, &thd->net, thd->server_status, thd->total_warn_count);
}
- DBUG_RETURN(prepare_for_send(list));
+ DBUG_RETURN(prepare_for_send(list->elements));
err:
my_message(ER_OUT_OF_RESOURCES, ER(ER_OUT_OF_RESOURCES),
@@ -1062,14 +1062,15 @@ bool Protocol_text::store_time(MYSQL_TIM
[..]..[[length]data] data
****************************************************************************/
-bool Protocol_binary::prepare_for_send(List<Item> *item_list)
+bool Protocol_binary::prepare_for_send(uint num_columns)
{
- Protocol::prepare_for_send(item_list);
+ Protocol::prepare_for_send(num_columns);
+
bit_fields= (field_count+9)/8;
- if (packet->alloc(bit_fields+1))
- return 1;
+
+ return packet->alloc(bit_fields+1);
+
/* prepare_for_resend will be called after this one */
- return 0;
}
=== modified file 'sql/protocol.h'
--- a/sql/protocol.h 2008-02-11 16:11:22 +0000
+++ b/sql/protocol.h 2008-06-17 08:07:59 +0000
@@ -71,9 +71,9 @@ public:
inline bool store(String *str)
{ return store((char*) str->ptr(), str->length(), str->charset()); }
- virtual bool prepare_for_send(List<Item> *item_list)
+ virtual bool prepare_for_send(uint num_columns)
{
- field_count=item_list->elements;
+ field_count= num_columns;
return 0;
}
virtual bool flush();
@@ -150,7 +150,7 @@ private:
public:
Protocol_binary() {}
Protocol_binary(THD *thd_arg) :Protocol(thd_arg) {}
- virtual bool prepare_for_send(List<Item> *item_list);
+ virtual bool prepare_for_send(uint num_columns);
virtual void prepare_for_resend();
#ifdef EMBEDDED_LIBRARY
virtual bool write();
=== modified file 'sql/sp_head.cc'
--- a/sql/sp_head.cc 2008-05-21 10:17:29 +0000
+++ b/sql/sp_head.cc 2008-06-17 08:07:59 +0000
@@ -1825,6 +1825,8 @@ err_with_cleanup:
TRUE on error
*/
+extern bool ps_send_out_parameters(THD *thd);
+
bool
sp_head::execute_procedure(THD *thd, List<Item> *args)
{
@@ -1995,16 +1997,27 @@ sp_head::execute_procedure(THD *thd, Lis
if (spvar->mode == sp_param_in)
continue;
+ Out_param_info out_param_info;
+ out_param_info.db_name= m_db;
+ out_param_info.sp_name= m_name;
+ out_param_info.var_name= spvar->name;
+
Settable_routine_parameter *srp=
arg_item->get_settable_routine_parameter();
DBUG_ASSERT(srp);
- if (srp->set_value(thd, octx, nctx->get_item_addr(i)))
+ if (srp->set_value(thd, octx, nctx->get_item_addr(i), &out_param_info))
{
err_status= TRUE;
break;
}
+ }
+
+ if (!err_status)
+ {
+ ps_send_out_parameters(thd);
+ thd->main_da.reset_diagnostics_area();
}
}
=== modified file 'sql/sql_class.cc'
--- a/sql/sql_class.cc 2008-05-21 10:17:29 +0000
+++ b/sql/sql_class.cc 2008-06-17 08:07:59 +0000
@@ -533,6 +533,7 @@ THD::THD()
derived_tables_processing(FALSE),
spcont(NULL),
m_lip(NULL),
+ current_ps(NULL),
/*
@todo The following is a work around for online backup and the DDL blocker.
It should be removed when the generalized solution is in place.
=== modified file 'sql/sql_class.h'
--- a/sql/sql_class.h 2008-05-21 10:17:29 +0000
+++ b/sql/sql_class.h 2008-06-17 08:07:59 +0000
@@ -1303,6 +1303,7 @@ public:
pthread_mutex_t LOCK_delete; // Locked before thd is deleted
/* all prepared statements and cursors of this connection */
Statement_map stmt_map;
+ Statement *current_ps; // mat be NULL
/*
A pointer to the stack frame of handle_one_connection(),
which is called first in the thread for handling a client
=== modified file 'sql/sql_prepare.cc'
--- a/sql/sql_prepare.cc 2008-05-21 19:44:56 +0000
+++ b/sql/sql_prepare.cc 2008-06-17 08:07:59 +0000
@@ -119,6 +119,42 @@ public:
/****************************************************************************/
/**
+ Out_param: OUT-parameter representation.
+*/
+
+struct Out_param
+{
+ inline Out_param(const LEX_STRING *p_db_name,
+ const LEX_STRING *p_sp_name,
+ const LEX_STRING *p_var_name,
+ Item_param *p_param_item,
+ Item *p_value_item);
+
+ LEX_STRING db_name;
+ LEX_STRING sp_name;
+ LEX_STRING var_name;
+
+ Item_param *param_item;
+ Item *value_item;
+};
+
+inline Out_param::Out_param(const LEX_STRING *p_db_name,
+ const LEX_STRING *p_sp_name,
+ const LEX_STRING *p_var_name,
+ Item_param *p_param_item,
+ Item *p_value_item)
+{
+ db_name= *p_db_name;
+ sp_name= *p_sp_name;
+ var_name= *p_var_name;
+
+ param_item= p_param_item;
+ value_item= p_value_item;
+}
+
+/****************************************************************************/
+
+/**
Prepared_statement: a statement that can contain placeholders.
*/
@@ -138,6 +174,9 @@ public:
uint last_errno;
uint flags;
char last_error[MYSQL_ERRMSG_SIZE];
+
+ List<Out_param> m_out_param_lst;
+
#ifndef EMBEDDED_LIBRARY
bool (*set_params)(Prepared_statement *st, uchar *data, uchar *data_end,
uchar *read_pos, String *expanded_query);
@@ -1026,7 +1065,7 @@ static bool insert_params_from_vars(Prep
entry= (user_var_entry*)hash_search(&stmt->thd->user_vars,
(uchar*) varname->str,
varname->length);
- if (param->set_from_user_var(stmt->thd, entry) ||
+ if (param->set_from_user_var(stmt->thd, varname, entry) ||
param->convert_str_value(stmt->thd))
DBUG_RETURN(1);
}
@@ -1078,7 +1117,7 @@ static bool insert_params_from_vars_with
(e.g. value.cs_info.character_set_client is used in the query_val_str()).
*/
setup_one_conversion_function(thd, param, param->param_type);
- if (param->set_from_user_var(thd, entry))
+ if (param->set_from_user_var(thd, varname, entry))
DBUG_RETURN(1);
val= param->query_val_str(&buf);
@@ -3538,7 +3577,9 @@ bool Prepared_statement::execute(String
if (query_cache_send_result_to_client(thd, thd->query,
thd->query_length) <= 0)
{
+ thd->current_ps= this;
error= mysql_execute_command(thd);
+ thd->current_ps= NULL;
}
}
@@ -3599,4 +3640,227 @@ void Prepared_statement::deallocate()
status_var_increment(thd->status_var.com_stmt_close);
/* Statement map calls delete stmt on erase */
thd->stmt_map.erase(this);
+}
+
+///////////////////////////////////////////////////////////////////////////
+
+void ps_add_out_parameter(THD *thd,
+ Item_param *param_item,
+ Item *value_item,
+ Out_param_info *info)
+{
+ Prepared_statement *ps= (Prepared_statement *) thd->current_ps;
+
+ if (!ps)
+ {
+ // We've been called not from PS.
+ return;
+ }
+
+ if (!(thd->client_capabilities & CLIENT_PS_OUT_PARAMS))
+ {
+ // The client does not support OUT-parameters.
+ return;
+ }
+
+ ps->m_out_param_lst.push_back(new Out_param(&info->db_name,
+ &info->sp_name,
+ &info->var_name,
+ param_item,
+ value_item));
+}
+
+// FIXME
+void net_send_eof(THD *thd, uint server_status, uint total_warn_count);
+
+// TODO: this is actually a copy&paste from Protocol::send_field().
+
+bool ps_send_out_parameters(THD *thd)
+{
+ Prepared_statement *ps= (Prepared_statement *) thd->current_ps;
+
+ if (!ps)
+ {
+ // We've been called not from PS.
+ return FALSE;
+ }
+
+ if (!(thd->client_capabilities & CLIENT_PS_OUT_PARAMS))
+ {
+ // The client does not support OUT-parameters.
+ return FALSE;
+ }
+
+ // Write a number of columns (what's called SEND_NUM_ROWS).
+
+ {
+ uchar buffer[10];
+ uchar *pos= net_store_length(buffer, ps->m_out_param_lst.elements);
+ my_net_write(&thd->net, buffer, (size_t) (pos - buffer));
+ }
+
+ // Send meta-data (a number of Field-packets).
+ // Meta-data is encoded using text-protocol.
+
+ {
+ Protocol_text p(thd);
+ List_iterator_fast<Out_param> it(ps->m_out_param_lst);
+
+ while (true)
+ {
+ Out_param *out_param= it++;
+
+ if (!out_param)
+ break;
+
+ Send_field value_fld;
+ out_param->value_item->make_field(&value_fld);
+
+ p.prepare_for_resend();
+
+ bool status= FALSE;
+ CHARSET_INFO *sys_cs= system_charset_info;
+ CHARSET_INFO *res_cs= thd->variables.character_set_results;
+
+ // Store the following fields of Field-packet:
+ // - 'def'
+ // - database name
+ // - table name (procedure name)
+ // - original table name (procedure name)
+ // - column name (parameter name)
+ // - original column name (parameter name)
+
+ status= status || p.store(STRING_WITH_LEN("def"), sys_cs, res_cs);
+ status= status || p.store(out_param->db_name.str,
+ out_param->db_name.length,
+ sys_cs, res_cs);
+ status= status || p.store(out_param->sp_name.str,
+ out_param->sp_name.length,
+ sys_cs, res_cs);
+ status= status || p.store(out_param->sp_name.str,
+ out_param->sp_name.length,
+ sys_cs, res_cs);
+ status= status || p.store(out_param->var_name.str,
+ out_param->var_name.length,
+ sys_cs, res_cs);
+ status= status || p.store(out_param->var_name.str,
+ out_param->var_name.length,
+ sys_cs, res_cs);
+
+ if (status)
+ {
+ my_message(ER_OUT_OF_RESOURCES, ER(ER_OUT_OF_RESOURCES), MYF(0));
+ return TRUE;
+ }
+
+ String *packet_str= p.storage_packet();
+
+ if (packet_str->realloc(packet_str->length() + 12))
+ {
+ my_message(ER_OUT_OF_RESOURCES, ER(ER_OUT_OF_RESOURCES), MYF(0));
+ return TRUE;
+ }
+
+ // Store the filler (0x0c).
+
+ char *pos= (char *) packet_str->ptr() + packet_str->length();
+ *pos= 0x0c;
+ ++pos;
+
+ // Store collation.
+
+ if (out_param->param_item->collation.collation == &my_charset_bin ||
+ res_cs == NULL)
+ {
+ /* No conversion */
+ int2store(pos, value_fld.charsetnr);
+ int4store(pos + 2, value_fld.length);
+ }
+ else
+ {
+ /* With conversion */
+ uint max_char_len;
+ int2store(pos, res_cs->number);
+ /*
+ For TEXT/BLOB columns, field_length describes the maximum data
+ length in bytes. There is no limit to the number of characters
+ that a TEXT column can store, as long as the data fits into
+ the designated space.
+ For the rest of textual columns, field_length is evaluated as
+ char_count * mbmaxlen, where character count is taken from the
+ definition of the column. In other words, the maximum number
+ of characters here is limited by the column definition.
+ */
+ max_char_len= (value_fld.type >= (int) MYSQL_TYPE_TINY_BLOB &&
+ value_fld.type <= (int) MYSQL_TYPE_BLOB) ?
+ value_fld.length /
+ out_param->value_item->collation.collation->mbminlen :
+ value_fld.length /
+ out_param->value_item->collation.collation->mbmaxlen;
+ int4store(pos + 2, max_char_len * res_cs->mbmaxlen);
+ }
+
+ // Store other fields:
+ // - Field type;
+
+ pos[6]= value_fld.type;
+
+ // - Flags;
+
+ int2store(pos + 7, value_fld.flags);
+
+ // - Decimals;
+
+ pos[9]= (char) value_fld.decimals;
+ pos[10]= 0;
+ pos[11]= 0;
+
+ // That's it.
+
+ pos+= 12;
+ packet_str->length(pos - packet_str->ptr());
+
+ if (p.write())
+ {
+ my_message(ER_OUT_OF_RESOURCES, ER(ER_OUT_OF_RESOURCES), MYF(0));
+ return TRUE;
+ }
+ }
+ }
+
+ // Send EOF-packet.
+
+ net_send_eof(thd, thd->server_status, thd->total_warn_count);
+
+ // Send data.
+
+ {
+ Protocol *p= thd->protocol;
+ p->prepare_for_resend();
+
+ List_iterator_fast<Out_param> it(ps->m_out_param_lst);
+
+ while (true)
+ {
+ Out_param *out_param= it++;
+
+ if (!out_param)
+ break;
+
+ out_param->value_item->send(p, NULL);
+ }
+
+ p->write();
+ p->flush();
+ }
+
+ // Send EOF-packet.
+
+ net_send_eof(thd, thd->server_status, thd->total_warn_count);
+
+ // Clear Out-parameter list.
+
+ ps->m_out_param_lst.delete_elements();
+
+ return FALSE;
}
=== modified file 'tests/mysql_client_test.c'
--- a/tests/mysql_client_test.c 2008-05-26 12:12:28 +0000
+++ b/tests/mysql_client_test.c 2008-06-17 08:07:59 +0000
@@ -1497,6 +1497,163 @@ static void test_prepare_simple()
myquery(rc);
}
+///////////////////////////////////////////////////////////////////////////
+
+static void test_alik()
+{
+ MYSQL *con;
+ MYSQL_STMT *stmt;
+ int rc;
+ char query[MAX_TEST_QUERY_LENGTH];
+
+ char str_data[20];
+ ulong length;
+ my_bool is_null;
+ MYSQL_BIND ps_params[2];
+
+ int exec_counter;
+
+ myheader("test_alik");
+
+ con= mysql_init(NULL);
+ if (!con)
+ {
+ fprintf(stderr, "\n mysql_init() failed");
+ exit(1);
+ }
+
+ if (!(mysql_real_connect(con, opt_host, opt_user,
+ opt_password, current_db, opt_port,
+ opt_unix_socket, CLIENT_MULTI_RESULTS)))
+ {
+ fprintf(stderr, "\n connection failed(%s)", mysql_error(con));
+ exit(1);
+ }
+ con->reconnect= 1;
+
+ rc= mysql_query(con, "DROP PROCEDURE IF EXISTS p1");
+ myquery(rc);
+
+ rc= mysql_query(con, "DROP FUNCTION IF EXISTS f1");
+ myquery(rc);
+
+ rc= mysql_query(con, "DROP TABLE IF EXISTS t1");
+ myquery(rc);
+
+ rc= mysql_query(con, "DROP TABLE IF EXISTS t2");
+ myquery(rc);
+
+ // rc= mysql_query(con, "CREATE TABLE t1(a1 CHAR(10), a2 CHAR(10))");
+ rc= mysql_query(con, "CREATE TABLE t1(a1 INT, a2 INT)");
+ myquery(rc);
+
+ rc= mysql_query(con, "INSERT INTO t1 VALUES(1, 2), (3, 4), (5, 6)");
+ myquery(rc);
+
+ rc= mysql_query(con, "CREATE PROCEDURE p1(OUT v1 INT, OUT v2 INT) "
+ "BEGIN "
+ " SET v1 = 123; "
+ " SET v2 = 456; "
+ " SELECT * FROM t1; "
+ " SELECT * FROM t1; "
+ "END");
+ myquery(rc);
+
+ strmov(query, "CALL p1(?, ?)");
+ stmt= mysql_simple_prepare(con, query);
+ check_stmt(stmt);
+
+ bzero((char *) ps_params, sizeof (ps_params));
+
+ {
+ int i;
+ for (i = 0; i < 2; ++i)
+ {
+ ps_params[i].buffer_type= MYSQL_TYPE_STRING;
+ ps_params[i].buffer= str_data;
+ ps_params[i].buffer_length= 20;
+ ps_params[i].length= &length;
+ ps_params[i].is_null= &is_null;
+ is_null= 0;
+ length= 0;
+ }
+ }
+
+ rc= mysql_stmt_bind_param(stmt, ps_params);
+
+ for (exec_counter= 0; exec_counter < 3; ++exec_counter)
+ {
+ rc= mysql_stmt_execute(stmt);
+ check_execute(stmt, rc);
+
+ while (1)
+ {
+ // char str_data[20][2];
+ int int_data[2];
+ my_bool is_null[2];
+ unsigned long length[2];
+ my_bool error[2];
+ MYSQL_BIND results[2];
+ int c;
+
+ memset(results, 0, sizeof (results));
+
+ for (c = 0; c < 2; ++c)
+ {
+ // results[c].buffer_type= MYSQL_TYPE_STRING;
+ // results[c].buffer= (char *)str_data[c];
+ results[c].buffer_type= MYSQL_TYPE_LONG;
+ results[c].buffer= (void *) &int_data[c];
+ results[c].buffer_length= 20;
+ results[c].is_null= &is_null[c];
+ results[c].length= &length[c];
+ results[c].error= &error[c];
+ }
+
+ rc= mysql_stmt_bind_result(stmt, results);
+ check_execute(stmt, rc);
+
+ {
+ printf("--> Result set:\n");
+
+ while (1)
+ {
+ int rc= mysql_stmt_fetch(stmt);
+
+ if (rc == 1 || rc == MYSQL_NO_DATA)
+ break;
+
+ // debug printf(" - Row: '%.*s', '%.*s'\n",
+ // debug (int) length[0],
+ // debug (const char *) str_data[0],
+ // debug (int) length[1],
+ // debug (const char *) str_data[1]);
+ printf(" - Row: %d, %d\n",
+ (int) int_data[0], (int) int_data[1]);
+ }
+ }
+
+ rc= mysql_more_results(con);
+ printf("--> mysql_more_results(): %d\n", (int) rc);
+
+ rc= mysql_stmt_next_result(stmt);
+ printf("--> mysql_stmt_next_result(): %d\n", (int) rc);
+
+ if (rc > 0)
+ printf("--> Error: %s (errno: %d)\n", mysql_error(con), mysql_errno(con));
+
+ if (rc)
+ break;
+ }
+ }
+
+ mysql_stmt_close(stmt);
+
+ rc= mysql_commit(con);
+ myquery(rc);
+
+ mysql_close(con);
+}
/* Test simple prepare field results */
@@ -18138,6 +18295,7 @@ static struct my_tests_st my_tests[]= {
{ "test_wl4166_3", test_wl4166_3 },
{ "test_wl4166_4", test_wl4166_4 },
{ "test_bug36004", test_bug36004 },
+ { "test_alik", test_alik },
{ 0, 0 }
};