summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/my_global.h2
-rw-r--r--mysql-test/r/ps_1general.result19
-rw-r--r--mysql-test/r/query_cache_merge.result1
-rw-r--r--mysql-test/r/trigger.result1
-rw-r--r--mysql-test/t/disabled.def1
-rw-r--r--mysql-test/t/ps_1general.test5
-rw-r--r--mysql-test/t/query_cache_merge.test10
-rw-r--r--mysql-test/t/trigger.test7
-rw-r--r--sql/item.cc39
-rw-r--r--sql/item.h1
-rw-r--r--sql/my_decimal.h8
-rw-r--r--sql/mysql_priv.h40
-rw-r--r--sql/mysqld.cc9
-rw-r--r--sql/share/errmsg.txt6
-rw-r--r--sql/sql_base.cc110
-rw-r--r--sql/sql_class.cc10
-rw-r--r--sql/sql_class.h86
-rw-r--r--sql/sql_parse.cc25
-rw-r--r--sql/sql_prepare.cc516
-rw-r--r--sql/table.h130
-rw-r--r--tests/mysql_client_test.c120
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
@@ -789,6 +838,20 @@ 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<Item> 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);
-
- 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;
+ (void) stmt->execute_loop(&expanded_query, FALSE, NULL, NULL);
-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<Item> &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<Item> it_org(lex->select_lex.item_list);
+ List_iterator_fast<Item> 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 }
};