Commit 45e40892 authored by Alexander Barkov's avatar Alexander Barkov

MDEV-11134 Assertion `fixed' failed in Item::const_charset_converter(THD*,...

MDEV-11134 Assertion `fixed' failed in Item::const_charset_converter(THD*, CHARSET_INFO*, bool, const char*)

Problem: Item_param::basic_const_item() returned true when fixed==false.
This unexpected combination made Item::const_charset_converter() crash
on asserts.

Fix:
- Changing all Item_param::set_xxx() to set "fixed" to true.
  This fixes the problem.
- Additionally, changing all Item_param::set_xxx() to set
  Item_param::item_type, to avoid duplicate code, and for consistency,
  to make the code symmetric between different constant types.
  Before this patch only set_null() set item_type.
- Moving Item_param::state and Item_param::item_type from public to private,
  to make sure easier that these members are in sync with "fixed" and to
  each other.
- Adding a new argument "unsigned_arg" to Item::set_decimal(),
  and reusing it in two places instead of duplicate code.
- Adding a new method Item_param::fix_temporal() and reusing it in two places.
- Adding methods has_no_value(), has_long_data_value(), has_int_value(),
  instead of direct access to Item_param::state.
parent 31031a52
......@@ -3278,6 +3278,44 @@ INSERT INTO t1 VALUES (1),(2),(3);
EXECUTE IMMEDIATE 'EXPLAIN EXTENDED SELECT * FROM t1 WHERE ?+a<=>?+a' USING DEFAULT,DEFAULT;
ERROR HY000: Default/ignore value is not supported for such parameter usage
DROP TABLE t1;
#
# MDEV-11134 Assertion `fixed' failed in Item::const_charset_converter(THD*, CHARSET_INFO*, bool, const char*)
#
SET NAMES utf8;
PREPARE stmt FROM "CREATE OR REPLACE TABLE t1 (c CHAR(8) DEFAULT ?)";
SET @a='';
EXECUTE stmt USING @a;
EXECUTE stmt USING @a;
SHOW CREATE TABLE t1;
Table Create Table
t1 CREATE TABLE `t1` (
`c` char(8) DEFAULT ''
) ENGINE=MyISAM DEFAULT CHARSET=latin1
DROP TABLE t1;
SET @a='A';
EXECUTE stmt USING @a;
EXECUTE stmt USING @a;
SHOW CREATE TABLE t1;
Table Create Table
t1 CREATE TABLE `t1` (
`c` char(8) DEFAULT 'A'
) ENGINE=MyISAM DEFAULT CHARSET=latin1
DROP TABLE t1;
SET @a=_utf8 0xC380;
EXECUTE stmt USING @a;
EXECUTE stmt USING @a;
SHOW CREATE TABLE t1;
Table Create Table
t1 CREATE TABLE `t1` (
`c` char(8) DEFAULT 'À'
) ENGINE=MyISAM DEFAULT CHARSET=latin1
DROP TABLE t1;
SET @a=_utf8 0xD18F;
EXECUTE stmt USING @a;
ERROR 42000: Invalid default value for 'c'
EXECUTE stmt USING @a;
ERROR 42000: Invalid default value for 'c'
DEALLOCATE PREPARE stmt;
# end of 10.2 test
set sql_mode=ansi_quotes;
create table t1 (a int, b int default (a+1));
......
......@@ -2013,6 +2013,36 @@ EXECUTE IMMEDIATE 'EXPLAIN EXTENDED SELECT * FROM t1 WHERE ?+a<=>?+a' USING DEFA
DROP TABLE t1;
--echo #
--echo # MDEV-11134 Assertion `fixed' failed in Item::const_charset_converter(THD*, CHARSET_INFO*, bool, const char*)
--echo #
SET NAMES utf8;
PREPARE stmt FROM "CREATE OR REPLACE TABLE t1 (c CHAR(8) DEFAULT ?)";
SET @a='';
EXECUTE stmt USING @a;
EXECUTE stmt USING @a;
SHOW CREATE TABLE t1;
DROP TABLE t1;
SET @a='A';
EXECUTE stmt USING @a;
EXECUTE stmt USING @a;
SHOW CREATE TABLE t1;
DROP TABLE t1;
SET @a=_utf8 0xC380; # LATIN CAPITAL LETTER A WITH GRAVE
EXECUTE stmt USING @a;
EXECUTE stmt USING @a;
SHOW CREATE TABLE t1;
DROP TABLE t1;
SET @a=_utf8 0xD18F; # Cyrillic letter into a latin1 column
--error ER_INVALID_DEFAULT
EXECUTE stmt USING @a;
--error ER_INVALID_DEFAULT
EXECUTE stmt USING @a;
DEALLOCATE PREPARE stmt;
--echo # end of 10.2 test
#
......
......@@ -3309,9 +3309,9 @@ Item_param::Item_param(THD *thd, uint pos_in_query_arg):
Rewritable_query_parameter(pos_in_query_arg, 1),
Type_handler_hybrid_field_type(MYSQL_TYPE_VARCHAR),
state(NO_VALUE),
indicators(0), indicator(STMT_INDICATOR_NONE),
/* Don't pretend to be a literal unless value for this item is set. */
item_type(PARAM_ITEM),
indicators(0), indicator(STMT_INDICATOR_NONE),
set_param_func(default_set_param_func),
m_out_param_info(NULL)
{
......@@ -3338,7 +3338,7 @@ void Item_param::set_null()
max_length= 0;
decimals= 0;
state= NULL_VALUE;
item_type= Item::NULL_ITEM;
fix_type(Item::NULL_ITEM);
DBUG_VOID_RETURN;
}
......@@ -3350,6 +3350,7 @@ void Item_param::set_int(longlong i, uint32 max_length_arg)
max_length= max_length_arg;
decimals= 0;
maybe_null= 0;
fix_type(Item::INT_ITEM);
DBUG_VOID_RETURN;
}
......@@ -3361,6 +3362,7 @@ void Item_param::set_double(double d)
max_length= DBL_DIG + 8;
decimals= NOT_FIXED_DEC;
maybe_null= 0;
fix_type(Item::REAL_ITEM);
DBUG_VOID_RETURN;
}
......@@ -3390,21 +3392,41 @@ void Item_param::set_decimal(const char *str, ulong length)
my_decimal_precision_to_length_no_truncation(decimal_value.precision(),
decimals, unsigned_flag);
maybe_null= 0;
fix_type(Item::DECIMAL_ITEM);
DBUG_VOID_RETURN;
}
void Item_param::set_decimal(const my_decimal *dv)
void Item_param::set_decimal(const my_decimal *dv, bool unsigned_arg)
{
state= DECIMAL_VALUE;
my_decimal2decimal(dv, &decimal_value);
decimals= (uint8) decimal_value.frac;
unsigned_flag= !decimal_value.sign();
unsigned_flag= unsigned_arg;
max_length= my_decimal_precision_to_length(decimal_value.intg + decimals,
decimals, unsigned_flag);
fix_type(Item::DECIMAL_ITEM);
}
void Item_param::fix_temporal(uint32 max_length_arg, uint decimals_arg)
{
state= TIME_VALUE;
max_length= max_length_arg;
decimals= decimals_arg;
fix_type(Item::DATE_ITEM);
}
void Item_param::set_time(const MYSQL_TIME *tm,
uint32 max_length_arg, uint decimals_arg)
{
value.time= *tm;
fix_temporal(max_length_arg, decimals_arg);
}
/**
Set parameter value from MYSQL_TIME value.
......@@ -3433,11 +3455,9 @@ void Item_param::set_time(MYSQL_TIME *tm, timestamp_type time_type,
&str, time_type, 0);
set_zero_time(&value.time, MYSQL_TIMESTAMP_ERROR);
}
state= TIME_VALUE;
maybe_null= 0;
max_length= max_length_arg;
decimals= tm->second_part > 0 ? TIME_SECOND_PART_DIGITS : 0;
fix_temporal(max_length_arg,
tm->second_part > 0 ? TIME_SECOND_PART_DIGITS : 0);
DBUG_VOID_RETURN;
}
......@@ -3458,6 +3478,7 @@ bool Item_param::set_str(const char *str, ulong length)
maybe_null= 0;
/* max_length and decimals are set after charset conversion */
/* sic: str may be not null-terminated, don't add DBUG_PRINT here */
fix_type(Item::STRING_ITEM);
DBUG_RETURN(FALSE);
}
......@@ -3489,6 +3510,7 @@ bool Item_param::set_longdata(const char *str, ulong length)
DBUG_RETURN(TRUE);
state= LONG_DATA_VALUE;
maybe_null= 0;
fix_type(Item::STRING_ITEM);
DBUG_RETURN(FALSE);
}
......@@ -3547,7 +3569,6 @@ bool Item_param::set_from_item(THD *thd, Item *item)
{
unsigned_flag= item->unsigned_flag;
set_int(val, MY_INT64_NUM_DECIMAL_DIGITS);
item_type= Item::INT_ITEM;
set_handler_by_result_type(item->result_type());
DBUG_RETURN(!unsigned_flag && value.integer < 0 ? 1 : 0);
}
......@@ -3559,12 +3580,10 @@ bool Item_param::set_from_item(THD *thd, Item *item)
switch (item->cmp_type()) {
case REAL_RESULT:
set_double(tmp.value.m_double);
item_type= Item::REAL_ITEM;
set_handler_by_field_type(MYSQL_TYPE_DOUBLE);
break;
case INT_RESULT:
set_int(tmp.value.m_longlong, MY_INT64_NUM_DECIMAL_DIGITS);
item_type= Item::INT_ITEM;
set_handler_by_field_type(MYSQL_TYPE_LONGLONG);
break;
case STRING_RESULT:
......@@ -3574,7 +3593,6 @@ bool Item_param::set_from_item(THD *thd, Item *item)
Exact value of max_length is not known unless data is converted to
charset of connection, so we have to set it later.
*/
item_type= Item::STRING_ITEM;
set_handler_by_field_type(MYSQL_TYPE_VARCHAR);
if (set_str(tmp.m_string.ptr(), tmp.m_string.length()))
......@@ -3583,24 +3601,13 @@ bool Item_param::set_from_item(THD *thd, Item *item)
}
case DECIMAL_RESULT:
{
const my_decimal *ent_value= &tmp.m_decimal;
my_decimal2decimal(ent_value, &decimal_value);
state= DECIMAL_VALUE;
decimals= ent_value->frac;
max_length=
my_decimal_precision_to_length_no_truncation(ent_value->precision(),
decimals, unsigned_flag);
item_type= Item::DECIMAL_ITEM;
set_decimal(&tmp.m_decimal, unsigned_flag);
set_handler_by_field_type(MYSQL_TYPE_NEWDECIMAL);
break;
}
case TIME_RESULT:
{
value.time= tmp.value.m_time;
state= TIME_VALUE;
max_length= item->max_length;
decimals= item->decimals;
item_type= Item::DATE_ITEM;
set_time(&tmp.value.m_time, item->max_length, item->decimals);
set_handler(item->type_handler());
break;
}
......@@ -3641,6 +3648,7 @@ void Item_param::reset()
state= NO_VALUE;
maybe_null= 1;
null_value= 0;
fixed= false;
/*
Don't reset item_type to PARAM_ITEM: it's only needed to guard
us from item optimizations at prepare stage, when item doesn't yet
......@@ -3978,6 +3986,7 @@ bool Item_param::convert_str_value(THD *thd)
bool Item_param::basic_const_item() const
{
DBUG_ASSERT(fixed || state == NO_VALUE);
if (state == NO_VALUE || state == TIME_VALUE)
return FALSE;
return TRUE;
......@@ -4112,6 +4121,7 @@ Item_param::set_param_type_and_swap_value(Item_param *src)
maybe_null= src->maybe_null;
null_value= src->null_value;
state= src->state;
fixed= src->fixed;
value= src->value;
decimal_value.swap(src->decimal_value);
......@@ -4123,6 +4133,7 @@ Item_param::set_param_type_and_swap_value(Item_param *src)
void Item_param::set_default()
{
state= DEFAULT_VALUE;
fixed= true;
/*
When Item_param is set to DEFAULT_VALUE:
- its val_str() and val_decimal() return NULL
......@@ -4137,6 +4148,7 @@ void Item_param::set_default()
void Item_param::set_ignore()
{
state= IGNORE_VALUE;
fixed= true;
null_value= true;
}
......@@ -4182,18 +4194,15 @@ Item_param::set_value(THD *thd, sp_rcontext *ctx, Item **it)
str_value.charset());
collation.set(str_value.charset(), DERIVATION_COERCIBLE);
decimals= 0;
item_type= Item::STRING_ITEM;
break;
}
case REAL_RESULT:
set_double(arg->val_real());
item_type= Item::REAL_ITEM;
break;
case INT_RESULT:
set_int(arg->val_int(), arg->max_length);
item_type= Item::INT_ITEM;
break;
case DECIMAL_RESULT:
......@@ -4204,8 +4213,7 @@ Item_param::set_value(THD *thd, sp_rcontext *ctx, Item **it)
if (!dv)
return TRUE;
set_decimal(dv);
item_type= Item::DECIMAL_ITEM;
set_decimal(dv, !dv->sign());
break;
}
......@@ -4215,7 +4223,6 @@ Item_param::set_value(THD *thd, sp_rcontext *ctx, Item **it)
DBUG_ASSERT(TRUE); // Abort in debug mode.
set_null(); // Set to NULL in release mode.
item_type= Item::NULL_ITEM;
return FALSE;
}
......
......@@ -2762,7 +2762,47 @@ class Item_param :public Item_basic_value,
public Rewritable_query_parameter,
public Type_handler_hybrid_field_type
{
public:
/*
NO_VALUE is a special value meaning that the parameter has not been
assigned yet. Item_param::state is assigned to NO_VALUE in constructor
and is used at prepare time.
1. At prepare time
Item_param::fix_fields() sets "fixed" to true,
but as Item_param::state is still NO_VALUE,
Item_param::basic_const_item() returns false. This prevents various
optimizations to happen at prepare time fix_fields().
For example, in this query:
PREPARE stmt FROM 'SELECT FORMAT(10000,2,?)';
Item_param::basic_const_item() is tested from
Item_func_format::fix_length_and_dec().
2. At execute time:
When Item_param gets a value
(or a pseudo-value like DEFAULT_VALUE or IGNORE_VALUE):
- Item_param::state changes from NO_VALUE to something else
- Item_param::fixed is changed to true
All Item_param::set_xxx() make sure to do so.
In the state with an assigned value:
- Item_param::basic_const_item() returns true
- Item::type() returns NULL_ITEM, INT_ITEM, REAL_ITEM, DECIMAL_ITEM,
DATE_ITEM, STRING_ITEM, depending on the value assigned.
So in this state Item_param behaves in many cases like a literal.
When Item_param::cleanup() is called:
- Item_param::state does not change
- Item_param::fixed changes to false
Note, this puts Item_param into an inconsistent state:
- Item_param::basic_const_item() still returns "true"
- Item_param::type() still pretends to be a basic constant Item
Both are not expected in combination with fixed==false.
However, these methods are not really called in this state,
see asserts in Item_param::basic_const_item() and Item_param::type().
When Item_param::reset() is called:
- Item_param::state changes to NO_VALUE
- Item_param::fixed changes to false
*/
enum enum_item_param_state
{
NO_VALUE, NULL_VALUE, INT_VALUE, REAL_VALUE,
......@@ -2770,6 +2810,17 @@ class Item_param :public Item_basic_value,
DECIMAL_VALUE, DEFAULT_VALUE, IGNORE_VALUE
} state;
enum Type item_type;
void fix_type(Type type)
{
item_type= type;
fixed= true;
}
void fix_temporal(uint32 max_length_arg, uint decimals_arg);
public:
struct CONVERSION_INFO
{
/*
......@@ -2838,8 +2889,6 @@ class Item_param :public Item_basic_value,
MYSQL_TIME time;
} value;
enum Type item_type;
enum_field_types field_type() const
{ return Type_handler_hybrid_field_type::field_type(); }
enum Item_result result_type () const
......@@ -2849,7 +2898,11 @@ class Item_param :public Item_basic_value,
Item_param(THD *thd, uint pos_in_query_arg);
enum Type type() const { return item_type; }
enum Type type() const
{
DBUG_ASSERT(fixed || state == NO_VALUE);
return item_type;
}
double val_real();
longlong val_int();
......@@ -2864,10 +2917,11 @@ class Item_param :public Item_basic_value,
void set_int(longlong i, uint32 max_length_arg);
void set_double(double i);
void set_decimal(const char *str, ulong length);
void set_decimal(const my_decimal *dv);
void set_decimal(const my_decimal *dv, bool unsigned_arg);
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);
void set_time(const MYSQL_TIME *tm, uint32 max_length_arg, uint decimals_arg);
bool set_from_item(THD *thd, Item *item);
void reset();
/*
......@@ -2893,6 +2947,18 @@ class Item_param :public Item_basic_value,
bool is_null()
{ DBUG_ASSERT(state != NO_VALUE); return state == NULL_VALUE; }
bool basic_const_item() const;
bool has_no_value() const
{
return state == NO_VALUE;
}
bool has_long_data_value() const
{
return state == LONG_DATA_VALUE;
}
bool has_int_value() const
{
return state == INT_VALUE;
}
/*
This method is used to make a copy of a basic constant item when
propagating constants in the optimizer. The reason to create a new
......
......@@ -742,45 +742,35 @@ static void setup_one_conversion_function(THD *thd, Item_param *param,
switch (param_type) {
case MYSQL_TYPE_TINY:
param->set_param_func= set_param_tiny;
param->item_type= Item::INT_ITEM;
break;
case MYSQL_TYPE_SHORT:
param->set_param_func= set_param_short;
param->item_type= Item::INT_ITEM;
break;
case MYSQL_TYPE_LONG:
param->set_param_func= set_param_int32;
param->item_type= Item::INT_ITEM;
break;
case MYSQL_TYPE_LONGLONG:
param->set_param_func= set_param_int64;
param->item_type= Item::INT_ITEM;
break;
case MYSQL_TYPE_FLOAT:
param->set_param_func= set_param_float;
param->item_type= Item::REAL_ITEM;
break;
case MYSQL_TYPE_DOUBLE:
param->set_param_func= set_param_double;
param->item_type= Item::REAL_ITEM;
break;
case MYSQL_TYPE_DECIMAL:
case MYSQL_TYPE_NEWDECIMAL:
param->set_param_func= set_param_decimal;
param->item_type= Item::DECIMAL_ITEM;
break;
case MYSQL_TYPE_TIME:
param->set_param_func= set_param_time;
param->item_type= Item::STRING_ITEM;
break;
case MYSQL_TYPE_DATE:
param->set_param_func= set_param_date;
param->item_type= Item::STRING_ITEM;
break;
case MYSQL_TYPE_DATETIME:
case MYSQL_TYPE_TIMESTAMP:
param->set_param_func= set_param_datetime;
param->item_type= Item::STRING_ITEM;
break;
case MYSQL_TYPE_TINY_BLOB:
case MYSQL_TYPE_MEDIUM_BLOB:
......@@ -792,7 +782,6 @@ static void setup_one_conversion_function(THD *thd, Item_param *param,
thd->variables.character_set_client;
DBUG_ASSERT(thd->variables.character_set_client);
param->value.cs_info.final_character_set_of_str_value= &my_charset_bin;
param->item_type= Item::STRING_ITEM;
break;
default:
/*
......@@ -821,7 +810,6 @@ static void setup_one_conversion_function(THD *thd, Item_param *param,
Exact value of max_length is not known unless data is converted to
charset of connection, so we have to set it later.
*/
param->item_type= Item::STRING_ITEM;
}
}
param->set_handler_by_field_type((enum enum_field_types) param_type);
......@@ -892,7 +880,7 @@ static bool insert_params_with_log(Prepared_statement *stmt, uchar *null_array,
for (Item_param **it= begin; it < end; ++it)
{
Item_param *param= *it;
if (param->state != Item_param::LONG_DATA_VALUE)
if (!param->has_long_data_value())
{
if (is_param_null(null_array, (uint) (it - begin)))
param->set_null();
......@@ -901,13 +889,12 @@ static bool insert_params_with_log(Prepared_statement *stmt, uchar *null_array,
if (read_pos >= data_end)
DBUG_RETURN(1);
param->set_param_func(param, &read_pos, (uint) (data_end - read_pos));
if (param->state == Item_param::NO_VALUE)
if (param->has_no_value())
DBUG_RETURN(1);
if (param->limit_clause_param && param->state != Item_param::INT_VALUE)
if (param->limit_clause_param && !param->has_int_value())
{
param->set_int(param->val_int(), MY_INT64_NUM_DECIMAL_DIGITS);
param->item_type= Item::INT_ITEM;
if (!param->unsigned_flag && param->value.integer < 0)
DBUG_RETURN(1);
}
......@@ -947,7 +934,7 @@ static bool insert_params(Prepared_statement *stmt, uchar *null_array,
for (Item_param **it= begin; it < end; ++it)
{
Item_param *param= *it;
if (param->state != Item_param::LONG_DATA_VALUE)
if (!param->has_long_data_value())
{
if (is_param_null(null_array, (uint) (it - begin)))
param->set_null();
......@@ -956,7 +943,7 @@ static bool insert_params(Prepared_statement *stmt, uchar *null_array,
if (read_pos >= data_end)
DBUG_RETURN(1);
param->set_param_func(param, &read_pos, (uint) (data_end - read_pos));
if (param->state == Item_param::NO_VALUE)
if (param->has_no_value())
DBUG_RETURN(1);
}
}
......@@ -989,7 +976,7 @@ static bool insert_bulk_params(Prepared_statement *stmt,
Item_param *param= *it;
if (reset)
param->reset();
if (param->state != Item_param::LONG_DATA_VALUE)
if (!param->has_long_data_value())
{
if (param->indicators)
param->indicator= (enum_indicator_type) *((*read_pos)++);
......@@ -1003,7 +990,7 @@ static bool insert_bulk_params(Prepared_statement *stmt,
if ((*read_pos) >= data_end)
DBUG_RETURN(1);
param->set_param_func(param, read_pos, (uint) (data_end - (*read_pos)));
if (param->state == Item_param::NO_VALUE)
if (param->has_no_value())
DBUG_RETURN(1);
break;
case STMT_INDICATOR_NULL:
......@@ -1093,7 +1080,7 @@ static bool emb_insert_params(Prepared_statement *stmt, String *expanded_query)
{
Item_param *param= *it;
setup_one_conversion_function(thd, param, client_param->buffer_type);
if (param->state != Item_param::LONG_DATA_VALUE)
if (!param->has_long_data_value())
{
if (*client_param->is_null)
param->set_null();
......@@ -1105,7 +1092,7 @@ static bool emb_insert_params(Prepared_statement *stmt, String *expanded_query)
client_param->length ?
*client_param->length :
client_param->buffer_length);
if (param->state == Item_param::NO_VALUE)
if (param->has_no_value())
DBUG_RETURN(1);
}
}
......@@ -1129,7 +1116,7 @@ static bool emb_insert_params_with_log(Prepared_statement *stmt, String *query)
{
Item_param *param= *it;
setup_one_conversion_function(thd, param, client_param->buffer_type);
if (param->state != Item_param::LONG_DATA_VALUE)
if (!param->has_long_data_value())
{
if (*client_param->is_null)
param->set_null();
......@@ -1141,7 +1128,7 @@ static bool emb_insert_params_with_log(Prepared_statement *stmt, String *query)
client_param->length ?
*client_param->length :
client_param->buffer_length);
if (param->state == Item_param::NO_VALUE)
if (param->has_no_value())
DBUG_RETURN(1);
}
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment