diff options
-rw-r--r-- | libmysqld/lib_sql.cc | 12 | ||||
-rw-r--r-- | mysql-test/include/have_query_cache.inc | 3 | ||||
-rw-r--r-- | mysql-test/r/query_cache.result | 23 | ||||
-rw-r--r-- | mysql-test/r/query_cache_sql_prepare.result | 204 | ||||
-rw-r--r-- | mysql-test/t/grant_cache.test | 26 | ||||
-rw-r--r-- | mysql-test/t/ndb_cache_multi2.test | 31 | ||||
-rw-r--r-- | mysql-test/t/query_cache.test | 20 | ||||
-rw-r--r-- | mysql-test/t/query_cache_sql_prepare.test | 144 | ||||
-rw-r--r-- | sql/mysql_priv.h | 8 | ||||
-rw-r--r-- | sql/protocol.cc | 73 | ||||
-rw-r--r-- | sql/protocol.h | 23 | ||||
-rw-r--r-- | sql/set_var.cc | 4 | ||||
-rw-r--r-- | sql/sql_cache.cc | 20 | ||||
-rw-r--r-- | sql/sql_class.cc | 6 | ||||
-rw-r--r-- | sql/sql_class.h | 4 | ||||
-rw-r--r-- | sql/sql_prepare.cc | 102 | ||||
-rw-r--r-- | tests/mysql_client_test.c | 270 |
17 files changed, 876 insertions, 97 deletions
diff --git a/libmysqld/lib_sql.cc b/libmysqld/lib_sql.cc index 87518b41561..dbec5bf6bbf 100644 --- a/libmysqld/lib_sql.cc +++ b/libmysqld/lib_sql.cc @@ -822,7 +822,7 @@ int Protocol::begin_dataset() remove last row of current recordset SYNOPSIS - Protocol_simple::remove_last_row() + Protocol_text::remove_last_row() NOTES does the loop from the beginning of the current recordset to @@ -830,12 +830,12 @@ int Protocol::begin_dataset() Not supposed to be frequently called. */ -void Protocol_simple::remove_last_row() +void Protocol_text::remove_last_row() { MYSQL_DATA *data= thd->cur_data; MYSQL_ROWS **last_row_hook= &data->data; uint count= data->rows; - DBUG_ENTER("Protocol_simple::remove_last_row"); + DBUG_ENTER("Protocol_text::remove_last_row"); while (--count) last_row_hook= &(*last_row_hook)->next; @@ -964,7 +964,7 @@ bool Protocol::write() return false; } -bool Protocol_prep::write() +bool Protocol_binary::write() { MYSQL_ROWS *cur; MYSQL_DATA *data= thd->cur_data; @@ -1031,7 +1031,7 @@ void net_send_error_packet(THD *thd, uint sql_errno, const char *err) } -void Protocol_simple::prepare_for_resend() +void Protocol_text::prepare_for_resend() { MYSQL_ROWS *cur; MYSQL_DATA *data= thd->cur_data; @@ -1056,7 +1056,7 @@ void Protocol_simple::prepare_for_resend() DBUG_VOID_RETURN; } -bool Protocol_simple::store_null() +bool Protocol_text::store_null() { *(next_field++)= NULL; ++next_mysql_field; diff --git a/mysql-test/include/have_query_cache.inc b/mysql-test/include/have_query_cache.inc index 39549157849..e5e6052c9a7 100644 --- a/mysql-test/include/have_query_cache.inc +++ b/mysql-test/include/have_query_cache.inc @@ -1,7 +1,4 @@ -- require r/have_query_cache.require -# As PS are not cached we disable them to ensure the we get the right number -# of query cache hits --- disable_ps_protocol disable_query_log; show variables like "have_query_cache"; enable_query_log; diff --git a/mysql-test/r/query_cache.result b/mysql-test/r/query_cache.result index 54b35827cea..657f1387136 100644 --- a/mysql-test/r/query_cache.result +++ b/mysql-test/r/query_cache.result @@ -1325,4 +1325,27 @@ start transaction; insert into t1(c1) select c1 from v1; drop table t1, t2, t3; drop view v1; +create table t1(c1 int); +insert into t1 values(1),(10),(100); +select * from t1; +c1 +1 +10 +100 +select * from t1; +c1 +1 +10 +100 +select * from t1; +c1 +1 +10 +100 +select * from t1; +c1 +1 +10 +100 +drop table t1; set global query_cache_size=0; diff --git a/mysql-test/r/query_cache_sql_prepare.result b/mysql-test/r/query_cache_sql_prepare.result new file mode 100644 index 00000000000..64af5bc4ec2 --- /dev/null +++ b/mysql-test/r/query_cache_sql_prepare.result @@ -0,0 +1,204 @@ +set global query_cache_size=100000; +flush status; +create table t1(c1 int); +insert into t1 values(1),(10),(100); +prepare stmt1 from "select * from t1 where c1=10"; +show status like 'Qcache_hits'; +Variable_name Value +Qcache_hits 0 +execute stmt1; +c1 +10 +show status like 'Qcache_hits'; +Variable_name Value +Qcache_hits 0 +execute stmt1; +c1 +10 +show status like 'Qcache_hits'; +Variable_name Value +Qcache_hits 1 +execute stmt1; +c1 +10 +show status like 'Qcache_hits'; +Variable_name Value +Qcache_hits 2 +prepare stmt2 from "select * from t1 where c1=10"; +execute stmt2; +c1 +10 +show status like 'Qcache_hits'; +Variable_name Value +Qcache_hits 3 +execute stmt2; +c1 +10 +show status like 'Qcache_hits'; +Variable_name Value +Qcache_hits 4 +execute stmt2; +c1 +10 +show status like 'Qcache_hits'; +Variable_name Value +Qcache_hits 5 +prepare stmt3 from "select * from t1 where c1=10"; +execute stmt3; +c1 +10 +show status like 'Qcache_hits'; +Variable_name Value +Qcache_hits 6 +execute stmt3; +c1 +10 +show status like 'Qcache_hits'; +Variable_name Value +Qcache_hits 7 +execute stmt3; +c1 +10 +show status like 'Qcache_hits'; +Variable_name Value +Qcache_hits 8 +select * from t1 where c1=10; +c1 +10 +show status like 'Qcache_hits'; +Variable_name Value +Qcache_hits 9 +flush tables; +execute stmt1; +c1 +10 +show status like 'Qcache_hits'; +Variable_name Value +Qcache_hits 9 +select * from t1 where c1=10; +c1 +10 +show status like 'Qcache_hits'; +Variable_name Value +Qcache_hits 10 +prepare stmt1 from "select * from t1 where c1=?"; +show status like 'Qcache_hits'; +Variable_name Value +Qcache_hits 10 +set @a=1; +execute stmt1 using @a; +c1 +1 +show status like 'Qcache_hits'; +Variable_name Value +Qcache_hits 10 +set @a=100; +execute stmt1 using @a; +c1 +100 +show status like 'Qcache_hits'; +Variable_name Value +Qcache_hits 10 +set @a=10; +execute stmt1 using @a; +c1 +10 +show status like 'Qcache_hits'; +Variable_name Value +Qcache_hits 10 +prepare stmt1 from "select * from t1 where c1=10"; +set global query_cache_size=0; +show status like 'Qcache_hits'; +Variable_name Value +Qcache_hits 10 +execute stmt1; +c1 +10 +show status like 'Qcache_hits'; +Variable_name Value +Qcache_hits 10 +execute stmt1; +c1 +10 +show status like 'Qcache_hits'; +Variable_name Value +Qcache_hits 10 +execute stmt1; +c1 +10 +show status like 'Qcache_hits'; +Variable_name Value +Qcache_hits 10 +set global query_cache_size=100000; +execute stmt1; +c1 +10 +show status like 'Qcache_hits'; +Variable_name Value +Qcache_hits 10 +execute stmt1; +c1 +10 +show status like 'Qcache_hits'; +Variable_name Value +Qcache_hits 11 +execute stmt1; +c1 +10 +show status like 'Qcache_hits'; +Variable_name Value +Qcache_hits 12 +set global query_cache_size=0; +prepare stmt1 from "select * from t1 where c1=10"; +set global query_cache_size=100000; +show status like 'Qcache_hits'; +Variable_name Value +Qcache_hits 12 +execute stmt1; +c1 +10 +show status like 'Qcache_hits'; +Variable_name Value +Qcache_hits 12 +execute stmt1; +c1 +10 +show status like 'Qcache_hits'; +Variable_name Value +Qcache_hits 12 +execute stmt1; +c1 +10 +show status like 'Qcache_hits'; +Variable_name Value +Qcache_hits 12 +set global query_cache_size=0; +prepare stmt1 from "select * from t1 where c1=?"; +set global query_cache_size=100000; +show status like 'Qcache_hits'; +Variable_name Value +Qcache_hits 12 +set @a=1; +execute stmt1 using @a; +c1 +1 +show status like 'Qcache_hits'; +Variable_name Value +Qcache_hits 12 +set @a=100; +execute stmt1 using @a; +c1 +100 +show status like 'Qcache_hits'; +Variable_name Value +Qcache_hits 12 +set @a=10; +execute stmt1 using @a; +c1 +10 +show status like 'Qcache_hits'; +Variable_name Value +Qcache_hits 12 +drop table t1; +set global query_cache_size=0; +flush status; diff --git a/mysql-test/t/grant_cache.test b/mysql-test/t/grant_cache.test index 7e17a03ec21..c28dd12cdee 100644 --- a/mysql-test/t/grant_cache.test +++ b/mysql-test/t/grant_cache.test @@ -1,6 +1,8 @@ # Grant tests not performed with embedded server -- source include/not_embedded.inc -- source include/have_query_cache.inc +# See at the end of the test why we disable the ps protocol (*) +-- disable_ps_protocol # # Test grants with query cache @@ -151,3 +153,27 @@ drop database mysqltest; set GLOBAL query_cache_size=default; # End of 4.1 tests + +# (*) Why we disable the ps protocol: because in normal protocol, +# a SELECT failing due to insufficient privileges increments +# Qcache_not_cached, while in ps-protocol, no. +# In detail: in normal protocol, +# the "access denied" errors on SELECT are issued at (stack trace): +# mysql_parse/mysql_execute_command/execute_sqlcom_select/handle_select/ +# mysql_select/JOIN::prepare/setup_wild/insert_fields/ +# check_grant_all_columns/my_error/my_message_sql, which then calls +# push_warning/query_cache_abort: at this moment, +# query_cache_store_query() has been called, so query exists in cache, +# so thd->net.query_cache_query!=NULL, so query_cache_abort() removes +# the query from cache, which causes a query_cache.refused++ (thus, +# a Qcache_not_cached++). +# While in ps-protocol, the error is issued at prepare time; +# for this mysql_test_select() is called, not execute_sqlcom_select() +# (and that also leads to JOIN::prepare/etc). Thus, as +# query_cache_store_query() has not been called, +# thd->net.query_cache_query==NULL, so query_cache_abort() does nothing: +# Qcache_not_cached is not incremented. +# As this test prints Qcache_not_cached after SELECT failures, +# we cannot enable this test in ps-protocol. + +--enable_ps_protocol diff --git a/mysql-test/t/ndb_cache_multi2.test b/mysql-test/t/ndb_cache_multi2.test index 4abb537624a..2afcf0c18f7 100644 --- a/mysql-test/t/ndb_cache_multi2.test +++ b/mysql-test/t/ndb_cache_multi2.test @@ -36,7 +36,11 @@ insert into t1 value (2); insert into t2 value (3); select * from t1; # Run the check query once to load it into qc on server1 +# See at the end of this test why we need to disable ps-protocol for +# this query (*) +--disable_ps_protocol select a != 3 from t1; +--enable_ps_protocol select * from t2; show status like "Qcache_queries_in_cache"; show status like "Qcache_inserts"; @@ -93,3 +97,30 @@ set GLOBAL query_cache_size=0; set GLOBAL ndb_cache_check_time=0; reset query cache; flush status; + +# (*) Why we need to execute the query in non-ps mode. +# The principle of this test is: two mysqlds connected to one cluster, +# both using their query cache. Queries are cached in server1 +# ("select a!=3 from t1", "select * from t1"), +# table t1 is modified in server2, we want to see that this invalidates +# the query cache of server1. Invalidation with NDB works like this: +# when a query is found in the query cache, NDB is asked if the tables +# have changed. In this test, ha_ndbcluster calls NDB every millisecond +# to collect change information about tables. +# Due to this millisecond delay, there is need for a loop ("while...") +# in this test, which waits until a query1 ("select a!=3 from t1") is +# invalidated (which is equivalent to it returning +# up-to-date results), and then expects query2 ("select * from t1") +# to have been invalidated (see up-to-date results). +# But when enabling --ps-protocol in this test, the logic breaks, +# because query1 is still done via mysql_real_query() (see mysqltest.c: +# eval_expr() always uses mysql_real_query()). So, query1 returning +# up-to-date results is not a sign of it being invalidated in the cache, +# because it was NOT in the cache ("select a!=3 from t1" on line 39 +# was done with prep stmts, while `select a!=3 from t1` is not, +# thus the second does not see the first in the cache). Thus, we may run +# query2 when cache still has not been invalidated. +# The solution is to make the initial "select a!=3 from t1" run +# as a normal query, this repairs the broken logic. +# But note, "select * from t1" is still using prepared statements +# which was the goal of this test with --ps-protocol. diff --git a/mysql-test/t/query_cache.test b/mysql-test/t/query_cache.test index ad7fd90ed63..d475fc65136 100644 --- a/mysql-test/t/query_cache.test +++ b/mysql-test/t/query_cache.test @@ -907,4 +907,24 @@ start transaction; insert into t1(c1) select c1 from v1; drop table t1, t2, t3; drop view v1; + + +# +# If running with --ps-protocol: +# see if a query from the text protocol is served with results cached +# from a query which used the binary (which would be wrong, results +# are in different formats); if that happens, the results will +# be incorrect and the test will fail. +# + +create table t1(c1 int); +insert into t1 values(1),(10),(100); +select * from t1; +-- disable_ps_protocol +select * from t1; +select * from t1; +-- enable_ps_protocol +select * from t1; +drop table t1; + set global query_cache_size=0; diff --git a/mysql-test/t/query_cache_sql_prepare.test b/mysql-test/t/query_cache_sql_prepare.test new file mode 100644 index 00000000000..69b504e2fd1 --- /dev/null +++ b/mysql-test/t/query_cache_sql_prepare.test @@ -0,0 +1,144 @@ +# This is to see how statements prepared via the PREPARE SQL command +# go into the query cache: if using parameters they cannot; if not +# using parameters they can. +# Query cache is abbreviated as "QC" + +-- source include/have_query_cache.inc + +connect (con1,127.0.0.1,root,,test,$MASTER_MYPORT,); +connection default; + +set global query_cache_size=100000; +flush status; +create table t1(c1 int); +insert into t1 values(1),(10),(100); + +# Prepared statements has no parameters, query caching should happen +prepare stmt1 from "select * from t1 where c1=10"; +show status like 'Qcache_hits'; +execute stmt1; +show status like 'Qcache_hits'; +execute stmt1; +show status like 'Qcache_hits'; +execute stmt1; +show status like 'Qcache_hits'; +# Another prepared statement (same text, same connection), should hit the QC +prepare stmt2 from "select * from t1 where c1=10"; +execute stmt2; +show status like 'Qcache_hits'; +execute stmt2; +show status like 'Qcache_hits'; +execute stmt2; +show status like 'Qcache_hits'; +# Another prepared statement (same text, other connection), should hit the QC +connection con1; +prepare stmt3 from "select * from t1 where c1=10"; +execute stmt3; +show status like 'Qcache_hits'; +execute stmt3; +show status like 'Qcache_hits'; +execute stmt3; +show status like 'Qcache_hits'; +connection default; +# A non-prepared statement (same text, same connection), should hit +# the QC (as it uses the text protocol like SQL EXECUTE). +# But if it uses the binary protocol, it will not hit. So we make sure +# that it uses the text protocol: +-- disable_ps_protocol +select * from t1 where c1=10; +show status like 'Qcache_hits'; + # A non-prepared statement (same text, other connection), should hit +# the QC. To test that it hits the result of SQL EXECUTE, we need to +# empty/repopulate the QC (to remove the result from the non-prepared +# SELECT just above). +flush tables; +execute stmt1; +show status like 'Qcache_hits'; +connection con1; +select * from t1 where c1=10; +show status like 'Qcache_hits'; +-- enable_ps_protocol +connection default; + +# Prepared statement has parameters, query caching should not happen +prepare stmt1 from "select * from t1 where c1=?"; +show status like 'Qcache_hits'; +set @a=1; +execute stmt1 using @a; +show status like 'Qcache_hits'; +set @a=100; +execute stmt1 using @a; +show status like 'Qcache_hits'; +set @a=10; +execute stmt1 using @a; +show status like 'Qcache_hits'; + +# See if enabling/disabling the query cache between PREPARE and +# EXECUTE is an issue; the expected result is that the query cache +# will not be used. +# Indeed, decision to read/write the query cache is taken at PREPARE +# time, so if the query cache was disabled at PREPARE time then no +# execution of the statement will read/write the query cache. +# If the query cache was enabled at PREPARE time, but disabled at +# EXECUTE time, at EXECUTE time the query cache internal functions do +# nothing so again the query cache is not read/written. But if the +# query cache is re-enabled before another execution then that +# execution will read/write the query cache. + +# QC is enabled at PREPARE +prepare stmt1 from "select * from t1 where c1=10"; +# then QC is disabled at EXECUTE +set global query_cache_size=0; +show status like 'Qcache_hits'; +execute stmt1; +show status like 'Qcache_hits'; +execute stmt1; +show status like 'Qcache_hits'; +execute stmt1; +show status like 'Qcache_hits'; +# then QC is re-enabled for more EXECUTE. +set global query_cache_size=100000; +# Note that this execution will not hit results from the +# beginning of the test (because QC has been emptied meanwhile by +# setting its size to 0). +execute stmt1; +show status like 'Qcache_hits'; +execute stmt1; +show status like 'Qcache_hits'; +execute stmt1; +show status like 'Qcache_hits'; + +# QC is disabled at PREPARE +set global query_cache_size=0; +prepare stmt1 from "select * from t1 where c1=10"; +# then QC is enabled at EXECUTE +set global query_cache_size=100000; +show status like 'Qcache_hits'; +execute stmt1; +show status like 'Qcache_hits'; +execute stmt1; +show status like 'Qcache_hits'; +execute stmt1; +show status like 'Qcache_hits'; + +# QC is disabled at PREPARE +set global query_cache_size=0; +prepare stmt1 from "select * from t1 where c1=?"; +# then QC is enabled at EXECUTE +set global query_cache_size=100000; +show status like 'Qcache_hits'; +set @a=1; +execute stmt1 using @a; +show status like 'Qcache_hits'; +set @a=100; +execute stmt1 using @a; +show status like 'Qcache_hits'; +set @a=10; +execute stmt1 using @a; +show status like 'Qcache_hits'; + + +drop table t1; + +set global query_cache_size=0; +flush status; # reset Qcache status variables for next tests diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index ba8960950db..b7fe500324f 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -647,6 +647,7 @@ struct Query_cache_query_flags { unsigned int client_long_flag:1; unsigned int client_protocol_41:1; + unsigned int result_in_binary_protocol:1; unsigned int more_results_exists:1; unsigned int pkt_nr; uint character_set_client_num; @@ -673,6 +674,11 @@ struct Query_cache_query_flags query_cache.send_result_to_client(A, B, C) #define query_cache_invalidate_by_MyISAM_filename_ref \ &query_cache_invalidate_by_MyISAM_filename +/* note the "maybe": it's a read without mutex */ +#define query_cache_maybe_disabled(T) \ + (T->variables.query_cache_type == 0 || query_cache.query_cache_size == 0) +#define query_cache_is_cacheable_query(L) \ + (((L)->sql_command == SQLCOM_SELECT) && (L)->safe_to_cache_query) #else #define QUERY_CACHE_FLAGS_SIZE 0 #define query_cache_store_query(A, B) @@ -689,6 +695,8 @@ struct Query_cache_query_flags #define query_cache_abort(A) #define query_cache_end_of_result(A) #define query_cache_invalidate_by_MyISAM_filename_ref NULL +#define query_cache_maybe_disabled(T) 1 +#define query_cache_is_cacheable_query(L) 0 #endif /*HAVE_QUERY_CACHE*/ /* diff --git a/sql/protocol.cc b/sql/protocol.cc index 05e98c68e4e..5aa3b7b5055 100644 --- a/sql/protocol.cc +++ b/sql/protocol.cc @@ -35,7 +35,7 @@ static void write_eof_packet(THD *thd, NET *net); #ifndef EMBEDDED_LIBRARY bool Protocol::net_store_data(const char *from, uint length) #else -bool Protocol_prep::net_store_data(const char *from, uint length) +bool Protocol_binary::net_store_data(const char *from, uint length) #endif { ulong packet_length=packet->length(); @@ -557,7 +557,7 @@ bool Protocol::send_fields(List<Item> *list, uint flags) Item *item; char buff[80]; String tmp((char*) buff,sizeof(buff),&my_charset_bin); - Protocol_simple prot(thd); + Protocol_text prot(thd); String *local_packet= prot.storage_packet(); CHARSET_INFO *thd_charset= thd->variables.character_set_results; DBUG_ENTER("send_fields"); @@ -760,7 +760,7 @@ bool Protocol::store(I_List<i_string>* str_list) ****************************************************************************/ #ifndef EMBEDDED_LIBRARY -void Protocol_simple::prepare_for_resend() +void Protocol_text::prepare_for_resend() { packet->length(0); #ifndef DBUG_OFF @@ -768,7 +768,7 @@ void Protocol_simple::prepare_for_resend() #endif } -bool Protocol_simple::store_null() +bool Protocol_text::store_null() { #ifndef DBUG_OFF field_pos++; @@ -801,7 +801,7 @@ bool Protocol::store_string_aux(const char *from, uint length, } -bool Protocol_simple::store(const char *from, uint length, +bool Protocol_text::store(const char *from, uint length, CHARSET_INFO *fromcs, CHARSET_INFO *tocs) { #ifndef DBUG_OFF @@ -817,8 +817,8 @@ bool Protocol_simple::store(const char *from, uint length, } -bool Protocol_simple::store(const char *from, uint length, - CHARSET_INFO *fromcs) +bool Protocol_text::store(const char *from, uint length, + CHARSET_INFO *fromcs) { CHARSET_INFO *tocs= this->thd->variables.character_set_results; #ifndef DBUG_OFF @@ -834,7 +834,7 @@ bool Protocol_simple::store(const char *from, uint length, } -bool Protocol_simple::store_tiny(longlong from) +bool Protocol_text::store_tiny(longlong from) { #ifndef DBUG_OFF DBUG_ASSERT(field_types == 0 || field_types[field_pos] == MYSQL_TYPE_TINY); @@ -846,7 +846,7 @@ bool Protocol_simple::store_tiny(longlong from) } -bool Protocol_simple::store_short(longlong from) +bool Protocol_text::store_short(longlong from) { #ifndef DBUG_OFF DBUG_ASSERT(field_types == 0 || @@ -860,7 +860,7 @@ bool Protocol_simple::store_short(longlong from) } -bool Protocol_simple::store_long(longlong from) +bool Protocol_text::store_long(longlong from) { #ifndef DBUG_OFF DBUG_ASSERT(field_types == 0 || @@ -874,7 +874,7 @@ bool Protocol_simple::store_long(longlong from) } -bool Protocol_simple::store_longlong(longlong from, bool unsigned_flag) +bool Protocol_text::store_longlong(longlong from, bool unsigned_flag) { #ifndef DBUG_OFF DBUG_ASSERT(field_types == 0 || @@ -889,7 +889,7 @@ bool Protocol_simple::store_longlong(longlong from, bool unsigned_flag) } -bool Protocol_simple::store_decimal(const my_decimal *d) +bool Protocol_text::store_decimal(const my_decimal *d) { #ifndef DBUG_OFF DBUG_ASSERT(field_types == 0 || @@ -903,7 +903,7 @@ bool Protocol_simple::store_decimal(const my_decimal *d) } -bool Protocol_simple::store(float from, uint32 decimals, String *buffer) +bool Protocol_text::store(float from, uint32 decimals, String *buffer) { #ifndef DBUG_OFF DBUG_ASSERT(field_types == 0 || @@ -915,7 +915,7 @@ bool Protocol_simple::store(float from, uint32 decimals, String *buffer) } -bool Protocol_simple::store(double from, uint32 decimals, String *buffer) +bool Protocol_text::store(double from, uint32 decimals, String *buffer) { #ifndef DBUG_OFF DBUG_ASSERT(field_types == 0 || @@ -927,7 +927,7 @@ bool Protocol_simple::store(double from, uint32 decimals, String *buffer) } -bool Protocol_simple::store(Field *field) +bool Protocol_text::store(Field *field) { if (field->is_null()) return store_null(); @@ -961,7 +961,7 @@ bool Protocol_simple::store(Field *field) */ -bool Protocol_simple::store(TIME *tm) +bool Protocol_text::store(TIME *tm) { #ifndef DBUG_OFF DBUG_ASSERT(field_types == 0 || @@ -984,7 +984,7 @@ bool Protocol_simple::store(TIME *tm) } -bool Protocol_simple::store_date(TIME *tm) +bool Protocol_text::store_date(TIME *tm) { #ifndef DBUG_OFF DBUG_ASSERT(field_types == 0 || @@ -1003,7 +1003,7 @@ bool Protocol_simple::store_date(TIME *tm) we support 0-6 decimals for time. */ -bool Protocol_simple::store_time(TIME *tm) +bool Protocol_text::store_time(TIME *tm) { #ifndef DBUG_OFF DBUG_ASSERT(field_types == 0 || @@ -1043,7 +1043,7 @@ bool Protocol_simple::store_time(TIME *tm) [..]..[[length]data] data ****************************************************************************/ -bool Protocol_prep::prepare_for_send(List<Item> *item_list) +bool Protocol_binary::prepare_for_send(List<Item> *item_list) { Protocol::prepare_for_send(item_list); bit_fields= (field_count+9)/8; @@ -1054,7 +1054,7 @@ bool Protocol_prep::prepare_for_send(List<Item> *item_list) } -void Protocol_prep::prepare_for_resend() +void Protocol_binary::prepare_for_resend() { packet->length(bit_fields+1); bzero((char*) packet->ptr(), 1+bit_fields); @@ -1062,21 +1062,22 @@ void Protocol_prep::prepare_for_resend() } -bool Protocol_prep::store(const char *from, uint length, CHARSET_INFO *fromcs) +bool Protocol_binary::store(const char *from, uint length, + CHARSET_INFO *fromcs) { CHARSET_INFO *tocs= thd->variables.character_set_results; field_pos++; return store_string_aux(from, length, fromcs, tocs); } -bool Protocol_prep::store(const char *from,uint length, - CHARSET_INFO *fromcs, CHARSET_INFO *tocs) +bool Protocol_binary::store(const char *from,uint length, + CHARSET_INFO *fromcs, CHARSET_INFO *tocs) { field_pos++; return store_string_aux(from, length, fromcs, tocs); } -bool Protocol_prep::store_null() +bool Protocol_binary::store_null() { uint offset= (field_pos+2)/8+1, bit= (1 << ((field_pos+2) & 7)); /* Room for this as it's allocated in prepare_for_send */ @@ -1087,7 +1088,7 @@ bool Protocol_prep::store_null() } -bool Protocol_prep::store_tiny(longlong from) +bool Protocol_binary::store_tiny(longlong from) { char buff[1]; field_pos++; @@ -1096,7 +1097,7 @@ bool Protocol_prep::store_tiny(longlong from) } -bool Protocol_prep::store_short(longlong from) +bool Protocol_binary::store_short(longlong from) { field_pos++; char *to= packet->prep_append(2, PACKET_BUFFER_EXTRA_ALLOC); @@ -1107,7 +1108,7 @@ bool Protocol_prep::store_short(longlong from) } -bool Protocol_prep::store_long(longlong from) +bool Protocol_binary::store_long(longlong from) { field_pos++; char *to= packet->prep_append(4, PACKET_BUFFER_EXTRA_ALLOC); @@ -1118,7 +1119,7 @@ bool Protocol_prep::store_long(longlong from) } -bool Protocol_prep::store_longlong(longlong from, bool unsigned_flag) +bool Protocol_binary::store_longlong(longlong from, bool unsigned_flag) { field_pos++; char *to= packet->prep_append(8, PACKET_BUFFER_EXTRA_ALLOC); @@ -1128,7 +1129,7 @@ bool Protocol_prep::store_longlong(longlong from, bool unsigned_flag) return 0; } -bool Protocol_prep::store_decimal(const my_decimal *d) +bool Protocol_binary::store_decimal(const my_decimal *d) { #ifndef DBUG_OFF DBUG_ASSERT(field_types == 0 || @@ -1141,7 +1142,7 @@ bool Protocol_prep::store_decimal(const my_decimal *d) return store(str.ptr(), str.length(), str.charset()); } -bool Protocol_prep::store(float from, uint32 decimals, String *buffer) +bool Protocol_binary::store(float from, uint32 decimals, String *buffer) { field_pos++; char *to= packet->prep_append(4, PACKET_BUFFER_EXTRA_ALLOC); @@ -1152,7 +1153,7 @@ bool Protocol_prep::store(float from, uint32 decimals, String *buffer) } -bool Protocol_prep::store(double from, uint32 decimals, String *buffer) +bool Protocol_binary::store(double from, uint32 decimals, String *buffer) { field_pos++; char *to= packet->prep_append(8, PACKET_BUFFER_EXTRA_ALLOC); @@ -1163,7 +1164,7 @@ bool Protocol_prep::store(double from, uint32 decimals, String *buffer) } -bool Protocol_prep::store(Field *field) +bool Protocol_binary::store(Field *field) { /* We should not increment field_pos here as send_binary() will call another @@ -1175,7 +1176,7 @@ bool Protocol_prep::store(Field *field) } -bool Protocol_prep::store(TIME *tm) +bool Protocol_binary::store(TIME *tm) { char buff[12],*pos; uint length; @@ -1201,15 +1202,15 @@ bool Protocol_prep::store(TIME *tm) return packet->append(buff, length+1, PACKET_BUFFER_EXTRA_ALLOC); } -bool Protocol_prep::store_date(TIME *tm) +bool Protocol_binary::store_date(TIME *tm) { tm->hour= tm->minute= tm->second=0; tm->second_part= 0; - return Protocol_prep::store(tm); + return Protocol_binary::store(tm); } -bool Protocol_prep::store_time(TIME *tm) +bool Protocol_binary::store_time(TIME *tm) { char buff[13], *pos; uint length; diff --git a/sql/protocol.h b/sql/protocol.h index 6c4c7414ea5..da49cf769ae 100644 --- a/sql/protocol.h +++ b/sql/protocol.h @@ -98,16 +98,25 @@ public: #else void remove_last_row() {} #endif + enum enum_protocol_type + { + PROTOCOL_TEXT= 0, PROTOCOL_BINARY= 1 + /* + before adding here or change the values, consider that it is cast to a + bit in sql_cache.cc. + */ + }; + virtual enum enum_protocol_type type()= 0; }; /* Class used for the old (MySQL 4.0 protocol) */ -class Protocol_simple :public Protocol +class Protocol_text :public Protocol { public: - Protocol_simple() {} - Protocol_simple(THD *thd_arg) :Protocol(thd_arg) {} + Protocol_text() {} + Protocol_text(THD *thd_arg) :Protocol(thd_arg) {} virtual void prepare_for_resend(); virtual bool store_null(); virtual bool store_tiny(longlong from); @@ -127,16 +136,17 @@ public: #ifdef EMBEDDED_LIBRARY void remove_last_row(); #endif + virtual enum enum_protocol_type type() { return PROTOCOL_TEXT; }; }; -class Protocol_prep :public Protocol +class Protocol_binary :public Protocol { private: uint bit_fields; public: - Protocol_prep() {} - Protocol_prep(THD *thd_arg) :Protocol(thd_arg) {} + Protocol_binary() {} + Protocol_binary(THD *thd_arg) :Protocol(thd_arg) {} virtual bool prepare_for_send(List<Item> *item_list); virtual void prepare_for_resend(); #ifdef EMBEDDED_LIBRARY @@ -158,6 +168,7 @@ public: virtual bool store(float nr, uint32 decimals, String *buffer); virtual bool store(double from, uint32 decimals, String *buffer); virtual bool store(Field *field); + virtual enum enum_protocol_type type() { return PROTOCOL_BINARY; }; }; void send_warning(THD *thd, uint sql_errno, const char *err=0); diff --git a/sql/set_var.cc b/sql/set_var.cc index e2c90b72feb..91d97b71c1b 100644 --- a/sql/set_var.cc +++ b/sql/set_var.cc @@ -2739,8 +2739,8 @@ int set_var_collation_client::update(THD *thd) thd->variables.character_set_results= character_set_results; thd->variables.collation_connection= collation_connection; thd->update_charset(); - thd->protocol_simple.init(thd); - thd->protocol_prep.init(thd); + thd->protocol_text.init(thd); + thd->protocol_binary.init(thd); return 0; } diff --git a/sql/sql_cache.cc b/sql/sql_cache.cc index c06d7161ec1..89b7a25033f 100644 --- a/sql/sql_cache.cc +++ b/sql/sql_cache.cc @@ -844,6 +844,12 @@ void Query_cache::store_query(THD *thd, TABLE_LIST *tables_used) flags.client_long_flag= test(thd->client_capabilities & CLIENT_LONG_FLAG); flags.client_protocol_41= test(thd->client_capabilities & CLIENT_PROTOCOL_41); + /* + Protocol influences result format, so statement results in the binary + protocol (COM_EXECUTE) cannot be served to statements asking for results + in the text protocol (COM_QUERY) and vice-versa. + */ + flags.result_in_binary_protocol= (unsigned int) thd->protocol->type(); flags.more_results_exists= test(thd->server_status & SERVER_MORE_RESULTS_EXISTS); flags.pkt_nr= net->pkt_nr; @@ -861,11 +867,13 @@ void Query_cache::store_query(THD *thd, TABLE_LIST *tables_used) flags.max_sort_length= thd->variables.max_sort_length; flags.lc_time_names= thd->variables.lc_time_names; flags.group_concat_max_len= thd->variables.group_concat_max_len; - DBUG_PRINT("qcache", ("long %d, 4.1: %d, more results %d, pkt_nr: %d, \ + DBUG_PRINT("qcache", ("\ +long %d, 4.1: %d, bin_proto: %d, more results %d, pkt_nr: %d, \ CS client: %u, CS result: %u, CS conn: %u, limit: %lu, TZ: 0x%lx, \ sql mode: 0x%lx, sort len: %lu, conncat len: %lu", (int)flags.client_long_flag, (int)flags.client_protocol_41, + (int)flags.result_in_binary_protocol, (int)flags.more_results_exists, flags.pkt_nr, flags.character_set_client_num, @@ -1089,6 +1097,7 @@ Query_cache::send_result_to_client(THD *thd, char *sql, uint query_length) flags.client_long_flag= test(thd->client_capabilities & CLIENT_LONG_FLAG); flags.client_protocol_41= test(thd->client_capabilities & CLIENT_PROTOCOL_41); + flags.result_in_binary_protocol= (unsigned int)thd->protocol->type(); flags.more_results_exists= test(thd->server_status & SERVER_MORE_RESULTS_EXISTS); flags.pkt_nr= thd->net.pkt_nr; @@ -1104,11 +1113,13 @@ Query_cache::send_result_to_client(THD *thd, char *sql, uint query_length) flags.max_sort_length= thd->variables.max_sort_length; flags.group_concat_max_len= thd->variables.group_concat_max_len; flags.lc_time_names= thd->variables.lc_time_names; - DBUG_PRINT("qcache", ("long %d, 4.1: %d, more results %d, pkt_nr: %d, \ + DBUG_PRINT("qcache", ("\ +long %d, 4.1: %d, bin_proto: %d, more results %d, pkt_nr: %d, \ CS client: %u, CS result: %u, CS conn: %u, limit: %lu, TZ: 0x%lx, \ sql mode: 0x%lx, sort len: %lu, conncat len: %lu", (int)flags.client_long_flag, (int)flags.client_protocol_41, + (int)flags.result_in_binary_protocol, (int)flags.more_results_exists, flags.pkt_nr, flags.character_set_client_num, @@ -3048,11 +3059,10 @@ Query_cache::is_cacheable(THD *thd, uint32 query_len, char *query, LEX *lex, TABLE_COUNTER_TYPE table_count; DBUG_ENTER("Query_cache::is_cacheable"); - if (lex->sql_command == SQLCOM_SELECT && + if (query_cache_is_cacheable_query(lex) && (thd->variables.query_cache_type == 1 || (thd->variables.query_cache_type == 2 && (lex->select_lex.options & - OPTION_TO_QUERY_CACHE))) && - lex->safe_to_cache_query) + OPTION_TO_QUERY_CACHE)))) { DBUG_PRINT("qcache", ("options: %lx %lx type: %u", (long) OPTION_TO_QUERY_CACHE, diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 836a4d07f0b..e8e39972020 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -301,9 +301,9 @@ THD::THD() bzero((char*) &user_var_events, sizeof(user_var_events)); /* Protocol */ - protocol= &protocol_simple; // Default protocol - protocol_simple.init(this); - protocol_prep.init(this); + protocol= &protocol_text; // Default protocol + protocol_text.init(this); + protocol_binary.init(this); tablespace_op=FALSE; tmp= sql_rnd_with_mutex(); diff --git a/sql/sql_class.h b/sql/sql_class.h index c24e18ccffa..f492db82ec7 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -904,8 +904,8 @@ public: NET net; // client connection descriptor MEM_ROOT warn_root; // For warnings and errors Protocol *protocol; // Current protocol - Protocol_simple protocol_simple; // Normal protocol - Protocol_prep protocol_prep; // Binary protocol + Protocol_text protocol_text; // Normal protocol + Protocol_binary protocol_binary; // Binary protocol HASH user_vars; // hash for user variables String packet; // dynamic buffer for network I/O String convert_buffer; // buffer for charset conversions diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index 292f9ebd344..ae516485a2b 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -93,11 +93,11 @@ When one supplies long data for a placeholder: /* A result class used to send cursor rows using the binary protocol. */ -class Select_fetch_protocol_prep: public select_send +class Select_fetch_protocol_binary: public select_send { - Protocol_prep protocol; + Protocol_binary protocol; public: - Select_fetch_protocol_prep(THD *thd); + Select_fetch_protocol_binary(THD *thd); virtual bool send_fields(List<Item> &list, uint flags); virtual bool send_data(List<Item> &items); virtual bool send_eof(); @@ -125,7 +125,7 @@ public: }; THD *thd; - Select_fetch_protocol_prep result; + Select_fetch_protocol_binary result; Protocol *protocol; Item_param **param_array; uint param_count; @@ -247,9 +247,9 @@ static bool send_prep_stmt(Prepared_statement *stmt, uint columns) */ DBUG_RETURN(my_net_write(net, buff, sizeof(buff)) || (stmt->param_count && - stmt->thd->protocol_simple.send_fields((List<Item> *) - &stmt->lex->param_list, - Protocol::SEND_EOF))); + stmt->thd->protocol_text.send_fields((List<Item> *) + &stmt->lex->param_list, + Protocol::SEND_EOF))); } #else static bool send_prep_stmt(Prepared_statement *stmt, @@ -691,7 +691,7 @@ static void setup_one_conversion_function(THD *thd, Item_param *param, and generate a valid query for logging. NOTES - This function, along with other _withlog functions is called when one of + This function, along with other _with_log functions is called when one of binary, slow or general logs is open. Logging of prepared statements in all cases is performed by means of conventional queries: if parameter data was supplied from C API, each placeholder in the query is @@ -715,9 +715,9 @@ static void setup_one_conversion_function(THD *thd, Item_param *param, 0 if success, 1 otherwise */ -static bool insert_params_withlog(Prepared_statement *stmt, uchar *null_array, - uchar *read_pos, uchar *data_end, - String *query) +static bool insert_params_with_log(Prepared_statement *stmt, uchar *null_array, + uchar *read_pos, uchar *data_end, + String *query) { THD *thd= stmt->thd; Item_param **begin= stmt->param_array; @@ -725,7 +725,7 @@ static bool insert_params_withlog(Prepared_statement *stmt, uchar *null_array, uint32 length= 0; String str; const String *res; - DBUG_ENTER("insert_params_withlog"); + DBUG_ENTER("insert_params_with_log"); if (query->copy(stmt->query, stmt->query_length, default_charset_info)) DBUG_RETURN(1); @@ -869,7 +869,8 @@ static bool emb_insert_params(Prepared_statement *stmt, String *expanded_query) } -static bool emb_insert_params_withlog(Prepared_statement *stmt, String *query) +static bool emb_insert_params_with_log(Prepared_statement *stmt, + String *query) { THD *thd= stmt->thd; Item_param **it= stmt->param_array; @@ -880,7 +881,7 @@ static bool emb_insert_params_withlog(Prepared_statement *stmt, String *query) const String *res; uint32 length= 0; - DBUG_ENTER("emb_insert_params_withlog"); + DBUG_ENTER("emb_insert_params_with_log"); if (query->copy(stmt->query, stmt->query_length, default_charset_info)) DBUG_RETURN(1); @@ -1889,7 +1890,7 @@ void mysql_stmt_prepare(THD *thd, const char *packet, uint packet_length) /* First of all clear possible warnings from the previous command */ mysql_reset_thd_for_next_command(thd); - if (! (stmt= new Prepared_statement(thd, &thd->protocol_prep))) + if (! (stmt= new Prepared_statement(thd, &thd->protocol_binary))) DBUG_VOID_RETURN; /* out of memory: error is set in Sql_alloc */ if (thd->stmt_map.insert(thd, stmt)) @@ -2061,8 +2062,8 @@ void mysql_sql_stmt_prepare(THD *thd) const char *query; uint query_len; DBUG_ENTER("mysql_sql_stmt_prepare"); - DBUG_ASSERT(thd->protocol == &thd->protocol_simple); LINT_INIT(query_len); + DBUG_ASSERT(thd->protocol == &thd->protocol_text); if ((stmt= (Prepared_statement*) thd->stmt_map.find_by_name(name))) { @@ -2075,7 +2076,7 @@ void mysql_sql_stmt_prepare(THD *thd) } if (! (query= get_dynamic_sql_string(lex, &query_len)) || - ! (stmt= new Prepared_statement(thd, &thd->protocol_simple))) + ! (stmt= new Prepared_statement(thd, &thd->protocol_text))) { DBUG_VOID_RETURN; /* out of memory */ } @@ -2628,14 +2629,14 @@ void mysql_stmt_get_longdata(THD *thd, char *packet, ulong packet_length) /*************************************************************************** - Select_fetch_protocol_prep + Select_fetch_protocol_binary ****************************************************************************/ -Select_fetch_protocol_prep::Select_fetch_protocol_prep(THD *thd_arg) +Select_fetch_protocol_binary::Select_fetch_protocol_binary(THD *thd_arg) :protocol(thd_arg) {} -bool Select_fetch_protocol_prep::send_fields(List<Item> &list, uint flags) +bool Select_fetch_protocol_binary::send_fields(List<Item> &list, uint flags) { bool rc; Protocol *save_protocol= thd->protocol; @@ -2653,7 +2654,7 @@ bool Select_fetch_protocol_prep::send_fields(List<Item> &list, uint flags) return rc; } -bool Select_fetch_protocol_prep::send_eof() +bool Select_fetch_protocol_binary::send_eof() { Protocol *save_protocol= thd->protocol; @@ -2665,7 +2666,7 @@ bool Select_fetch_protocol_prep::send_eof() bool -Select_fetch_protocol_prep::send_data(List<Item> &fields) +Select_fetch_protocol_binary::send_data(List<Item> &fields) { Protocol *save_protocol= thd->protocol; bool rc; @@ -2699,15 +2700,26 @@ Prepared_statement::Prepared_statement(THD *thd_arg, Protocol *protocol_arg) void Prepared_statement::setup_set_params() { - /* Setup binary logging */ + /* + Note: BUG#25843 applies here too (query cache lookup uses thd->db, not + db from "prepare" time). + */ + if (query_cache_maybe_disabled(thd)) // we won't expand the query + lex->safe_to_cache_query= FALSE; // so don't cache it at Execution + + /* + Decide if we have to expand the query (because we must write it to logs or + because we want to look it up in the query cache) or not. + */ if (mysql_bin_log.is_open() && is_update_query(lex->sql_command) || - opt_log || opt_slow_log) + opt_log || opt_slow_log || + query_cache_is_cacheable_query(lex)) { set_params_from_vars= insert_params_from_vars_with_log; #ifndef EMBEDDED_LIBRARY - set_params= insert_params_withlog; + set_params= insert_params_with_log; #else - set_params_data= emb_insert_params_withlog; + set_params_data= emb_insert_params_with_log; #endif } else @@ -2844,7 +2856,6 @@ bool Prepared_statement::prepare(const char *packet, uint packet_len) error= MYSQLparse((void *)thd) || thd->is_fatal_error || thd->net.report_error || init_param_array(this); - lex->safe_to_cache_query= FALSE; /* While doing context analysis of the query (in check_prepared_statement) we allocate a lot of additional memory: for open tables, JOINs, derived @@ -2887,6 +2898,18 @@ bool Prepared_statement::prepare(const char *packet, uint packet_len) thd->restore_backup_statement(this, &stmt_backup); thd->stmt_arena= old_stmt_arena; + if ((protocol->type() == Protocol::PROTOCOL_TEXT) && (param_count > 0)) + { + /* + This is a mysql_sql_stmt_prepare(); query expansion will insert user + variable references, and user variables are uncacheable, thus we have to + mark this statement as uncacheable. + This has to be done before setup_set_params(), as it may make expansion + unneeded. + */ + lex->safe_to_cache_query= FALSE; + } + if (error == 0) { setup_set_params(); @@ -3003,11 +3026,26 @@ bool Prepared_statement::execute(String *expanded_query, bool open_cursor) reinit_stmt_before_use(thd, lex); thd->protocol= protocol; /* activate stmt protocol */ - error= (open_cursor ? - mysql_open_cursor(thd, (uint) ALWAYS_MATERIALIZED_CURSOR, - &result, &cursor) : - mysql_execute_command(thd)); - thd->protocol= &thd->protocol_simple; /* use normal protocol */ + + if (open_cursor) + error= mysql_open_cursor(thd, (uint) ALWAYS_MATERIALIZED_CURSOR, + &result, &cursor); + else + { + /* + Try to find it in the query cache, if not, execute it. + Note that multi-statements cannot exist here (they are not supported in + prepared statements). + */ + if (query_cache_send_result_to_client(thd, thd->query, + thd->query_length) <= 0) + { + error= mysql_execute_command(thd); + query_cache_end_of_result(thd); + } + } + + thd->protocol= &thd->protocol_text; /* use normal protocol */ /* Assert that if an error, no cursor is open */ DBUG_ASSERT(! (error && cursor)); diff --git a/tests/mysql_client_test.c b/tests/mysql_client_test.c index 841050a104e..b12d5fa1db0 100644 --- a/tests/mysql_client_test.c +++ b/tests/mysql_client_test.c @@ -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 } }; |