revision-id: 5e63f616a7ed511e6e69581f6659f60f1ec8633b (mariadb-10.1.37-9-g5e63f616a7e) parent(s): 1368a63589d0b4900f7d7efb57444c4ea34e6c26 author: Oleksandr Byelkin committer: Oleksandr Byelkin timestamp: 2018-11-13 10:10:09 +0100 message: MDEV-17124: mariadb 10.1.34, views and prepared statements: ERROR 1615 (HY000): Prepared statement needs to be re-prepared The problem is that if table definition cache (TDC) is full of real tables which are in tables cache, view definition can not stay there so will me removed by its own underlying tables. In situation above old mechanism of detection matching definition in PS and current version always require reprepare and so prevent executing the PS. One work arount is to increase TDC, other - improve version check for views (which is done here). Now in suspiciouse cases we check MD5 of the view to be sure that version really have chenged. --- mysql-test/r/view.result | 28 ++++++++++++++++++++++++++++ mysql-test/t/view.test | 39 +++++++++++++++++++++++++++++++++++++++ sql/parse_file.cc | 17 +++++++++++++++++ sql/parse_file.h | 4 +++- sql/sql_view.cc | 41 +++++++++++++++++++++++++++++++++++++++++ sql/sql_view.h | 1 + sql/table.cc | 26 ++++++++++++++++++++++++++ sql/table.h | 8 +------- 8 files changed, 156 insertions(+), 8 deletions(-) diff --git a/mysql-test/r/view.result b/mysql-test/r/view.result index e8c96e49977..030d0f0c520 100644 --- a/mysql-test/r/view.result +++ b/mysql-test/r/view.result @@ -6259,5 +6259,33 @@ t1col1 t1col2 t1col3 drop view v1; drop table t1,t2; # +# MDEV-17124: mariadb 10.1.34, views and prepared statements: +# ERROR 1615 (HY000): Prepared statement needs to be re-prepared +# +set @tdc= @@table_definition_cache, @tc= @@table_open_cache; +set global table_definition_cache= 400, table_open_cache= 400; +create table tt (a int, primary key(a)) engine=MyISAM; +create view v as select * from tt; +insert into tt values(1),(2),(3),(4); +prepare stmt from 'select * from tt'; +#fill table definition cache +execute stmt; +a +1 +2 +3 +4 +prepare stmt from 'select * from v'; +execute stmt; +a +1 +2 +3 +4 +drop database db; +drop view v; +drop table tt; +set global table_definition_cache= @tdc, table_open_cache= @tc; +# # End of 10.1 tests # diff --git a/mysql-test/t/view.test b/mysql-test/t/view.test index 6ff226d738f..ee587181aa4 100644 --- a/mysql-test/t/view.test +++ b/mysql-test/t/view.test @@ -6080,6 +6080,45 @@ select * from v1; drop view v1; drop table t1,t2; +--echo # +--echo # MDEV-17124: mariadb 10.1.34, views and prepared statements: +--echo # ERROR 1615 (HY000): Prepared statement needs to be re-prepared +--echo # + +set @tdc= @@table_definition_cache, @tc= @@table_open_cache; +set global table_definition_cache= 400, table_open_cache= 400; + +create table tt (a int, primary key(a)) engine=MyISAM; +create view v as select * from tt; +insert into tt values(1),(2),(3),(4); + +prepare stmt from 'select * from tt'; +--echo #fill table definition cache +--disable_query_log +--disable_result_log +create database db; +use db; +--let $tables=401 +while ($tables) +{ + --eval create table t$tables (i int) engine=MyISAM + --eval select * from t$tables + --dec $tables +} + +use test; + +--enable_query_log +--enable_result_log +execute stmt; +prepare stmt from 'select * from v'; +execute stmt; + +# Cleanup +drop database db; +drop view v; +drop table tt; +set global table_definition_cache= @tdc, table_open_cache= @tc; --echo # --echo # End of 10.1 tests --echo # diff --git a/sql/parse_file.cc b/sql/parse_file.cc index f3dab4f7b2f..6f188660407 100644 --- a/sql/parse_file.cc +++ b/sql/parse_file.cc @@ -145,6 +145,7 @@ write_parameter(IO_CACHE *file, uchar* base, File_option *parameter) switch (parameter->type) { case FILE_OPTIONS_STRING: + case FILE_OPTIONS_FIXSTRING: { LEX_STRING *val_s= (LEX_STRING *)(base + parameter->offset); if (my_b_write(file, (const uchar *)val_s->str, val_s->length)) @@ -830,6 +831,22 @@ File_parser::parse(uchar* base, MEM_ROOT *mem_root, } ptr= eol+1; break; + case FILE_OPTIONS_FIXSTRING: + { + /* string have to be allocated already and length set */ + LEX_STRING *val= (LEX_STRING *)(base + parameter->offset); + DBUG_ASSERT(val->length != 0); + if (ptr[val->length] != '\n') + { + my_error(ER_FPARSER_ERROR_IN_PARAMETER, MYF(0), + parameter->name.str, line); + DBUG_RETURN(TRUE); + } + memcpy(val->str, ptr, val->length); + val->str[val->length]= '\0'; + ptr+= (val->length + 1); + break; + } case FILE_OPTIONS_TIMESTAMP: { /* string have to be allocated already */ diff --git a/sql/parse_file.h b/sql/parse_file.h index 87917dbd71b..28f4070b437 100644 --- a/sql/parse_file.h +++ b/sql/parse_file.h @@ -36,8 +36,10 @@ enum file_opt_type { allocated with length 20 (19+1) */ FILE_OPTIONS_STRLIST, /**< list of escaped strings (List<LEX_STRING>) */ - FILE_OPTIONS_ULLLIST /**< list of ulonglong values + FILE_OPTIONS_ULLLIST, /**< list of ulonglong values (List<ulonglong>) */ + + FILE_OPTIONS_FIXSTRING /**< fixed length String (LEX_STRING) */ }; struct File_option diff --git a/sql/sql_view.cc b/sql/sql_view.cc index 9a9309a133b..ee169de4c93 100644 --- a/sql/sql_view.cc +++ b/sql/sql_view.cc @@ -782,6 +782,16 @@ static File_option view_parameters[]= FILE_OPTIONS_STRING} }; + +static File_option view_md5_parameters[]= +{ + + {{ C_STRING_WITH_LEN("md5")}, 0, FILE_OPTIONS_FIXSTRING}, + {{NullS, 0}, 0, FILE_OPTIONS_STRING} +}; + + + static LEX_STRING view_file_type[]= {{(char*) STRING_WITH_LEN("VIEW") }}; @@ -1125,7 +1135,38 @@ static int mysql_register_view(THD *thd, TABLE_LIST *view, DBUG_RETURN(error); } +#define MD5_LEN 32 +/** + Check is TABLE_LEST and SHARE match + @param[in] view TABLE_LIST of the view + @param[in] share Share object of view + + @return false on error or misspatch +*/ +bool mariadb_view_version_check(TABLE_LIST *view, TABLE_SHARE *share) +{ + LEX_STRING md5; + char md5_buffer[MD5_LEN + 1]; + md5.str= md5_buffer; + md5.length= MD5_LEN; + + /* + Check that both were views (view->is_view() could not be checked + because it is not opened). + */ + if (!share->is_view || view->md5.length != MD5_LEN) + return FALSE; + + DBUG_ASSERT(share->view_def != NULL); + if (share->view_def->parse((uchar*)&md5, NULL, + view_md5_parameters, + 1, + &file_parser_dummy_hook)) + return FALSE; + DBUG_ASSERT(md5.length == MD5_LEN); + return (strncmp(md5.str, view->md5.str, MD5_LEN) == 0); +} /** read VIEW .frm and create structures diff --git a/sql/sql_view.h b/sql/sql_view.h index ce83dc656ad..1685169420d 100644 --- a/sql/sql_view.h +++ b/sql/sql_view.h @@ -37,6 +37,7 @@ bool mysql_create_view(THD *thd, TABLE_LIST *view, bool mysql_make_view(THD *thd, TABLE_SHARE *share, TABLE_LIST *table, bool open_view_no_parse); +bool mariadb_view_version_check(TABLE_LIST *view, TABLE_SHARE *share); bool mysql_drop_view(THD *thd, TABLE_LIST *view, enum_drop_mode drop_mode); diff --git a/sql/table.cc b/sql/table.cc index e1edcc0b407..a6fbce9f3dd 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -7495,6 +7495,32 @@ void TABLE_LIST::set_lock_type(THD *thd, enum thr_lock_type lock) } } + +bool TABLE_LIST::is_table_ref_id_equal(TABLE_SHARE *s) +{ + enum enum_table_ref_type tp= s->get_table_ref_type(); + if (m_table_ref_type == tp) + { + bool res= m_table_ref_version == s->get_table_ref_version(); + + /* + If definition is different object with view we can check MD5 in frm + to check if the same view got into table definition cache again. + */ + if (!res && + tp == TABLE_REF_VIEW && + mariadb_view_version_check(this, s)) + { + // to avoid relatively expensive parsing of frm next time + set_table_ref_id(s); + return TRUE; + } + return res; + } + return FALSE; +} + + uint TABLE_SHARE::actual_n_key_parts(THD *thd) { return use_ext_keys && diff --git a/sql/table.h b/sql/table.h index a7913844e9d..9e1a061e606 100644 --- a/sql/table.h +++ b/sql/table.h @@ -2271,13 +2271,7 @@ struct TABLE_LIST @sa check_and_update_table_version() */ - inline - bool is_table_ref_id_equal(TABLE_SHARE *s) const - { - return (m_table_ref_type == s->get_table_ref_type() && - m_table_ref_version == s->get_table_ref_version()); - } - + bool is_table_ref_id_equal(TABLE_SHARE *s); /** Record the value of metadata version of the corresponding table definition cache element in this parse tree node.