From a63dde5a5b9a519ad2e4343c1d0b27de5bf41fc0 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 8 Apr 2008 20:01:20 +0400 Subject: [PATCH 1/8] Tentative implementation of WL#4165 Prepared statements: validation WL#4166 Prepared statements: automatic re-prepare Fixes Bug#27430 Crash in subquery code when in PS and table DDL changed after PREPARE Bug#27690 Re-execution of prepared statement after table was replaced with a view crashes Bug#27420 A combination of PS and view operations cause error + assertion on shutdown The basic idea of the patch is to keep track of table metadata between prepared statement prepare and execute. If some table used in the statement has changed, the prepared statement is re-prepared before execution. See WL#4165 and WL#4166 contents and comments in the code for details of the implementation. include/my_global.h: Remove 'register' keyword to avoid warnings when swapping large structures that don't fit into a register. Any modern compiler is capable of placing a variable in a register when that would benefit performance. mysql-test/r/ps_1general.result: Update test results: since now we re-prepare automatically, more correct results are produced in prepare-ddl-execute scenario. mysql-test/r/query_cache_merge.result: Ensure that the table definition cache is large enough for the test to pass in --ps-protocol mysql-test/r/trigger.result: Update test results to reflect automatic statement reprepare. mysql-test/t/disabled.def: Enable ps_ddl.test, which now passes. mysql-test/t/ps_1general.test: Since now we re-execute prepared statements after DDL successfully, change the test to produce repeatable results. Remove expectancy of an error in one place where now we automatically reprepare the prepared statement. mysql-test/t/query_cache_merge.test: Ensure the table definition cache is large enough for the test to pass in --ps-protocol mysql-test/t/trigger.test: Sinc sql/item.cc: Implement Item_param "copy" functionality, used at re-prepare of a prepared statement. We copy the type of the original parameter, and move the assigned value, if any. Sic, the value is "moved", since it can be quite big -- e.g. in case we deal with a LONG DATA parameter. It's essential to move the value from the old parameter since at the time of re-prepare the client packet with the necessary information may be not available. sql/item.h: Declare a new method used for reprepare. sql/my_decimal.h: Implement "swap()" functionality of class my_decimal to be able to easily swap two decimal values. sql/mysql_priv.h: Declare enum_metadata_type. sql/mysqld.cc: Implement a status variable for the number of reprepared statements. sql/sql_base.cc: Implement metadata version validation. sql/share/errmsg.txt: Add two new error messages: ER_NEED_REPREPARE and ER_PS_REBIND. The first error (theoretically) never reaches the user. It is issued by the metadata validation framework when a metadata version has changed between prepare and execute. Later on it's intercepted and the statement is automatically re-prepared. Only if the error has occurred repeatedly MAX_REPREPARE_ATTEMTS (3) times do we return it to the user. The second error is issued when after re-prepare we discover that the metadata we sent over to the client using the binary protocol differs drammatically from the new result set metadata that the reprepared statement produces (e.g. number of result set columns is different). sql/sql_class.cc: Implement metadata version validation framework. sql/sql_class.h: Declarations for metadata version validation framework. sql/sql_parse.cc: Mark commands for which we must invalidate and reprepare a prepared statement when metadata has changed. sql/sql_prepare.cc: Implement WL#4165 and WL#4166 (limited support of metadata validation and re-prepare). sql/table.h: Implement metadata validation. tests/mysql_client_test.c: Add a test case for WL#4166 --- include/my_global.h | 2 +- mysql-test/r/ps_1general.result | 19 +- mysql-test/r/query_cache_merge.result | 1 + mysql-test/r/trigger.result | 1 - mysql-test/t/disabled.def | 1 - mysql-test/t/ps_1general.test | 5 +- mysql-test/t/query_cache_merge.test | 10 + mysql-test/t/trigger.test | 7 +- sql/item.cc | 39 ++ sql/item.h | 1 + sql/my_decimal.h | 8 + sql/mysql_priv.h | 40 ++ sql/mysqld.cc | 9 +- sql/share/errmsg.txt | 6 + sql/sql_base.cc | 110 +++++- sql/sql_class.cc | 10 +- sql/sql_class.h | 86 ++++- sql/sql_parse.cc | 25 +- sql/sql_prepare.cc | 516 +++++++++++++++++++++----- sql/table.h | 130 +++++++ tests/mysql_client_test.c | 120 +++++- 21 files changed, 1028 insertions(+), 118 deletions(-) diff --git a/include/my_global.h b/include/my_global.h index d9a8aeca881..a1118f0cb3d 100644 --- a/include/my_global.h +++ b/include/my_global.h @@ -568,7 +568,7 @@ typedef unsigned short ushort; #define CMP_NUM(a,b) (((a) < (b)) ? -1 : ((a) == (b)) ? 0 : 1) #define sgn(a) (((a) < 0) ? -1 : ((a) > 0) ? 1 : 0) -#define swap_variables(t, a, b) { register t dummy; dummy= a; a= b; b= dummy; } +#define swap_variables(t, a, b) { t dummy; dummy= a; a= b; b= dummy; } #define test(a) ((a) ? 1 : 0) #define set_if_bigger(a,b) do { if ((a) < (b)) (a)=(b); } while(0) #define set_if_smaller(a,b) do { if ((a) > (b)) (a)=(b); } while(0) diff --git a/mysql-test/r/ps_1general.result b/mysql-test/r/ps_1general.result index 1c5f0e4dfd6..33849eefe7a 100644 --- a/mysql-test/r/ps_1general.result +++ b/mysql-test/r/ps_1general.result @@ -143,32 +143,32 @@ b char(30) ); insert into t5( a, b, c) values( 9, 'recreated table', 9); execute stmt2 ; -a b c -9 recreated table 9 +a c b +9 9 recreated table drop table t5 ; create table t5 ( a int primary key, b char(30), c int, -d timestamp default current_timestamp +d timestamp default '2008-02-23 09:23:45' ); insert into t5( a, b, c) values( 9, 'recreated table', 9); execute stmt2 ; -a b c -9 recreated table 9 +a b c d +9 recreated table 9 2008-02-23 09:23:45 drop table t5 ; create table t5 ( a int primary key, -d timestamp default current_timestamp, +d timestamp default '2008-02-23 09:23:45', b char(30), c int ); insert into t5( a, b, c) values( 9, 'recreated table', 9); execute stmt2 ; -a b c -9 recreated table 9 +a d b c +9 2008-02-23 09:23:45 recreated table 9 drop table t5 ; create table t5 ( @@ -189,7 +189,8 @@ f3 int ); insert into t5( f1, f2, f3) values( 9, 'recreated table', 9); execute stmt2 ; -ERROR 42S22: Unknown column 'test.t5.a' in 'field list' +f1 f2 f3 +9 recreated table 9 drop table t5 ; prepare stmt1 from ' select * from t1 where a <= 2 ' ; execute stmt1 ; diff --git a/mysql-test/r/query_cache_merge.result b/mysql-test/r/query_cache_merge.result index c6df4266de2..d2bbe217815 100644 --- a/mysql-test/r/query_cache_merge.result +++ b/mysql-test/r/query_cache_merge.result @@ -18,3 +18,4 @@ Variable_name Value Qcache_queries_in_cache 0 drop table t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15,t16,t17,t18,t19,t20,t21,t22,t23,t24,t25,t26,t27,t28,t29,t30,t31,t32,t33,t34,t35,t36,t37,t38,t39,t40,t41,t42,t43,t44,t45,t46,t47,t48,t49,t50,t51,t52,t53,t54,t55,t56,t57,t58,t59,t60,t61,t62,t63,t64,t65,t66,t67,t68,t69,t70,t71,t72,t73,t74,t75,t76,t77,t78,t79,t80,t81,t82,t83,t84,t85,t86,t87,t88,t89,t90,t91,t92,t93,t94,t95,t96,t97,t98,t99,t100,t101,t102,t103,t104,t105,t106,t107,t108,t109,t110,t111,t112,t113,t114,t115,t116,t117,t118,t119,t120,t121,t122,t123,t124,t125,t126,t127,t128,t129,t130,t131,t132,t133,t134,t135,t136,t137,t138,t139,t140,t141,t142,t143,t144,t145,t146,t147,t148,t149,t150,t151,t152,t153,t154,t155,t156,t157,t158,t159,t160,t161,t162,t163,t164,t165,t166,t167,t168,t169,t170,t171,t172,t173,t174,t175,t176,t177,t178,t179,t180,t181,t182,t183,t184,t185,t186,t187,t188,t189,t190,t191,t192,t193,t194,t195,t196,t197,t198,t199,t200,t201,t202,t203,t204,t205,t206,t207,t208,t209,t210,t211,t212,t213,t214,t215,t216,t217,t218,t219,t220,t221,t222,t223,t224,t225,t226,t227,t228,t229,t230,t231,t232,t233,t234,t235,t236,t237,t238,t239,t240,t241,t242,t243,t244,t245,t246,t247,t248,t249,t250,t251,t252,t253,t254,t255,t256,t257,t00; SET @@global.query_cache_size=0; +set @@global.table_definition_cache=@save_table_definition_cache; diff --git a/mysql-test/r/trigger.result b/mysql-test/r/trigger.result index e7f5c41513b..35d5134fa6b 100644 --- a/mysql-test/r/trigger.result +++ b/mysql-test/r/trigger.result @@ -820,7 +820,6 @@ call p1(); drop trigger t1_bi; create trigger t1_bi after insert on t1 for each row insert into t3 values (new.id); execute stmt1; -ERROR 42S02: Table 'test.t3' doesn't exist call p1(); ERROR 42S02: Table 'test.t3' doesn't exist deallocate prepare stmt1; diff --git a/mysql-test/t/disabled.def b/mysql-test/t/disabled.def index af7b9c25f18..24a5522b3dc 100644 --- a/mysql-test/t/disabled.def +++ b/mysql-test/t/disabled.def @@ -18,6 +18,5 @@ federated_transactions : Bug#29523 Transactions do not work lowercase_table3 : Bug#32667 lowercase_table3.test reports to error log ctype_create : Bug#32965 main.ctype_create fails status : Bug#32966 main.status fails -ps_ddl : Bug#12093 2007-12-14 pending WL#4165 / WL#4166 csv_alter_table : Bug#33696 2008-01-21 pcrews no .result file - bug allows NULL columns in CSV tables cast : Bug#35594 2008-03-27 main.cast fails on Windows2003-64 diff --git a/mysql-test/t/ps_1general.test b/mysql-test/t/ps_1general.test index 42bf39890de..95cbd908933 100644 --- a/mysql-test/t/ps_1general.test +++ b/mysql-test/t/ps_1general.test @@ -181,7 +181,7 @@ create table t5 a int primary key, b char(30), c int, - d timestamp default current_timestamp + d timestamp default '2008-02-23 09:23:45' ); insert into t5( a, b, c) values( 9, 'recreated table', 9); execute stmt2 ; @@ -191,7 +191,7 @@ drop table t5 ; create table t5 ( a int primary key, - d timestamp default current_timestamp, + d timestamp default '2008-02-23 09:23:45', b char(30), c int ); @@ -218,7 +218,6 @@ create table t5 f3 int ); insert into t5( f1, f2, f3) values( 9, 'recreated table', 9); ---error 1054 execute stmt2 ; drop table t5 ; diff --git a/mysql-test/t/query_cache_merge.test b/mysql-test/t/query_cache_merge.test index 36b8662f088..5247d29a83c 100644 --- a/mysql-test/t/query_cache_merge.test +++ b/mysql-test/t/query_cache_merge.test @@ -25,6 +25,15 @@ while ($1) } --enable_warnings +# +# In order for the test to pass in --ps-protocol, we must +# set table_definition_cache size to at least 258 elements. +# Otherwise table versions are bound to change between +# prepare and execute, and we will get a constant validation +# error. See WL#4165 for details. +# +set @save_table_definition_cache= @@global.table_definition_cache; +set @@global.table_definition_cache=512; create table t00 (a int) engine=MERGE UNION=(t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15,t16,t17,t18,t19,t20,t21,t22,t23,t24,t25,t26,t27,t28,t29,t30,t31,t32,t33,t34,t35,t36,t37,t38,t39,t40,t41,t42,t43,t44,t45,t46,t47,t48,t49,t50,t51,t52,t53,t54,t55,t56,t57,t58,t59,t60,t61,t62,t63,t64,t65,t66,t67,t68,t69,t70,t71,t72,t73,t74,t75,t76,t77,t78,t79,t80,t81,t82,t83,t84,t85,t86,t87,t88,t89,t90,t91,t92,t93,t94,t95,t96,t97,t98,t99,t100,t101,t102,t103,t104,t105,t106,t107,t108,t109,t110,t111,t112,t113,t114,t115,t116,t117,t118,t119,t120,t121,t122,t123,t124,t125,t126,t127,t128,t129,t130,t131,t132,t133,t134,t135,t136,t137,t138,t139,t140,t141,t142,t143,t144,t145,t146,t147,t148,t149,t150,t151,t152,t153,t154,t155,t156,t157,t158,t159,t160,t161,t162,t163,t164,t165,t166,t167,t168,t169,t170,t171,t172,t173,t174,t175,t176,t177,t178,t179,t180,t181,t182,t183,t184,t185,t186,t187,t188,t189,t190,t191,t192,t193,t194,t195,t196,t197,t198,t199,t200,t201,t202,t203,t204,t205,t206,t207,t208,t209,t210,t211,t212,t213,t214,t215,t216,t217,t218,t219,t220,t221,t222,t223,t224,t225,t226,t227,t228,t229,t230,t231,t232,t233,t234,t235,t236,t237,t238,t239,t240,t241,t242,t243,t244,t245,t246,t247,t248,t249,t250,t251,t252,t253,t254,t255,t256,t257) INSERT_METHOD=FIRST; enable_query_log; select count(*) from t00; @@ -36,5 +45,6 @@ show status like "Qcache_queries_in_cache"; drop table t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15,t16,t17,t18,t19,t20,t21,t22,t23,t24,t25,t26,t27,t28,t29,t30,t31,t32,t33,t34,t35,t36,t37,t38,t39,t40,t41,t42,t43,t44,t45,t46,t47,t48,t49,t50,t51,t52,t53,t54,t55,t56,t57,t58,t59,t60,t61,t62,t63,t64,t65,t66,t67,t68,t69,t70,t71,t72,t73,t74,t75,t76,t77,t78,t79,t80,t81,t82,t83,t84,t85,t86,t87,t88,t89,t90,t91,t92,t93,t94,t95,t96,t97,t98,t99,t100,t101,t102,t103,t104,t105,t106,t107,t108,t109,t110,t111,t112,t113,t114,t115,t116,t117,t118,t119,t120,t121,t122,t123,t124,t125,t126,t127,t128,t129,t130,t131,t132,t133,t134,t135,t136,t137,t138,t139,t140,t141,t142,t143,t144,t145,t146,t147,t148,t149,t150,t151,t152,t153,t154,t155,t156,t157,t158,t159,t160,t161,t162,t163,t164,t165,t166,t167,t168,t169,t170,t171,t172,t173,t174,t175,t176,t177,t178,t179,t180,t181,t182,t183,t184,t185,t186,t187,t188,t189,t190,t191,t192,t193,t194,t195,t196,t197,t198,t199,t200,t201,t202,t203,t204,t205,t206,t207,t208,t209,t210,t211,t212,t213,t214,t215,t216,t217,t218,t219,t220,t221,t222,t223,t224,t225,t226,t227,t228,t229,t230,t231,t232,t233,t234,t235,t236,t237,t238,t239,t240,t241,t242,t243,t244,t245,t246,t247,t248,t249,t250,t251,t252,t253,t254,t255,t256,t257,t00; SET @@global.query_cache_size=0; +set @@global.table_definition_cache=@save_table_definition_cache; # End of 4.1 tests diff --git a/mysql-test/t/trigger.test b/mysql-test/t/trigger.test index 921c9579bfb..c57178c1928 100644 --- a/mysql-test/t/trigger.test +++ b/mysql-test/t/trigger.test @@ -997,11 +997,10 @@ call p1(); # Altering trigger forcing it use different set of tables drop trigger t1_bi; create trigger t1_bi after insert on t1 for each row insert into t3 values (new.id); -# Until we implement proper mechanism for invalidation of PS/SP when table -# or SP's are changed these two statements will fail with 'Table ... was -# not locked' error (this mechanism should be based on the new TDC). ---error ER_NO_SUCH_TABLE execute stmt1; +# Until we implement proper mechanism for invalidation of SP statements +# invoked whenever a table used in SP changes, this statement will fail with +# 'Table ... does not exist' error. --error ER_NO_SUCH_TABLE call p1(); deallocate prepare stmt1; diff --git a/sql/item.cc b/sql/item.cc index 883c293f645..04c75107d14 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -3161,6 +3161,45 @@ void Item_param::print(String *str, enum_query_type query_type) } +/** + Preserve the original parameter types and values + when re-preparing a prepared statement. + + Copy parameter type information and conversion function + pointers from a parameter of the old statement to the + corresponding parameter of the new one. + + Move parameter values from the old parameters to the new + one. We simply "exchange" the values, which allows + to save on allocation and character set conversion in + case a parameter is a string or a blob/clob. + + @param[in] src parameter item of the original + prepared statement +*/ + +void +Item_param::set_param_type_and_swap_value(Item_param *src) +{ + unsigned_flag= src->unsigned_flag; + param_type= src->param_type; + set_param_func= src->set_param_func; + item_type= src->item_type; + item_result_type= src->item_result_type; + + collation.set(src->collation.collation); + maybe_null= src->maybe_null; + null_value= src->null_value; + max_length= src->max_length; + decimals= src->decimals; + state= src->state; + value= src->value; + + decimal_value.swap(src->decimal_value); + str_value.swap(src->str_value); + str_value_ptr.swap(src->str_value_ptr); +} + /**************************************************************************** Item_copy_string ****************************************************************************/ diff --git a/sql/item.h b/sql/item.h index cd8bb4faccb..465d6f4d54c 100644 --- a/sql/item.h +++ b/sql/item.h @@ -1684,6 +1684,7 @@ public: bool eq(const Item *item, bool binary_cmp) const; /** Item is a argument to a limit clause. */ bool limit_clause_param; + void set_param_type_and_swap_value(Item_param *from); }; diff --git a/sql/my_decimal.h b/sql/my_decimal.h index 1885036f42b..b096abaf8ae 100644 --- a/sql/my_decimal.h +++ b/sql/my_decimal.h @@ -114,6 +114,14 @@ public: bool sign() const { return decimal_t::sign; } void sign(bool s) { decimal_t::sign= s; } uint precision() const { return intg + frac; } + + /** Swap two my_decimal values */ + void swap(my_decimal &rhs) + { + swap_variables(my_decimal, *this, rhs); + /* Swap the buffer pointers back */ + swap_variables(decimal_digit_t *, buf, rhs.buf); + } }; diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index 26f253a6f8e..491a4c8ca1b 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -259,6 +259,21 @@ protected: #define USER_VARS_HASH_SIZE 16 #define TABLE_OPEN_CACHE_MIN 64 #define TABLE_OPEN_CACHE_DEFAULT 64 +#define TABLE_DEF_CACHE_DEFAULT 256 +/** + We must have room for at least 256 table definitions in the table + cache, since otherwise there is no chance prepared + statements that use these many tables can work. + Prepared statements use table definition cache ids (table_map_id) + as table version identifiers. If the table definition + cache size is less than the number of tables used in a statement, + the contents of the table definition cache is guaranteed to rotate + between a prepare and execute. This leads to stable validation + errors. In future we shall use more stable version identifiers, + for now the only solution is to ensure that the table definition + cache can contain at least all tables of a given statement. +*/ +#define TABLE_DEF_CACHE_MIN 256 /* Value of 9236 discovered through binary search 2006-09-26 on Ubuntu Dapper @@ -670,6 +685,31 @@ const char *set_thd_proc_info(THD *thd, const char *info, const char *calling_file, const unsigned int calling_line); +/** + Enumerate possible types of a table from re-execution + standpoint. + TABLE_LIST class has a member of this type. + At prepared statement prepare, this member is assigned a value + as of the current state of the database. Before (re-)execution + of a prepared statement, we check that the value recorded at + prepare matches the type of the object we obtained from the + table definition cache. + + @sa check_and_update_metadata_version() + @sa Execute_observer + @sa Prepared_statement::reprepare() +*/ + +enum enum_metadata_type +{ + /** Initial value set by the parser */ + METADATA_NULL= 0, + METADATA_VIEW, + METADATA_BASE_TABLE, + METADATA_I_S_TABLE, + METADATA_TMP_TABLE +}; + /* External variables */ diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 36a5b1a10d9..d93100a068f 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -3089,6 +3089,7 @@ SHOW_VAR com_status_vars[]= { {"stmt_execute", (char*) offsetof(STATUS_VAR, com_stmt_execute), SHOW_LONG_STATUS}, {"stmt_fetch", (char*) offsetof(STATUS_VAR, com_stmt_fetch), SHOW_LONG_STATUS}, {"stmt_prepare", (char*) offsetof(STATUS_VAR, com_stmt_prepare), SHOW_LONG_STATUS}, + {"stmt_reprepare", (char*) offsetof(STATUS_VAR, com_stmt_reprepare), SHOW_LONG_STATUS}, {"stmt_reset", (char*) offsetof(STATUS_VAR, com_stmt_reset), SHOW_LONG_STATUS}, {"stmt_send_long_data", (char*) offsetof(STATUS_VAR, com_stmt_send_long_data), SHOW_LONG_STATUS}, {"truncate", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_TRUNCATE]), SHOW_LONG_STATUS}, @@ -3177,7 +3178,7 @@ static int init_common_variables(const char *conf_file_name, int argc, We have few debug-only commands in com_status_vars, only visible in debug builds. for simplicity we enable the assert only in debug builds - There are 7 Com_ variables which don't have corresponding SQLCOM_ values: + There are 8 Com_ variables which don't have corresponding SQLCOM_ values: (TODO strictly speaking they shouldn't be here, should not have Com_ prefix that is. Perhaps Stmt_ ? Comstmt_ ? Prepstmt_ ?) @@ -3186,6 +3187,7 @@ static int init_common_variables(const char *conf_file_name, int argc, Com_stmt_execute => com_stmt_execute Com_stmt_fetch => com_stmt_fetch Com_stmt_prepare => com_stmt_prepare + Com_stmt_reprepare => com_stmt_reprepare Com_stmt_reset => com_stmt_reset Com_stmt_send_long_data => com_stmt_send_long_data @@ -3194,7 +3196,7 @@ static int init_common_variables(const char *conf_file_name, int argc, of SQLCOM_ constants. */ compile_time_assert(sizeof(com_status_vars)/sizeof(com_status_vars[0]) - 1 == - SQLCOM_END + 7); + SQLCOM_END + 8); #endif load_defaults(conf_file_name, groups, &argc, &argv); @@ -6757,7 +6759,8 @@ The minimum value for this variable is 4096.", {"table_definition_cache", OPT_TABLE_DEF_CACHE, "The number of cached table definitions.", (uchar**) &table_def_size, (uchar**) &table_def_size, - 0, GET_ULONG, REQUIRED_ARG, 128, 1, 512*1024L, 0, 1, 0}, + 0, GET_ULONG, REQUIRED_ARG, TABLE_DEF_CACHE_DEFAULT, TABLE_DEF_CACHE_MIN, + 512*1024L, 0, 1, 0}, {"table_open_cache", OPT_TABLE_OPEN_CACHE, "The number of cached open tables.", (uchar**) &table_cache_size, (uchar**) &table_cache_size, 0, GET_ULONG, diff --git a/sql/share/errmsg.txt b/sql/share/errmsg.txt index 7990baa6bb7..684d1ce9421 100644 --- a/sql/share/errmsg.txt +++ b/sql/share/errmsg.txt @@ -6120,3 +6120,9 @@ ER_NO_FORMAT_DESCRIPTION_EVENT_BEFORE_BINLOG_STATEMENT eng "The BINLOG statement of type `%s` was not preceded by a format description BINLOG statement." ER_SLAVE_CORRUPT_EVENT eng "Corrupted replication event was detected" + +ER_NEED_REPREPARE + eng "Prepared statement needs to be re-prepared" + +ER_PS_REBIND + eng "Prepared statement result set has changed, a rebind needed" diff --git a/sql/sql_base.cc b/sql/sql_base.cc index f0faf034027..62a723953ed 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -3717,6 +3717,72 @@ void assign_new_table_id(TABLE_SHARE *share) DBUG_VOID_RETURN; } + +/** + Compare metadata versions of an element obtained from the table + definition cache and its corresponding node in the parse tree. + + If the new and the old values mismatch, invoke + Metadata_version_observer. + + At prepared statement prepare, all TABLE_LIST version values are + NULL and we always have a mismatch. But there is no observer set + in THD, and therefore no error is reported. Instead, we update + the value in the parse tree, effectively recording the original + version. + At prepared statement execute, an observer may be installed. If + there is a version mismatch, we push an error and return TRUE. + + For conventional execution (no prepared statements), the + observer is never installed. + + @sa Execute_observer + @sa check_prepared_statement() to see cases when an observer is installed + @sa TABLE_LIST::is_metadata_version_equal() + @sa TABLE_SHARE::get_metadata_version() + + @param[in] thd used to report errors + @param[in,out] tables TABLE_LIST instance created by the parser + Metadata version information in this object + is updated upon success. + @param[in] table_share an element from the table definition cache + + @retval TRUE an error, which has been reported + @retval FALSE success, version in TABLE_LIST has been updated +*/ + +bool +check_and_update_table_version(THD *thd, + TABLE_LIST *tables, TABLE_SHARE *table_share) +{ + if (! tables->is_metadata_version_equal(table_share)) + { + if (thd->m_metadata_observer && + thd->m_metadata_observer->check_metadata_change(thd)) + { + /* + Version of the table share is different from the + previous execution of the prepared statement, and it is + unacceptable for this SQLCOM. Error has been reported. + */ + return TRUE; + } + /* Always maintain the latest version */ + tables->set_metadata_version(table_share); + } +#if 0 +#ifndef DBUG_OFF + /* Spuriously reprepare each statement. */ + if (thd->m_metadata_observer && thd->stmt_arena->is_reprepared == FALSE) + { + thd->m_metadata_observer->check_metadata_change(thd); + return TRUE; + } +#endif +#endif + return FALSE; +} + /* Load a table definition from file and open unireg table @@ -3762,6 +3828,8 @@ retry: if (share->is_view) { + if (check_and_update_table_version(thd, table_list, share)) + goto err; if (table_list->i_s_requested_object & OPEN_TABLE_ONLY) goto err; @@ -3779,6 +3847,26 @@ retry: release_table_share(share, RELEASE_NORMAL); DBUG_RETURN((flags & OPEN_VIEW_NO_PARSE)? -1 : 0); } + else if (table_list->view) + { + /* + We're trying to open a table for what was a view. + This can only happen during (re-)execution. + At prepared statement prepare the view has been opened and + merged into the statement parse tree. After that, someone + performed a DDL and replaced the view with a base table. + Don't try to open the table inside a prepared statement, + invalidate it instead. + + Note, the assert below is known to fail inside stored + procedures (Bug#27011). + */ + DBUG_ASSERT(thd->m_metadata_observer); + check_and_update_table_version(thd, table_list, share); + /* Always an error. */ + DBUG_ASSERT(thd->is_error()); + goto err; + } if (table_list->i_s_requested_object & OPEN_VIEW_ONLY) goto err; @@ -4375,8 +4463,18 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags) */ if (tables->schema_table) { - if (!mysql_schema_table(thd, thd->lex, tables)) + /* + If this information_schema table is merged into a mergeable + view, ignore it for now -- it will be filled when its respective + TABLE_LIST is processed. This code works only during re-execution. + */ + if (tables->view) + goto process_view_routines; + if (!mysql_schema_table(thd, thd->lex, tables) && + !check_and_update_table_version(thd, tables, tables->table->s)) + { continue; + } DBUG_RETURN(-1); } (*counter)++; @@ -4524,6 +4622,12 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags) } tables->table->grant= tables->grant; + if (check_and_update_table_version(thd, tables, tables->table->s)) + { + result= -1; + goto err; + } + /* Attach MERGE children if not locked already. */ DBUG_PRINT("tcache", ("is parent: %d is child: %d", test(tables->table->child_l), @@ -4582,7 +4686,11 @@ process_view_routines: error happens on a MERGE child, clear the parents TABLE reference. */ if (tables->parent_l) + { + if (tables->parent_l->next_global == tables->parent_l->table->child_l) + tables->parent_l->next_global= *tables->parent_l->table->child_last_l; tables->parent_l->table= NULL; + } tables->table= NULL; } DBUG_PRINT("tcache", ("returning: %d", result)); diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 7c57e3fc40e..6a4ef0781a2 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -359,6 +359,10 @@ char *thd_security_context(THD *thd, char *buffer, unsigned int length, return thd->strmake(str.ptr(), str.length()); } +Metadata_version_observer::~Metadata_version_observer() +{ +} + /** Clear this diagnostics area. @@ -2767,7 +2771,8 @@ void THD::restore_backup_open_tables_state(Open_tables_state *backup) DBUG_ASSERT(open_tables == 0 && temporary_tables == 0 && handler_tables == 0 && derived_tables == 0 && lock == 0 && locked_tables == 0 && - prelocked_mode == NON_PRELOCKED); + prelocked_mode == NON_PRELOCKED && + m_metadata_observer == NULL); set_open_tables_state(backup); DBUG_VOID_RETURN; } @@ -2871,6 +2876,7 @@ void THD::reset_sub_statement_state(Sub_statement_state *backup, first_successful_insert_id_in_prev_stmt; backup->first_successful_insert_id_in_cur_stmt= first_successful_insert_id_in_cur_stmt; + backup->m_metadata_observer= m_metadata_observer; if ((!lex->requires_prelocking() || is_update_query(lex->sql_command)) && !current_stmt_binlog_row_based) @@ -2890,6 +2896,7 @@ void THD::reset_sub_statement_state(Sub_statement_state *backup, cuted_fields= 0; transaction.savepoints= 0; first_successful_insert_id_in_cur_stmt= 0; + m_metadata_observer= 0; } @@ -2938,6 +2945,7 @@ void THD::restore_sub_statement_state(Sub_statement_state *backup) */ examined_row_count+= backup->examined_row_count; cuted_fields+= backup->cuted_fields; + m_metadata_observer= backup->m_metadata_observer; } diff --git a/sql/sql_class.h b/sql/sql_class.h index 2bfcc43a454..837b74b3b60 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -23,6 +23,53 @@ #include "log.h" #include "rpl_tblmap.h" +/** + An abstract interface that can be used to take an action when + the locking module notices that a table version has changed + since the last execution. "Table" here may refer to any kind of + table -- a base table, a temporary table, a view or an + information schema table. + + When we open and lock tables for execution of a prepared + statement, we must verify that they did not change + since statement prepare. If some table did change, the statement + parse tree *may* be no longer valid, e.g. in case it contains + optimizations that depend on table metadata. + + This class provides an abstract interface (a method) that is + invoked when such a situation takes place. + The implementation of the interface in most cases simply + reports an error, but the exact details depend on the nature of + the SQL statement. + + At most 1 instance of this class is active at a time, in which + case THD::m_metadata_observer is not NULL. + + @sa check_and_update_metadata_version() for details of the + version tracking algorithm + + @sa Execute_observer for details of how we detect that + a metadata change is fatal and a re-prepare is necessary + + @sa Open_tables_state::m_metadata_observer for the life cycle + of metadata observers. +*/ + +class Metadata_version_observer +{ +protected: + virtual ~Metadata_version_observer(); +public: + /** + Check if a change of metadata is OK. In future + the signature of this method may be extended to accept the old + and the new versions, but since currently the check is very + simple, we only need the THD to report an error. + */ + virtual bool check_metadata_change(THD *thd)= 0; +}; + + class Relay_log_info; class Query_log_event; @@ -406,6 +453,7 @@ typedef struct system_status_var ulong filesort_scan_count; /* Prepared statements and binary protocol */ ulong com_stmt_prepare; + ulong com_stmt_reprepare; ulong com_stmt_execute; ulong com_stmt_send_long_data; ulong com_stmt_fetch; @@ -436,7 +484,7 @@ void free_tmp_table(THD *thd, TABLE *entry); /* The following macro is to make init of Query_arena simpler */ #ifndef DBUG_OFF -#define INIT_ARENA_DBUG_INFO is_backup_arena= 0 +#define INIT_ARENA_DBUG_INFO is_backup_arena= 0; is_reprepared= FALSE; #else #define INIT_ARENA_DBUG_INFO #endif @@ -452,6 +500,7 @@ public: MEM_ROOT *mem_root; // Pointer to current memroot #ifndef DBUG_OFF bool is_backup_arena; /* True if this arena is used for backup. */ + bool is_reprepared; #endif /* The states relfects three diffrent life cycles for three @@ -788,6 +837,20 @@ enum prelocked_mode_type {NON_PRELOCKED= 0, PRELOCKED= 1, class Open_tables_state { public: + /** + As part of class THD, this member is set during execution + of a prepared statement. When it is set, it is used + by the locking subsystem to report a change in table metadata. + + When Open_tables_state part of THD is reset to open + a system or INFORMATION_SCHEMA table, the member is cleared + to avoid spurious ER_NEED_REPREPARE errors -- system and + INFORMATION_SCHEMA tables are not subject to metadata version + tracking. + @sa check_and_update_metadata_version() + */ + Metadata_version_observer *m_metadata_observer; + /** List of regular tables in use by this thread. Contains temporary and base tables that were opened with @see open_tables(). @@ -891,6 +954,7 @@ public: extra_lock= lock= locked_tables= 0; prelocked_mode= NON_PRELOCKED; state_flags= 0U; + m_metadata_observer= NULL; } }; @@ -919,6 +983,25 @@ public: bool enable_slow_log; bool last_insert_id_used; SAVEPOINT *savepoints; + /** + When inside a substatement (a stored function or trigger + statement), clear the metadata observer in THD, if any. + Remember the value of the observer here, to be able + to restore it when leaving the substatement. + + We reset the observer to suppress errors when a substatement + uses temporary tables. If a temporary table does not exist + at start of the main statement, it's not prelocked + and thus is not validated with other prelocked tables. + + Later on, when the temporary table is opened, metadata + versions mismatch, expectedly. + + The proper solution for the problem is to re-validate tables + of substatements (Bug#12257, Bug#27011, Bug#32868, Bug#33000), + but it's not implemented yet. + */ + Metadata_version_observer *m_metadata_observer; }; @@ -2777,6 +2860,7 @@ public: #define CF_STATUS_COMMAND 4 #define CF_SHOW_TABLE_COMMAND 8 #define CF_WRITE_LOGS_COMMAND 16 +#define CF_REEXECUTION_FRAGILE 32 /* Functions in sql_class.cc */ diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index c28fea57c64..faf48b38fd0 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -218,14 +218,23 @@ void init_update_queries(void) sql_command_flags[SQLCOM_ALTER_EVENT]= CF_CHANGES_DATA; sql_command_flags[SQLCOM_DROP_EVENT]= CF_CHANGES_DATA; - sql_command_flags[SQLCOM_UPDATE]= CF_CHANGES_DATA | CF_HAS_ROW_COUNT; - sql_command_flags[SQLCOM_UPDATE_MULTI]= CF_CHANGES_DATA | CF_HAS_ROW_COUNT; - sql_command_flags[SQLCOM_INSERT]= CF_CHANGES_DATA | CF_HAS_ROW_COUNT; - sql_command_flags[SQLCOM_INSERT_SELECT]= CF_CHANGES_DATA | CF_HAS_ROW_COUNT; - sql_command_flags[SQLCOM_DELETE]= CF_CHANGES_DATA | CF_HAS_ROW_COUNT; - sql_command_flags[SQLCOM_DELETE_MULTI]= CF_CHANGES_DATA | CF_HAS_ROW_COUNT; - sql_command_flags[SQLCOM_REPLACE]= CF_CHANGES_DATA | CF_HAS_ROW_COUNT; - sql_command_flags[SQLCOM_REPLACE_SELECT]= CF_CHANGES_DATA | CF_HAS_ROW_COUNT; + sql_command_flags[SQLCOM_UPDATE]= CF_CHANGES_DATA | CF_HAS_ROW_COUNT | + CF_REEXECUTION_FRAGILE; + sql_command_flags[SQLCOM_UPDATE_MULTI]= CF_CHANGES_DATA | CF_HAS_ROW_COUNT | + CF_REEXECUTION_FRAGILE; + sql_command_flags[SQLCOM_INSERT]= CF_CHANGES_DATA | CF_HAS_ROW_COUNT | + CF_REEXECUTION_FRAGILE; + sql_command_flags[SQLCOM_INSERT_SELECT]= CF_CHANGES_DATA | CF_HAS_ROW_COUNT | + CF_REEXECUTION_FRAGILE; + sql_command_flags[SQLCOM_DELETE]= CF_CHANGES_DATA | CF_HAS_ROW_COUNT | + CF_REEXECUTION_FRAGILE; + sql_command_flags[SQLCOM_DELETE_MULTI]= CF_CHANGES_DATA | CF_HAS_ROW_COUNT | + CF_REEXECUTION_FRAGILE; + sql_command_flags[SQLCOM_REPLACE]= CF_CHANGES_DATA | CF_HAS_ROW_COUNT | + CF_REEXECUTION_FRAGILE; + sql_command_flags[SQLCOM_REPLACE_SELECT]= CF_CHANGES_DATA | CF_HAS_ROW_COUNT | + CF_REEXECUTION_FRAGILE; + sql_command_flags[SQLCOM_SELECT]= CF_REEXECUTION_FRAGILE; sql_command_flags[SQLCOM_SHOW_STATUS_PROC]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_STATUS]= CF_STATUS_COMMAND; diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index c221e29e1d0..7550226adff 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -116,6 +116,39 @@ public: #endif }; +/** + If a metadata changed, report a respective error to trigger + re-prepare of a prepared statement. +*/ + +class Execute_observer: public Metadata_version_observer +{ +public: + virtual bool check_metadata_change(THD *thd); + /** Set to TRUE if metadata of some used table has changed since prepare */ + bool m_invalidated; +}; + +/** + Push an error to the error stack and return TRUE for now. + In future we must take special care of statements like CREATE + TABLE ... SELECT. Should we re-prepare such statements every + time? +*/ + +bool +Execute_observer::check_metadata_change(THD *thd) +{ + bool save_thd_no_warnings_for_error= thd->no_warnings_for_error; + DBUG_ENTER("Execute_observer::notify_about_metadata_change"); + + thd->no_warnings_for_error= TRUE; + my_error(ER_NEED_REPREPARE, MYF(0)); + thd->no_warnings_for_error= save_thd_no_warnings_for_error; + m_invalidated= TRUE; + DBUG_RETURN(TRUE); +} + /****************************************************************************/ /** @@ -156,20 +189,27 @@ public: bool set_name(LEX_STRING *name); inline void close_cursor() { delete cursor; cursor= 0; } inline bool is_in_use() { return flags & (uint) IS_IN_USE; } + inline bool is_protocol_text() const { return protocol == &thd->protocol_text; } bool prepare(const char *packet, uint packet_length); - bool execute(String *expanded_query, bool open_cursor); + bool execute_loop(String *expanded_query, + bool open_cursor, + uchar *packet_arg, uchar *packet_end_arg); /* Destroy this statement */ void deallocate(); private: - /** - Store the parsed tree of a prepared statement here. - */ - LEX main_lex; /** The memory root to allocate parsed tree elements (instances of Item, SELECT_LEX and other classes). */ MEM_ROOT main_mem_root; +private: + bool set_db(const char *db, uint db_length); + bool set_parameters(String *expanded_query, + uchar *packet, uchar *packet_end); + bool execute(String *expanded_query, bool open_cursor); + bool reprepare(); + bool validate_metadata(Prepared_statement *copy); + void swap_prepared_statement(Prepared_statement *copy); }; @@ -941,6 +981,55 @@ static bool emb_insert_params_with_log(Prepared_statement *stmt, #endif /*!EMBEDDED_LIBRARY*/ +/** + Setup data conversion routines using an array of parameter + markers from the original prepared statement. + Move the parameter data of the original prepared + statement to the new one. + + Used only when we re-prepare a prepared statement. + There are two reasons for this function to exist: + + 1) In the binary client/server protocol, parameter metadata + is sent only at first execute. Consequently, if we need to + reprepare a prepared statement at a subsequent execution, + we may not have metadata information in the packet. + In that case we use the parameter array of the original + prepared statement to setup parameter types of the new + prepared statement. + + 2) In the binary client/server protocol, we may supply + long data in pieces. When the last piece is supplied, + we assemble the pieces and convert them from client + character set to the connection character set. After + that the parameter value is only available inside + the parameter, the original pieces are lost, and thus + we can only assign the corresponding parameter of the + reprepared statement from the original value. + + @param[out] param_array_dst parameter markers of the new statement + @param[in] param_array_src parameter markers of the original + statement + @param[in] param_count total number of parameters. Is the + same in src and dst arrays, since + the statement query is the same + + @return this function never fails +*/ + +static void +swap_parameter_array(Item_param **param_array_dst, + Item_param **param_array_src, + uint param_count) +{ + Item_param **dst= param_array_dst; + Item_param **src= param_array_src; + Item_param **end= param_array_dst + param_count; + + for (; dst < end; ++src, ++dst) + (*dst)->set_param_type_and_swap_value(*src); +} + /** Assign prepared statement parameters from user variables. @@ -1264,7 +1353,7 @@ error: */ static int mysql_test_select(Prepared_statement *stmt, - TABLE_LIST *tables, bool text_protocol) + TABLE_LIST *tables) { THD *thd= stmt->thd; LEX *lex= stmt->lex; @@ -1300,7 +1389,7 @@ static int mysql_test_select(Prepared_statement *stmt, */ if (unit->prepare(thd, 0, 0)) goto error; - if (!lex->describe && !text_protocol) + if (!lex->describe && !stmt->is_protocol_text()) { /* Make copy of item list, as change_columns may change it */ List fields(lex->select_lex.item_list); @@ -1708,8 +1797,7 @@ static bool mysql_test_insert_select(Prepared_statement *stmt, TRUE error, error message is set in THD (but not sent) */ -static bool check_prepared_statement(Prepared_statement *stmt, - bool text_protocol) +static bool check_prepared_statement(Prepared_statement *stmt) { THD *thd= stmt->thd; LEX *lex= stmt->lex; @@ -1752,7 +1840,7 @@ static bool check_prepared_statement(Prepared_statement *stmt, break; case SQLCOM_SELECT: - res= mysql_test_select(stmt, tables, text_protocol); + res= mysql_test_select(stmt, tables); if (res == 2) { /* Statement and field info has already been sent */ @@ -1867,8 +1955,8 @@ static bool check_prepared_statement(Prepared_statement *stmt, break; } if (res == 0) - DBUG_RETURN(text_protocol? FALSE : (send_prep_stmt(stmt, 0) || - thd->protocol->flush())); + DBUG_RETURN(stmt->is_protocol_text() ? + FALSE : (send_prep_stmt(stmt, 0) || thd->protocol->flush())); error: DBUG_RETURN(TRUE); } @@ -2309,11 +2397,10 @@ void mysql_stmt_execute(THD *thd, char *packet_arg, uint packet_length) ulong flags= (ulong) packet[4]; /* Query text for binary, general or slow log, if any of them is open */ String expanded_query; -#ifndef EMBEDDED_LIBRARY uchar *packet_end= packet + packet_length; -#endif Prepared_statement *stmt; bool error; + bool open_cursor; DBUG_ENTER("mysql_stmt_execute"); packet+= 9; /* stmt_id + 5 bytes of flags */ @@ -2338,44 +2425,12 @@ void mysql_stmt_execute(THD *thd, char *packet_arg, uint packet_length) sp_cache_flush_obsolete(&thd->sp_proc_cache); sp_cache_flush_obsolete(&thd->sp_func_cache); -#ifndef EMBEDDED_LIBRARY - if (stmt->param_count) - { - uchar *null_array= packet; - if (setup_conversion_functions(stmt, &packet, packet_end) || - stmt->set_params(stmt, null_array, packet, packet_end, - &expanded_query)) - goto set_params_data_err; - } -#else - /* - In embedded library we re-install conversion routines each time - we set params, and also we don't need to parse packet. - So we do it in one function. - */ - if (stmt->param_count && stmt->set_params_data(stmt, &expanded_query)) - goto set_params_data_err; -#endif - if (!(specialflag & SPECIAL_NO_PRIOR)) - my_pthread_setprio(pthread_self(),QUERY_PRIOR); + open_cursor= test(flags & (ulong) CURSOR_TYPE_READ_ONLY); - /* - If the free_list is not empty, we'll wrongly free some externally - allocated items when cleaning up after validation of the prepared - statement. - */ - DBUG_ASSERT(thd->free_list == NULL); + stmt->execute_loop(&expanded_query, open_cursor, packet, packet_end); - error= stmt->execute(&expanded_query, - test(flags & (ulong) CURSOR_TYPE_READ_ONLY)); - if (!(specialflag & SPECIAL_NO_PRIOR)) - my_pthread_setprio(pthread_self(), WAIT_PRIOR); DBUG_VOID_RETURN; -set_params_data_err: - my_error(ER_WRONG_ARGUMENTS, MYF(0), "mysql_stmt_execute"); - reset_stmt_params(stmt); - DBUG_VOID_RETURN; } @@ -2421,24 +2476,8 @@ void mysql_sql_stmt_execute(THD *thd) DBUG_PRINT("info",("stmt: 0x%lx", (long) stmt)); - /* - If the free_list is not empty, we'll wrongly free some externally - allocated items when cleaning up after validation of the prepared - statement. - */ - DBUG_ASSERT(thd->free_list == NULL); + (void) stmt->execute_loop(&expanded_query, FALSE, NULL, NULL); - if (stmt->set_params_from_vars(stmt, lex->prepared_stmt_params, - &expanded_query)) - goto set_params_data_err; - - (void) stmt->execute(&expanded_query, FALSE); - - DBUG_VOID_RETURN; - -set_params_data_err: - my_error(ER_WRONG_ARGUMENTS, MYF(0), "EXECUTE"); - reset_stmt_params(stmt); DBUG_VOID_RETURN; } @@ -2742,7 +2781,7 @@ Select_fetch_protocol_binary::send_data(List &fields) ****************************************************************************/ Prepared_statement::Prepared_statement(THD *thd_arg, Protocol *protocol_arg) - :Statement(&main_lex, &main_mem_root, + :Statement(0, &main_mem_root, INITIALIZED, ++thd_arg->statement_id_counter), thd(thd_arg), result(thd_arg), @@ -2813,7 +2852,11 @@ Prepared_statement::~Prepared_statement() like Item_param, don't free everything until free_items() */ free_items(); - delete lex->result; + if (lex) + { + delete lex->result; + delete (st_lex_local *) lex; + } free_root(&main_mem_root, MYF(0)); DBUG_VOID_RETURN; } @@ -2849,6 +2892,34 @@ bool Prepared_statement::set_name(LEX_STRING *name_arg) return name.str == 0; } + +/** + Remember the current database. + + We must reset/restore the current database during execution of + a prepared statement since it affects execution environment: + privileges, @@character_set_database, and other. + + @return Returns an error if out of memory. +*/ + +bool +Prepared_statement::set_db(const char *db_arg, uint db_length_arg) +{ + /* Remember the current database. */ + if (db_arg && db_length_arg) + { + db= this->strmake(db_arg, db_length_arg); + db_length= db_length_arg; + } + else + { + db= NULL; + db_length= 0; + } + return db_arg != NULL && db == NULL; +} + /************************************************************************** Common parts of mysql_[sql]_stmt_prepare, mysql_[sql]_stmt_execute. Essentially, these functions do all the magic of preparing/executing @@ -2890,6 +2961,12 @@ bool Prepared_statement::prepare(const char *packet, uint packet_len) */ status_var_increment(thd->status_var.com_stmt_prepare); + if (! (lex= new (mem_root) st_lex_local)) + DBUG_RETURN(TRUE); + + if (set_db(thd->db, thd->db_length)) + DBUG_RETURN(TRUE); + /* alloc_query() uses thd->memroot && thd->query, so we should call both of backup_statement() and backup_query_arena() here. @@ -2917,19 +2994,6 @@ bool Prepared_statement::prepare(const char *packet, uint packet_len) lex->set_trg_event_type_for_tables(); - /* Remember the current database. */ - - if (thd->db && thd->db_length) - { - db= this->strmake(thd->db, thd->db_length); - db_length= thd->db_length; - } - else - { - db= NULL; - db_length= 0; - } - /* While doing context analysis of the query (in check_prepared_statement) we allocate a lot of additional memory: for open tables, JOINs, derived @@ -2953,7 +3017,7 @@ bool Prepared_statement::prepare(const char *packet, uint packet_len) */ if (error == 0) - error= check_prepared_statement(this, name.str != 0); + error= check_prepared_statement(this); /* Currently CREATE PROCEDURE/TRIGGER/EVENT are prohibited in prepared @@ -3000,6 +3064,293 @@ bool Prepared_statement::prepare(const char *packet, uint packet_len) DBUG_RETURN(error); } + +bool +Prepared_statement::set_parameters(String *expanded_query, + uchar *packet, uchar *packet_end) +{ + bool is_sql_ps= packet == NULL; + + if (is_sql_ps) + { + /* SQL prepared statement */ + if (set_params_from_vars(this, thd->lex->prepared_stmt_params, + expanded_query)) + goto set_params_data_err; + } + else if (param_count) + { +#ifndef EMBEDDED_LIBRARY + uchar *null_array= packet; + if (setup_conversion_functions(this, &packet, packet_end) || + set_params(this, null_array, packet, packet_end, + expanded_query)) + goto set_params_data_err; +#else + /* + In embedded library we re-install conversion routines each time + we set params, and also we don't need to parse packet. + So we do it in one function. + */ + if (set_params_data(this, expanded_query)) + goto set_params_data_err; +#endif + } + return FALSE; +set_params_data_err: + my_error(ER_WRONG_ARGUMENTS, MYF(0), + is_sql_ps ? "EXECUTE" : "mysql_stmt_execute"); + reset_stmt_params(this); + return TRUE; +} + + +/** + Execute a prepared statement. Re-prepare it a limited number + of times if necessary. + + Try to execute a prepared statement. If there is a metadata + validation error, prepare a new copy of the prepared statement, + swap the old and the new statements, and try again. + If there is a validation error again, repeat the above, but + perform no more than MAX_REPREPARE_ATTEMPTS. + + @note We have to try several times in a loop since we + release metadata locks on tables after prepared statement + prepare. Therefore, a DDL statement may sneak in between prepare + and execute of a new statement. If this happens repeatedly + more than MAX_REPREPARE_ATTEMPTS times, we give up. + + In future we need to be able to keep the metadata locks between + prepare and execute, but right now open_and_lock_tables(), as + well as close_thread_tables() are buried deep inside + execution code (mysql_execute_command()). + + @return TRUE if an error, FALSE if success + @retval TRUE either MAX_REPREPARE_ATTEMPTS has been reached, + or some general error + @retval FALSE successfully executed the statement, perhaps + after having reprepared it a few times. +*/ + +bool +Prepared_statement::execute_loop(String *expanded_query, + bool open_cursor, + uchar *packet, + uchar *packet_end) +{ + const int MAX_REPREPARE_ATTEMPTS= 3; + Execute_observer execute_observer; + bool error; + int reprepare_attempt= 0; + + if (set_parameters(expanded_query, packet, packet_end)) + return TRUE; + +reexecute: + execute_observer.m_invalidated= FALSE; + + /* + If the free_list is not empty, we'll wrongly free some externally + allocated items when cleaning up after validation of the prepared + statement. + */ + DBUG_ASSERT(thd->free_list == NULL); + + /* + Install the metadata observer. If some metadata version is + different from prepare time and an observer is installed, + the observer method will be invoked to push an error into + the error stack. + */ + if (sql_command_flags[lex->sql_command] & + CF_REEXECUTION_FRAGILE) + { + thd->m_metadata_observer= &execute_observer; + } + + if (!(specialflag & SPECIAL_NO_PRIOR)) + my_pthread_setprio(pthread_self(),QUERY_PRIOR); + + error= execute(expanded_query, open_cursor) || thd->is_error(); + + if (!(specialflag & SPECIAL_NO_PRIOR)) + my_pthread_setprio(pthread_self(), WAIT_PRIOR); + + thd->m_metadata_observer= 0; + + if (error && !thd->is_fatal_error && !thd->killed && + execute_observer.m_invalidated && + reprepare_attempt++ < MAX_REPREPARE_ATTEMPTS) + { + thd->clear_error(); + + error= reprepare(); + + if (! error) /* Success */ + goto reexecute; + } + reset_stmt_params(this); + + return error; +} + + +/** + Reprepare this prepared statement. + + Currently this is implemented by creating a new prepared + statement, preparing it with the original query and then + swapping the new statement and the original one. + + @retval TRUE an error occurred. Possible errors include + incompatibility of new and old result set + metadata + @retval FALSE success, the statement has been reprepared +*/ + +bool +Prepared_statement::reprepare() +{ + char saved_cur_db_name_buf[NAME_LEN+1]; + LEX_STRING saved_cur_db_name= + { saved_cur_db_name_buf, sizeof(saved_cur_db_name_buf) }; + LEX_STRING stmt_db_name= { db, db_length }; + Prepared_statement *copy; + bool cur_db_changed; + bool error= TRUE; + + status_var_increment(thd->status_var.com_stmt_reprepare); + + copy= new Prepared_statement(thd, &thd->protocol_text); + + if (! copy) + return TRUE; + + if (mysql_opt_change_db(thd, &stmt_db_name, &saved_cur_db_name, TRUE, + &cur_db_changed)) + goto end; + + error= (name.str && copy->set_name(&name) || + copy->prepare(query, query_length) || + validate_metadata(copy)); + + if (cur_db_changed) + mysql_change_db(thd, &saved_cur_db_name, TRUE); + + if (! error) + { + swap_prepared_statement(copy); + swap_parameter_array(param_array, copy->param_array, param_count); + is_reprepared= TRUE; + /* + Clear possible warnigns during re-prepare, it has to be completely + transparent to the user. We use mysql_reset_errors() since + there were no separate query id issued for re-prepare. + */ + mysql_reset_errors(thd, TRUE); + } +end: + delete copy; + return error; +} + + +/** + Validate statement result set metadata (if the statement returns + a result set). + + Currently we only check that the number of columns of the result + set did not change. + This is a helper method used during re-prepare. + + @param[in] copy the re-prepared prepared statement to verify + the metadata of + + @retval TRUE error, ER_PS_NEED_REBIND is reported + @retval FALSE statement return no or compatible metadata +*/ + + +bool Prepared_statement::validate_metadata(Prepared_statement *copy) +{ + List_iterator_fast it_org(lex->select_lex.item_list); + List_iterator_fast it_new(copy->lex->select_lex.item_list); + Item *item_org; + Item *item_new; + + /** + If this is an SQL prepared statement or EXPLAIN, + return FALSE -- the metadata of the original SELECT, + if any, has not been sent to the client. + */ + if (is_protocol_text() || lex->describe) + return FALSE; + + if (lex->select_lex.item_list.elements != + copy->lex->select_lex.item_list.elements) + { + /** Column counts mismatch. */ + my_error(ER_PS_REBIND, MYF(0)); + return TRUE; + } + + return FALSE; +} + + +/** + Replace the original prepared statement with a prepared copy. + + This is a private helper that is used as part of statement + reprepare + + @return This function does not return any errors. +*/ + +void +Prepared_statement::swap_prepared_statement(Prepared_statement *copy) +{ + Statement tmp_stmt; + + /* Swap memory roots. */ + swap_variables(MEM_ROOT, main_mem_root, copy->main_mem_root); + + /* Swap the arenas */ + tmp_stmt.set_query_arena(this); + set_query_arena(copy); + copy->set_query_arena(&tmp_stmt); + + /* Swap the statement parent classes */ + tmp_stmt.set_statement(this); + set_statement(copy); + copy->set_statement(&tmp_stmt); + + /* Swap ids back, we need the original id */ + swap_variables(ulong, id, copy->id); + /* Swap mem_roots back, they must continue pointing at the main_mem_roots */ + swap_variables(MEM_ROOT *, mem_root, copy->mem_root); + /* + Swap the old and the new parameters array. The old array + is allocated in the old arena. + */ + swap_variables(Item_param **, param_array, copy->param_array); + /* Swap flags: this is perhaps unnecessary */ + swap_variables(uint, flags, copy->flags); + /* Swap names, the old name is allocated in the wrong memory root */ + swap_variables(LEX_STRING, name, copy->name); + /* Ditto */ + swap_variables(char *, db, copy->db); + + DBUG_ASSERT(db_length == copy->db_length); + DBUG_ASSERT(param_count == copy->param_count); + DBUG_ASSERT(thd == copy->thd); + last_error[0]= '\0'; + last_errno= 0; + /* Do not swap protocols, the copy always has protocol_text */ +} + + /** Execute a prepared statement. @@ -3162,10 +3513,7 @@ bool Prepared_statement::execute(String *expanded_query, bool open_cursor) DBUG_ASSERT(! (error && cursor)); if (! cursor) - { cleanup_stmt(); - reset_stmt_params(this); - } thd->set_statement(&stmt_backup); thd->stmt_arena= old_stmt_arena; diff --git a/sql/table.h b/sql/table.h index d448485a117..2b3c9b8d5b1 100644 --- a/sql/table.h +++ b/sql/table.h @@ -437,6 +437,105 @@ typedef struct st_table_share return table_map_id; } + /** + Convert unrelated members of TABLE_SHARE to one enum + representing its metadata type. + + @todo perhaps we need to have a member instead of a function. + */ + enum enum_metadata_type get_metadata_type() const + { + if (is_view) + return METADATA_VIEW; + switch (tmp_table) { + case NO_TMP_TABLE: + return METADATA_BASE_TABLE; + case SYSTEM_TMP_TABLE: + return METADATA_I_S_TABLE; + default: + return METADATA_TMP_TABLE; + } + } + /** + Return a table metadata version. + * for base tables, we return table_map_id. + It is assigned from a global counter incremented for each + new table loaded into the table definition cache (TDC). + * for temporary tables it's table_map_id again. But for + temporary tables table_map_id is assigned from + thd->query_id. The latter is assigned from a thread local + counter incremented for every new SQL statement. Since + temporary tables are thread-local, each temporary table + gets a unique id. + * for everything else (views, information schema tables), + the version id is zero. + + This choice of version id is a large compromise + to have a working prepared statement validation in 5.1. In + future version ids will be persistent, as described in WL#4180. + + Let's try to explain why and how this limited solution allows + to validate prepared statements. + + Firstly, spaces (in mathematical sense) of version numbers + never intersect for different metadata types. Therefore, + version id of a temporary table is never compared with + a version id of a view or a temporary table, and vice versa. + + Secondly, for base tables, we know that each DDL flushes the + respective share from the TDC. This ensures that whenever + a table is altered or dropped and recreated, it gets a new + version id. + Unfortunately, since elements of the TDC are also flushed on + LRU basis, this choice of version ids leads to false positives. + E.g. when the TDC size is too small, we may have a SELECT + * FROM INFORMATION_SCHEMA.TABLES flush all its elements, which + in turn will lead to a validation error and a subsequent + reprepare of all prepared statements. This is + considered acceptable, since as long as prepared statements are + automatically reprepared, spurious invalidation is only + a performance hit. Besides, no better simple solution exists. + + For temporary tables, using thd->query_id ensures that if + a temporary table was altered or recreated, a new version id is + assigned. This suits validation needs very well and will perhaps + never change. + + Metadata of information schema tables never changes. + Thus we can safely assume 0 for a good enough version id. + + Views are a special and tricky case. A view is always inlined + into the parse tree of a prepared statement at prepare. + Thus, when we execute a prepared statement, the parse tree + will not get modified even if the view is replaced with another + view. Therefore, we can safely choose 0 for version id of + views and effectively never invalidate a prepared statement + when a view definition is altered. Note, that this leads to + wrong binary log in statement-based replication, since we log + prepared statement execution in form Query_log_events + containing conventional statements. But since there is no + metadata locking for views, the very same problem exists for + conventional statements alone, as reported in Bug#25144. The only + difference between prepared and conventional execution is, + effectively, that for prepared statements the race condition + window is much wider. + In 6.0 we plan to support view metadata locking (WL#3726) and + extend table definition cache to cache views (WL#4298). + When this is done, views will be handled in the same fashion + as the base tables. + + Finally, by taking into account metadata type, we always + track that a change has taken place when a view is replaced + with a base table, a base table is replaced with a temporary + table and so on. + + @sa TABLE_LIST::is_metadata_version_equal() + */ + ulong get_metadata_version() const + { + return tmp_table == SYSTEM_TMP_TABLE || is_view ? 0 : table_map_id; + } + } TABLE_SHARE; @@ -1232,6 +1331,33 @@ struct TABLE_LIST child_def_version= ~0UL; } + /** + Compare the version of metadata from the previous execution + (if any) with values obtained from the current table + definition cache element. + + @sa check_and_update_metadata_version() + */ + inline + bool is_metadata_version_equal(TABLE_SHARE *s) const + { + return (m_metadata_type == s->get_metadata_type() && + m_metadata_version == s->get_metadata_version()); + } + + /** + Record the value of metadata version of the corresponding + table definition cache element in this parse tree node. + + @sa check_and_update_metadata_version() + */ + inline + void set_metadata_version(TABLE_SHARE *s) + { + m_metadata_type= s->get_metadata_type(); + m_metadata_version= s->get_metadata_version(); + } + private: bool prep_check_option(THD *thd, uint8 check_opt_type); bool prep_where(THD *thd, Item **conds, bool no_where_clause); @@ -1242,6 +1368,10 @@ private: /* Remembered MERGE child def version. See top comment in ha_myisammrg.cc */ ulong child_def_version; + /** See comments for set_metadata_version() */ + enum enum_metadata_type m_metadata_type; + /** See comments for set_metadata_version() */ + ulong m_metadata_version; }; class Item; diff --git a/tests/mysql_client_test.c b/tests/mysql_client_test.c index 0df2d4ed4a5..748f04b11d3 100644 --- a/tests/mysql_client_test.c +++ b/tests/mysql_client_test.c @@ -11132,7 +11132,7 @@ static void test_bug4236() int rc; MYSQL_STMT backup; - myheader("test_bug4296"); + myheader("test_bug4236"); stmt= mysql_stmt_init(mysql); @@ -17383,6 +17383,123 @@ static void test_bug28386() DBUG_VOID_RETURN; } +static void test_wl4166() +{ + MYSQL_STMT *stmt; + int int_data; + char str_data[50]; + char tiny_data; + short small_data; + longlong big_data; + float real_data; + double double_data; + ulong length[7]; + my_bool is_null[7]; + MYSQL_BIND my_bind[7]; + int rc; + int i; + + myheader("test_wl4166"); + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS table_4166"); + myquery(rc); + + rc= mysql_query(mysql, "CREATE TABLE table_4166(col1 tinyint NOT NULL, " + "col2 varchar(15), col3 int, " + "col4 smallint, col5 bigint, " + "col6 float, col7 double, " + "colX varchar(10) default NULL)"); + myquery(rc); + + stmt= mysql_simple_prepare(mysql, + "INSERT INTO table_4166(col1, col2, col3, col4, col5, col6, col7) " + "VALUES(?, ?, ?, ?, ?, ?, ?)"); + check_stmt(stmt); + + verify_param_count(stmt, 7); + + /* tinyint */ + my_bind[0].buffer_type= MYSQL_TYPE_TINY; + my_bind[0].buffer= (void *)&tiny_data; + /* string */ + my_bind[1].buffer_type= MYSQL_TYPE_STRING; + my_bind[1].buffer= (void *)str_data; + my_bind[1].buffer_length= 1000; /* Max string length */ + /* integer */ + my_bind[2].buffer_type= MYSQL_TYPE_LONG; + my_bind[2].buffer= (void *)&int_data; + /* short */ + my_bind[3].buffer_type= MYSQL_TYPE_SHORT; + my_bind[3].buffer= (void *)&small_data; + /* bigint */ + my_bind[4].buffer_type= MYSQL_TYPE_LONGLONG; + my_bind[4].buffer= (void *)&big_data; + /* float */ + my_bind[5].buffer_type= MYSQL_TYPE_FLOAT; + my_bind[5].buffer= (void *)&real_data; + /* double */ + my_bind[6].buffer_type= MYSQL_TYPE_DOUBLE; + my_bind[6].buffer= (void *)&double_data; + + for (i= 0; i < (int) array_elements(my_bind); i++) + { + my_bind[i].length= &length[i]; + my_bind[i].is_null= &is_null[i]; + is_null[i]= 0; + } + + rc= mysql_stmt_bind_param(stmt, my_bind); + check_execute(stmt, rc); + + int_data= 320; + small_data= 1867; + big_data= 1000; + real_data= 2; + double_data= 6578.001; + + /* now, execute the prepared statement to insert 10 records.. */ + for (tiny_data= 0; tiny_data < 10; tiny_data++) + { + length[1]= my_sprintf(str_data, (str_data, "MySQL%d", int_data)); + rc= mysql_stmt_execute(stmt); + check_execute(stmt, rc); + int_data += 25; + small_data += 10; + big_data += 100; + real_data += 1; + double_data += 10.09; + } + + /* force a re-prepare with some DDL */ + + rc= mysql_query(mysql, + "ALTER TABLE table_4166 change colX colX varchar(20) default NULL"); + myquery(rc); + + /* + execute the prepared statement again, + without changing the types of parameters already bound. + */ + + for (tiny_data= 50; tiny_data < 60; tiny_data++) + { + length[1]= my_sprintf(str_data, (str_data, "MySQL%d", int_data)); + rc= mysql_stmt_execute(stmt); + check_execute(stmt, rc); + int_data += 25; + small_data += 10; + big_data += 100; + real_data += 1; + double_data += 10.09; + } + + mysql_stmt_close(stmt); + + rc= mysql_query(mysql, "DROP TABLE table_4166"); + myquery(rc); + +} + /* Read and parse arguments and MySQL options from my.cnf */ @@ -17689,6 +17806,7 @@ static struct my_tests_st my_tests[]= { { "test_bug31418", test_bug31418 }, { "test_bug31669", test_bug31669 }, { "test_bug28386", test_bug28386 }, + { "test_wl4166", test_wl4166 }, { 0, 0 } }; From 1ff9a2437a5a7ecd5d6b8bb409fa473613f14460 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 8 Apr 2008 21:49:31 +0400 Subject: [PATCH 2/8] Fix pushbuild errors and warnings. --- sql/sql_prepare.cc | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index 7550226adff..720deac116d 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -2399,7 +2399,6 @@ void mysql_stmt_execute(THD *thd, char *packet_arg, uint packet_length) String expanded_query; uchar *packet_end= packet + packet_length; Prepared_statement *stmt; - bool error; bool open_cursor; DBUG_ENTER("mysql_stmt_execute"); @@ -3242,7 +3241,9 @@ Prepared_statement::reprepare() { swap_prepared_statement(copy); swap_parameter_array(param_array, copy->param_array, param_count); +#ifndef DBUG_OFF is_reprepared= TRUE; +#endif /* Clear possible warnigns during re-prepare, it has to be completely transparent to the user. We use mysql_reset_errors() since @@ -3274,11 +3275,6 @@ end: bool Prepared_statement::validate_metadata(Prepared_statement *copy) { - List_iterator_fast it_org(lex->select_lex.item_list); - List_iterator_fast it_new(copy->lex->select_lex.item_list); - Item *item_org; - Item *item_new; - /** If this is an SQL prepared statement or EXPLAIN, return FALSE -- the metadata of the original SELECT, From bd2a73281243ad5c9f190e4fa17b752e41c11f3d Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 17 Apr 2008 01:04:49 +0400 Subject: [PATCH 3/8] WL#4165 "Prepared statements: validation". Add metadata validation to ~20 more SQL commands. Make sure that these commands actually work in ps-protocol, since until now they were enabled, but not carefully tested. Fixes the ml003 bug found by Matthias during internal testing of the patch. mysql-test/r/ps_ddl.result: Update test results (WL#4165) mysql-test/t/ps_ddl.test: Cover with tests metadata validation of 26 SQL statements. sql/mysql_priv.h: Fix the name in the comment. sql/sp_head.cc: Changed the way the observer is removed in case of stored procedures to support validation prepare stmt from "call p1()": whereas tables used in the expression must be validated, substatements of p1 must not. The previous scheme used to silence the observer only in stored functions and triggers. sql/sql_class.cc: Now the observer is silenced in sp_head::execute(). Remove it from Sub_statement_state. sql/sql_class.h: Now the observer is silenced in sp_head::execute(). Remove it from Sub_statement_state. sql/sql_parse.cc: Add CF_REEXECUTION_FRAGILE to 20 more SQLCOMs that need it. sql/sql_prepare.cc: Add metadata validation to ~20 new SQLCOMs that need it. Fix memory leaks with expressions used in SHOW DATABASES and CALL (and prepared statements). We need to fix all expressions at prepare, since if these expressions use subqueries, there are one-time transformations of the parse tree that must be done at prepare. List of fixed commands includes: SHOW TABLES, SHOW DATABASES, SHOW TRIGGERS, SHOW EVENTS, SHOW OPEN TABLES,SHOW KEYS, SHOW FIELDS, SHOW COLLATIONS, SHOW CHARSETS, SHOW VARIABLES, SHOW TATUS, SHOW TABLE STATUS, SHOW PROCEDURE STATUS, SHOW FUNCTION STATUS, CALL. Add comment to set_parameters(). sql/table.h: Update comments. --- mysql-test/r/ps_ddl.result | 537 ++++++++++++++++++++++++++++++++++ mysql-test/t/ps_ddl.test | 584 +++++++++++++++++++++++++++++++++++++ sql/mysql_priv.h | 2 +- sql/sp_head.cc | 21 ++ sql/sql_class.cc | 3 - sql/sql_class.h | 23 +- sql/sql_parse.cc | 40 +-- sql/sql_prepare.cc | 88 +++++- sql/table.h | 4 +- 9 files changed, 1255 insertions(+), 47 deletions(-) diff --git a/mysql-test/r/ps_ddl.result b/mysql-test/r/ps_ddl.result index fe7062716ea..3121650508d 100644 --- a/mysql-test/r/ps_ddl.result +++ b/mysql-test/r/ps_ddl.result @@ -663,6 +663,8 @@ a b c # Currently a different result from conventional statements. # A view is inlined once at prepare, later on view DDL # does not affect prepared statement and it is not re-prepared. +# This is reported in Bug#36002 Prepared statements: if a view +# used in a statement is replaced, bad data execute stmt; a b c 10 20 30 @@ -1497,6 +1499,541 @@ drop function f_12093; drop procedure p_12093; deallocate prepare stmt_sf; deallocate prepare stmt_sp; +===================================================================== +Ensure that metadata validation is performed for every type of +SQL statement where it is needed. +===================================================================== +drop table if exists t1; +create table t1 (a int); +# +# SQLCOM_SELECT +# +prepare stmt from "select 1 as res from dual where (1) in (select * from t1)"; +drop table t1; +create table t1 (x int); +execute stmt; +res +drop table t1; +deallocate prepare stmt; +call p_verify_reprepare_count(1); +SUCCESS + +# +# SQLCOM_CREATE_TABLE +# +drop table if exists t1; +drop table if exists t2; +create table t1 (a int); +prepare stmt from 'create table t2 as select * from t1'; +execute stmt; +drop table t2; +execute stmt; +drop table t2; +execute stmt; +call p_verify_reprepare_count(0); +SUCCESS + +execute stmt; +ERROR 42S01: Table 't2' already exists +call p_verify_reprepare_count(1); +SUCCESS + +execute stmt; +ERROR 42S01: Table 't2' already exists +call p_verify_reprepare_count(0); +SUCCESS + +drop table t2; +create temporary table t2 (a int); +execute stmt; +ERROR 42S01: Table 't2' already exists +call p_verify_reprepare_count(1); +SUCCESS + +execute stmt; +ERROR 42S01: Table 't2' already exists +call p_verify_reprepare_count(0); +SUCCESS + +drop temporary table t2; +execute stmt; +call p_verify_reprepare_count(1); +SUCCESS + +drop table t2; +execute stmt; +call p_verify_reprepare_count(0); +SUCCESS + +drop table t2; +drop table t1; +create table t1 (x varchar(20)); +execute stmt; +call p_verify_reprepare_count(1); +SUCCESS + +select * from t2; +x +drop table t2; +execute stmt; +call p_verify_reprepare_count(0); +SUCCESS + +drop table t2; +drop table t1; +deallocate prepare stmt; +# XXX: no validation of the first table in case of +# CREATE TEMPORARY TABLE. This is a shortcoming of the current code, +# but since validation is not strictly necessary, nothing is done +# about it. +# Will be fixed as part of work on Bug#21431 "Incomplete support of +# temporary tables" +create table t1 (a int); +insert into t1 (a) values (1); +prepare stmt from "create temporary table if not exists t2 as select * from t1"; +execute stmt; +drop table t2; +execute stmt; +execute stmt; +Warnings: +Note 1050 Table 't2' already exists +select * from t2; +a +1 +1 +execute stmt; +Warnings: +Note 1050 Table 't2' already exists +select * from t2; +a +1 +1 +1 +drop table t2; +create temporary table t2 (a varchar(10)); +execute stmt; +Warnings: +Note 1050 Table 't2' already exists +select * from t2; +a +1 +call p_verify_reprepare_count(0); +SUCCESS + +drop table t1; +create table t1 (x int); +execute stmt; +Warnings: +Note 1050 Table 't2' already exists +call p_verify_reprepare_count(1); +SUCCESS + +execute stmt; +Warnings: +Note 1050 Table 't2' already exists +call p_verify_reprepare_count(0); +SUCCESS + +drop table t1; +drop table t2; +deallocate prepare stmt; +# +# SQLCOM_UPDATE +# +drop table if exists t1, t2; +create table t1 (a int); +create table t2 (a int); +prepare stmt from "update t2 set a=a+1 where (1) in (select * from t1)"; +execute stmt; +drop table t1; +create table t1 (x int); +execute stmt; +drop table t1, t2; +deallocate prepare stmt; +# +# SQLCOM_INSERT +# +drop table if exists t1, t2; +create table t1 (a int); +create table t2 (a int); +prepare stmt from "insert into t2 set a=((1) in (select * from t1))"; +execute stmt; +drop table t1; +create table t1 (x int); +execute stmt; +drop table t1, t2; +deallocate prepare stmt; +# +# SQLCOM_INSERT_SELECT +# +drop table if exists t1, t2; +create table t1 (a int); +create table t2 (a int); +prepare stmt from "insert into t2 select * from t1"; +execute stmt; +drop table t1; +create table t1 (x int); +execute stmt; +drop table t1, t2; +deallocate prepare stmt; +# +# SQLCOM_REPLACE +# +drop table if exists t1, t2; +create table t1 (a int); +create table t2 (a int); +prepare stmt from "replace t2 set a=((1) in (select * from t1))"; +execute stmt; +drop table t1; +create table t1 (x int); +execute stmt; +drop table t1, t2; +deallocate prepare stmt; +# +# SQLCOM_REPLACE_SELECT +# +drop table if exists t1, t2; +create table t1 (a int); +create table t2 (a int); +prepare stmt from "replace t2 select * from t1"; +execute stmt; +drop table t1; +create table t1 (x int); +execute stmt; +drop table t1, t2; +deallocate prepare stmt; +# +# SQLCOM_DELETE +# +drop table if exists t1, t2; +create table t1 (a int); +create table t2 (a int); +prepare stmt from "delete from t2 where (1) in (select * from t1)"; +execute stmt; +drop table t1; +create table t1 (x int); +execute stmt; +drop table t1, t2; +deallocate prepare stmt; +# +# SQLCOM_DELETE_MULTI +# +drop table if exists t1, t2, t3; +create table t1 (a int); +create table t2 (a int); +create table t3 (a int); +prepare stmt from "delete t2, t3 from t2, t3 where (1) in (select * from t1)"; +execute stmt; +drop table t1; +create table t1 (x int); +execute stmt; +drop table t1, t2, t3; +deallocate prepare stmt; +# +# SQLCOM_UPDATE_MULTI +# +drop table if exists t1, t2, t3; +create table t1 (a int); +create table t2 (a int); +create table t3 (a int); +prepare stmt from "update t2, t3 set t3.a=t2.a, t2.a=null where (1) in (select * from t1)"; +drop table t1; +create table t1 (x int); +execute stmt; +drop table t1, t2, t3; +deallocate prepare stmt; +# Intermediate results: 8 SQLCOMs tested, 8 automatic reprepares +call p_verify_reprepare_count(8); +SUCCESS + +# +# SQLCOM_LOAD +# +drop table if exists t1; +create table t1 (a varchar(20)); +prepare stmt from "load data infile '../std_data_ln/words.dat' into table t1"; +ERROR HY000: This command is not supported in the prepared statement protocol yet +drop table t1; +# +# SQLCOM_SHOW_DATABASES +# +drop table if exists t1; +create table t1 (a int); +prepare stmt from "show databases where (1) in (select * from t1)"; +execute stmt; +Database +drop table t1; +create table t1 (x int); +execute stmt; +Database +drop table t1; +deallocate prepare stmt; +# +# SQLCOM_SHOW_TABLES +# +drop table if exists t1; +create table t1 (a int); +prepare stmt from "show tables where (1) in (select * from t1)"; +execute stmt; +Tables_in_test +drop table t1; +create table t1 (x int); +execute stmt; +Tables_in_test +drop table t1; +deallocate prepare stmt; +# +# SQLCOM_SHOW_FIELDS +# +drop table if exists t1; +create table t1 (a int); +prepare stmt from "show fields from t1 where (1) in (select * from t1)"; +execute stmt; +Field Type Null Key Default Extra +drop table t1; +create table t1 (x int); +execute stmt; +Field Type Null Key Default Extra +drop table t1; +deallocate prepare stmt; +# +# SQLCOM_SHOW_KEYS +# +drop table if exists t1; +create table t1 (a int); +prepare stmt from "show keys from t1 where (1) in (select * from t1)"; +execute stmt; +Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment +drop table t1; +create table t1 (x int); +execute stmt; +Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment +drop table t1; +deallocate prepare stmt; +# +# SQLCOM_SHOW_VARIABLES +# +drop table if exists t1; +create table t1 (a int); +prepare stmt from "show variables where (1) in (select * from t1)"; +execute stmt; +Variable_name Value +drop table t1; +create table t1 (x int); +execute stmt; +Variable_name Value +drop table t1; +deallocate prepare stmt; +# +# SQLCOM_SHOW_STATUS +# +drop table if exists t1; +create table t1 (a int); +prepare stmt from "show status where (1) in (select * from t1)"; +execute stmt; +Variable_name Value +drop table t1; +create table t1 (x int); +execute stmt; +Variable_name Value +drop table t1; +deallocate prepare stmt; +# +# SQLCOM_SHOW_ENGINE_STATUS, SQLCOM_SHOW_ENGINE_LOGS, +# SQLCOM_SHOW_ENGINE_MUTEX, SQLCOM_SHOW_PROCESSLIST +# +# Currently can not have a where clause, need to be covered +# with tests +drop table if exists t1; +create table t1 (a int); +prepare stmt from "show engine all status where (1) in (select * from t1)"; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'where (1) in (select * from t1)' at line 1 +prepare stmt from "show engine all logs where (1) in (select * from t1)"; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'where (1) in (select * from t1)' at line 1 +prepare stmt from "show engine all mutex where (1) in (select * from t1)"; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'where (1) in (select * from t1)' at line 1 +prepare stmt from "show processlist where (1) in (select * from t1)"; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'where (1) in (select * from t1)' at line 1 +drop table t1; +# +# SQLCOM_SHOW_CHARSETS +# +drop table if exists t1; +create table t1 (a int); +prepare stmt from "show charset where (1) in (select * from t1)"; +execute stmt; +Charset Description Default collation Maxlen +drop table t1; +create table t1 (x int); +execute stmt; +Charset Description Default collation Maxlen +drop table t1; +deallocate prepare stmt; +# +# SQLCOM_SHOW_COLLATIONS +# +drop table if exists t1; +create table t1 (a int); +prepare stmt from "show collation where (1) in (select * from t1)"; +execute stmt; +Collation Charset Id Default Compiled Sortlen +drop table t1; +create table t1 (x int); +execute stmt; +Collation Charset Id Default Compiled Sortlen +drop table t1; +deallocate prepare stmt; +# +# SQLCOM_SHOW_TABLE_STATUS +# +drop table if exists t1; +create table t1 (a int); +prepare stmt from "show table status where (1) in (select * from t1)"; +execute stmt; +Name Engine Version Row_format Rows Avg_row_length Data_length Max_data_length Index_length Data_free Auto_increment Create_time Update_time Check_time Collation Checksum Create_options Comment +drop table t1; +create table t1 (x int); +execute stmt; +Name Engine Version Row_format Rows Avg_row_length Data_length Max_data_length Index_length Data_free Auto_increment Create_time Update_time Check_time Collation Checksum Create_options Comment +drop table t1; +deallocate prepare stmt; +# +# SQLCOM_SHOW_TRIGGERS +# +drop table if exists t1; +create table t1 (a int); +prepare stmt from "show triggers where (1) in (select * from t1)"; +execute stmt; +Trigger Event Table Statement Timing Created sql_mode Definer character_set_client collation_connection Database Collation +drop table t1; +create table t1 (x int); +execute stmt; +Trigger Event Table Statement Timing Created sql_mode Definer character_set_client collation_connection Database Collation +drop table t1; +deallocate prepare stmt; +# +# SQLCOM_SHOW_OPEN_TABLES +# +drop table if exists t1; +create table t1 (a int); +prepare stmt from "show open tables where (1) in (select * from t1)"; +execute stmt; +Database Table In_use Name_locked +drop table t1; +create table t1 (x int); +execute stmt; +Database Table In_use Name_locked +drop table t1; +deallocate prepare stmt; +# +# SQLCOM_SHOW_STATUS_PROC +# +drop table if exists t1; +create table t1 (a int); +prepare stmt from "show procedure status where (1) in (select * from t1)"; +execute stmt; +Db Name Type Definer Modified Created Security_type Comment character_set_client collation_connection Database Collation +drop table t1; +create table t1 (x int); +execute stmt; +Db Name Type Definer Modified Created Security_type Comment character_set_client collation_connection Database Collation +drop table t1; +deallocate prepare stmt; +# +# SQLCOM_SHOW_STATUS_FUNC +# +drop table if exists t1; +create table t1 (a int); +prepare stmt from "show function status where (1) in (select * from t1)"; +execute stmt; +Db Name Type Definer Modified Created Security_type Comment character_set_client collation_connection Database Collation +drop table t1; +create table t1 (x int); +execute stmt; +Db Name Type Definer Modified Created Security_type Comment character_set_client collation_connection Database Collation +drop table t1; +deallocate prepare stmt; +# +# SQLCOM_SHOW_EVENTS +# +drop table if exists t1; +create table t1 (a int); +prepare stmt from "show events where (1) in (select * from t1)"; +execute stmt; +Db Name Definer Time zone Type Execute at Interval value Interval field Starts Ends Status Originator character_set_client collation_connection Database Collation +drop table t1; +create table t1 (x int); +execute stmt; +Db Name Definer Time zone Type Execute at Interval value Interval field Starts Ends Status Originator character_set_client collation_connection Database Collation +drop table t1; +deallocate prepare stmt; +# +# SQLCOM_SET_OPTION +# +drop table if exists t1; +create table t1 (a int); +prepare stmt from "set @a=((1) in (select * from t1))"; +execute stmt; +drop table t1; +create table t1 (x int); +execute stmt; +drop table t1; +deallocate prepare stmt; +# +# SQLCOM_DO +# +drop table if exists t1; +create table t1 (a int); +prepare stmt from "do ((1) in (select * from t1))"; +execute stmt; +drop table t1; +create table t1 (x int); +execute stmt; +drop table t1; +deallocate prepare stmt; +# +# SQLCOM_CALL +# +drop table if exists t1; +drop procedure if exists p1; +create procedure p1(a int) begin end; +create table t1 (a int); +prepare stmt from "call p1((1) in (select * from t1))"; +execute stmt; +drop table t1; +create table t1 (x int); +execute stmt; +drop table t1; +drop procedure p1; +deallocate prepare stmt; +# +# SQLCOM_CREATE_VIEW +# +drop table if exists t1; +drop view if exists v1; +create table t1 (a int); +prepare stmt from "create view v1 as select * from t1"; +execute stmt; +drop view v1; +drop table t1; +create table t1 (x int); +execute stmt; +drop view v1; +drop table t1; +deallocate prepare stmt; +# Intermediate result: number of reprepares matches the number +# of tests +call p_verify_reprepare_count(18); +SUCCESS + +# +# SQLCOM_ALTER_VIEW +# +drop view if exists v1; +create view v1 as select 1; +prepare stmt from "alter view v1 as select 2"; +ERROR HY000: This command is not supported in the prepared statement protocol yet +drop view v1; # Cleanup # drop temporary table if exists t1, t2, t3; diff --git a/mysql-test/t/ps_ddl.test b/mysql-test/t/ps_ddl.test index d3df035444c..94791cd81f8 100644 --- a/mysql-test/t/ps_ddl.test +++ b/mysql-test/t/ps_ddl.test @@ -614,6 +614,8 @@ select * from t1; --echo # Currently a different result from conventional statements. --echo # A view is inlined once at prepare, later on view DDL --echo # does not affect prepared statement and it is not re-prepared. +--echo # This is reported in Bug#36002 Prepared statements: if a view +--echo # used in a statement is replaced, bad data execute stmt; call p_verify_reprepare_count(0); flush table t2; @@ -1347,6 +1349,588 @@ drop procedure p_12093; deallocate prepare stmt_sf; deallocate prepare stmt_sp; + +--echo ===================================================================== +--echo Ensure that metadata validation is performed for every type of +--echo SQL statement where it is needed. +--echo ===================================================================== +--disable_warnings +drop table if exists t1; +--enable_warnings +create table t1 (a int); +--echo # +--echo # SQLCOM_SELECT +--echo # +prepare stmt from "select 1 as res from dual where (1) in (select * from t1)"; +drop table t1; +create table t1 (x int); +execute stmt; +drop table t1; +deallocate prepare stmt; +call p_verify_reprepare_count(1); +--echo # +--echo # SQLCOM_CREATE_TABLE +--echo # +--disable_warnings +drop table if exists t1; +drop table if exists t2; +--enable_warnings + +create table t1 (a int); + +prepare stmt from 'create table t2 as select * from t1'; +execute stmt; +drop table t2; +execute stmt; +drop table t2; +execute stmt; +call p_verify_reprepare_count(0); +--error ER_TABLE_EXISTS_ERROR +execute stmt; +call p_verify_reprepare_count(1); +--error ER_TABLE_EXISTS_ERROR +execute stmt; +call p_verify_reprepare_count(0); +drop table t2; +create temporary table t2 (a int); +--error ER_TABLE_EXISTS_ERROR +execute stmt; +call p_verify_reprepare_count(1); +--error ER_TABLE_EXISTS_ERROR +execute stmt; +call p_verify_reprepare_count(0); +drop temporary table t2; +execute stmt; +call p_verify_reprepare_count(1); +drop table t2; +execute stmt; +call p_verify_reprepare_count(0); +drop table t2; +drop table t1; +create table t1 (x varchar(20)); +execute stmt; +call p_verify_reprepare_count(1); +select * from t2; +drop table t2; +execute stmt; +call p_verify_reprepare_count(0); +drop table t2; +drop table t1; +deallocate prepare stmt; +--echo # XXX: no validation of the first table in case of +--echo # CREATE TEMPORARY TABLE. This is a shortcoming of the current code, +--echo # but since validation is not strictly necessary, nothing is done +--echo # about it. +--echo # Will be fixed as part of work on Bug#21431 "Incomplete support of +--echo # temporary tables" +create table t1 (a int); +insert into t1 (a) values (1); +prepare stmt from "create temporary table if not exists t2 as select * from t1"; +execute stmt; +drop table t2; +execute stmt; +execute stmt; +select * from t2; +execute stmt; +select * from t2; +drop table t2; +create temporary table t2 (a varchar(10)); +execute stmt; +select * from t2; +call p_verify_reprepare_count(0); +drop table t1; +create table t1 (x int); +execute stmt; +call p_verify_reprepare_count(1); +execute stmt; +call p_verify_reprepare_count(0); +drop table t1; +drop table t2; +deallocate prepare stmt; + +--echo # +--echo # SQLCOM_UPDATE +--echo # + +--disable_warnings +drop table if exists t1, t2; +--enable_warnings +create table t1 (a int); +create table t2 (a int); +prepare stmt from "update t2 set a=a+1 where (1) in (select * from t1)"; +execute stmt; +drop table t1; +create table t1 (x int); +execute stmt; +drop table t1, t2; +deallocate prepare stmt; + +--echo # +--echo # SQLCOM_INSERT +--echo # + +--disable_warnings +drop table if exists t1, t2; +--enable_warnings +create table t1 (a int); +create table t2 (a int); +prepare stmt from "insert into t2 set a=((1) in (select * from t1))"; +execute stmt; +drop table t1; +create table t1 (x int); +execute stmt; + +drop table t1, t2; +deallocate prepare stmt; + +--echo # +--echo # SQLCOM_INSERT_SELECT +--echo # + +--disable_warnings +drop table if exists t1, t2; +--enable_warnings +create table t1 (a int); +create table t2 (a int); +prepare stmt from "insert into t2 select * from t1"; +execute stmt; +drop table t1; +create table t1 (x int); +execute stmt; +drop table t1, t2; +deallocate prepare stmt; + +--echo # +--echo # SQLCOM_REPLACE +--echo # + +--disable_warnings +drop table if exists t1, t2; +--enable_warnings +create table t1 (a int); +create table t2 (a int); +prepare stmt from "replace t2 set a=((1) in (select * from t1))"; +execute stmt; +drop table t1; +create table t1 (x int); +execute stmt; +drop table t1, t2; +deallocate prepare stmt; + +--echo # +--echo # SQLCOM_REPLACE_SELECT +--echo # + +--disable_warnings +drop table if exists t1, t2; +--enable_warnings +create table t1 (a int); +create table t2 (a int); +prepare stmt from "replace t2 select * from t1"; +execute stmt; +drop table t1; +create table t1 (x int); +execute stmt; +drop table t1, t2; +deallocate prepare stmt; + +--echo # +--echo # SQLCOM_DELETE +--echo # + +--disable_warnings +drop table if exists t1, t2; +--enable_warnings +create table t1 (a int); +create table t2 (a int); +prepare stmt from "delete from t2 where (1) in (select * from t1)"; +execute stmt; +drop table t1; +create table t1 (x int); +execute stmt; +drop table t1, t2; +deallocate prepare stmt; + +--echo # +--echo # SQLCOM_DELETE_MULTI +--echo # + +--disable_warnings +drop table if exists t1, t2, t3; +--enable_warnings +create table t1 (a int); +create table t2 (a int); +create table t3 (a int); +prepare stmt from "delete t2, t3 from t2, t3 where (1) in (select * from t1)"; +execute stmt; +drop table t1; +create table t1 (x int); +execute stmt; +drop table t1, t2, t3; +deallocate prepare stmt; + +--echo # +--echo # SQLCOM_UPDATE_MULTI +--echo # + +--disable_warnings +drop table if exists t1, t2, t3; +--enable_warnings +create table t1 (a int); +create table t2 (a int); +create table t3 (a int); +prepare stmt from "update t2, t3 set t3.a=t2.a, t2.a=null where (1) in (select * from t1)"; +drop table t1; +create table t1 (x int); +execute stmt; +drop table t1, t2, t3; +deallocate prepare stmt; +--echo # Intermediate results: 8 SQLCOMs tested, 8 automatic reprepares +call p_verify_reprepare_count(8); + +--echo # +--echo # SQLCOM_LOAD +--echo # + +--disable_warnings +drop table if exists t1; +--enable_warnings +create table t1 (a varchar(20)); +--error ER_UNSUPPORTED_PS +prepare stmt from "load data infile '../std_data_ln/words.dat' into table t1"; +drop table t1; + +--echo # +--echo # SQLCOM_SHOW_DATABASES +--echo # + +--disable_warnings +drop table if exists t1; +--enable_warnings +create table t1 (a int); +prepare stmt from "show databases where (1) in (select * from t1)"; +execute stmt; +drop table t1; +create table t1 (x int); +execute stmt; +drop table t1; +deallocate prepare stmt; + +--echo # +--echo # SQLCOM_SHOW_TABLES +--echo # + +--disable_warnings +drop table if exists t1; +--enable_warnings +create table t1 (a int); +prepare stmt from "show tables where (1) in (select * from t1)"; +execute stmt; +drop table t1; +create table t1 (x int); +execute stmt; +drop table t1; +deallocate prepare stmt; + +--echo # +--echo # SQLCOM_SHOW_FIELDS +--echo # + +--disable_warnings +drop table if exists t1; +--enable_warnings +create table t1 (a int); +prepare stmt from "show fields from t1 where (1) in (select * from t1)"; +execute stmt; +drop table t1; +create table t1 (x int); +execute stmt; +drop table t1; +deallocate prepare stmt; + +--echo # +--echo # SQLCOM_SHOW_KEYS +--echo # + +--disable_warnings +drop table if exists t1; +--enable_warnings +create table t1 (a int); +prepare stmt from "show keys from t1 where (1) in (select * from t1)"; +execute stmt; +drop table t1; +create table t1 (x int); +execute stmt; +drop table t1; +deallocate prepare stmt; + +--echo # +--echo # SQLCOM_SHOW_VARIABLES +--echo # + +--disable_warnings +drop table if exists t1; +--enable_warnings +create table t1 (a int); +prepare stmt from "show variables where (1) in (select * from t1)"; +execute stmt; +drop table t1; +create table t1 (x int); +execute stmt; +drop table t1; +deallocate prepare stmt; + +--echo # +--echo # SQLCOM_SHOW_STATUS +--echo # + +--disable_warnings +drop table if exists t1; +--enable_warnings +create table t1 (a int); +prepare stmt from "show status where (1) in (select * from t1)"; +execute stmt; +drop table t1; +create table t1 (x int); +execute stmt; +drop table t1; +deallocate prepare stmt; + +--echo # +--echo # SQLCOM_SHOW_ENGINE_STATUS, SQLCOM_SHOW_ENGINE_LOGS, +--echo # SQLCOM_SHOW_ENGINE_MUTEX, SQLCOM_SHOW_PROCESSLIST +--echo # + +--echo # Currently can not have a where clause, need to be covered +--echo # with tests + +--disable_warnings +drop table if exists t1; +--enable_warnings +create table t1 (a int); +--error ER_PARSE_ERROR +prepare stmt from "show engine all status where (1) in (select * from t1)"; +--error ER_PARSE_ERROR +prepare stmt from "show engine all logs where (1) in (select * from t1)"; +--error ER_PARSE_ERROR +prepare stmt from "show engine all mutex where (1) in (select * from t1)"; +--error ER_PARSE_ERROR +prepare stmt from "show processlist where (1) in (select * from t1)"; +drop table t1; + +--echo # +--echo # SQLCOM_SHOW_CHARSETS +--echo # + +--disable_warnings +drop table if exists t1; +--enable_warnings +create table t1 (a int); +prepare stmt from "show charset where (1) in (select * from t1)"; +execute stmt; +drop table t1; +create table t1 (x int); +execute stmt; +drop table t1; +deallocate prepare stmt; + +--echo # +--echo # SQLCOM_SHOW_COLLATIONS +--echo # + +--disable_warnings +drop table if exists t1; +--enable_warnings +create table t1 (a int); +prepare stmt from "show collation where (1) in (select * from t1)"; +execute stmt; +drop table t1; +create table t1 (x int); +execute stmt; +drop table t1; +deallocate prepare stmt; + +--echo # +--echo # SQLCOM_SHOW_TABLE_STATUS +--echo # + +--disable_warnings +drop table if exists t1; +--enable_warnings +create table t1 (a int); +prepare stmt from "show table status where (1) in (select * from t1)"; +execute stmt; +drop table t1; +create table t1 (x int); +execute stmt; +drop table t1; +deallocate prepare stmt; + +--echo # +--echo # SQLCOM_SHOW_TRIGGERS +--echo # + +--disable_warnings +drop table if exists t1; +--enable_warnings +create table t1 (a int); +prepare stmt from "show triggers where (1) in (select * from t1)"; +execute stmt; +drop table t1; +create table t1 (x int); +execute stmt; +drop table t1; +deallocate prepare stmt; + +--echo # +--echo # SQLCOM_SHOW_OPEN_TABLES +--echo # + +--disable_warnings +drop table if exists t1; +--enable_warnings +create table t1 (a int); +prepare stmt from "show open tables where (1) in (select * from t1)"; +execute stmt; +drop table t1; +create table t1 (x int); +execute stmt; +drop table t1; +deallocate prepare stmt; + +--echo # +--echo # SQLCOM_SHOW_STATUS_PROC +--echo # + +--disable_warnings +drop table if exists t1; +--enable_warnings +create table t1 (a int); +prepare stmt from "show procedure status where (1) in (select * from t1)"; +execute stmt; +drop table t1; +create table t1 (x int); +execute stmt; +drop table t1; +deallocate prepare stmt; + +--echo # +--echo # SQLCOM_SHOW_STATUS_FUNC +--echo # + +--disable_warnings +drop table if exists t1; +--enable_warnings +create table t1 (a int); +prepare stmt from "show function status where (1) in (select * from t1)"; +execute stmt; +drop table t1; +create table t1 (x int); +execute stmt; +drop table t1; +deallocate prepare stmt; + +--echo # +--echo # SQLCOM_SHOW_EVENTS +--echo # + +--disable_warnings +drop table if exists t1; +--enable_warnings +create table t1 (a int); +prepare stmt from "show events where (1) in (select * from t1)"; +execute stmt; +drop table t1; +create table t1 (x int); +execute stmt; +drop table t1; +deallocate prepare stmt; + +--echo # +--echo # SQLCOM_SET_OPTION +--echo # + +--disable_warnings +drop table if exists t1; +--enable_warnings +create table t1 (a int); +prepare stmt from "set @a=((1) in (select * from t1))"; +execute stmt; +drop table t1; +create table t1 (x int); +execute stmt; +drop table t1; +deallocate prepare stmt; + +--echo # +--echo # SQLCOM_DO +--echo # + +--disable_warnings +drop table if exists t1; +--enable_warnings +create table t1 (a int); +prepare stmt from "do ((1) in (select * from t1))"; +execute stmt; +drop table t1; +create table t1 (x int); +execute stmt; +drop table t1; +deallocate prepare stmt; + +--echo # +--echo # SQLCOM_CALL +--echo # + +--disable_warnings +drop table if exists t1; +drop procedure if exists p1; +--enable_warnings +create procedure p1(a int) begin end; +create table t1 (a int); +prepare stmt from "call p1((1) in (select * from t1))"; +execute stmt; +drop table t1; +create table t1 (x int); +execute stmt; +drop table t1; +drop procedure p1; +deallocate prepare stmt; + +--echo # +--echo # SQLCOM_CREATE_VIEW +--echo # + +--disable_warnings +drop table if exists t1; +drop view if exists v1; +--enable_warnings +create table t1 (a int); +prepare stmt from "create view v1 as select * from t1"; +execute stmt; +drop view v1; +drop table t1; +create table t1 (x int); +execute stmt; +drop view v1; +drop table t1; +deallocate prepare stmt; +--echo # Intermediate result: number of reprepares matches the number +--echo # of tests +call p_verify_reprepare_count(18); + +--echo # +--echo # SQLCOM_ALTER_VIEW +--echo # + +--disable_warnings +drop view if exists v1; +--enable_warnings +create view v1 as select 1; +--error ER_UNSUPPORTED_PS +prepare stmt from "alter view v1 as select 2"; +drop view v1; + --echo # Cleanup --echo # --disable_warnings diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index 491a4c8ca1b..dd26a2d8103 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -695,7 +695,7 @@ const char *set_thd_proc_info(THD *thd, const char *info, prepare matches the type of the object we obtained from the table definition cache. - @sa check_and_update_metadata_version() + @sa check_and_update_table_version() @sa Execute_observer @sa Prepared_statement::reprepare() */ diff --git a/sql/sp_head.cc b/sql/sp_head.cc index 8bd10e00f15..ab95575a965 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -1068,6 +1068,7 @@ sp_head::execute(THD *thd) LEX *old_lex; Item_change_list old_change_list; String old_packet; + Metadata_version_observer *save_metadata_observer= thd->m_metadata_observer; Object_creation_ctx *saved_creation_ctx; @@ -1135,6 +1136,25 @@ sp_head::execute(THD *thd) thd->variables.sql_mode= m_sql_mode; save_abort_on_warning= thd->abort_on_warning; thd->abort_on_warning= 0; + /** + When inside a substatement (a stored function or trigger + statement), clear the metadata observer in THD, if any. + Remember the value of the observer here, to be able + to restore it when leaving the substatement. + + We reset the observer to suppress errors when a substatement + uses temporary tables. If a temporary table does not exist + at start of the main statement, it's not prelocked + and thus is not validated with other prelocked tables. + + Later on, when the temporary table is opened, metadata + versions mismatch, expectedly. + + The proper solution for the problem is to re-validate tables + of substatements (Bug#12257, Bug#27011, Bug#32868, Bug#33000), + but it's not implemented yet. + */ + thd->m_metadata_observer= 0; /* It is also more efficient to save/restore current thd->lex once when @@ -1297,6 +1317,7 @@ sp_head::execute(THD *thd) thd->derived_tables= old_derived_tables; thd->variables.sql_mode= save_sql_mode; thd->abort_on_warning= save_abort_on_warning; + thd->m_metadata_observer= save_metadata_observer; thd->stmt_arena= old_arena; state= EXECUTED; diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 6a4ef0781a2..85f8e607213 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -2876,7 +2876,6 @@ void THD::reset_sub_statement_state(Sub_statement_state *backup, first_successful_insert_id_in_prev_stmt; backup->first_successful_insert_id_in_cur_stmt= first_successful_insert_id_in_cur_stmt; - backup->m_metadata_observer= m_metadata_observer; if ((!lex->requires_prelocking() || is_update_query(lex->sql_command)) && !current_stmt_binlog_row_based) @@ -2896,7 +2895,6 @@ void THD::reset_sub_statement_state(Sub_statement_state *backup, cuted_fields= 0; transaction.savepoints= 0; first_successful_insert_id_in_cur_stmt= 0; - m_metadata_observer= 0; } @@ -2945,7 +2943,6 @@ void THD::restore_sub_statement_state(Sub_statement_state *backup) */ examined_row_count+= backup->examined_row_count; cuted_fields+= backup->cuted_fields; - m_metadata_observer= backup->m_metadata_observer; } diff --git a/sql/sql_class.h b/sql/sql_class.h index 837b74b3b60..cdb2300cabc 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -45,7 +45,7 @@ At most 1 instance of this class is active at a time, in which case THD::m_metadata_observer is not NULL. - @sa check_and_update_metadata_version() for details of the + @sa check_and_update_table_version() for details of the version tracking algorithm @sa Execute_observer for details of how we detect that @@ -847,7 +847,7 @@ public: to avoid spurious ER_NEED_REPREPARE errors -- system and INFORMATION_SCHEMA tables are not subject to metadata version tracking. - @sa check_and_update_metadata_version() + @sa check_and_update_table_version() */ Metadata_version_observer *m_metadata_observer; @@ -983,25 +983,6 @@ public: bool enable_slow_log; bool last_insert_id_used; SAVEPOINT *savepoints; - /** - When inside a substatement (a stored function or trigger - statement), clear the metadata observer in THD, if any. - Remember the value of the observer here, to be able - to restore it when leaving the substatement. - - We reset the observer to suppress errors when a substatement - uses temporary tables. If a temporary table does not exist - at start of the main statement, it's not prelocked - and thus is not validated with other prelocked tables. - - Later on, when the temporary table is opened, metadata - versions mismatch, expectedly. - - The proper solution for the problem is to re-validate tables - of substatements (Bug#12257, Bug#27011, Bug#32868, Bug#33000), - but it's not implemented yet. - */ - Metadata_version_observer *m_metadata_observer; }; diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index faf48b38fd0..b495b2a268b 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -200,19 +200,19 @@ void init_update_queries(void) { bzero((uchar*) &sql_command_flags, sizeof(sql_command_flags)); - sql_command_flags[SQLCOM_CREATE_TABLE]= CF_CHANGES_DATA; + sql_command_flags[SQLCOM_CREATE_TABLE]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE; sql_command_flags[SQLCOM_CREATE_INDEX]= CF_CHANGES_DATA; sql_command_flags[SQLCOM_ALTER_TABLE]= CF_CHANGES_DATA | CF_WRITE_LOGS_COMMAND; sql_command_flags[SQLCOM_TRUNCATE]= CF_CHANGES_DATA | CF_WRITE_LOGS_COMMAND; sql_command_flags[SQLCOM_DROP_TABLE]= CF_CHANGES_DATA; - sql_command_flags[SQLCOM_LOAD]= CF_CHANGES_DATA; + sql_command_flags[SQLCOM_LOAD]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE; sql_command_flags[SQLCOM_CREATE_DB]= CF_CHANGES_DATA; sql_command_flags[SQLCOM_DROP_DB]= CF_CHANGES_DATA; sql_command_flags[SQLCOM_RENAME_TABLE]= CF_CHANGES_DATA; sql_command_flags[SQLCOM_BACKUP_TABLE]= CF_CHANGES_DATA; sql_command_flags[SQLCOM_RESTORE_TABLE]= CF_CHANGES_DATA; sql_command_flags[SQLCOM_DROP_INDEX]= CF_CHANGES_DATA; - sql_command_flags[SQLCOM_CREATE_VIEW]= CF_CHANGES_DATA; + sql_command_flags[SQLCOM_CREATE_VIEW]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE; sql_command_flags[SQLCOM_DROP_VIEW]= CF_CHANGES_DATA; sql_command_flags[SQLCOM_CREATE_EVENT]= CF_CHANGES_DATA; sql_command_flags[SQLCOM_ALTER_EVENT]= CF_CHANGES_DATA; @@ -235,19 +235,21 @@ void init_update_queries(void) sql_command_flags[SQLCOM_REPLACE_SELECT]= CF_CHANGES_DATA | CF_HAS_ROW_COUNT | CF_REEXECUTION_FRAGILE; sql_command_flags[SQLCOM_SELECT]= CF_REEXECUTION_FRAGILE; + sql_command_flags[SQLCOM_SET_OPTION]= CF_REEXECUTION_FRAGILE; + sql_command_flags[SQLCOM_DO]= CF_REEXECUTION_FRAGILE; - sql_command_flags[SQLCOM_SHOW_STATUS_PROC]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_STATUS]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_DATABASES]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_TRIGGERS]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_EVENTS]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_OPEN_TABLES]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_SHOW_STATUS_PROC]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE; + sql_command_flags[SQLCOM_SHOW_STATUS]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE; + sql_command_flags[SQLCOM_SHOW_DATABASES]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE; + sql_command_flags[SQLCOM_SHOW_TRIGGERS]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE; + sql_command_flags[SQLCOM_SHOW_EVENTS]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE; + sql_command_flags[SQLCOM_SHOW_OPEN_TABLES]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE; sql_command_flags[SQLCOM_SHOW_PLUGINS]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_FIELDS]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_KEYS]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_VARIABLES]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_CHARSETS]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_COLLATIONS]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_SHOW_FIELDS]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE; + sql_command_flags[SQLCOM_SHOW_KEYS]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE; + sql_command_flags[SQLCOM_SHOW_VARIABLES]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE; + sql_command_flags[SQLCOM_SHOW_CHARSETS]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE; + sql_command_flags[SQLCOM_SHOW_COLLATIONS]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE; sql_command_flags[SQLCOM_SHOW_NEW_MASTER]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_BINLOGS]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_SLAVE_HOSTS]= CF_STATUS_COMMAND; @@ -271,7 +273,7 @@ void init_update_queries(void) sql_command_flags[SQLCOM_SHOW_CREATE_PROC]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_CREATE_FUNC]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_CREATE_TRIGGER]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_STATUS_FUNC]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_SHOW_STATUS_FUNC]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE; sql_command_flags[SQLCOM_SHOW_PROC_CODE]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_FUNC_CODE]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_CREATE_EVENT]= CF_STATUS_COMMAND; @@ -279,9 +281,11 @@ void init_update_queries(void) sql_command_flags[SQLCOM_SHOW_PROFILE]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_TABLES]= (CF_STATUS_COMMAND | - CF_SHOW_TABLE_COMMAND); + CF_SHOW_TABLE_COMMAND | + CF_REEXECUTION_FRAGILE); sql_command_flags[SQLCOM_SHOW_TABLE_STATUS]= (CF_STATUS_COMMAND | - CF_SHOW_TABLE_COMMAND); + CF_SHOW_TABLE_COMMAND | + CF_REEXECUTION_FRAGILE); /* The following is used to preserver CF_ROW_COUNT during the @@ -289,7 +293,7 @@ void init_update_queries(void) last called (or executed) statement is preserved. See mysql_execute_command() for how CF_ROW_COUNT is used. */ - sql_command_flags[SQLCOM_CALL]= CF_HAS_ROW_COUNT; + sql_command_flags[SQLCOM_CALL]= CF_HAS_ROW_COUNT | CF_REEXECUTION_FRAGILE; sql_command_flags[SQLCOM_EXECUTE]= CF_HAS_ROW_COUNT; /* diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index 720deac116d..b815b993591 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -1480,6 +1480,43 @@ error: } +/** + Validate and prepare for execution CALL statement expressions. + + @param stmt prepared statement + @param tables list of tables used in this query + @param value_list list of expressions + + @retval FALSE success + @retval TRUE error, error message is set in THD +*/ + +static bool mysql_test_call_fields(Prepared_statement *stmt, + TABLE_LIST *tables, + List *value_list) +{ + DBUG_ENTER("mysql_test_call_fields"); + + List_iterator it(*value_list); + THD *thd= stmt->thd; + Item *item; + + if (tables && check_table_access(thd, SELECT_ACL, tables, UINT_MAX, FALSE) || + open_normal_and_derived_tables(thd, tables, 0)) + goto err; + + while ((item= it++)) + { + if (!item->fixed && item->fix_fields(thd, it.ref()) || + item->check_cols(1)) + goto err; + } + DBUG_RETURN(FALSE); +err: + DBUG_RETURN(TRUE); +} + + /** Check internal SELECT of the prepared command. @@ -1601,6 +1638,17 @@ static bool mysql_test_create_table(Prepared_statement *stmt) res= select_like_stmt_test(stmt, 0, 0); } + else if (lex->create_info.options & HA_LEX_CREATE_TABLE_LIKE) + { + /* + Check that the source table exist, and also record + its metadata version. Even though not strictly necessary, + we validate metadata of all CREATE TABLE statements, + which keeps metadata validation code simple. + */ + if (open_normal_and_derived_tables(stmt->thd, lex->query_tables, 0)) + DBUG_RETURN(TRUE); + } /* put tables back for PS rexecuting */ lex->link_first_table_back(create_table, link_to_local); @@ -1838,7 +1886,21 @@ static bool check_prepared_statement(Prepared_statement *stmt) case SQLCOM_DELETE: res= mysql_test_delete(stmt, tables); break; - + /* The following allow WHERE clause, so they must be tested like SELECT */ + case SQLCOM_SHOW_DATABASES: + case SQLCOM_SHOW_TABLES: + case SQLCOM_SHOW_TRIGGERS: + case SQLCOM_SHOW_EVENTS: + case SQLCOM_SHOW_OPEN_TABLES: + case SQLCOM_SHOW_FIELDS: + case SQLCOM_SHOW_KEYS: + case SQLCOM_SHOW_COLLATIONS: + case SQLCOM_SHOW_CHARSETS: + case SQLCOM_SHOW_VARIABLES: + case SQLCOM_SHOW_STATUS: + case SQLCOM_SHOW_TABLE_STATUS: + case SQLCOM_SHOW_STATUS_PROC: + case SQLCOM_SHOW_STATUS_FUNC: case SQLCOM_SELECT: res= mysql_test_select(stmt, tables); if (res == 2) @@ -1863,6 +1925,9 @@ static bool check_prepared_statement(Prepared_statement *stmt) res= mysql_test_do_fields(stmt, tables, lex->insert_list); break; + case SQLCOM_CALL: + res= mysql_test_call_fields(stmt, tables, &lex->value_list); + break; case SQLCOM_SET_OPTION: res= mysql_test_set_fields(stmt, tables, &lex->var_list); break; @@ -1912,7 +1977,6 @@ static bool check_prepared_statement(Prepared_statement *stmt) case SQLCOM_DROP_INDEX: case SQLCOM_ROLLBACK: case SQLCOM_TRUNCATE: - case SQLCOM_CALL: case SQLCOM_DROP_VIEW: case SQLCOM_REPAIR: case SQLCOM_ANALYZE: @@ -3064,6 +3128,26 @@ bool Prepared_statement::prepare(const char *packet, uint packet_len) } +/** + Assign parameter values either from variables, in case of SQL PS + or from the execute packet. + + @param expanded_query a container with the original SQL statement. + '?' placeholders will be replaced with + their values in case of success. + The result is used for logging and replication + @param packet pointer to execute packet. + NULL in case of SQL PS + @param packet_end end of the packet. NULL in case of SQL PS + + @todo Use a paremeter source class family instead of 'if's, and + support stored procedure variables. + + @retval TRUE an error occurred when assigning a parameter (likely + a conversion error or out of memory, or malformed packet) + @retval FALSE success +*/ + bool Prepared_statement::set_parameters(String *expanded_query, uchar *packet, uchar *packet_end) diff --git a/sql/table.h b/sql/table.h index 2b3c9b8d5b1..603557fee78 100644 --- a/sql/table.h +++ b/sql/table.h @@ -1336,7 +1336,7 @@ struct TABLE_LIST (if any) with values obtained from the current table definition cache element. - @sa check_and_update_metadata_version() + @sa check_and_update_table_version() */ inline bool is_metadata_version_equal(TABLE_SHARE *s) const @@ -1349,7 +1349,7 @@ struct TABLE_LIST Record the value of metadata version of the corresponding table definition cache element in this parse tree node. - @sa check_and_update_metadata_version() + @sa check_and_update_table_version() */ inline void set_metadata_version(TABLE_SHARE *s) From 9590918851d39f323e66514638226993fc034abd Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 17 Apr 2008 03:27:14 +0400 Subject: [PATCH 4/8] Fix create.test in --ps-protocol broken by the previous push. sql/sql_table.cc: Fix create.test in --ps-protocol broken by the previous push, that added prepared statement validation of CREATE TABLE LIKE: move assignment of src_table->required_type to the parser. sql/sql_yacc.yy: Fix create.test in --ps-protocol broken by the previous push, that added prepared statement validation of CREATE TABLE LIKE: move assignment of src_table->required_type to the parser. --- sql/sql_table.cc | 3 --- sql/sql_yacc.yy | 14 ++++++++++++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/sql/sql_table.cc b/sql/sql_table.cc index f69f5fadff3..4997f99fd3a 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -4775,9 +4775,6 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST* src_table, DBUG_ENTER("mysql_create_like_table"); - /* CREATE TABLE ... LIKE is not allowed for views. */ - src_table->required_type= FRMTYPE_TABLE; - /* By opening source table we guarantee that it exists and no concurrent DDL operation will mess with it. Later we also take an exclusive diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 5e28bdb45e8..37f29a44644 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -3577,20 +3577,30 @@ create2: | LIKE table_ident { THD *thd= YYTHD; + TABLE_LIST *src_table; LEX *lex= thd->lex; lex->create_info.options|= HA_LEX_CREATE_TABLE_LIKE; - if (!lex->select_lex.add_table_to_list(thd, $2, NULL, 0, TL_READ)) + src_table= lex->select_lex.add_table_to_list(thd, $2, NULL, 0, + TL_READ); + if (! src_table) MYSQL_YYABORT; + /* CREATE TABLE ... LIKE is not allowed for views. */ + src_table->required_type= FRMTYPE_TABLE; } | '(' LIKE table_ident ')' { THD *thd= YYTHD; + TABLE_LIST *src_table; LEX *lex= thd->lex; lex->create_info.options|= HA_LEX_CREATE_TABLE_LIKE; - if (!lex->select_lex.add_table_to_list(thd, $3, NULL, 0, TL_READ)) + src_table= lex->select_lex.add_table_to_list(thd, $3, NULL, 0, + TL_READ); + if (! src_table) MYSQL_YYABORT; + /* CREATE TABLE ... LIKE is not allowed for views. */ + src_table->required_type= FRMTYPE_TABLE; } ; From e119174cfe8c9835214d3f34a616ded43f453db3 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 17 Apr 2008 14:31:43 +0200 Subject: [PATCH 5/8] Minor corrections within the script mysql-test/r/ps_ddl.result: Updated results mysql-test/t/ps_ddl.test: - remove trailing spaces - correct wrong written word "echo" --- mysql-test/r/ps_ddl.result | 26 +++++++++++----------- mysql-test/t/ps_ddl.test | 44 +++++++++++++++++++------------------- 2 files changed, 36 insertions(+), 34 deletions(-) diff --git a/mysql-test/r/ps_ddl.result b/mysql-test/r/ps_ddl.result index 3121650508d..cca00dd4781 100644 --- a/mysql-test/r/ps_ddl.result +++ b/mysql-test/r/ps_ddl.result @@ -192,6 +192,7 @@ SUCCESS select @message; @message new trigger: 11 +Test 6-e: removing a relevant trigger drop trigger t1_bi; set @val=12; execute stmt using @val; @@ -338,7 +339,7 @@ SUCCESS # # Sic: the insert went into t3, even though the view now # points at t2. This is because neither the merged view -# nor its prelocking list are affected by view DDL +# nor its prelocking list are affected by view DDL # The binary log is of course wrong, since it is not # using prepared statements # @@ -380,7 +381,7 @@ set @var=2; # Since the dependent table is tracked in the prelocked # list of the prepared statement, invalidation happens # and the statement is re-prepared. This is an unnecessary -# side effect, since the statement that *is* dependent +# side effect, since the statement that *is* dependent # on t2 definition is inside the trigger, and it is currently # not reprepared (see the previous test case). execute stmt using @var; @@ -398,7 +399,7 @@ a comment drop table t1,t2; # Test 7-e: dependent TABLE TRIGGER has changed create table t1 (a int); -create trigger t1_ai after insert on t1 for each row +create trigger t1_ai after insert on t1 for each row insert into t2 (a) values (new.a); create table t2 (a int unique); create trigger t2_ai after insert on t2 for each row @@ -740,6 +741,7 @@ drop procedure p1; create procedure p1(out x int) select max(a) from t2 into x; # XXX: bug. The prelocked list is not invalidated # and we keep opening table t1, whereas the procedure +# is now referring to table t2 execute stmt; ERROR HY000: View 'test.v1' references invalid table(s) or column(s) or function(s) or definer/invoker of view lack rights to use them call p_verify_reprepare_count(0); @@ -1007,7 +1009,7 @@ prepare stmt from "alter table t1 add column b int"; execute stmt; drop table t1; create table t1 (a1 int, a2 int); -# t1 has changed, and it's does not lead to reprepare +# t1 has changed, and it's does not lead to reprepare execute stmt; alter table t1 drop column b; execute stmt; @@ -1030,7 +1032,7 @@ test.t1 repair status OK drop table t1; create table t1 (a1 int, a2 int); insert into t1 values (1, 10), (2, 20), (3, 30); -# t1 has changed, and it's does not lead to reprepare +# t1 has changed, and it's does not lead to reprepare execute stmt; Table Op Msg_type Msg_text test.t1 repair status OK @@ -1333,7 +1335,7 @@ insert into t_27430_2 values (2, 2), (1234, 3), (1234, 4); -prepare stmt from +prepare stmt from "select oref, a, a in (select a from t_27430_1 where oref=t_27430_2.oref) Z from t_27430_2"; execute stmt; oref a Z @@ -1346,7 +1348,7 @@ SUCCESS drop table t_27430_1, t_27430_2; create table t_27430_1 (a int, oref int, key(a)); -insert into t_27430_1 values +insert into t_27430_1 values (1, 1), (1, NULL), (2, 3), @@ -1500,7 +1502,7 @@ drop procedure p_12093; deallocate prepare stmt_sf; deallocate prepare stmt_sp; ===================================================================== -Ensure that metadata validation is performed for every type of +Ensure that metadata validation is performed for every type of SQL statement where it is needed. ===================================================================== drop table if exists t1; @@ -1586,7 +1588,7 @@ deallocate prepare stmt; # CREATE TEMPORARY TABLE. This is a shortcoming of the current code, # but since validation is not strictly necessary, nothing is done # about it. -# Will be fixed as part of work on Bug#21431 "Incomplete support of +# Will be fixed as part of work on Bug#21431 "Incomplete support of # temporary tables" create table t1 (a int); insert into t1 (a) values (1); @@ -1839,11 +1841,11 @@ Variable_name Value drop table t1; deallocate prepare stmt; # -# SQLCOM_SHOW_ENGINE_STATUS, SQLCOM_SHOW_ENGINE_LOGS, +# SQLCOM_SHOW_ENGINE_STATUS, SQLCOM_SHOW_ENGINE_LOGS, # SQLCOM_SHOW_ENGINE_MUTEX, SQLCOM_SHOW_PROCESSLIST # # Currently can not have a where clause, need to be covered -# with tests +# with tests drop table if exists t1; create table t1 (a int); prepare stmt from "show engine all status where (1) in (select * from t1)"; @@ -2035,7 +2037,7 @@ prepare stmt from "alter view v1 as select 2"; ERROR HY000: This command is not supported in the prepared statement protocol yet drop view v1; # Cleanup -# +# drop temporary table if exists t1, t2, t3; drop table if exists t1, t2, t3, v1, v2; drop procedure if exists p_verify_reprepare_count; diff --git a/mysql-test/t/ps_ddl.test b/mysql-test/t/ps_ddl.test index 94791cd81f8..810dc568eee 100644 --- a/mysql-test/t/ps_ddl.test +++ b/mysql-test/t/ps_ddl.test @@ -181,7 +181,7 @@ select @message; # Unrelated trigger: reprepare may or may not happen, implementation dependent create trigger t1_bd before delete on t1 for each row set @message= old.a; - + set @val=5; execute stmt using @val; call p_verify_reprepare_count(1); @@ -229,7 +229,7 @@ execute stmt using @val; call p_verify_reprepare_count(1); select @message; ---ehco Test 6-e: removing a relevant trigger +--echo Test 6-e: removing a relevant trigger drop trigger t1_bi; @@ -338,7 +338,7 @@ call p_verify_reprepare_count(0); --echo # --echo # Sic: the insert went into t3, even though the view now --echo # points at t2. This is because neither the merged view ---echo # nor its prelocking list are affected by view DDL +--echo # nor its prelocking list are affected by view DDL --echo # The binary log is of course wrong, since it is not --echo # using prepared statements --echo # @@ -367,7 +367,7 @@ set @var=2; --echo # Since the dependent table is tracked in the prelocked --echo # list of the prepared statement, invalidation happens --echo # and the statement is re-prepared. This is an unnecessary ---echo # side effect, since the statement that *is* dependent +--echo # side effect, since the statement that *is* dependent --echo # on t2 definition is inside the trigger, and it is currently --echo # not reprepared (see the previous test case). execute stmt using @var; @@ -378,7 +378,7 @@ drop table t1,t2; --echo # Test 7-e: dependent TABLE TRIGGER has changed create table t1 (a int); -create trigger t1_ai after insert on t1 for each row +create trigger t1_ai after insert on t1 for each row insert into t2 (a) values (new.a); create table t2 (a int unique); create trigger t2_ai after insert on t2 for each row @@ -674,7 +674,7 @@ drop procedure p1; create procedure p1(out x int) select max(a) from t2 into x; --echo # XXX: bug. The prelocked list is not invalidated --echo # and we keep opening table t1, whereas the procedure ---ehco # is now referring to table t2 +--echo # is now referring to table t2 --error ER_VIEW_INVALID execute stmt; call p_verify_reprepare_count(0); @@ -702,7 +702,7 @@ execute stmt; call p_verify_reprepare_count(1); execute stmt; --echo # Test 18-d: dependent TABLE has changed -drop view v2; +drop view v2; create table v2 as select * from t1; execute stmt; call p_verify_reprepare_count(1); @@ -854,7 +854,7 @@ execute stmt; drop table t1; create table t1 (a1 int, a2 int); ---echo # t1 has changed, and it's does not lead to reprepare +--echo # t1 has changed, and it's does not lead to reprepare execute stmt; alter table t1 drop column b; @@ -881,7 +881,7 @@ drop table t1; create table t1 (a1 int, a2 int); insert into t1 values (1, 10), (2, 20), (3, 30); ---echo # t1 has changed, and it's does not lead to reprepare +--echo # t1 has changed, and it's does not lead to reprepare execute stmt; alter table t1 add column b varchar(50) default NULL; @@ -1190,16 +1190,16 @@ insert into t_27430_2 values (1234, 3), (1234, 4); -prepare stmt from +prepare stmt from "select oref, a, a in (select a from t_27430_1 where oref=t_27430_2.oref) Z from t_27430_2"; -execute stmt; +execute stmt; call p_verify_reprepare_count(0); drop table t_27430_1, t_27430_2; create table t_27430_1 (a int, oref int, key(a)); -insert into t_27430_1 values +insert into t_27430_1 values (1, 1), (1, NULL), (2, 3), @@ -1237,7 +1237,7 @@ insert into t_27690_1 values (1,1),(2,2); create table v_27690_1 as select * from t_27690_1; create table v_27690_2 as select * from t_27690_1; -prepare stmt from "select * from v_27690_1, v_27690_2"; +prepare stmt from "select * from v_27690_1, v_27690_2"; execute stmt; execute stmt; @@ -1327,17 +1327,17 @@ drop procedure p_12093_unrelated; connection default; --echo # XXX: bug ---error ER_SP_DOES_NOT_EXIST +--error ER_SP_DOES_NOT_EXIST execute stmt_sf; --echo # XXX: bug ---error ER_SP_DOES_NOT_EXIST +--error ER_SP_DOES_NOT_EXIST execute stmt_sp; --echo # XXX: bug ---error ER_SP_DOES_NOT_EXIST +--error ER_SP_DOES_NOT_EXIST execute stmt_sf; --echo # XXX: bug ---error ER_SP_DOES_NOT_EXIST +--error ER_SP_DOES_NOT_EXIST execute stmt_sp; call p_verify_reprepare_count(0); @@ -1351,7 +1351,7 @@ deallocate prepare stmt_sp; --echo ===================================================================== ---echo Ensure that metadata validation is performed for every type of +--echo Ensure that metadata validation is performed for every type of --echo SQL statement where it is needed. --echo ===================================================================== --disable_warnings @@ -1421,7 +1421,7 @@ deallocate prepare stmt; --echo # CREATE TEMPORARY TABLE. This is a shortcoming of the current code, --echo # but since validation is not strictly necessary, nothing is done --echo # about it. ---echo # Will be fixed as part of work on Bug#21431 "Incomplete support of +--echo # Will be fixed as part of work on Bug#21431 "Incomplete support of --echo # temporary tables" create table t1 (a int); insert into t1 (a) values (1); @@ -1697,12 +1697,12 @@ drop table t1; deallocate prepare stmt; --echo # ---echo # SQLCOM_SHOW_ENGINE_STATUS, SQLCOM_SHOW_ENGINE_LOGS, +--echo # SQLCOM_SHOW_ENGINE_STATUS, SQLCOM_SHOW_ENGINE_LOGS, --echo # SQLCOM_SHOW_ENGINE_MUTEX, SQLCOM_SHOW_PROCESSLIST --echo # --echo # Currently can not have a where clause, need to be covered ---echo # with tests +--echo # with tests --disable_warnings drop table if exists t1; @@ -1932,7 +1932,7 @@ prepare stmt from "alter view v1 as select 2"; drop view v1; --echo # Cleanup ---echo # +--echo # --disable_warnings drop temporary table if exists t1, t2, t3; drop table if exists t1, t2, t3, v1, v2; From c1444fce1b42beac74380cad263cbb5ca7df82c7 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 17 Apr 2008 23:02:01 +0400 Subject: [PATCH 6/8] Fix broken --cursor-protocol (all tests). sql/sql_cursor.cc: Fix broken --cursor-protocol (all tests). Initialize a variable that was for some reason not initialized. --- sql/sql_cursor.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sql/sql_cursor.cc b/sql/sql_cursor.cc index 5c4e93d4c74..d33680e1e0b 100644 --- a/sql/sql_cursor.cc +++ b/sql/sql_cursor.cc @@ -111,7 +111,8 @@ class Select_materialize: public select_union select_result *result; /**< the result object of the caller (PS or SP) */ public: Materialized_cursor *materialized_cursor; - Select_materialize(select_result *result_arg) :result(result_arg) {} + Select_materialize(select_result *result_arg) + :result(result_arg), materialized_cursor(0) {} virtual bool send_fields(List &list, uint flags); }; From 0a3d14a3950a8f07fecd4184f50ac3a781bc33dd Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 18 Apr 2008 21:18:53 +0200 Subject: [PATCH 7/8] Add tests + modifications according to review mysql-test/r/ps_ddl.result: Updated results mysql-test/t/ps_ddl.test: - add missing subtests - add comments + minor reformatting for better overview on content mysql-test/r/ps_ddl1.result: Expected results mysql-test/t/ps_ddl1.test: Script for tests which do not really fit into ps_ddl --- mysql-test/r/ps_ddl.result | 170 ++++++++++++- mysql-test/r/ps_ddl1.result | 482 ++++++++++++++++++++++++++++++++++++ mysql-test/t/ps_ddl.test | 140 ++++++++++- mysql-test/t/ps_ddl1.test | 398 +++++++++++++++++++++++++++++ 4 files changed, 1182 insertions(+), 8 deletions(-) create mode 100644 mysql-test/r/ps_ddl1.result create mode 100644 mysql-test/t/ps_ddl1.test diff --git a/mysql-test/r/ps_ddl.result b/mysql-test/r/ps_ddl.result index cca00dd4781..9e1db062368 100644 --- a/mysql-test/r/ps_ddl.result +++ b/mysql-test/r/ps_ddl.result @@ -35,6 +35,7 @@ Part 3: NOTHING -> VIEW transitions ===================================================================== Part 4: TABLE -> NOTHING transitions ===================================================================== +# Test 4-a: select ... from create table t1 (a int); prepare stmt from "select * from t1"; execute stmt; @@ -59,6 +60,32 @@ call p_verify_reprepare_count(0); SUCCESS deallocate prepare stmt; +# Test 4-b: TABLE -> NOTHING by renaming the table +create table t1 (a int); +prepare stmt from "select * from t1"; +execute stmt; +a +call p_verify_reprepare_count(0); +SUCCESS + +execute stmt; +a +call p_verify_reprepare_count(0); +SUCCESS + +rename table t1 to t2; +execute stmt; +ERROR 42S02: Table 'test.t1' doesn't exist +call p_verify_reprepare_count(0); +SUCCESS + +execute stmt; +ERROR 42S02: Table 'test.t1' doesn't exist +call p_verify_reprepare_count(0); +SUCCESS + +deallocate prepare stmt; +drop table t2; ===================================================================== Part 5: TABLE -> TABLE (DDL) transitions ===================================================================== @@ -445,6 +472,7 @@ deallocate prepare stmt; ===================================================================== Part 8: TABLE -> TEMPORARY TABLE transitions ===================================================================== +# Test 8-a: base table used recreated as temporary table create table t1 (a int); prepare stmt from "select * from t1"; execute stmt; @@ -463,6 +491,37 @@ SUCCESS drop table t1; deallocate prepare stmt; +# Test 8-b: temporary table has precedence over base table with same name +create table t1 (a int); +prepare stmt from 'select count(*) from t1'; +execute stmt; +count(*) +0 +call p_verify_reprepare_count(0); +SUCCESS + +execute stmt; +count(*) +0 +call p_verify_reprepare_count(0); +SUCCESS + +create temporary table t1 AS SELECT 1; +execute stmt; +count(*) +1 +call p_verify_reprepare_count(1); +SUCCESS + +execute stmt; +count(*) +1 +call p_verify_reprepare_count(0); +SUCCESS + +deallocate prepare stmt; +drop temporary table t1; +drop table t1; ===================================================================== Part 9: TABLE -> VIEW transitions ===================================================================== @@ -504,6 +563,7 @@ deallocate prepare stmt; ===================================================================== Part 11: TEMPORARY TABLE -> TABLE transitions ===================================================================== +# Test 11-a: temporary table replaced by base table create table t1 (a int); insert into t1 (a) value (1); create temporary table t1 (a int); @@ -525,6 +585,38 @@ a 1 drop table t1; deallocate prepare stmt; +# Test 11-b: temporary table has precedence over base table with same name +# temporary table disappears +create table t1 (a int); +create temporary table t1 as select 1 as a; +prepare stmt from "select count(*) from t1"; +execute stmt; +count(*) +1 +call p_verify_reprepare_count(0); +SUCCESS + +execute stmt; +count(*) +1 +call p_verify_reprepare_count(0); +SUCCESS + +drop temporary table t1; +execute stmt; +count(*) +0 +call p_verify_reprepare_count(1); +SUCCESS + +execute stmt; +count(*) +0 +call p_verify_reprepare_count(0); +SUCCESS + +deallocate prepare stmt; +drop table t1; ===================================================================== Part 12: TEMPORARY TABLE -> TEMPORARY TABLE (DDL) transitions ===================================================================== @@ -1505,11 +1597,11 @@ deallocate prepare stmt_sp; Ensure that metadata validation is performed for every type of SQL statement where it is needed. ===================================================================== -drop table if exists t1; -create table t1 (a int); # # SQLCOM_SELECT # +drop table if exists t1; +create table t1 (a int); prepare stmt from "select 1 as res from dual where (1) in (select * from t1)"; drop table t1; create table t1 (x int); @@ -1568,6 +1660,18 @@ call p_verify_reprepare_count(0); SUCCESS drop table t2; +create view t2 as select 1; +execute stmt; +Got one of the listed errors +call p_verify_reprepare_count(1); +SUCCESS + +execute stmt; +Got one of the listed errors +call p_verify_reprepare_count(0); +SUCCESS + +drop view t2; drop table t1; create table t1 (x varchar(20)); execute stmt; @@ -1582,6 +1686,18 @@ call p_verify_reprepare_count(0); SUCCESS drop table t2; +alter table t1 add column y decimal(10,3); +execute stmt; +call p_verify_reprepare_count(1); +SUCCESS + +select * from t2; +x y +drop table t2; +execute stmt; +call p_verify_reprepare_count(0); +SUCCESS + drop table t1; deallocate prepare stmt; # XXX: no validation of the first table in case of @@ -1636,6 +1752,56 @@ Note 1050 Table 't2' already exists call p_verify_reprepare_count(0); SUCCESS +drop table t1; +drop temporary table t2; +drop table t2; +deallocate prepare stmt; +create table t1 (a int); +prepare stmt from "create table t2 like t1"; +execute stmt; +call p_verify_reprepare_count(0); +SUCCESS + +drop table t2; +execute stmt; +call p_verify_reprepare_count(0); +SUCCESS + +drop table t2; +drop table t1; +execute stmt; +ERROR 42S02: Table 'test.t1' doesn't exist +call p_verify_reprepare_count(0); +SUCCESS + +execute stmt; +ERROR 42S02: Table 'test.t1' doesn't exist +call p_verify_reprepare_count(0); +SUCCESS + +create table t1 (x char(17)); +execute stmt; +call p_verify_reprepare_count(1); +SUCCESS + +drop table t2; +execute stmt; +call p_verify_reprepare_count(0); +SUCCESS + +drop table t2; +alter table t1 add column y time; +execute stmt; +call p_verify_reprepare_count(1); +SUCCESS + +select * from t2; +x y +drop table t2; +execute stmt; +call p_verify_reprepare_count(0); +SUCCESS + drop table t1; drop table t2; deallocate prepare stmt; diff --git a/mysql-test/r/ps_ddl1.result b/mysql-test/r/ps_ddl1.result new file mode 100644 index 00000000000..87abcd90590 --- /dev/null +++ b/mysql-test/r/ps_ddl1.result @@ -0,0 +1,482 @@ +drop temporary table if exists t1; +drop table if exists t1, t2; +drop procedure if exists p_verify_reprepare_count; +drop procedure if exists p1; +drop function if exists f1; +drop view if exists t1; +drop schema if exists mysqltest; +create procedure p_verify_reprepare_count(expected int) +begin +declare old_reprepare_count int default @reprepare_count; +select variable_value from +information_schema.session_status where +variable_name='com_stmt_reprepare' + into @reprepare_count; +if old_reprepare_count + expected <> @reprepare_count then +select concat("Expected: ", expected, +", actual: ", @reprepare_count - old_reprepare_count) +as "ERROR"; +else +select '' as "SUCCESS"; +end if; +end| +set @reprepare_count= 0; +flush status; +drop table if exists t1; +# Column added or dropped is not within the list of selected columns +# or table comment has changed. +# A reprepare is probably not needed. +create table t1 (a int, b int); +prepare stmt from "select a from t1"; +execute stmt; +a +call p_verify_reprepare_count(0); +SUCCESS + +alter table t1 add column c int; +execute stmt; +a +call p_verify_reprepare_count(1); +SUCCESS + +execute stmt; +a +call p_verify_reprepare_count(0); +SUCCESS + +alter table t1 drop column b; +execute stmt; +a +call p_verify_reprepare_count(1); +SUCCESS + +execute stmt; +a +call p_verify_reprepare_count(0); +SUCCESS + +alter table t1 comment "My best table"; +execute stmt; +a +call p_verify_reprepare_count(1); +SUCCESS + +execute stmt; +a +call p_verify_reprepare_count(0); +SUCCESS + +drop table t1; +deallocate prepare stmt; +# Selects using the table at various positions, inser,update ... +# + the table disappears +create table t1 (a int); +prepare stmt1 from "truncate t1"; +prepare stmt2 from "select 1 as my_column from t1"; +prepare stmt3 from "select 1 as my_column from (select * from t1) as t2"; +prepare stmt4 from +"select 1 as my_column from (select 1) as t2 where exists (select 1 from t1)"; +prepare stmt5 from "select * from (select 1 as b) as t2, t1"; +prepare stmt6 from "select * from t1 union all select 1.5"; +prepare stmt7 from "select 1 as my_column union all select 1 from t1"; +prepare stmt8 from "insert into t1 values(1),(2)"; +prepare stmt9 from "update t1 set a = 3 where a = 2"; +prepare stmt10 from "delete from t1 where a = 1"; +# Attention: Result logging is disabled. +execute stmt10; +execute stmt9; +execute stmt8; +execute stmt7; +execute stmt6; +execute stmt5; +execute stmt4; +execute stmt3; +execute stmt2; +execute stmt1; +call p_verify_reprepare_count(0); +SUCCESS + +drop table t1; +execute stmt10; +ERROR 42S02: Table 'test.t1' doesn't exist +execute stmt9; +ERROR 42S02: Table 'test.t1' doesn't exist +execute stmt8; +ERROR 42S02: Table 'test.t1' doesn't exist +execute stmt7; +ERROR 42S02: Table 'test.t1' doesn't exist +execute stmt6; +ERROR 42S02: Table 'test.t1' doesn't exist +execute stmt5; +ERROR 42S02: Table 'test.t1' doesn't exist +execute stmt4; +ERROR 42S02: Table 'test.t1' doesn't exist +execute stmt3; +ERROR 42S02: Table 'test.t1' doesn't exist +execute stmt2; +ERROR 42S02: Table 'test.t1' doesn't exist +execute stmt1; +ERROR 42S02: Table 'test.t1' doesn't exist +call p_verify_reprepare_count(0); +SUCCESS + +deallocate prepare stmt10; +deallocate prepare stmt9; +deallocate prepare stmt8; +deallocate prepare stmt7; +deallocate prepare stmt6; +deallocate prepare stmt5; +deallocate prepare stmt4; +deallocate prepare stmt3; +deallocate prepare stmt2; +deallocate prepare stmt1; +# Selects using the table at various positions, inser,update ... +# + layout change (drop column) which must cause a reprepare +create table t1 (a int, b int); +insert into t1 values(1,1),(2,2),(3,3); +create table t2 like t1; +insert into t1 values(2,2); +prepare stmt1 from "select a,b from t1"; +prepare stmt2 from "select a,b from (select * from t1) as t1"; +prepare stmt3 from "select * from t1 where a = 2 and b = 2"; +prepare stmt4 from "select * from t2 where (a,b) in (select * from t1)"; +prepare stmt5 from "select * from t1 union select * from t2"; +prepare stmt6 from "select * from t1 union all select * from t2"; +prepare stmt7 from "insert into t1 set a = 4, b = 4"; +prepare stmt8 from "insert into t1 select * from t2"; +# Attention: Result logging is disabled. +execute stmt8; +execute stmt7; +execute stmt6; +execute stmt5; +execute stmt4; +execute stmt3; +execute stmt2; +execute stmt1; +call p_verify_reprepare_count(0); +SUCCESS + +alter table t1 drop column b; +execute stmt8; +ERROR 21S01: Column count doesn't match value count at row 1 +call p_verify_reprepare_count(1); +SUCCESS + +execute stmt7; +ERROR 42S22: Unknown column 'b' in 'field list' +call p_verify_reprepare_count(1); +SUCCESS + +execute stmt6; +ERROR 21000: The used SELECT statements have a different number of columns +call p_verify_reprepare_count(1); +SUCCESS + +execute stmt5; +ERROR 21000: The used SELECT statements have a different number of columns +call p_verify_reprepare_count(1); +SUCCESS + +execute stmt4; +ERROR 21000: Operand should contain 2 column(s) +call p_verify_reprepare_count(1); +SUCCESS + +execute stmt3; +ERROR 42S22: Unknown column 'b' in 'where clause' +call p_verify_reprepare_count(1); +SUCCESS + +execute stmt2; +ERROR 42S22: Unknown column 'b' in 'field list' +call p_verify_reprepare_count(1); +SUCCESS + +execute stmt1; +ERROR 42S22: Unknown column 'b' in 'field list' +call p_verify_reprepare_count(1); +SUCCESS + +execute stmt8; +ERROR 21S01: Column count doesn't match value count at row 1 +call p_verify_reprepare_count(1); +ERROR +Expected: 1, actual: 0 +execute stmt7; +ERROR 42S22: Unknown column 'b' in 'field list' +call p_verify_reprepare_count(1); +SUCCESS + +execute stmt6; +ERROR 21000: The used SELECT statements have a different number of columns +call p_verify_reprepare_count(1); +SUCCESS + +execute stmt5; +ERROR 21000: The used SELECT statements have a different number of columns +call p_verify_reprepare_count(1); +SUCCESS + +execute stmt4; +ERROR 21000: Operand should contain 2 column(s) +call p_verify_reprepare_count(1); +SUCCESS + +execute stmt3; +ERROR 42S22: Unknown column 'b' in 'where clause' +call p_verify_reprepare_count(1); +SUCCESS + +execute stmt2; +ERROR 42S22: Unknown column 'b' in 'field list' +call p_verify_reprepare_count(1); +SUCCESS + +execute stmt1; +ERROR 42S22: Unknown column 'b' in 'field list' +call p_verify_reprepare_count(1); +SUCCESS + +# Why does the INSERT ... SELECT does not get a reprepare or is +# only the counter not incremented? +execute stmt8; +ERROR 21S01: Column count doesn't match value count at row 1 +call p_verify_reprepare_count(1); +ERROR +Expected: 1, actual: 0 +alter table t2 add column c int; +execute stmt8; +ERROR 21S01: Column count doesn't match value count at row 1 +call p_verify_reprepare_count(1); +SUCCESS + +deallocate prepare stmt8; +deallocate prepare stmt7; +deallocate prepare stmt6; +deallocate prepare stmt5; +deallocate prepare stmt4; +deallocate prepare stmt3; +deallocate prepare stmt2; +deallocate prepare stmt1; +drop table t1; +drop table t2; +# select AVG() + optimizer uses index meets loss of the index +create table t1 (a int, b int, primary key(b),unique index t1_unq_idx(a)); +insert into t1 set a = 0, b = 0; +insert into t1 select a + 1, b + 1 from t1; +insert into t1 select a + 2, b + 2 from t1; +insert into t1 select a + 4, b + 4 from t1; +insert into t1 select a + 8, b + 8 from t1; +# Optimizer strategy: Possible keys = NULL , Extra = Using index +prepare stmt from "select avg(a) from t1"; +execute stmt; +avg(a) +7.5000 +call p_verify_reprepare_count(0); +SUCCESS + +execute stmt; +avg(a) +7.5000 +call p_verify_reprepare_count(0); +SUCCESS + +alter table t1 drop index t1_unq_idx; +# Optimizer strategy: Possible keys = NULL , Extra = +execute stmt; +avg(a) +7.5000 +call p_verify_reprepare_count(1); +SUCCESS + +execute stmt; +avg(a) +7.5000 +call p_verify_reprepare_count(0); +SUCCESS + +# select AVG() + optimizer uses table scan meets a new index +alter table t1 add unique index t1_unq_idx(a); +# Optimizer strategy: Possible keys = NULL , Extra = Using index +execute stmt; +avg(a) +7.5000 +call p_verify_reprepare_count(1); +SUCCESS + +execute stmt; +avg(a) +7.5000 +call p_verify_reprepare_count(0); +SUCCESS + +deallocate prepare stmt; +drop table t1; +# table replaced by not updatable view - Insert +create table t1 (a int); +prepare stmt from "insert into t1 values(1)"; +execute stmt; +call p_verify_reprepare_count(0); +SUCCESS + +drop table t1; +create view t1 as select 1; +execute stmt; +ERROR HY000: The target table t1 of the INSERT is not insertable-into +call p_verify_reprepare_count(1); +SUCCESS + +drop view t1; +create table t2 (a int); +create view t1 as select * from t2 with check option; +execute stmt; +call p_verify_reprepare_count(1); +SUCCESS + +execute stmt; +call p_verify_reprepare_count(0); +SUCCESS + +select * from t1; +a +1 +1 +deallocate prepare stmt; +drop view t1; +drop table t2; +===================================================================== +Some freestyle tests +===================================================================== +create temporary table t1 as select 1 as a; +create procedure p1() +begin +drop temporary table t1; +end| +create function f1() returns int +begin +call p1(); +return 1; +end| +prepare stmt from "select f1() as my_column, a from t1"; +execute stmt; +ERROR HY000: Can't reopen table: 't1' +call p_verify_reprepare_count(0); +SUCCESS + +select * from t1; +a +1 +prepare stmt from "select a, f1() as my_column from t1"; +execute stmt; +ERROR HY000: Can't reopen table: 't1' +call p_verify_reprepare_count(0); +SUCCESS + +select * from t1; +a +1 +prepare stmt from "select f1() as my_column, count(*) from t1"; +execute stmt; +ERROR HY000: Can't reopen table: 't1' +call p_verify_reprepare_count(0); +SUCCESS + +select * from t1; +a +1 +prepare stmt from "select count(*), f1() as my_column from t1"; +execute stmt; +ERROR HY000: Can't reopen table: 't1' +call p_verify_reprepare_count(0); +SUCCESS + +select * from t1; +a +1 +# Execute fails, no drop of temporary table +prepare stmt from "select 1 as my_column from (select 1) as t2 + where exists (select f1() from t1)"; +execute stmt; +my_column +1 +call p_verify_reprepare_count(0); +SUCCESS + +execute stmt; +my_column +1 +call p_verify_reprepare_count(0); +SUCCESS + +select * from t1; +a +1 +# Execute drops temporary table +prepare stmt from "select f1()"; +execute stmt; +f1() +1 +call p_verify_reprepare_count(0); +SUCCESS + +execute stmt; +ERROR 42S02: Unknown table 't1' +call p_verify_reprepare_count(0); +SUCCESS + +drop function f1; +drop procedure p1; +deallocate prepare stmt; +# Execute fails, temporary table is not replaced by another +create temporary table t1 as select 1 as a; +create procedure p1() +begin +drop temporary table t1; +create temporary table t1 as select 'abc' as a; +end| +create function f1() returns int +begin +call p1(); +return 1; +end| +prepare stmt from "select count(*), f1() as my_column from t1"; +execute stmt; +ERROR HY000: Can't reopen table: 't1' +call p_verify_reprepare_count(0); +SUCCESS + +select * from t1; +a +1 +deallocate prepare stmt; +prepare stmt from "call p1"; +execute stmt; +drop procedure p1; +create schema mysqltest; +create procedure mysqltest.p1() +begin +drop schema mysqltest; +create schema mysqltest; +end| +execute stmt; +ERROR 42000: PROCEDURE test.p1 does not exist +call p_verify_reprepare_count(0); +SUCCESS + +execute stmt; +ERROR 42000: PROCEDURE test.p1 does not exist +call p_verify_reprepare_count(0); +SUCCESS + +deallocate prepare stmt; +drop schema mysqltest; +drop temporary table t1; +# Cleanup +# +drop temporary table if exists t1; +drop table if exists t1, t2; +drop procedure if exists p_verify_reprepare_count; +drop procedure if exists p1; +drop function if exists f1; +drop view if exists t1; +drop schema if exists mysqltest; diff --git a/mysql-test/t/ps_ddl.test b/mysql-test/t/ps_ddl.test index 810dc568eee..e19e73df3ac 100644 --- a/mysql-test/t/ps_ddl.test +++ b/mysql-test/t/ps_ddl.test @@ -104,6 +104,7 @@ prepare stmt from "select * from t1"; --echo Part 4: TABLE -> NOTHING transitions --echo ===================================================================== +--echo # Test 4-a: select ... from
create table t1 (a int); prepare stmt from "select * from t1"; @@ -121,6 +122,25 @@ execute stmt; call p_verify_reprepare_count(0); deallocate prepare stmt; +--echo # Test 4-b: TABLE -> NOTHING by renaming the table +create table t1 (a int); +prepare stmt from "select * from t1"; +execute stmt; +call p_verify_reprepare_count(0); +execute stmt; +call p_verify_reprepare_count(0); + +rename table t1 to t2; +--error ER_NO_SUCH_TABLE +execute stmt; +call p_verify_reprepare_count(0); +--error ER_NO_SUCH_TABLE +execute stmt; +call p_verify_reprepare_count(0); + +deallocate prepare stmt; +drop table t2; + --echo ===================================================================== --echo Part 5: TABLE -> TABLE (DDL) transitions --echo ===================================================================== @@ -143,11 +163,11 @@ call p_verify_reprepare_count(0); drop table t1; deallocate prepare stmt; + --echo ===================================================================== --echo Part 6: TABLE -> TABLE (TRIGGER) transitions --echo ===================================================================== - --echo # Test 6-a: adding a relevant trigger create table t1 (a int); @@ -414,6 +434,7 @@ deallocate prepare stmt; --echo Part 8: TABLE -> TEMPORARY TABLE transitions --echo ===================================================================== +--echo # Test 8-a: base table used recreated as temporary table create table t1 (a int); prepare stmt from "select * from t1"; @@ -430,6 +451,25 @@ call p_verify_reprepare_count(0); drop table t1; deallocate prepare stmt; +--echo # Test 8-b: temporary table has precedence over base table with same name +create table t1 (a int); +prepare stmt from 'select count(*) from t1'; +execute stmt; +call p_verify_reprepare_count(0); +execute stmt; +call p_verify_reprepare_count(0); + +create temporary table t1 AS SELECT 1; +execute stmt; +call p_verify_reprepare_count(1); +execute stmt; +call p_verify_reprepare_count(0); + +deallocate prepare stmt; +drop temporary table t1; +drop table t1; + + --echo ===================================================================== --echo Part 9: TABLE -> VIEW transitions --echo ===================================================================== @@ -471,6 +511,7 @@ deallocate prepare stmt; --echo Part 11: TEMPORARY TABLE -> TABLE transitions --echo ===================================================================== +--echo # Test 11-a: temporary table replaced by base table create table t1 (a int); insert into t1 (a) value (1); create temporary table t1 (a int); @@ -488,6 +529,27 @@ select * from t1; drop table t1; deallocate prepare stmt; + +--echo # Test 11-b: temporary table has precedence over base table with same name +--echo # temporary table disappears +create table t1 (a int); +create temporary table t1 as select 1 as a; +prepare stmt from "select count(*) from t1"; +execute stmt; +call p_verify_reprepare_count(0); +execute stmt; +call p_verify_reprepare_count(0); + +drop temporary table t1; +execute stmt; +call p_verify_reprepare_count(1); +execute stmt; +call p_verify_reprepare_count(0); + +deallocate prepare stmt; +drop table t1; + + --echo ===================================================================== --echo Part 12: TEMPORARY TABLE -> TEMPORARY TABLE (DDL) transitions --echo ===================================================================== @@ -1354,13 +1416,15 @@ deallocate prepare stmt_sp; --echo Ensure that metadata validation is performed for every type of --echo SQL statement where it is needed. --echo ===================================================================== + +--echo # +--echo # SQLCOM_SELECT +--echo # + --disable_warnings drop table if exists t1; --enable_warnings create table t1 (a int); ---echo # ---echo # SQLCOM_SELECT ---echo # prepare stmt from "select 1 as res from dual where (1) in (select * from t1)"; drop table t1; create table t1 (x int); @@ -1368,16 +1432,16 @@ execute stmt; drop table t1; deallocate prepare stmt; call p_verify_reprepare_count(1); + --echo # --echo # SQLCOM_CREATE_TABLE --echo # + --disable_warnings drop table if exists t1; drop table if exists t2; --enable_warnings - create table t1 (a int); - prepare stmt from 'create table t2 as select * from t1'; execute stmt; drop table t2; @@ -1385,6 +1449,7 @@ execute stmt; drop table t2; execute stmt; call p_verify_reprepare_count(0); +# Base table with name of table to be created exists --error ER_TABLE_EXISTS_ERROR execute stmt; call p_verify_reprepare_count(1); @@ -1392,6 +1457,7 @@ call p_verify_reprepare_count(1); execute stmt; call p_verify_reprepare_count(0); drop table t2; +# Temporary table with name of table to be created exists create temporary table t2 (a int); --error ER_TABLE_EXISTS_ERROR execute stmt; @@ -1406,7 +1472,23 @@ drop table t2; execute stmt; call p_verify_reprepare_count(0); drop table t2; +# View with name of table to be created exists +# Attention: +# We cannot print the error message because it contains a random filename. +# Example: 1050: Table '/var/tmp/#sql_6979_0' already exists +# Therefore we mangle it via +# "--error ER_TABLE_EXISTS_ERROR,9999" (9999 is currently not used) +# to "Got one of the listed errors". +create view t2 as select 1; +--error ER_TABLE_EXISTS_ERROR,9999 +execute stmt; +call p_verify_reprepare_count(1); +--error ER_TABLE_EXISTS_ERROR,9999 +execute stmt; +call p_verify_reprepare_count(0); +drop view t2; drop table t1; +# Table to be used recreated (drop,create) with different layout create table t1 (x varchar(20)); execute stmt; call p_verify_reprepare_count(1); @@ -1415,6 +1497,14 @@ drop table t2; execute stmt; call p_verify_reprepare_count(0); drop table t2; +# Table to be used has a modified (alter table) layout +alter table t1 add column y decimal(10,3); +execute stmt; +call p_verify_reprepare_count(1); +select * from t2; +drop table t2; +execute stmt; +call p_verify_reprepare_count(0); drop table t1; deallocate prepare stmt; --echo # XXX: no validation of the first table in case of @@ -1445,9 +1535,47 @@ call p_verify_reprepare_count(1); execute stmt; call p_verify_reprepare_count(0); drop table t1; +drop temporary table t2; drop table t2; deallocate prepare stmt; +create table t1 (a int); +prepare stmt from "create table t2 like t1"; +execute stmt; +call p_verify_reprepare_count(0); +drop table t2; +execute stmt; +call p_verify_reprepare_count(0); +drop table t2; +# Table to be used does not exist +drop table t1; +--error ER_NO_SUCH_TABLE +execute stmt; +call p_verify_reprepare_count(0); +--error ER_NO_SUCH_TABLE +execute stmt; +call p_verify_reprepare_count(0); +# Table to be used recreated (drop,create) with different layout +create table t1 (x char(17)); +execute stmt; +call p_verify_reprepare_count(1); +drop table t2; +execute stmt; +call p_verify_reprepare_count(0); +drop table t2; +# Table to be used has a modified (alter table) layout +alter table t1 add column y time; +execute stmt; +call p_verify_reprepare_count(1); +select * from t2; +drop table t2; +execute stmt; +call p_verify_reprepare_count(0); +drop table t1; +drop table t2; +deallocate prepare stmt; + + --echo # --echo # SQLCOM_UPDATE --echo # diff --git a/mysql-test/t/ps_ddl1.test b/mysql-test/t/ps_ddl1.test new file mode 100644 index 00000000000..0145d445a14 --- /dev/null +++ b/mysql-test/t/ps_ddl1.test @@ -0,0 +1,398 @@ +# +# Testing the behavior of 'PREPARE', 'DDL', 'EXECUTE' scenarios +# +# There are several subtests which are probably "superfluous" because a DDL +# statement before the EXECUTE contained a keyword +# or action (Example: Alter) which causes that all prepared statements using +# the modified object are reprepared before execution. +# Please do not delete these subtests if they disturb. Just disable them by +# if (0) +# { +# +# }. +# There might be future optimisations of the server which decrease the amount +# of unneeded reprepares or skip unneeded prepare steps and than these subtests +# might become valuable. +# Example: +# Every preceding ALTER TABLE seems to cause a reprepare. +# But if the ALTER only changed the table comment ... +# +# Created: 2008-04-18 mleich +# + +--disable_warnings +drop temporary table if exists t1; +drop table if exists t1, t2; +drop procedure if exists p_verify_reprepare_count; +drop procedure if exists p1; +drop function if exists f1; +drop view if exists t1; +drop schema if exists mysqltest; +--enable_warnings + +delimiter |; +create procedure p_verify_reprepare_count(expected int) +begin + declare old_reprepare_count int default @reprepare_count; + + select variable_value from + information_schema.session_status where + variable_name='com_stmt_reprepare' + into @reprepare_count; + + if old_reprepare_count + expected <> @reprepare_count then + select concat("Expected: ", expected, + ", actual: ", @reprepare_count - old_reprepare_count) + as "ERROR"; + else + select '' as "SUCCESS"; + end if; +end| +delimiter ;| +set @reprepare_count= 0; +flush status; + +--disable_warnings +drop table if exists t1; +--disable_warnings + +--echo # Column added or dropped is not within the list of selected columns +--echo # or table comment has changed. +--echo # A reprepare is probably not needed. +create table t1 (a int, b int); +prepare stmt from "select a from t1"; +execute stmt; +call p_verify_reprepare_count(0); +alter table t1 add column c int; +execute stmt; +call p_verify_reprepare_count(1); +execute stmt; +call p_verify_reprepare_count(0); +alter table t1 drop column b; +execute stmt; +call p_verify_reprepare_count(1); +execute stmt; +call p_verify_reprepare_count(0); +alter table t1 comment "My best table"; +execute stmt; +call p_verify_reprepare_count(1); +execute stmt; +call p_verify_reprepare_count(0); +drop table t1; +deallocate prepare stmt; + +--echo # Selects using the table at various positions, inser,update ... +--echo # + the table disappears +create table t1 (a int); +# Attention: +# "truncate" must have the first position (= executed as last prepared +# statement), because it recreates the table which has leads to reprepare +# (is this really needed) of all statements. +prepare stmt1 from "truncate t1"; +prepare stmt2 from "select 1 as my_column from t1"; +prepare stmt3 from "select 1 as my_column from (select * from t1) as t2"; +prepare stmt4 from +"select 1 as my_column from (select 1) as t2 where exists (select 1 from t1)"; +prepare stmt5 from "select * from (select 1 as b) as t2, t1"; +prepare stmt6 from "select * from t1 union all select 1.5"; +prepare stmt7 from "select 1 as my_column union all select 1 from t1"; +prepare stmt8 from "insert into t1 values(1),(2)"; +prepare stmt9 from "update t1 set a = 3 where a = 2"; +prepare stmt10 from "delete from t1 where a = 1"; +let ps_stmt_count= 10; +--echo # Attention: Result logging is disabled. +# Checks of correct results of statements are not the goal of this test. +let $num= $ps_stmt_count; +while ($num) +{ + --disable_result_log + eval execute stmt$num; + --enable_result_log + dec $num; +} +# There was no reprepare needed, because none of the objects has changed. +call p_verify_reprepare_count(0); +drop table t1; +let $num= $ps_stmt_count; +while ($num) +{ + --error ER_NO_SUCH_TABLE + eval execute stmt$num; + dec $num; +} +# There was no reprepare needed, because the statement is no more applicable. +call p_verify_reprepare_count(0); +let $num= $ps_stmt_count; +while ($num) +{ + eval deallocate prepare stmt$num; + dec $num; +} + +--echo # Selects using the table at various positions, inser,update ... +--echo # + layout change (drop column) which must cause a reprepare +create table t1 (a int, b int); +insert into t1 values(1,1),(2,2),(3,3); +create table t2 like t1; +insert into t1 values(2,2); +prepare stmt1 from "select a,b from t1"; +prepare stmt2 from "select a,b from (select * from t1) as t1"; +prepare stmt3 from "select * from t1 where a = 2 and b = 2"; +prepare stmt4 from "select * from t2 where (a,b) in (select * from t1)"; +prepare stmt5 from "select * from t1 union select * from t2"; +prepare stmt6 from "select * from t1 union all select * from t2"; +prepare stmt7 from "insert into t1 set a = 4, b = 4"; +prepare stmt8 from "insert into t1 select * from t2"; +let ps_stmt_count= 8; +--echo # Attention: Result logging is disabled. +# Checks of correct results of statements are not the goal of this test. +let $num= $ps_stmt_count; +while ($num) +{ + --disable_result_log + eval execute stmt$num; + --enable_result_log + dec $num; +} +call p_verify_reprepare_count(0); +alter table t1 drop column b; +--disable_abort_on_error +let $num= $ps_stmt_count; +while ($num) +{ + eval execute stmt$num; + # A reprepare is needed, because layout change of t1 affects statement. + call p_verify_reprepare_count(1); + dec $num; +} +let $num= $ps_stmt_count; +while ($num) +{ + eval execute stmt$num; + call p_verify_reprepare_count(1); + dec $num; +} +--echo # Why does the INSERT ... SELECT does not get a reprepare or is +--echo # only the counter not incremented? +eval execute stmt8; +call p_verify_reprepare_count(1); +--enable_abort_on_error +alter table t2 add column c int; +--error ER_WRONG_VALUE_COUNT_ON_ROW +eval execute stmt8; +call p_verify_reprepare_count(1); +let $num= $ps_stmt_count; +while ($num) +{ + eval deallocate prepare stmt$num; + dec $num; +} +drop table t1; +drop table t2; + + +--echo # select AVG() + optimizer uses index meets loss of the index +create table t1 (a int, b int, primary key(b),unique index t1_unq_idx(a)); +# We need an index which is not converted to PRIMARY KEY (becomes in +# case of InnoDB the key used for table clustering). +insert into t1 set a = 0, b = 0; +insert into t1 select a + 1, b + 1 from t1; +insert into t1 select a + 2, b + 2 from t1; +insert into t1 select a + 4, b + 4 from t1; +insert into t1 select a + 8, b + 8 from t1; +# "using index" optimizer strategy is intended +let $possible_keys= + query_get_value(explain select avg(a) from t1, possible_keys, 1); +let $extra= + query_get_value(explain select avg(a) from t1, Extra, 1); +--echo # Optimizer strategy: Possible keys = $possible_keys , Extra = $extra +prepare stmt from "select avg(a) from t1"; +execute stmt; +call p_verify_reprepare_count(0); +execute stmt; +call p_verify_reprepare_count(0); + +alter table t1 drop index t1_unq_idx; +let $possible_keys= + query_get_value(explain select avg(a) from t1, possible_keys, 1); +let $extra= + query_get_value(explain select avg(a) from t1, Extra, 1); +--echo # Optimizer strategy: Possible keys = $possible_keys , Extra = $extra +execute stmt; +call p_verify_reprepare_count(1); +execute stmt; +call p_verify_reprepare_count(0); + + +--echo # select AVG() + optimizer uses table scan meets a new index +alter table t1 add unique index t1_unq_idx(a); +let $possible_keys= + query_get_value(explain select avg(a) from t1, possible_keys, 1); +let $extra= + query_get_value(explain select avg(a) from t1, Extra, 1); +--echo # Optimizer strategy: Possible keys = $possible_keys , Extra = $extra +execute stmt; +call p_verify_reprepare_count(1); +execute stmt; +call p_verify_reprepare_count(0); + +deallocate prepare stmt; +drop table t1; + + +--echo # table replaced by not updatable view - Insert +create table t1 (a int); +prepare stmt from "insert into t1 values(1)"; +execute stmt; +call p_verify_reprepare_count(0); + +drop table t1; +create view t1 as select 1; +--error ER_NON_INSERTABLE_TABLE +execute stmt; +call p_verify_reprepare_count(1); + +drop view t1; +create table t2 (a int); +create view t1 as select * from t2 with check option; +execute stmt; +call p_verify_reprepare_count(1); +execute stmt; +call p_verify_reprepare_count(0); +select * from t1; + +deallocate prepare stmt; +drop view t1; +drop table t2; + + +--echo ===================================================================== +--echo Some freestyle tests +--echo ===================================================================== + +create temporary table t1 as select 1 as a; +delimiter |; +create procedure p1() +begin + drop temporary table t1; +end| +create function f1() returns int +begin + call p1(); + return 1; +end| +delimiter ;| + +prepare stmt from "select f1() as my_column, a from t1"; +--error ER_CANT_REOPEN_TABLE +execute stmt; +call p_verify_reprepare_count(0); +select * from t1; + +prepare stmt from "select a, f1() as my_column from t1"; +--error ER_CANT_REOPEN_TABLE +execute stmt; +call p_verify_reprepare_count(0); +select * from t1; + +prepare stmt from "select f1() as my_column, count(*) from t1"; +--error ER_CANT_REOPEN_TABLE +execute stmt; +call p_verify_reprepare_count(0); +select * from t1; + +prepare stmt from "select count(*), f1() as my_column from t1"; +--error ER_CANT_REOPEN_TABLE +execute stmt; +call p_verify_reprepare_count(0); +select * from t1; + + +--echo # Execute fails, no drop of temporary table +prepare stmt from "select 1 as my_column from (select 1) as t2 + where exists (select f1() from t1)"; +execute stmt; +call p_verify_reprepare_count(0); +execute stmt; +call p_verify_reprepare_count(0); +select * from t1; + +--echo # Execute drops temporary table +prepare stmt from "select f1()"; +execute stmt; +call p_verify_reprepare_count(0); +--error ER_BAD_TABLE_ERROR +execute stmt; +call p_verify_reprepare_count(0); + +drop function f1; +drop procedure p1; +deallocate prepare stmt; + +--echo # Execute fails, temporary table is not replaced by another +create temporary table t1 as select 1 as a; +delimiter |; +create procedure p1() +begin + drop temporary table t1; + create temporary table t1 as select 'abc' as a; +end| +create function f1() returns int +begin + call p1(); + return 1; +end| +delimiter ;| +prepare stmt from "select count(*), f1() as my_column from t1"; +--error ER_CANT_REOPEN_TABLE +execute stmt; +call p_verify_reprepare_count(0); +select * from t1; +deallocate prepare stmt; + +prepare stmt from "call p1"; +execute stmt; +drop procedure p1; +create schema mysqltest; +delimiter |; +create procedure mysqltest.p1() +begin + drop schema mysqltest; + create schema mysqltest; +end| +delimiter ;| +--error ER_SP_DOES_NOT_EXIST +execute stmt; +call p_verify_reprepare_count(0); +--error ER_SP_DOES_NOT_EXIST +execute stmt; +call p_verify_reprepare_count(0); +deallocate prepare stmt; +drop schema mysqltest; +drop temporary table t1; + + +# Bug#36089 drop temp table in SP called by function, crash +# Note: A non prepared "select 1 from t1 having count(*) = f1();" is sufficient. +if (0) +{ +create temporary table t1 as select 1 as a; +prepare stmt from "select 1 from t1 having count(*) = f1()"; +execute stmt; +call p_verify_reprepare_count(0); +deallocate prepare stmt; +drop temporary table t1; +} + + +--echo # Cleanup +--echo # +--disable_warnings +drop temporary table if exists t1; +drop table if exists t1, t2; +drop procedure if exists p_verify_reprepare_count; +drop procedure if exists p1; +drop function if exists f1; +drop view if exists t1; +drop schema if exists mysqltest; +--enable_warnings From 642b4c27c5920da3285d2f68fd77090057e84b57 Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 19 Apr 2008 14:27:17 +0400 Subject: [PATCH 8/8] Attempt to fix a sporadic failure of innodb_mysql.test --- mysql-test/include/mix1.inc | 1 + 1 file changed, 1 insertion(+) diff --git a/mysql-test/include/mix1.inc b/mysql-test/include/mix1.inc index a4cd6019be8..fdac8deb87e 100644 --- a/mysql-test/include/mix1.inc +++ b/mysql-test/include/mix1.inc @@ -1309,6 +1309,7 @@ SELECT * FROM t1; --echo # Switch to connection con2 connection con2; +--reap SELECT * FROM t1; --echo # Switch to connection con1