Skip to content

Commit

Permalink
MDEV-14454 Binary protocol returns wrong collation ID for SP OUT para…
Browse files Browse the repository at this point in the history
…meters

Item_param::set_value() did not set Item::collation and
Item_param::str_value_ptr.str_charset properly. So both
metadata and data for OUT parameters were sent in a wrong
way to the client.

This patch removes the old implementation of Item_param::set_value()
and rewrites it using Type_handler::Item_param_set_from_value(),
so now setting IN and OUT parameters share the a lot of code.

1. Item_param::set_str() now:
  - accepts two additional parameters fromcs, tocs
  - sets str_value_ptr, to make sure it's always in sync with str_value,
    even without Item_param::convert_str_value()
  - does collation.set(tocs, DERIVATION_COERCIBLE),
    to make sure that DTCollation is valid even without
    Item_param::convert_str_value()

2. Item_param::set_value(), which is used to set OUT parameters,
   now reuses Type_handler::Item_param_set_from_value().

3. Cleanup: moving Item_param::str_value_ptr to private,
   as it's not needed outside.

4. Cleanup: adding a new virtual method
   Settable_routine_parameter::get_item_param()
   and using it a few new DBUG_ASSERTs, where
   Item_param cannot appear.

After this change:
1. Assigning of IN parameters works as before:
a. Item_param::set_str() is called and sets the value as a binary string
b. The original value is sent to the query used for binary/general logging
c. Item_param::convert_str_value() converts the value from the client
   character set to the connection character set

2. Assigning of OUT parameters works in the new way:
a. Item_param::set_str() and sets the value
   using the source Item's collation, so both Item::collation
   and Item_param::str_value_ptr.str_charset are properly set.
b. Protocol_binary::send_out_parameters() sends the
   value to the client correctly:
   - Protocol::send_result_set_metadata() uses Item::collation.collation
     (which is now properly set), to detect if conversion is needed,
     and sends a correct collation ID.
   - Protocol::send_result_set_row() calls Type_handler::Item_send_str(),
     which uses Item_param::str_value_ptr.str_charset
     (which is now properly set) to actually perform the conversion.
  • Loading branch information
