diff --git a/plugin/type_inet/mysql-test/type_inet/type_inet6.result b/plugin/type_inet/mysql-test/type_inet/type_inet6.result index 9c43725c733..7484a10962c 100644 --- a/plugin/type_inet/mysql-test/type_inet/type_inet6.result +++ b/plugin/type_inet/mysql-test/type_inet/type_inet6.result @@ -2255,3 +2255,78 @@ a Warnings: Warning 1292 Incorrect inet6 value: '' DROP TABLE t1; +# +# MDEV-32879 Server crash in my_decimal::operator= or unexpected ER_DUP_ENTRY upon comparison with INET6 and similar types +# +CREATE TABLE t1 (a CHAR(36) NOT NULL, b INET6 NOT NULL, c DATETIME(6) NOT NULL); +INSERT INTO t1 VALUES ('','::','2000-01-01'),('','::','1900-01-01'); +SELECT c + (b = a) AS f, GROUP_CONCAT(c) FROM t1 GROUP BY f; +f GROUP_CONCAT(c) +NULL 2000-01-01 00:00:00.000000,1900-01-01 00:00:00.000000 +Warnings: +Warning 1292 Incorrect inet6 value: '' +Warning 1292 Incorrect inet6 value: '' +Warning 1292 Incorrect inet6 value: '' +Warning 1292 Incorrect inet6 value: '' +Warning 1292 Incorrect inet6 value: '' +DROP TABLE t1; +CREATE TABLE t1 (a CHAR(36) NOT NULL, b INET6 NOT NULL, c DATETIME(6) NOT NULL); +INSERT INTO t1 VALUES ('','::','2000-01-01'),('','::','1900-01-01'); +SELECT c + (b = a) AS f, COUNT(c) FROM t1 GROUP BY f; +f COUNT(c) +NULL 2 +Warnings: +Warning 1292 Incorrect inet6 value: '' +Warning 1292 Incorrect inet6 value: '' +Warning 1292 Incorrect inet6 value: '' +DROP TABLE t1; +CREATE OR REPLACE TABLE t1 (a CHAR(36) NOT NULL, b INET6 NOT NULL, c DATETIME(6) NOT NULL); +INSERT INTO t1 VALUES ('','::','2000-01-01'),('','::','1900-01-01'); +SELECT c + (b = a) AS f FROM t1 ORDER BY f; +f +NULL +NULL +Warnings: +Warning 1292 Incorrect inet6 value: '' +Warning 1292 Incorrect inet6 value: '' +Warning 1292 Incorrect inet6 value: '' +Warning 1292 Incorrect inet6 value: '' +DROP TABLE t1; +CREATE OR REPLACE TABLE t1 (a CHAR(36) NOT NULL, b INET6 NOT NULL); +INSERT INTO t1 VALUES ('','::'),('','::'); +SELECT 1.00 + (b = a) AS f FROM t1 ORDER BY f; +f +NULL +NULL +Warnings: +Warning 1292 Incorrect inet6 value: '' +Warning 1292 Incorrect inet6 value: '' +Warning 1292 Incorrect inet6 value: '' +Warning 1292 Incorrect inet6 value: '' +SELECT 1.00 + (b BETWEEN a AND '') AS f FROM t1 ORDER BY f; +f +NULL +NULL +Warnings: +Warning 1292 Incorrect inet6 value: '' +Warning 1292 Incorrect inet6 value: '' +Warning 1292 Incorrect inet6 value: '' +Warning 1292 Incorrect inet6 value: '' +Warning 1292 Incorrect inet6 value: '' +Warning 1292 Incorrect inet6 value: '' +Warning 1292 Incorrect inet6 value: '' +Warning 1292 Incorrect inet6 value: '' +SELECT 1.00 + (b IN (a,'')) AS f FROM t1 ORDER BY f; +f +NULL +NULL +Warnings: +Warning 1292 Incorrect inet6 value: '' +Warning 1292 Incorrect inet6 value: '' +Warning 1292 Incorrect inet6 value: '' +Warning 1292 Incorrect inet6 value: '' +Warning 1292 Incorrect inet6 value: '' +Warning 1292 Incorrect inet6 value: '' +Warning 1292 Incorrect inet6 value: '' +Warning 1292 Incorrect inet6 value: '' +DROP TABLE t1; diff --git a/plugin/type_inet/mysql-test/type_inet/type_inet6.test b/plugin/type_inet/mysql-test/type_inet/type_inet6.test index b0dffb098f2..abe9071962f 100644 --- a/plugin/type_inet/mysql-test/type_inet/type_inet6.test +++ b/plugin/type_inet/mysql-test/type_inet/type_inet6.test @@ -1660,3 +1660,29 @@ CREATE OR REPLACE TABLE t1 (a INET6); INSERT INTO t1 VALUES ('::'); SELECT * FROM t1 WHERE a IN ('','::1'); DROP TABLE t1; + +--echo # +--echo # MDEV-32879 Server crash in my_decimal::operator= or unexpected ER_DUP_ENTRY upon comparison with INET6 and similar types +--echo # + +CREATE TABLE t1 (a CHAR(36) NOT NULL, b INET6 NOT NULL, c DATETIME(6) NOT NULL); +INSERT INTO t1 VALUES ('','::','2000-01-01'),('','::','1900-01-01'); +SELECT c + (b = a) AS f, GROUP_CONCAT(c) FROM t1 GROUP BY f; +DROP TABLE t1; + +CREATE TABLE t1 (a CHAR(36) NOT NULL, b INET6 NOT NULL, c DATETIME(6) NOT NULL); +INSERT INTO t1 VALUES ('','::','2000-01-01'),('','::','1900-01-01'); +SELECT c + (b = a) AS f, COUNT(c) FROM t1 GROUP BY f; +DROP TABLE t1; + +CREATE OR REPLACE TABLE t1 (a CHAR(36) NOT NULL, b INET6 NOT NULL, c DATETIME(6) NOT NULL); +INSERT INTO t1 VALUES ('','::','2000-01-01'),('','::','1900-01-01'); +SELECT c + (b = a) AS f FROM t1 ORDER BY f; +DROP TABLE t1; + +CREATE OR REPLACE TABLE t1 (a CHAR(36) NOT NULL, b INET6 NOT NULL); +INSERT INTO t1 VALUES ('','::'),('','::'); +SELECT 1.00 + (b = a) AS f FROM t1 ORDER BY f; +SELECT 1.00 + (b BETWEEN a AND '') AS f FROM t1 ORDER BY f; +SELECT 1.00 + (b IN (a,'')) AS f FROM t1 ORDER BY f; +DROP TABLE t1; diff --git a/plugin/type_inet/sql_type_inet.h b/plugin/type_inet/sql_type_inet.h index 80d8544e6c9..a83dddb4c26 100644 --- a/plugin/type_inet/sql_type_inet.h +++ b/plugin/type_inet/sql_type_inet.h @@ -188,6 +188,21 @@ public: */ static bool fix_fields_maybe_null_on_conversion_to_inet6(Item *item); + /* + Check at fix_fields() time if any of the items can return a nullable + value on conversion to Fbt. + */ + static bool fix_fields_maybe_null_on_conversion_to_inet6(Item **items, + uint count) + { + for (uint i= 0; i < count; i++) + { + if (fix_fields_maybe_null_on_conversion_to_inet6(items[i])) + return true; + } + return false; + } + public: Inet6(Item *item, bool *error, bool warn= true) @@ -714,6 +729,16 @@ public: Inet6_null nb(b); return !na.is_null() && !nb.is_null() && !na.cmp(nb); } + bool Item_bool_rowready_func2_fix_length_and_dec(THD *thd, + Item_bool_rowready_func2 *func) const override + { + if (Type_handler::Item_bool_rowready_func2_fix_length_and_dec(thd, func)) + return true; + if (!func->maybe_null && + Inet6::fix_fields_maybe_null_on_conversion_to_inet6(func->arguments(), 2)) + func->maybe_null= true; + return false; + } bool Item_hybrid_func_fix_attributes(THD *thd, const char *name, Type_handler_hybrid_field_type *h, @@ -902,6 +927,10 @@ public: bool Item_func_between_fix_length_and_dec(Item_func_between *func) const override { + if (!func->maybe_null && + Inet6::fix_fields_maybe_null_on_conversion_to_inet6(func->arguments(), 3)) + func->maybe_null= true; + return false; } longlong Item_func_between_val_int(Item_func_between *func) const override @@ -918,6 +947,10 @@ public: Item_func_in *func) const override { + if (!func->maybe_null && + Inet6::fix_fields_maybe_null_on_conversion_to_inet6(func->arguments(), + func->argument_count())) + func->maybe_null= true; if (func->compatible_types_scalar_bisection_possible()) { return func->value_list_convert_const_to_int(thd) || diff --git a/sql/item.cc b/sql/item.cc index 1853f7b560f..44965a1feb9 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -2497,7 +2497,8 @@ bool DTCollation::aggregate(const DTCollation &dt, uint flags) /******************************/ static -void my_coll_agg_error(DTCollation &c1, DTCollation &c2, const char *fname) +void my_coll_agg_error(const DTCollation &c1, const DTCollation &c2, + const char *fname) { my_error(ER_CANT_AGGREGATE_2COLLATIONS,MYF(0), c1.collation->name,c1.derivation_name(), @@ -2579,10 +2580,17 @@ bool Type_std_attributes::agg_item_collations(DTCollation &c, const char *fname, } +/* + @param single_err When nargs==1, use *single_err as the second aggregated + collation when producing error message. +*/ + bool Type_std_attributes::agg_item_set_converter(const DTCollation &coll, const char *fname, Item **args, uint nargs, - uint flags, int item_sep) + uint flags, int item_sep, + const Single_coll_err + *single_err) { THD *thd= current_thd; if (thd->lex->is_ps_or_view_context_analysis()) @@ -2620,7 +2628,19 @@ bool Type_std_attributes::agg_item_set_converter(const DTCollation &coll, args[0]= safe_args[0]; args[item_sep]= safe_args[1]; } - my_coll_agg_error(args, nargs, fname, item_sep); + if (nargs == 1 && single_err) + { + /* + Use *single_err to produce an error message mentioning two + collations. + */ + if (single_err->first) + my_coll_agg_error(args[0]->collation, single_err->coll, fname); + else + my_coll_agg_error(single_err->coll, args[0]->collation, fname); + } + else + my_coll_agg_error(args, nargs, fname, item_sep); return TRUE; } diff --git a/sql/item.h b/sql/item.h index e7125cccd14..16679776053 100644 --- a/sql/item.h +++ b/sql/item.h @@ -5237,10 +5237,17 @@ public: func_name()); return true; } + /* + If necessary, convert both *a and *b to the collation in tmp: + */ + Single_coll_err error_for_a= {(*b)->collation, true}; + Single_coll_err error_for_b= {(*a)->collation, false}; if (agg_item_set_converter(tmp, func_name(), - a, 1, MY_COLL_CMP_CONV, 1) || + a, 1, MY_COLL_CMP_CONV, 1, + /*just for error message*/ &error_for_a) || agg_item_set_converter(tmp, func_name(), - b, 1, MY_COLL_CMP_CONV, 1)) + b, 1, MY_COLL_CMP_CONV, 1, + /*just for error message*/ &error_for_b)) return true; *cs= tmp.collation; return false; diff --git a/sql/item_cmpfunc.cc b/sql/item_cmpfunc.cc index 4a2de58e748..324aceafd41 100644 --- a/sql/item_cmpfunc.cc +++ b/sql/item_cmpfunc.cc @@ -413,26 +413,6 @@ void Item_func::convert_const_compared_to_int_field(THD *thd) } -bool Item_func::setup_args_and_comparator(THD *thd, Arg_comparator *cmp) -{ - DBUG_ASSERT(arg_count >= 2); // Item_func_nullif has arg_count == 3 - - if (args[0]->cmp_type() == STRING_RESULT && - args[1]->cmp_type() == STRING_RESULT) - { - DTCollation tmp; - if (agg_arg_charsets_for_comparison(tmp, args, 2)) - return true; - cmp->m_compare_collation= tmp.collation; - } - // Convert constants when compared to int/year field - DBUG_ASSERT(functype() != LIKE_FUNC); - convert_const_compared_to_int_field(thd); - - return cmp->set_cmp_func(this, &args[0], &args[1], true); -} - - /* Comparison operators remove arguments' dependency on PAD_CHAR_TO_FULL_LENGTH in case of PAD SPACE comparison collations: trailing spaces do not affect @@ -452,6 +432,7 @@ Item_bool_rowready_func2::value_depends_on_sql_mode() const bool Item_bool_rowready_func2::fix_length_and_dec() { + THD *thd= current_thd; max_length= 1; // Function returns 0 or 1 /* @@ -460,7 +441,16 @@ bool Item_bool_rowready_func2::fix_length_and_dec() */ if (!args[0] || !args[1]) return FALSE; - return setup_args_and_comparator(current_thd, &cmp); + convert_const_compared_to_int_field(thd); + Type_handler_hybrid_field_type tmp; + if (tmp.aggregate_for_comparison(func_name(), args, 2, false) || + tmp.type_handler()->Item_bool_rowready_func2_fix_length_and_dec(thd, + this)) + { + DBUG_ASSERT(thd->is_error()); + return true; + } + return false; } @@ -477,27 +467,22 @@ bool Item_bool_rowready_func2::fix_length_and_dec() items, holding the cached converted value of the original (constant) item. */ -int Arg_comparator::set_cmp_func(Item_func_or_sum *owner_arg, +int Arg_comparator::set_cmp_func(THD *thd, Item_func_or_sum *owner_arg, + const Type_handler *compare_handler, Item **a1, Item **a2) { owner= owner_arg; set_null= set_null && owner_arg; a= a1; b= a2; - Item *tmp_args[2]= {*a1, *a2}; - Type_handler_hybrid_field_type tmp; - if (tmp.aggregate_for_comparison(owner_arg->func_name(), tmp_args, 2, false)) - { - DBUG_ASSERT(current_thd->is_error()); - return 1; - } - m_compare_handler= tmp.type_handler(); + m_compare_handler= compare_handler; return m_compare_handler->set_comparator_func(this); } bool Arg_comparator::set_cmp_func_for_row_arguments() { + THD *thd= current_thd; uint n= (*a)->cols(); if (n != (*b)->cols()) { @@ -514,8 +499,8 @@ bool Arg_comparator::set_cmp_func_for_row_arguments() my_error(ER_OPERAND_COLUMNS, MYF(0), (*a)->element_index(i)->cols()); return true; } - if (comparators[i].set_cmp_func(owner, (*a)->addr(i), - (*b)->addr(i), set_null)) + if (comparators[i].set_cmp_func(thd, owner, (*a)->addr(i), + (*b)->addr(i), set_null)) return true; } return false; @@ -541,7 +526,16 @@ bool Arg_comparator::set_cmp_func_string() { /* We must set cmp_collation here as we may be called from for an automatic - generated item, like in natural join + generated item, like in natural join. + Allow reinterpted superset as subset. + Use charset narrowing only for equalities, as that would allow + to construct ref access. + Non-equality comparisons with constants work without charset narrowing, + the constant gets converted. + Non-equality comparisons with non-constants would need narrowing to + enable range optimizer to handle e.g. + t1.mb3key_col <= const_table.mb4_col + But this doesn't look important. */ if (owner->agg_arg_charsets_for_comparison(&m_compare_collation, a, b)) return true; @@ -2748,8 +2742,9 @@ Item_func_nullif::fix_length_and_dec() fix_char_length(args[2]->max_char_length()); maybe_null=1; m_arg0= args[0]; - if (setup_args_and_comparator(thd, &cmp)) - return TRUE; + convert_const_compared_to_int_field(thd); + if (cmp.set_cmp_func(thd, this, &args[0], &args[1], true/*set_null*/)) + return true; /* A special code for EXECUTE..PREPARE. diff --git a/sql/item_cmpfunc.h b/sql/item_cmpfunc.h index 7cbc1236a03..3929b4c7d30 100644 --- a/sql/item_cmpfunc.h +++ b/sql/item_cmpfunc.h @@ -56,7 +56,9 @@ class Arg_comparator: public Sql_alloc Item *a_cache, *b_cache; // Cached values of a and b items // when one of arguments is NULL. - int set_cmp_func(Item_func_or_sum *owner_arg, Item **a1, Item **a2); + int set_cmp_func(THD *thd, Item_func_or_sum *owner_arg, + const Type_handler *compare_handler, + Item **a1, Item **a2); int compare_not_null_values(longlong val1, longlong val2) { @@ -93,12 +95,25 @@ public: bool set_cmp_func_real(); bool set_cmp_func_decimal(); - inline int set_cmp_func(Item_func_or_sum *owner_arg, - Item **a1, Item **a2, bool set_null_arg) + inline int set_cmp_func(THD *thd, Item_func_or_sum *owner_arg, + const Type_handler *compare_handler, + Item **a1, Item **a2, bool set_null_arg) { set_null= set_null_arg; - return set_cmp_func(owner_arg, a1, a2); + return set_cmp_func(thd, owner_arg, compare_handler, a1, a2); } + int set_cmp_func(THD *thd, Item_func_or_sum *owner_arg, + Item **a1, Item **a2, bool set_null_arg) + { + Item *tmp_args[2]= { *a1, *a2 }; + Type_handler_hybrid_field_type tmp; + if (tmp.aggregate_for_comparison(owner_arg->func_name(), + tmp_args, 2, false)) + return 1; + return set_cmp_func(thd, owner_arg, tmp.type_handler(), + a1, a2, set_null_arg); + } + inline int compare() { return (this->*func)(); } int compare_string(); // compare args[0] & args[1] @@ -533,9 +548,17 @@ public: return this; } bool fix_length_and_dec(); - int set_cmp_func() + bool fix_length_and_dec_generic(THD *thd, + const Type_handler *compare_handler) { - return cmp.set_cmp_func(this, tmp_arg, tmp_arg + 1, true); + DBUG_ASSERT(args == tmp_arg); + return cmp.set_cmp_func(thd, this, compare_handler, + tmp_arg, tmp_arg + 1, true/*set_null*/); + } + int set_cmp_func(THD *thd) + { + DBUG_ASSERT(args == tmp_arg); + return cmp.set_cmp_func(thd, this, tmp_arg, tmp_arg + 1, true/*set_null*/); } CHARSET_INFO *compare_collation() const { return cmp.compare_collation(); } const Type_handler *compare_type_handler() const diff --git a/sql/item_func.h b/sql/item_func.h index bd04af926b0..ef020fa9054 100644 --- a/sql/item_func.h +++ b/sql/item_func.h @@ -395,16 +395,6 @@ public: } } void convert_const_compared_to_int_field(THD *thd); - /** - Prepare arguments and setup a comparator. - Used in Item_func_xxx with two arguments and a comparator, - e.g. Item_bool_func2 and Item_func_nullif. - args[0] or args[1] can be modified: - - converted to character set and collation of the operation - - or replaced to an Item_int_with_ref - */ - bool setup_args_and_comparator(THD *thd, Arg_comparator *cmp); - bool with_sum_func() const { return m_with_sum_func; } With_sum_func_cache* get_with_sum_func_cache() { return this; } Item_func *get_item_func() { return this; } diff --git a/sql/item_sum.cc b/sql/item_sum.cc index 46942c0c785..584fd21ca4a 100644 --- a/sql/item_sum.cc +++ b/sql/item_sum.cc @@ -1277,9 +1277,14 @@ void Item_sum_min_max::setup_hybrid(THD *thd, Item *item, Item *value_arg) /* Don't cache value, as it will change */ if (!item->const_item()) arg_cache->set_used_tables(RAND_TABLE_BIT); - cmp= new Arg_comparator(); + DBUG_ASSERT(item->type_handler_for_comparison() == + value->type_handler_for_comparison()); + DBUG_ASSERT(item->type_handler_for_comparison() == + arg_cache->type_handler_for_comparison()); + cmp= new (thd->mem_root) Arg_comparator(); if (cmp) - cmp->set_cmp_func(this, (Item**)&arg_cache, (Item**)&value, FALSE); + cmp->set_cmp_func(thd, this, item->type_handler_for_comparison(), + (Item**)&arg_cache, (Item**)&value, FALSE); DBUG_VOID_RETURN; } diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 62766cdb9cf..21132651b07 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -15405,7 +15405,7 @@ static bool check_row_equality(THD *thd, const Arg_comparator *comparators, { Item_func_eq *eq_item; if (!(eq_item= new (thd->mem_root) Item_func_eq(thd, left_item, right_item)) || - eq_item->set_cmp_func()) + eq_item->set_cmp_func(thd)) return FALSE; eq_item->quick_fix_field(); eq_list->push_back(eq_item, thd->mem_root); @@ -16155,7 +16155,7 @@ Item *eliminate_item_equal(THD *thd, COND *cond, COND_EQUAL *upper_levels, Don't produce equality if const is equal to item_const. */ Item_func_eq *func= new (thd->mem_root) Item_func_eq(thd, item_const, upper_const); - func->set_cmp_func(); + func->set_cmp_func(thd); func->quick_fix_field(); if (func->val_int()) item= 0; @@ -16203,7 +16203,7 @@ Item *eliminate_item_equal(THD *thd, COND *cond, COND_EQUAL *upper_levels, field_item->remove_item_direct_ref(), head_item->remove_item_direct_ref()); - if (!eq_item || eq_item->set_cmp_func()) + if (!eq_item || eq_item->set_cmp_func(thd)) return 0; eq_item->quick_fix_field(); } @@ -16621,7 +16621,7 @@ change_cond_ref_to_const(THD *thd, I_List *save_list, So make sure to use set_cmp_func() only for non-LIKE operators. */ if (functype != Item_func::LIKE_FUNC) - ((Item_bool_rowready_func2*) func)->set_cmp_func(); + ((Item_bool_rowready_func2*) func)->set_cmp_func(thd); } } else if (can_change_cond_ref_to_const(func, left_item, right_item, @@ -16646,7 +16646,7 @@ change_cond_ref_to_const(THD *thd, I_List *save_list, save_list->push_back(tmp2); } if (functype != Item_func::LIKE_FUNC) - ((Item_bool_rowready_func2*) func)->set_cmp_func(); + ((Item_bool_rowready_func2*) func)->set_cmp_func(thd); } } } diff --git a/sql/sql_type.cc b/sql/sql_type.cc index 6bdc96ac401..e7058f6b4ed 100644 --- a/sql/sql_type.cc +++ b/sql/sql_type.cc @@ -5675,6 +5675,14 @@ Type_handler_string_result::Item_func_hybrid_field_type_get_date( /***************************************************************************/ +bool Type_handler::Item_bool_rowready_func2_fix_length_and_dec(THD *thd, + Item_bool_rowready_func2 *func) const +{ + return func->fix_length_and_dec_generic(thd, this); +} + +/***************************************************************************/ + bool Type_handler_numeric:: Item_func_between_fix_length_and_dec(Item_func_between *func) const { diff --git a/sql/sql_type.h b/sql/sql_type.h index aa89c8850af..ee3eb454ead 100644 --- a/sql/sql_type.h +++ b/sql/sql_type.h @@ -55,6 +55,7 @@ class Item_hybrid_func; class Item_func_min_max; class Item_func_hybrid_field_type; class Item_bool_func2; +class Item_bool_rowready_func2; class Item_func_between; class Item_func_in; class Item_func_round; @@ -3233,9 +3234,15 @@ public: bool agg_item_collations(DTCollation &c, const char *name, Item **items, uint nitems, uint flags, int item_sep); + struct Single_coll_err + { + const DTCollation& coll; + bool first; + }; bool agg_item_set_converter(const DTCollation &coll, const char *fname, Item **args, uint nargs, - uint flags, int item_sep); + uint flags, int item_sep, + const Single_coll_err *single_item_err= NULL); /* Collect arguments' character sets together. @@ -4232,6 +4239,8 @@ public: } virtual bool Item_eq_value(THD *thd, const Type_cmp_attributes *attr, Item *a, Item *b) const= 0; + virtual bool Item_bool_rowready_func2_fix_length_and_dec(THD *thd, + Item_bool_rowready_func2 *func) const; virtual bool Item_hybrid_func_fix_attributes(THD *thd, const char *name, Type_handler_hybrid_field_type *,