mirror of
https://github.com/MariaDB/server.git
synced 2025-07-30 16:24:05 +03:00
Fix crash in embedded server due to incorrect storage of results
in the query cache. (Bug #9549) libmysqld/emb_qcache.h: Fix Querycache_stream::use_next_block() to actually use the next block and mark blocks as used when it writes to them. mysql-test/r/query_cache.result: Update results. mysql-test/t/query_cache.test: Add new regression test. libmysqld/emb_qcache.cc: Fix calls to use_next_block() to indicate whether we are writing to the next block or not. sql/sql_cache.cc: Initialize the first block properly when storing a result in the embedded server.
This commit is contained in:
@ -22,7 +22,7 @@
|
|||||||
void Querycache_stream::store_char(char c)
|
void Querycache_stream::store_char(char c)
|
||||||
{
|
{
|
||||||
if (data_end == cur_data)
|
if (data_end == cur_data)
|
||||||
use_next_block();
|
use_next_block(TRUE);
|
||||||
*(cur_data++)= c;
|
*(cur_data++)= c;
|
||||||
#ifndef DBUG_OFF
|
#ifndef DBUG_OFF
|
||||||
stored_size++;
|
stored_size++;
|
||||||
@ -42,13 +42,13 @@ void Querycache_stream::store_short(ushort s)
|
|||||||
}
|
}
|
||||||
if (data_end == cur_data)
|
if (data_end == cur_data)
|
||||||
{
|
{
|
||||||
use_next_block();
|
use_next_block(TRUE);
|
||||||
int2store(cur_data, s);
|
int2store(cur_data, s);
|
||||||
cur_data+= 2;
|
cur_data+= 2;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
*cur_data= ((byte *)(&s))[0];
|
*cur_data= ((byte *)(&s))[0];
|
||||||
use_next_block();
|
use_next_block(TRUE);
|
||||||
*(cur_data++)= ((byte *)(&s))[1];
|
*(cur_data++)= ((byte *)(&s))[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,7 +66,7 @@ void Querycache_stream::store_int(uint i)
|
|||||||
}
|
}
|
||||||
if (!rest_len)
|
if (!rest_len)
|
||||||
{
|
{
|
||||||
use_next_block();
|
use_next_block(TRUE);
|
||||||
int4store(cur_data, i);
|
int4store(cur_data, i);
|
||||||
cur_data+= 4;
|
cur_data+= 4;
|
||||||
return;
|
return;
|
||||||
@ -74,7 +74,7 @@ void Querycache_stream::store_int(uint i)
|
|||||||
char buf[4];
|
char buf[4];
|
||||||
int4store(buf, i);
|
int4store(buf, i);
|
||||||
memcpy(cur_data, buf, rest_len);
|
memcpy(cur_data, buf, rest_len);
|
||||||
use_next_block();
|
use_next_block(TRUE);
|
||||||
memcpy(cur_data, buf+rest_len, 4-rest_len);
|
memcpy(cur_data, buf+rest_len, 4-rest_len);
|
||||||
cur_data+= 4-rest_len;
|
cur_data+= 4-rest_len;
|
||||||
}
|
}
|
||||||
@ -93,13 +93,13 @@ void Querycache_stream::store_ll(ulonglong ll)
|
|||||||
}
|
}
|
||||||
if (!rest_len)
|
if (!rest_len)
|
||||||
{
|
{
|
||||||
use_next_block();
|
use_next_block(TRUE);
|
||||||
int8store(cur_data, ll);
|
int8store(cur_data, ll);
|
||||||
cur_data+= 8;
|
cur_data+= 8;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
memcpy(cur_data, &ll, rest_len);
|
memcpy(cur_data, &ll, rest_len);
|
||||||
use_next_block();
|
use_next_block(TRUE);
|
||||||
memcpy(cur_data, ((byte*)&ll)+rest_len, 8-rest_len);
|
memcpy(cur_data, ((byte*)&ll)+rest_len, 8-rest_len);
|
||||||
cur_data+= 8-rest_len;
|
cur_data+= 8-rest_len;
|
||||||
}
|
}
|
||||||
@ -112,14 +112,14 @@ void Querycache_stream::store_str_only(const char *str, uint str_len)
|
|||||||
do
|
do
|
||||||
{
|
{
|
||||||
size_t rest_len= data_end - cur_data;
|
size_t rest_len= data_end - cur_data;
|
||||||
if (rest_len > str_len)
|
if (rest_len >= str_len)
|
||||||
{
|
{
|
||||||
memcpy(cur_data, str, str_len);
|
memcpy(cur_data, str, str_len);
|
||||||
cur_data+= str_len;
|
cur_data+= str_len;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
memcpy(cur_data, str, rest_len);
|
memcpy(cur_data, str, rest_len);
|
||||||
use_next_block();
|
use_next_block(TRUE);
|
||||||
str_len-= rest_len;
|
str_len-= rest_len;
|
||||||
str+= rest_len;
|
str+= rest_len;
|
||||||
} while(str_len);
|
} while(str_len);
|
||||||
@ -145,7 +145,7 @@ void Querycache_stream::store_safe_str(const char *str, uint str_len)
|
|||||||
char Querycache_stream::load_char()
|
char Querycache_stream::load_char()
|
||||||
{
|
{
|
||||||
if (cur_data == data_end)
|
if (cur_data == data_end)
|
||||||
use_next_block();
|
use_next_block(FALSE);
|
||||||
return *(cur_data++);
|
return *(cur_data++);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,13 +160,13 @@ ushort Querycache_stream::load_short()
|
|||||||
}
|
}
|
||||||
if (data_end == cur_data)
|
if (data_end == cur_data)
|
||||||
{
|
{
|
||||||
use_next_block();
|
use_next_block(FALSE);
|
||||||
result= uint2korr(cur_data);
|
result= uint2korr(cur_data);
|
||||||
cur_data+= 2;
|
cur_data+= 2;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
((byte*)&result)[0]= *cur_data;
|
((byte*)&result)[0]= *cur_data;
|
||||||
use_next_block();
|
use_next_block(FALSE);
|
||||||
((byte*)&result)[1]= *(cur_data++);
|
((byte*)&result)[1]= *(cur_data++);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -183,14 +183,14 @@ uint Querycache_stream::load_int()
|
|||||||
}
|
}
|
||||||
if (!rest_len)
|
if (!rest_len)
|
||||||
{
|
{
|
||||||
use_next_block();
|
use_next_block(FALSE);
|
||||||
result= uint4korr(cur_data);
|
result= uint4korr(cur_data);
|
||||||
cur_data+= 4;
|
cur_data+= 4;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
char buf[4];
|
char buf[4];
|
||||||
memcpy(buf, cur_data, rest_len);
|
memcpy(buf, cur_data, rest_len);
|
||||||
use_next_block();
|
use_next_block(FALSE);
|
||||||
memcpy(buf+rest_len, cur_data, 4-rest_len);
|
memcpy(buf+rest_len, cur_data, 4-rest_len);
|
||||||
cur_data+= 4-rest_len;
|
cur_data+= 4-rest_len;
|
||||||
result= uint4korr(buf);
|
result= uint4korr(buf);
|
||||||
@ -209,13 +209,13 @@ ulonglong Querycache_stream::load_ll()
|
|||||||
}
|
}
|
||||||
if (!rest_len)
|
if (!rest_len)
|
||||||
{
|
{
|
||||||
use_next_block();
|
use_next_block(FALSE);
|
||||||
result= uint8korr(cur_data);
|
result= uint8korr(cur_data);
|
||||||
cur_data+= 8;
|
cur_data+= 8;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
memcpy(&result, cur_data, rest_len);
|
memcpy(&result, cur_data, rest_len);
|
||||||
use_next_block();
|
use_next_block(FALSE);
|
||||||
memcpy(((byte*)&result)+rest_len, cur_data, 8-rest_len);
|
memcpy(((byte*)&result)+rest_len, cur_data, 8-rest_len);
|
||||||
cur_data+= 8-rest_len;
|
cur_data+= 8-rest_len;
|
||||||
return result;
|
return result;
|
||||||
@ -226,7 +226,7 @@ void Querycache_stream::load_str_only(char *buffer, uint str_len)
|
|||||||
do
|
do
|
||||||
{
|
{
|
||||||
size_t rest_len= data_end - cur_data;
|
size_t rest_len= data_end - cur_data;
|
||||||
if (rest_len > str_len)
|
if (rest_len >= str_len)
|
||||||
{
|
{
|
||||||
memcpy(buffer, cur_data, str_len);
|
memcpy(buffer, cur_data, str_len);
|
||||||
cur_data+= str_len;
|
cur_data+= str_len;
|
||||||
@ -234,7 +234,7 @@ void Querycache_stream::load_str_only(char *buffer, uint str_len)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
memcpy(buffer, cur_data, rest_len);
|
memcpy(buffer, cur_data, rest_len);
|
||||||
use_next_block();
|
use_next_block(FALSE);
|
||||||
str_len-= rest_len;
|
str_len-= rest_len;
|
||||||
buffer+= rest_len;
|
buffer+= rest_len;
|
||||||
} while(str_len);
|
} while(str_len);
|
||||||
|
@ -22,18 +22,39 @@ class Querycache_stream
|
|||||||
uint headers_len;
|
uint headers_len;
|
||||||
public:
|
public:
|
||||||
#ifndef DBUG_OFF
|
#ifndef DBUG_OFF
|
||||||
|
Query_cache_block *first_block;
|
||||||
uint stored_size;
|
uint stored_size;
|
||||||
#endif
|
#endif
|
||||||
Querycache_stream(Query_cache_block *ini_block, uint ini_headers_len) :
|
Querycache_stream(Query_cache_block *ini_block, uint ini_headers_len) :
|
||||||
block(ini_block), headers_len(ini_headers_len)
|
block(ini_block), headers_len(ini_headers_len)
|
||||||
{
|
{
|
||||||
use_next_block();
|
cur_data= ((byte*)block)+headers_len;
|
||||||
|
data_end= cur_data + (block->used-headers_len);
|
||||||
#ifndef DBUG_OFF
|
#ifndef DBUG_OFF
|
||||||
|
first_block= ini_block;
|
||||||
stored_size= 0;
|
stored_size= 0;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
void use_next_block()
|
void use_next_block(bool writing)
|
||||||
{
|
{
|
||||||
|
/*
|
||||||
|
This shouldn't be called if there is only one block, or to loop
|
||||||
|
around to the first block again. That means we're trying to write
|
||||||
|
more data than we allocated space for.
|
||||||
|
*/
|
||||||
|
DBUG_ASSERT(block->next != block);
|
||||||
|
DBUG_ASSERT(block->next != first_block);
|
||||||
|
|
||||||
|
block= block->next;
|
||||||
|
/*
|
||||||
|
While writing, update the type of each block as we write to it.
|
||||||
|
While reading, make sure that the block is of the expected type.
|
||||||
|
*/
|
||||||
|
if (writing)
|
||||||
|
block->type= Query_cache_block::RES_CONT;
|
||||||
|
else
|
||||||
|
DBUG_ASSERT(block->type == Query_cache_block::RES_CONT);
|
||||||
|
|
||||||
cur_data= ((byte*)block)+headers_len;
|
cur_data= ((byte*)block)+headers_len;
|
||||||
data_end= cur_data + (block->used-headers_len);
|
data_end= cur_data + (block->used-headers_len);
|
||||||
}
|
}
|
||||||
|
File diff suppressed because one or more lines are too long
@ -684,4 +684,49 @@ repair table t1;
|
|||||||
show status like 'qcache_queries_in_cache';
|
show status like 'qcache_queries_in_cache';
|
||||||
drop table t1;
|
drop table t1;
|
||||||
|
|
||||||
|
# Bug #9549: Make sure cached queries that span more than one cache block
|
||||||
|
# are handled properly in the embedded server.
|
||||||
|
|
||||||
|
# We just want a small query cache, so we can fragment it easily
|
||||||
|
set GLOBAL query_cache_size=64*1024;
|
||||||
|
# This actually gives us a usable cache size of about 48K
|
||||||
|
|
||||||
|
# Each table is about 14K
|
||||||
|
create table t1 (a text);
|
||||||
|
insert into t1 values (repeat('abcdefghijklmnopqrstuvwxyz', 550));
|
||||||
|
create table t2 (a text);
|
||||||
|
insert into t2 values (repeat('ijklmnopqrstuvwxyzabcdefgh', 550));
|
||||||
|
|
||||||
|
# Load a query from each table into the query cache
|
||||||
|
--disable_result_log
|
||||||
|
select a from t1; # Q1
|
||||||
|
select a from t2; # Q2
|
||||||
|
--enable_result_log
|
||||||
|
show status like 'Qcache_%_blocks';
|
||||||
|
|
||||||
|
# Now the cache looks like (14K for Q1)(14K for Q2)(20K free)
|
||||||
|
|
||||||
|
# Flush Q1 from the cache by adding an out-of-order chunk to t1
|
||||||
|
insert into t1 select reverse(a) from t1;
|
||||||
|
show status like 'Qcache_%_blocks';
|
||||||
|
|
||||||
|
# Now the cache looks like (14K free)(14K for Q2)(20K free)
|
||||||
|
|
||||||
|
# Load our new data into the query cache
|
||||||
|
--disable_result_log
|
||||||
|
select a from t1; # Q3
|
||||||
|
--enable_result_log
|
||||||
|
show status like 'Qcache_%_blocks';
|
||||||
|
|
||||||
|
# Now the cache should be like (14K for Q3)(14K for Q2)(14K for Q3)(6K free)
|
||||||
|
|
||||||
|
# Note that Q3 is split across two chunks!
|
||||||
|
|
||||||
|
# Load Q3 from the cache, and actually pay attention to the results
|
||||||
|
select a from t1;
|
||||||
|
|
||||||
|
flush query cache;
|
||||||
|
|
||||||
|
drop table t1, t2;
|
||||||
|
|
||||||
set GLOBAL query_cache_size=0;
|
set GLOBAL query_cache_size=0;
|
||||||
|
@ -1933,6 +1933,11 @@ my_bool Query_cache::write_result_data(Query_cache_block **result_block,
|
|||||||
type = Query_cache_block::RES_CONT;
|
type = Query_cache_block::RES_CONT;
|
||||||
} while (block != *result_block);
|
} while (block != *result_block);
|
||||||
#else
|
#else
|
||||||
|
/*
|
||||||
|
Set type of first block, emb_store_querycache_result() will handle
|
||||||
|
the others.
|
||||||
|
*/
|
||||||
|
(*result_block)->type= type;
|
||||||
Querycache_stream qs(*result_block, headers_len);
|
Querycache_stream qs(*result_block, headers_len);
|
||||||
emb_store_querycache_result(&qs, (THD*)data);
|
emb_store_querycache_result(&qs, (THD*)data);
|
||||||
#endif /*!EMBEDDED_LIBRARY*/
|
#endif /*!EMBEDDED_LIBRARY*/
|
||||||
|
Reference in New Issue
Block a user