Alexander Barkov committed Nov 21, 2017
1 parent 4a8039b commit 563f1d8
Show file tree
Hide file tree
Showing 10 changed files with 171 additions and 72 deletions.
30 changes: 22 additions & 8 deletions mysql-test/r/ps.result
Expand Up @@ -3550,7 +3550,7 @@ CREATE TEMPORARY TABLE tmp1 AS SELECT @a AS c1;
SHOW CREATE TABLE tmp1;
Table Create Table
tmp1 CREATE TEMPORARY TABLE `tmp1` (
`c1` longblob DEFAULT NULL
`c1` longtext DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1
SELECT @a, @a = REPEAT('a', 16);
@a @a = REPEAT('a', 16)
Expand All @@ -3568,7 +3568,7 @@ CREATE TEMPORARY TABLE tmp1 AS SELECT @a AS c1;
SHOW CREATE TABLE tmp1;
Table Create Table
tmp1 CREATE TEMPORARY TABLE `tmp1` (
`c1` longblob DEFAULT NULL
`c1` longtext DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1
SELECT @a, @a = REPEAT('b', 16);
@a @a = REPEAT('b', 16)
Expand All @@ -3586,7 +3586,7 @@ CREATE TEMPORARY TABLE tmp1 AS SELECT @a AS c1;
SHOW CREATE TABLE tmp1;
Table Create Table
tmp1 CREATE TEMPORARY TABLE `tmp1` (
`c1` longblob DEFAULT NULL
`c1` longtext DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1
SELECT @a, @a = REPEAT('c', 16);
@a @a = REPEAT('c', 16)
Expand All @@ -3604,7 +3604,7 @@ CREATE TEMPORARY TABLE tmp1 AS SELECT @a AS c1;
SHOW CREATE TABLE tmp1;
Table Create Table
tmp1 CREATE TEMPORARY TABLE `tmp1` (
`c1` longblob DEFAULT NULL
`c1` longtext DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1
SELECT @a, @a = REPEAT('d', 16);
@a @a = REPEAT('d', 16)
Expand All @@ -3622,7 +3622,7 @@ CREATE TEMPORARY TABLE tmp1 AS SELECT @a AS c1;
SHOW CREATE TABLE tmp1;
Table Create Table
tmp1 CREATE TEMPORARY TABLE `tmp1` (
`c1` longblob DEFAULT NULL
`c1` longtext DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1
SELECT @a, @a = REPEAT('e', 16);
@a @a = REPEAT('e', 16)
Expand All @@ -3640,7 +3640,7 @@ CREATE TEMPORARY TABLE tmp1 AS SELECT @a AS c1;
SHOW CREATE TABLE tmp1;
Table Create Table
tmp1 CREATE TEMPORARY TABLE `tmp1` (
`c1` longblob DEFAULT NULL
`c1` longtext DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1
SELECT @a, @a = REPEAT('f', 16);
@a @a = REPEAT('f', 16)
Expand Down Expand Up @@ -3766,7 +3766,7 @@ CREATE TEMPORARY TABLE tmp1 AS SELECT @a AS c1;
SHOW CREATE TABLE tmp1;
Table Create Table
tmp1 CREATE TEMPORARY TABLE `tmp1` (
`c1` longblob DEFAULT NULL
`c1` longtext DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1
SELECT @a, @a = 'aaa';
@a @a = 'aaa'
Expand All @@ -3784,7 +3784,7 @@ CREATE TEMPORARY TABLE tmp1 AS SELECT @a AS c1;
SHOW CREATE TABLE tmp1;
Table Create Table
tmp1 CREATE TEMPORARY TABLE `tmp1` (
`c1` longblob DEFAULT NULL
`c1` longtext DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1
SELECT @a, @a = 'aaa';
@a @a = 'aaa'
Expand Down Expand Up @@ -5074,3 +5074,17 @@ t1 CREATE TABLE `t1` (
) ENGINE=MyISAM DEFAULT CHARSET=latin1
DROP TABLE t1;
DROP PROCEDURE p1;
#
# MDEV-14454 Binary protocol returns wrong collation ID for SP OUT parameters
#
CREATE PROCEDURE p1(OUT v CHAR(32) CHARACTER SET utf8) SET v='aaa';
PREPARE stmt1 FROM 'CALL p1(?)';
EXECUTE stmt1 USING @a;
CREATE TABLE t1 AS SELECT @a AS c1;
SHOW CREATE TABLE t1;
Table Create Table
t1 CREATE TABLE `t1` (
`c1` longtext CHARACTER SET utf8 DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1
DROP TABLE t1;
DROP PROCEDURE p1;
12 changes: 12 additions & 0 deletions mysql-test/t/ps.test
Expand Up @@ -4529,3 +4529,15 @@ CREATE TABLE t1 AS SELECT @a AS a, @b AS b;
SHOW CREATE TABLE t1;
DROP TABLE t1;
DROP PROCEDURE p1;

--echo #
--echo # MDEV-14454 Binary protocol returns wrong collation ID for SP OUT parameters
--echo #

CREATE PROCEDURE p1(OUT v CHAR(32) CHARACTER SET utf8) SET v='aaa';
PREPARE stmt1 FROM 'CALL p1(?)';
EXECUTE stmt1 USING @a;
CREATE TABLE t1 AS SELECT @a AS c1;
SHOW CREATE TABLE t1;
DROP TABLE t1;
DROP PROCEDURE p1;
83 changes: 23 additions & 60 deletions sql/item.cc
Expand Up @@ -3903,18 +3903,33 @@ void Item_param::set_time(MYSQL_TIME *tm, timestamp_type time_type,
}


bool Item_param::set_str(const char *str, ulong length)
bool Item_param::set_str(const char *str, ulong length,
CHARSET_INFO *fromcs, CHARSET_INFO *tocs)
{
DBUG_ENTER("Item_param::set_str");
/*
Assign string with no conversion: data is converted only after it's
been written to the binary log.
*/
uint dummy_errors;
if (str_value.copy(str, length, &my_charset_bin, &my_charset_bin,
&dummy_errors))
if (str_value.copy(str, length, fromcs, tocs, &dummy_errors))
DBUG_RETURN(TRUE);
/*
Set str_value_ptr to make sure it's in sync with str_value.
This is needed in case if we're called from Item_param::set_value(),
from the code responsible for setting OUT parameters in
sp_head::execute_procedure(). This makes sure that
Protocol_binary::send_out_parameters() later gets a valid value
from Item_param::val_str().
Note, for IN parameters, Item_param::convert_str_value() will be called
later, which will convert the value from the client character set to the
connection character set, and will reset both str_value and str_value_ptr.
*/
str_value_ptr.set(str_value.ptr(),
str_value.length(),
str_value.charset());
state= STRING_VALUE;
collation.set(tocs, DERIVATION_COERCIBLE);
max_length= length;
maybe_null= 0;
/* max_length and decimals are set after charset conversion */
Expand Down Expand Up @@ -4576,66 +4591,14 @@ bool
Item_param::set_value(THD *thd, sp_rcontext *ctx, Item **it)
{
Item *arg= *it;

if (arg->is_null())
struct st_value tmp;
if (arg->save_in_value(&tmp) ||
arg->type_handler()->Item_param_set_from_value(thd, this, arg, &tmp))
{
set_null();
return FALSE;
}

null_value= FALSE;
unsigned_flag= arg->unsigned_flag;

switch (arg->result_type()) {
case STRING_RESULT:
{
char str_buffer[STRING_BUFFER_USUAL_SIZE];
String sv_buffer(str_buffer, sizeof(str_buffer), &my_charset_bin);
String *sv= arg->val_str(&sv_buffer);

if (!sv)
return TRUE;

set_str(sv->c_ptr_safe(), sv->length());
str_value_ptr.set(str_value.ptr(),
str_value.length(),
str_value.charset());
collation.set(str_value.charset(), DERIVATION_COERCIBLE);
decimals= 0;
break;
}

case REAL_RESULT:
set_double(arg->val_real());
break;

case INT_RESULT:
set_int(arg->val_int(), arg->max_length);
break;

case DECIMAL_RESULT:
{
my_decimal dv_buf;
my_decimal *dv= arg->val_decimal(&dv_buf);

if (!dv)
return TRUE;

set_decimal(dv, !dv->sign());
break;
}

default:
/* That can not happen. */

DBUG_ASSERT(TRUE); // Abort in debug mode.

set_null(); // Set to NULL in release mode.
return FALSE;
return false;
}

set_handler_by_result_type(arg->result_type());
return FALSE;
return null_value= false;
}


Expand Down
10 changes: 9 additions & 1 deletion sql/item.h
Expand Up @@ -391,6 +391,8 @@ class Settable_routine_parameter

virtual const Send_field *get_out_param_info() const
{ return NULL; }

virtual Item_param *get_item_param() { return 0; }
};


Expand Down Expand Up @@ -3173,6 +3175,7 @@ class Item_param :public Item_basic_value,
*/
enum enum_indicator_type indicator;

private:
/*
A buffer for string and long data values. Historically all allocated
values returned from val_str() were treated as eligible to
Expand All @@ -3184,6 +3187,8 @@ class Item_param :public Item_basic_value,
Can not be declared inside the union as it's not a POD type.
*/
String str_value_ptr;

public:
my_decimal decimal_value;
union
{
Expand Down Expand Up @@ -3225,7 +3230,8 @@ class Item_param :public Item_basic_value,
void set_double(double i);
void set_decimal(const char *str, ulong length);
void set_decimal(const my_decimal *dv, bool unsigned_arg);
bool set_str(const char *str, ulong length);
bool set_str(const char *str, ulong length,
CHARSET_INFO *fromcs, CHARSET_INFO *tocs);
bool set_longdata(const char *str, ulong length);
void set_time(MYSQL_TIME *tm, timestamp_type type, uint32 max_length_arg);
void set_time(const MYSQL_TIME *tm, uint32 max_length_arg, uint decimals_arg);
Expand Down Expand Up @@ -3305,6 +3311,8 @@ class Item_param :public Item_basic_value,
public:
virtual const Send_field *get_out_param_info() const;

Item_param *get_item_param() { return this; }

virtual void make_field(THD *thd, Send_field *field);

private:
Expand Down
1 change: 1 addition & 0 deletions sql/protocol.cc
Expand Up @@ -1327,6 +1327,7 @@ bool Protocol_text::send_out_parameters(List<Item_param> *sp_params)
continue;
}

DBUG_ASSERT(sparam->get_item_param() == NULL);
sparam->set_value(thd, thd->spcont, reinterpret_cast<Item **>(&item_param));
}

Expand Down
3 changes: 3 additions & 0 deletions sql/sql_get_diagnostics.cc
Expand Up @@ -109,6 +109,9 @@ Diagnostics_information_item::set_value(THD *thd, Item **value)

DBUG_ASSERT(srp);

/* GET DIAGNOSTICS is not allowed in prepared statements */
DBUG_ASSERT(srp->get_item_param() == NULL);

/* Set variable/parameter value. */
rv= srp->set_value(thd, thd->spcont, value);

Expand Down
8 changes: 7 additions & 1 deletion sql/sql_prepare.cc
Expand Up @@ -735,7 +735,13 @@ static void set_param_str_or_null(Item_param *param, uchar **pos, ulong len,
{
if (length > len)
length= len;
param->set_str((const char *) *pos, length);
/*
We use &my_charset_bin here. Conversion and setting real character
sets will be done in Item_param::convert_str_value(), after the
original value is appended to the query used for logging.
*/
param->set_str((const char *) *pos, length,
&my_charset_bin, &my_charset_bin);
*pos+= length;
}
}
Expand Down
7 changes: 5 additions & 2 deletions sql/sql_type.cc
Expand Up @@ -5059,7 +5059,9 @@ bool Type_handler_string_result::
charset of connection, so we have to set it later.
*/
param->set_handler(&type_handler_varchar);
return param->set_str(val->m_string.ptr(), val->m_string.length());
return param->set_str(val->m_string.ptr(), val->m_string.length(),
attr->collation.collation,
attr->collation.collation);
}


Expand Down Expand Up @@ -5087,7 +5089,8 @@ bool Type_handler_geometry::
param->value.cs_info.set(thd, &my_charset_bin);
param->set_handler(&type_handler_geometry);
param->set_geometry_type(attr->uint_geometry_type());
return param->set_str(val->m_string.ptr(), val->m_string.length());
return param->set_str(val->m_string.ptr(), val->m_string.length(),
&my_charset_bin, &my_charset_bin);
}
#endif

Expand Down
9 changes: 9 additions & 0 deletions tests/mysql_client_fw.c
Expand Up @@ -1177,6 +1177,15 @@ static my_bool thread_query(const char *query)
}


static int mysql_query_or_error(MYSQL *mysql, const char *query)
{
int rc= mysql_query(mysql, query);
if (rc)
fprintf(stderr, "ERROR %d: %s", mysql_errno(mysql), mysql_error(mysql));
return rc;
}


/*
Read and parse arguments and MySQL options from my.cnf
*/
Expand Down

0 comments on commit 563f1d8

Please sign in to comment.