1
0
mirror of https://github.com/MariaDB/server.git synced 2025-07-29 05:21:33 +03:00

Fix for BUG#735 "Prepared Statements: there is no support for Query

Cache".
WL#1569 "Prepared Statements: implement support of Query Cache".
Prepared SELECTs did not look up in the query cache, and their results
were not stored in the query cache. This made them slower than
non-prepared SELECTs in some cases.
The fix is to re-use the expanded query (the prepared query where
"?" placeholders are replaced by their values, at execution time)
for searching/storing in the query cache.
It works fine for statements prepared via mysql_stmt_prepare(), which
are the most commonly used and were the scope of this bugfix and WL.
It works less fine for statements prepared via the SQL command
PREPARE...FROM, which are still not using the query cache if they
have at least one parameter (because then the expanded query contains
names of user variables, and user variables don't work with the
query cache, even in non-prepared queries).
Note that results from prepared SELECTs, which are in the binary
protocol, and results from normal SELECTs, which are in the text
protocol, ignore each other in the query cache, because a result in the
binary protocol should never be served to a SELECT expecting the text
protocol and vice-versa.
Note, after this patch, bug 25843 starts applying to query cache
("changing default database between PREPARE and EXECUTE of statement
breaks binlog"), we need to fix it.
This commit is contained in:
guilhem@gbichot3.local
2007-03-09 18:09:57 +01:00
parent 8efe1b1fae
commit eaf7728d9f
13 changed files with 808 additions and 29 deletions

View File

@ -2354,6 +2354,271 @@ static void test_ps_conj_select()
}
/* reads Qcache_hits from server and returns its value */
static uint query_cache_hits(MYSQL *conn)
{
MYSQL_RES *res;
MYSQL_ROW row;
int rc;
uint result;
rc= mysql_query(conn, "show status like 'qcache_hits'");
myquery(rc);
res= mysql_use_result(conn);
DIE_UNLESS(res);
row= mysql_fetch_row(res);
DIE_UNLESS(row);
result= atoi(row[1]);
mysql_free_result(res);
return result;
}
/*
utility for the next test; expects 3 rows in the result from a SELECT,
compares each row/field with an expected value.
*/
#define test_ps_query_cache_result(i1,s1,l1,i2,s2,l2,i3,s3,l3) \
r_metadata= mysql_stmt_result_metadata(stmt); \
DIE_UNLESS(r_metadata != NULL); \
rc= mysql_stmt_fetch(stmt); \
check_execute(stmt, rc); \
if (!opt_silent) \
fprintf(stdout, "\n row 1: %d, %s(%lu)", r_int_data, \
r_str_data, r_str_length); \
DIE_UNLESS((r_int_data == i1) && (r_str_length == l1) && \
(strcmp(r_str_data, s1) == 0)); \
rc= mysql_stmt_fetch(stmt); \
check_execute(stmt, rc); \
if (!opt_silent) \
fprintf(stdout, "\n row 2: %d, %s(%lu)", r_int_data, \
r_str_data, r_str_length); \
DIE_UNLESS((r_int_data == i2) && (r_str_length == l2) && \
(strcmp(r_str_data, s2) == 0)); \
rc= mysql_stmt_fetch(stmt); \
check_execute(stmt, rc); \
if (!opt_silent) \
fprintf(stdout, "\n row 3: %d, %s(%lu)", r_int_data, \
r_str_data, r_str_length); \
DIE_UNLESS((r_int_data == i3) && (r_str_length == l3) && \
(strcmp(r_str_data, s3) == 0)); \
rc= mysql_stmt_fetch(stmt); \
DIE_UNLESS(rc == MYSQL_NO_DATA); \
mysql_free_result(r_metadata);
/*
Test that prepared statements make use of the query cache just as normal
statements (BUG#735).
*/
static void test_ps_query_cache()
{
MYSQL *org_mysql= mysql, *lmysql;
MYSQL_STMT *stmt;
int rc;
MYSQL_BIND p_bind[2],r_bind[2]; /* p: param bind; r: result bind */
int32 p_int_data, r_int_data;
char p_str_data[32], r_str_data[32];
unsigned long p_str_length, r_str_length;
MYSQL_RES *r_metadata;
char query[MAX_TEST_QUERY_LENGTH];
uint hits1, hits2;
enum enum_test_ps_query_cache
{
/*
We iterate the same prepare/executes block, but have iterations where
we vary the query cache conditions.
*/
/* the query cache is enabled for the duration of prep&execs: */
TEST_QCACHE_ON= 0,
/*
same but using a new connection (to see if qcache serves results from
the previous connection as it should):
*/
TEST_QCACHE_ON_WITH_OTHER_CONN,
/*
First border case: disables the query cache before prepare and
re-enables it before execution (to test if we have no bug then):
*/
TEST_QCACHE_OFF_ON,
/*
Second border case: enables the query cache before prepare and
disables it before execution:
*/
TEST_QCACHE_ON_OFF
};
enum enum_test_ps_query_cache iteration;
LINT_INIT(lmysql);
myheader("test_ps_query_cache");
/* prepare the table */
rc= mysql_query(mysql, "drop table if exists t1");
myquery(rc);
rc= mysql_query(mysql, "create table t1 (id1 int(11) NOT NULL default '0', "
"value2 varchar(100), value1 varchar(100))");
myquery(rc);
rc= mysql_query(mysql, "insert into t1 values (1, 'hh', 'hh'), "
"(2, 'hh', 'hh'), (1, 'ii', 'ii'), (2, 'ii', 'ii')");
myquery(rc);
for (iteration= TEST_QCACHE_ON; iteration < TEST_QCACHE_ON_OFF; iteration++)
{
switch (iteration)
{
case TEST_QCACHE_ON:
case TEST_QCACHE_ON_OFF:
rc= mysql_query(mysql, "set global query_cache_size=1000000");
myquery(rc);
break;
case TEST_QCACHE_OFF_ON:
rc= mysql_query(mysql, "set global query_cache_size=0");
myquery(rc);
break;
case TEST_QCACHE_ON_WITH_OTHER_CONN:
if (!opt_silent)
fprintf(stdout, "\n Establishing a test connection ...");
if (!(lmysql= mysql_init(NULL)))
{
myerror("mysql_init() failed");
exit(1);
}
if (!(mysql_real_connect(lmysql, opt_host, opt_user,
opt_password, current_db, opt_port,
opt_unix_socket, 0)))
{
myerror("connection failed");
mysql_close(lmysql);
exit(1);
}
if (!opt_silent)
fprintf(stdout, " OK");
mysql= lmysql;
}
strmov(query, "select id1, value1 from t1 where id1= ? or "
"CONVERT(value1 USING utf8)= ?");
stmt= mysql_simple_prepare(mysql, query);
check_stmt(stmt);
verify_param_count(stmt, 2);
switch(iteration)
{
case TEST_QCACHE_OFF_ON:
rc= mysql_query(mysql, "set global query_cache_size=1000000");
myquery(rc);
break;
case TEST_QCACHE_ON_OFF:
rc= mysql_query(mysql, "set global query_cache_size=0");
myquery(rc);
default:
break;
}
bzero((char*) p_bind, sizeof(p_bind));
p_bind[0].buffer_type= MYSQL_TYPE_LONG;
p_bind[0].buffer= (void *)&p_int_data;
p_bind[1].buffer_type= MYSQL_TYPE_VAR_STRING;
p_bind[1].buffer= (void *)p_str_data;
p_bind[1].buffer_length= array_elements(p_str_data);
p_bind[1].length= &p_str_length;
rc= mysql_stmt_bind_param(stmt, p_bind);
check_execute(stmt, rc);
p_int_data= 1;
strmov(p_str_data, "hh");
p_str_length= strlen(p_str_data);
bzero((char*) r_bind, sizeof(r_bind));
r_bind[0].buffer_type= MYSQL_TYPE_LONG;
r_bind[0].buffer= (void *)&r_int_data;
r_bind[1].buffer_type= MYSQL_TYPE_VAR_STRING;
r_bind[1].buffer= (void *)r_str_data;
r_bind[1].buffer_length= array_elements(r_str_data);
r_bind[1].length= &r_str_length;
rc= mysql_stmt_bind_result(stmt, r_bind);
check_execute(stmt, rc);
rc= mysql_stmt_execute(stmt);
check_execute(stmt, rc);
test_ps_query_cache_result(1, "hh", 2, 2, "hh", 2, 1, "ii", 2);
/* now retry with the same parameter values and see qcache hits */
hits1= query_cache_hits(mysql);
rc= mysql_stmt_execute(stmt);
check_execute(stmt, rc);
test_ps_query_cache_result(1, "hh", 2, 2, "hh", 2, 1, "ii", 2);
hits2= query_cache_hits(mysql);
switch(iteration)
{
case TEST_QCACHE_ON_WITH_OTHER_CONN:
case TEST_QCACHE_ON: /* should have hit */
DIE_UNLESS(hits2-hits1 == 1);
break;
case TEST_QCACHE_OFF_ON:
case TEST_QCACHE_ON_OFF: /* should not have hit */
DIE_UNLESS(hits2-hits1 == 0);
}
/* now modify parameter values and see qcache hits */
strmov(p_str_data, "ii");
p_str_length= strlen(p_str_data);
rc= mysql_stmt_execute(stmt);
check_execute(stmt, rc);
test_ps_query_cache_result(1, "hh", 2, 1, "ii", 2, 2, "ii", 2);
hits1= query_cache_hits(mysql);
switch(iteration)
{
case TEST_QCACHE_ON:
case TEST_QCACHE_OFF_ON:
case TEST_QCACHE_ON_OFF: /* should not have hit */
DIE_UNLESS(hits2-hits1 == 0);
break;
case TEST_QCACHE_ON_WITH_OTHER_CONN: /* should have hit */
DIE_UNLESS(hits1-hits2 == 1);
}
rc= mysql_stmt_execute(stmt);
check_execute(stmt, rc);
test_ps_query_cache_result(1, "hh", 2, 1, "ii", 2, 2, "ii", 2);
hits2= query_cache_hits(mysql);
mysql_stmt_close(stmt);
switch(iteration)
{
case TEST_QCACHE_ON: /* should have hit */
DIE_UNLESS(hits2-hits1 == 1);
break;
case TEST_QCACHE_OFF_ON:
case TEST_QCACHE_ON_OFF: /* should not have hit */
DIE_UNLESS(hits2-hits1 == 0);
break;
case TEST_QCACHE_ON_WITH_OTHER_CONN:
mysql_close(lmysql);
mysql= org_mysql;
}
} /* for(iteration=...) */
rc= mysql_query(mysql, "set global query_cache_size=0");
myquery(rc);
}
/* Test BUG#1115 (incorrect string parameter value allocation) */
static void test_bug1115()
@ -4722,7 +4987,7 @@ static void test_stmt_close()
close statements by hand once mysql_close() had been called.
Now mysql_close() doesn't free any statements, so this test doesn't
serve its original designation any more.
Here we free stmt2 and stmt3 by hande to avoid memory leaks.
Here we free stmt2 and stmt3 by hand to avoid memory leaks.
*/
mysql_stmt_close(stmt2);
mysql_stmt_close(stmt3);
@ -16067,8 +16332,9 @@ static struct my_tests_st my_tests[]= {
{ "test_bug15518", test_bug15518 },
{ "test_bug23383", test_bug23383 },
{ "test_bug21635", test_bug21635 },
{ "test_status", test_status},
{ "test_status", test_status },
{ "test_bug24179", test_bug24179 },
{ "test_ps_query_cache", test_ps_query_cache },
{ 0, 0 }
};