Skip to content
Permalink
Browse files
MDEV-17124: mariadb 10.1.34, views and prepared statements: ERROR 161…
…5 (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 be
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 around is to increase TDC, other - improve version check for
views/triggers (which is done here). Now in suspicious cases we check:
 - timestamp (microseconds) of the view to be sure that version really
   have changed;
 - time (microseconds) of creation of a trigger related to time
   (microseconds) of statement preparation.
  • Loading branch information
sanja-byelkin committed Sep 30, 2022
1 parent 98e62e6 commit f65ba9a
Show file tree
Hide file tree
Showing 16 changed files with 329 additions and 74 deletions.
@@ -964,6 +964,17 @@ extern ulonglong my_getcputime(void);
#define hrtime_sec_part(X) ((ulong)((X).val % HRTIME_RESOLUTION))
#define my_time(X) hrtime_to_time(my_hrtime_coarse())

/**
Make high resolution time from two parts.
*/

static inline my_hrtime_t make_hr_time(my_time_t time, ulong time_sec_part)
{
my_hrtime_t res= {((ulonglong) time)*1000000 + time_sec_part};
return res;
}


#if STACK_DIRECTION < 0
#define available_stack_size(CUR,END) (long) ((char*)(CUR) - (char*)(END))
#else
@@ -214,7 +214,7 @@ new trigger: 10
drop trigger t1_bd;
set @val=11;
execute stmt using @val;
call p_verify_reprepare_count(1);
call p_verify_reprepare_count(0);
SUCCESS

select @message;
@@ -224,7 +224,7 @@ Test 6-e: removing a relevant trigger
drop trigger t1_bi;
set @val=12;
execute stmt using @val;
call p_verify_reprepare_count(1);
call p_verify_reprepare_count(0);
SUCCESS

select @message;
@@ -384,7 +384,7 @@ a
flush table t1;
set @var=9;
execute stmt using @var;
call p_verify_reprepare_count(1);
call p_verify_reprepare_count(0);
SUCCESS

select * from t2;
@@ -830,7 +830,7 @@ a b c
10 20 50
20 40 100
30 60 150
call p_verify_reprepare_count(1);
call p_verify_reprepare_count(0);
SUCCESS

# Check that we properly handle ALTER VIEW statements.
@@ -1206,7 +1206,7 @@ drop trigger v2_bi;
set @message=null;
set @var=9;
execute stmt using @var;
call p_verify_reprepare_count(1);
call p_verify_reprepare_count(0);
SUCCESS

select @message;
@@ -2578,7 +2578,7 @@ SELECT * FROM t1;
a
2048
1025
1024
2048
DROP TABLE t1;
#
# End of 10.1 tests
@@ -250,7 +250,8 @@ drop trigger t1_bd;

set @val=11;
execute stmt using @val;
call p_verify_reprepare_count(1);
# No trigger in opened table => nothing to check => no reprepare
call p_verify_reprepare_count(0);
select @message;

--echo Test 6-e: removing a relevant trigger
@@ -259,7 +260,8 @@ drop trigger t1_bi;

set @val=12;
execute stmt using @val;
call p_verify_reprepare_count(1);
# No trigger in opened table => nothing to check => no reprepare
call p_verify_reprepare_count(0);
select @message;
set @val=13;
execute stmt using @val;
@@ -374,7 +376,8 @@ select * from t3;
flush table t1;
set @var=9;
execute stmt using @var;
call p_verify_reprepare_count(1);
# flush tables now do not mean reprepare
call p_verify_reprepare_count(0);
select * from t2;
select * from t3;
drop view v1;
@@ -743,7 +746,7 @@ call p_verify_reprepare_count(1);
flush table t2;

execute stmt;
call p_verify_reprepare_count(1);
call p_verify_reprepare_count(0);

--echo # Check that we properly handle ALTER VIEW statements.
execute stmt;
@@ -967,7 +970,8 @@ drop trigger v2_bi;
set @message=null;
set @var=9;
execute stmt using @var;
call p_verify_reprepare_count(1);
# No trigger in opened table => nothing to check => no reprepare
call p_verify_reprepare_count(0);
select @message;
create trigger v2_bi after insert on v2 for each row set @message="v2_ai";
set @var= 10;
@@ -6816,6 +6816,34 @@ r
drop view v1;
drop table t1;
#
# 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.2 tests
#
#
@@ -6539,6 +6539,46 @@ select * from (select sum((select * from cte)) as r) dt2;
drop view v1;
drop table t1;

--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.2 tests
--echo #
@@ -173,11 +173,12 @@ write_parameter(IO_CACHE *file, const uchar* base, File_option *parameter)
{
/* string have to be allocated already */
LEX_STRING *val_s= (LEX_STRING *)(base + parameter->offset);
time_t tm= my_time(0);

get_date(val_s->str, GETDATE_DATE_TIME|GETDATE_GMT|GETDATE_FIXEDLENGTH,
tm);
val_s->length= PARSE_FILE_TIMESTAMPLENGTH;
// number of microseconds since Epoch, timezone-independent
my_hrtime_t tm= my_hrtime();
// Paded to 19 characters for compatibility
val_s->length= snprintf(val_s->str, MICROSECOND_TIMESTAMP_BUFFER_SIZE,
"%019lld", tm.val);
DBUG_ASSERT(val_s->length == MICROSECOND_TIMESTAMP_BUFFER_SIZE-1);
if (my_b_write(file, (const uchar *)val_s->str,
PARSE_FILE_TIMESTAMPLENGTH))
DBUG_RETURN(TRUE);
@@ -833,16 +834,16 @@ File_parser::parse(uchar* base, MEM_ROOT *mem_root,
{
/* string have to be allocated already */
LEX_STRING *val= (LEX_STRING *)(base + parameter->offset);
/* yyyy-mm-dd HH:MM:SS = 19(PARSE_FILE_TIMESTAMPLENGTH) characters */
if (ptr[PARSE_FILE_TIMESTAMPLENGTH] != '\n')
/* 19 characters of timestamp */
if (ptr[MICROSECOND_TIMESTAMP_BUFFER_SIZE-1] != '\n')
{
my_error(ER_FPARSER_ERROR_IN_PARAMETER, MYF(0),
parameter->name.str, line);
DBUG_RETURN(TRUE);
}
memcpy(val->str, ptr, PARSE_FILE_TIMESTAMPLENGTH);
val->str[val->length= PARSE_FILE_TIMESTAMPLENGTH]= '\0';
ptr+= (PARSE_FILE_TIMESTAMPLENGTH+1);
memcpy(val->str, ptr, MICROSECOND_TIMESTAMP_BUFFER_SIZE-1);
val->str[val->length= MICROSECOND_TIMESTAMP_BUFFER_SIZE-1]= '\0';
ptr+= MICROSECOND_TIMESTAMP_BUFFER_SIZE;
break;
}
case FILE_OPTIONS_STRLIST:
@@ -1906,17 +1906,18 @@ bool open_table(THD *thd, TABLE_LIST *table_list, Open_table_context *ot_ctx)
table_list->alias.str);
goto err_lock;
}

/* Open view */
if (mysql_make_view(thd, share, table_list, false))
goto err_lock;

/*
This table is a view. Validate its metadata version: in particular,
that it was a view when the statement was prepared.
*/
if (check_and_update_table_version(thd, table_list, share))
goto err_lock;

/* Open view */
if (mysql_make_view(thd, share, table_list, false))
goto err_lock;


/* TODO: Don't free this */
tdc_release_share(share);
@@ -2721,7 +2722,7 @@ static bool inject_reprepare(THD *thd)

@sa Execute_observer
@sa check_prepared_statement() to see cases when an observer is installed
@sa TABLE_LIST::is_table_ref_id_equal()
@sa TABLE_LIST::is_the_same_definition()
@sa TABLE_SHARE::get_table_ref_id()

@param[in] thd used to report errors
@@ -2738,7 +2739,7 @@ static bool
check_and_update_table_version(THD *thd,
TABLE_LIST *tables, TABLE_SHARE *table_share)
{
if (! tables->is_table_ref_id_equal(table_share))
if (! tables->is_the_same_definition(thd, table_share))
{
if (thd->m_reprepare_observer &&
thd->m_reprepare_observer->report_error(thd))
@@ -2844,7 +2845,9 @@ bool tdc_open_view(THD *thd, TABLE_LIST *table_list, uint flags)

DBUG_ASSERT(share->is_view);

if (flags & CHECK_METADATA_VERSION)
err= mysql_make_view(thd, share, table_list, (flags & OPEN_VIEW_NO_PARSE));

if (!err && (flags & CHECK_METADATA_VERSION))
{
/*
Check TABLE_SHARE-version of view only if we have been instructed to do
@@ -2859,7 +2862,6 @@ bool tdc_open_view(THD *thd, TABLE_LIST *table_list, uint flags)
goto ret;
}

err= mysql_make_view(thd, share, table_list, (flags & OPEN_VIEW_NO_PARSE));
ret:
tdc_release_share(share);

@@ -3879,6 +3879,7 @@ Statement::Statement(LEX *lex_arg, MEM_ROOT *mem_root_arg,
id(id_arg),
column_usage(MARK_COLUMNS_READ),
lex(lex_arg),
hr_prepare_time({0}),
db(null_clex_str)
{
name= null_clex_str;
@@ -3897,6 +3898,7 @@ void Statement::set_statement(Statement *stmt)
column_usage= stmt->column_usage;
lex= stmt->lex;
query_string= stmt->query_string;
hr_prepare_time= stmt->hr_prepare_time;
}


@@ -1122,6 +1122,7 @@ class Statement: public ilink, public Query_arena

LEX_CSTRING name; /* name for named prepared statements */
LEX *lex; // parse tree descriptor
my_hrtime_t hr_prepare_time; // time of preparation in microseconds
/*
Points to the query associated with this statement. It's const, but
we need to declare it char * because all table handlers are written
@@ -4311,6 +4311,8 @@ bool Prepared_statement::prepare(const char *packet, uint packet_len)
if (thd->spcont == NULL)
general_log_write(thd, COM_STMT_PREPARE, query(), query_length());
}
// The same format as for triggers to compare
hr_prepare_time= my_hrtime();
DBUG_RETURN(error);
}

@@ -4404,8 +4406,8 @@ Prepared_statement::execute_loop(String *expanded_query,
uchar *packet_end)
{
Reprepare_observer reprepare_observer;
bool error;
int reprepare_attempt= 0;
bool error;
iterations= FALSE;

/*
@@ -7117,13 +7117,15 @@ static bool store_trigger(THD *thd, Trigger *trigger,
table->field[14]->store(STRING_WITH_LEN("OLD"), cs);
table->field[15]->store(STRING_WITH_LEN("NEW"), cs);

if (trigger->create_time)
if (trigger->hr_create_time.val)
{
/* timestamp is in microseconds */
table->field[16]->set_notnull();
thd->variables.time_zone->gmt_sec_to_TIME(&timestamp,
(my_time_t)(trigger->create_time/100));
/* timestamp is with 6 digits */
timestamp.second_part= (trigger->create_time % 100) * 10000;
thd->variables.time_zone->
gmt_sec_to_TIME(&timestamp,
(my_time_t)
hrtime_to_time(trigger->hr_create_time));
timestamp.second_part= hrtime_sec_part(trigger->hr_create_time);
((Field_temporal_with_date*) table->field[16])->store_time_dec(&timestamp,
2);
}
@@ -10294,12 +10296,14 @@ static bool show_create_trigger_impl(THD *thd, Trigger *trigger)
trigger->db_cl_name.length,
system_charset_info);

if (trigger->create_time)
if (trigger->hr_create_time.val)
{
MYSQL_TIME timestamp;
thd->variables.time_zone->gmt_sec_to_TIME(&timestamp,
(my_time_t)(trigger->create_time/100));
timestamp.second_part= (trigger->create_time % 100) * 10000;
thd->variables.time_zone->
gmt_sec_to_TIME(&timestamp,
(my_time_t)
hrtime_to_time(trigger->hr_create_time));
timestamp.second_part= hrtime_sec_part(trigger->hr_create_time);
p->store(&timestamp, 2);
}
else

0 comments on commit f65ba9a

Please sign in to comment.