mirror of
https://github.com/MariaDB/server.git
synced 2025-07-30 16:24:05 +03:00
MDEV-13790 UNHEX() of a somewhat complicated CONCAT() returns NULL
This commit is contained in:
@ -262,3 +262,9 @@ c2
|
|||||||
abcdefghi-abcdefghi
|
abcdefghi-abcdefghi
|
||||||
DROP TABLE t1;
|
DROP TABLE t1;
|
||||||
SET optimizer_switch=@save_optimizer_switch;
|
SET optimizer_switch=@save_optimizer_switch;
|
||||||
|
#
|
||||||
|
# MDEV-13790 UNHEX() of a somewhat complicated CONCAT() returns NULL
|
||||||
|
#
|
||||||
|
SELECT UNHEX(CONCAT('414C2', HEX(8 + ROUND(RAND()*7)), SUBSTR(SHA(UUID()),6,33),HEX(2+ROUND(RAND()*8)))) IS NULL AS c1;
|
||||||
|
c1
|
||||||
|
0
|
||||||
|
@ -236,3 +236,9 @@ SELECT CONCAT(t2,'-',t2) c2 FROM (SELECT TRIM(t) t2 FROM t1) sub;
|
|||||||
DROP TABLE t1;
|
DROP TABLE t1;
|
||||||
|
|
||||||
SET optimizer_switch=@save_optimizer_switch;
|
SET optimizer_switch=@save_optimizer_switch;
|
||||||
|
|
||||||
|
--echo #
|
||||||
|
--echo # MDEV-13790 UNHEX() of a somewhat complicated CONCAT() returns NULL
|
||||||
|
--echo #
|
||||||
|
|
||||||
|
SELECT UNHEX(CONCAT('414C2', HEX(8 + ROUND(RAND()*7)), SUBSTR(SHA(UUID()),6,33),HEX(2+ROUND(RAND()*8)))) IS NULL AS c1;
|
||||||
|
@ -630,138 +630,84 @@ String *Item_func_decode_histogram::val_str(String *str)
|
|||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/*
|
||||||
|
Realloc the result buffer.
|
||||||
|
NOTE: We should be prudent in the initial allocation unit -- the
|
||||||
|
size of the arguments is a function of data distribution, which
|
||||||
|
can be any. Instead of overcommitting at the first row, we grow
|
||||||
|
the allocated amount by the factor of 2. This ensures that no
|
||||||
|
more than 25% of memory will be overcommitted on average.
|
||||||
|
|
||||||
|
@param IN/OUT str - the result string
|
||||||
|
@param IN length - new total space required in "str"
|
||||||
|
@retval false - on success
|
||||||
|
@retval true - on error
|
||||||
|
*/
|
||||||
|
|
||||||
|
bool Item_func_concat::realloc_result(String *str, uint length) const
|
||||||
|
{
|
||||||
|
if (str->alloced_length() >= length)
|
||||||
|
return false; // Alloced space is big enough, nothing to do.
|
||||||
|
|
||||||
|
if (str->alloced_length() == 0)
|
||||||
|
return str->alloc(length);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Item_func_concat::val_str() makes sure the result length does not grow
|
||||||
|
higher than max_allowed_packet. So "length" is limited to 1G here.
|
||||||
|
We can't say anything about the current value of str->alloced_length(),
|
||||||
|
as str was initially set by args[0]->val_str(str).
|
||||||
|
So multiplication by 2 can overflow, if args[0] for some reasons
|
||||||
|
did not limit the result to max_alloced_packet. But it's not harmful,
|
||||||
|
"str" will be realloced exactly to "length" bytes in case of overflow.
|
||||||
|
*/
|
||||||
|
uint new_length= MY_MAX(str->alloced_length() * 2, length);
|
||||||
|
return str->realloc(new_length);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Concatenate args with the following premises:
|
Concatenate args with the following premises:
|
||||||
If only one arg (which is ok), return value of arg;
|
If only one arg (which is ok), return value of arg;
|
||||||
Don't reallocate val_str() if not absolute necessary.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
String *Item_func_concat::val_str(String *str)
|
String *Item_func_concat::val_str(String *str)
|
||||||
{
|
{
|
||||||
DBUG_ASSERT(fixed == 1);
|
DBUG_ASSERT(fixed == 1);
|
||||||
String *res,*res2,*use_as_buff;
|
THD *thd= current_thd;
|
||||||
uint i;
|
String *res;
|
||||||
bool is_const= 0;
|
|
||||||
|
|
||||||
null_value=0;
|
null_value=0;
|
||||||
if (!(res=args[0]->val_str(str)))
|
if (!(res= args[0]->val_str(str)))
|
||||||
goto null;
|
goto null;
|
||||||
use_as_buff= &tmp_value;
|
|
||||||
/* Item_subselect in --ps-protocol mode will state it as a non-const */
|
if (res != str)
|
||||||
is_const= args[0]->const_item() || !args[0]->used_tables();
|
str->copy(res->ptr(), res->length(), res->charset());
|
||||||
for (i=1 ; i < arg_count ; i++)
|
|
||||||
|
for (uint i= 1 ; i < arg_count ; i++)
|
||||||
{
|
{
|
||||||
if (res->length() == 0)
|
uint concat_len;
|
||||||
|
if (!(res= args[i]->val_str(&tmp_value)))
|
||||||
|
goto null;
|
||||||
|
if (res->length() == 0)
|
||||||
|
continue;
|
||||||
|
if ((concat_len= str->length() + res->length()) >
|
||||||
|
thd->variables.max_allowed_packet)
|
||||||
{
|
{
|
||||||
if (!(res=args[i]->val_str(str)))
|
push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
|
||||||
goto null;
|
ER_WARN_ALLOWED_PACKET_OVERFLOWED,
|
||||||
/*
|
ER(ER_WARN_ALLOWED_PACKET_OVERFLOWED), func_name(),
|
||||||
CONCAT accumulates its result in the result of its the first
|
thd->variables.max_allowed_packet);
|
||||||
non-empty argument. Because of this we need is_const to be
|
goto null;
|
||||||
evaluated only for it.
|
|
||||||
*/
|
|
||||||
is_const= args[i]->const_item() || !args[i]->used_tables();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (!(res2=args[i]->val_str(use_as_buff)))
|
|
||||||
goto null;
|
|
||||||
if (res2->length() == 0)
|
|
||||||
continue;
|
|
||||||
if (res->length()+res2->length() >
|
|
||||||
current_thd->variables.max_allowed_packet)
|
|
||||||
{
|
|
||||||
push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
|
|
||||||
ER_WARN_ALLOWED_PACKET_OVERFLOWED,
|
|
||||||
ER(ER_WARN_ALLOWED_PACKET_OVERFLOWED), func_name(),
|
|
||||||
current_thd->variables.max_allowed_packet);
|
|
||||||
goto null;
|
|
||||||
}
|
|
||||||
if (!is_const && res->alloced_length() >= res->length()+res2->length())
|
|
||||||
{ // Use old buffer
|
|
||||||
res->append(*res2);
|
|
||||||
}
|
|
||||||
else if (str->alloced_length() >= res->length()+res2->length())
|
|
||||||
{
|
|
||||||
if (str->ptr() == res2->ptr())
|
|
||||||
str->replace(0,0,*res);
|
|
||||||
else
|
|
||||||
{
|
|
||||||
str->copy(*res);
|
|
||||||
str->append(*res2);
|
|
||||||
}
|
|
||||||
res= str;
|
|
||||||
use_as_buff= &tmp_value;
|
|
||||||
}
|
|
||||||
else if (res == &tmp_value)
|
|
||||||
{
|
|
||||||
if (res->append(*res2)) // Must be a blob
|
|
||||||
goto null;
|
|
||||||
}
|
|
||||||
else if (res2 == &tmp_value)
|
|
||||||
{ // This can happend only 1 time
|
|
||||||
if (tmp_value.replace(0,0,*res))
|
|
||||||
goto null;
|
|
||||||
res= &tmp_value;
|
|
||||||
use_as_buff=str; // Put next arg here
|
|
||||||
}
|
|
||||||
else if (tmp_value.is_alloced() && res2->ptr() >= tmp_value.ptr() &&
|
|
||||||
res2->ptr() <= tmp_value.ptr() + tmp_value.alloced_length())
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
This happens really seldom:
|
|
||||||
In this case res2 is sub string of tmp_value. We will
|
|
||||||
now work in place in tmp_value to set it to res | res2
|
|
||||||
*/
|
|
||||||
/* Chop the last characters in tmp_value that isn't in res2 */
|
|
||||||
tmp_value.length((uint32) (res2->ptr() - tmp_value.ptr()) +
|
|
||||||
res2->length());
|
|
||||||
/* Place res2 at start of tmp_value, remove chars before res2 */
|
|
||||||
if (tmp_value.replace(0,(uint32) (res2->ptr() - tmp_value.ptr()),
|
|
||||||
*res))
|
|
||||||
goto null;
|
|
||||||
res= &tmp_value;
|
|
||||||
use_as_buff=str; // Put next arg here
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{ // Two big const strings
|
|
||||||
/*
|
|
||||||
NOTE: We should be prudent in the initial allocation unit -- the
|
|
||||||
size of the arguments is a function of data distribution, which
|
|
||||||
can be any. Instead of overcommitting at the first row, we grow
|
|
||||||
the allocated amount by the factor of 2. This ensures that no
|
|
||||||
more than 25% of memory will be overcommitted on average.
|
|
||||||
*/
|
|
||||||
|
|
||||||
uint concat_len= res->length() + res2->length();
|
|
||||||
|
|
||||||
if (tmp_value.alloced_length() < concat_len)
|
|
||||||
{
|
|
||||||
if (tmp_value.alloced_length() == 0)
|
|
||||||
{
|
|
||||||
if (tmp_value.alloc(concat_len))
|
|
||||||
goto null;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
uint new_len = MY_MAX(tmp_value.alloced_length() * 2, concat_len);
|
|
||||||
|
|
||||||
if (tmp_value.realloc(new_len))
|
|
||||||
goto null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tmp_value.copy(*res) || tmp_value.append(*res2))
|
|
||||||
goto null;
|
|
||||||
|
|
||||||
res= &tmp_value;
|
|
||||||
use_as_buff=str;
|
|
||||||
}
|
|
||||||
is_const= 0;
|
|
||||||
}
|
}
|
||||||
|
DBUG_ASSERT(!res->uses_buffer_owned_by(str));
|
||||||
|
DBUG_ASSERT(!str->uses_buffer_owned_by(res));
|
||||||
|
if (realloc_result(str, concat_len) || str->append(*res))
|
||||||
|
goto null;
|
||||||
}
|
}
|
||||||
res->set_charset(collation.collation);
|
|
||||||
return res;
|
str->set_charset(collation.collation);
|
||||||
|
return str;
|
||||||
|
|
||||||
null:
|
null:
|
||||||
null_value=1;
|
null_value=1;
|
||||||
|
@ -156,6 +156,7 @@ public:
|
|||||||
class Item_func_concat :public Item_str_func
|
class Item_func_concat :public Item_str_func
|
||||||
{
|
{
|
||||||
String tmp_value;
|
String tmp_value;
|
||||||
|
bool realloc_result(String *str, uint length) const;
|
||||||
public:
|
public:
|
||||||
Item_func_concat(List<Item> &list) :Item_str_func(list) {}
|
Item_func_concat(List<Item> &list) :Item_str_func(list) {}
|
||||||
Item_func_concat(Item *a,Item *b) :Item_str_func(a,b) {}
|
Item_func_concat(Item *a,Item *b) :Item_str_func(a,b) {}
|
||||||
|
Reference in New Issue
Block a user