Skip to content

Commit 920f373

Browse files
author
Daniel Blanchard
committed
Bug#35221658 Prepared statement error
When an expression is used in the index clause of a CREATE TABLE statement in a prepared statement the first execution of the prepared statement will succeed, but a subsequent execution will encounter an error. Similar errors also occur with expressions used in CREATE INDEX and ALTER TABLE ADD INDEX statements. A similar error is encountered when using expressions in the PARTITION clauses of a CREATE TABLE statement in a prepared statement. Likewise, these forms of CREATE TABLE, CREATE INDEX and ALTER TABLE ADD INDEX statements cause errors when used in stored procedures. The root cause of the issue is a mismatch of lifetimes of parse tree objects used for the offending statements. When an offending statement is initially parsed during the creation of a prepared statement the parse tree objects are allocated in a MEM_ROOT whose lifetime is associated with the lifetime of the prepared statement. When the prepared statement is executed, the parse tree objects corresponding to the index or partition expressions are allocated in a MEM_ROOT whose lifetime is limited to the execution of the prepared statement. Unfortunately, pointers to these expression parse tree objects are held in the memory belonging to the first MEM_ROOT, so subsequent executions of the prepared statement are attempted with parse tree structures that hold pointers to invalid memory (i.e. pointers into the second MEM_ROOT that was freed/invalidated at the end of the first execution). A similar pattern of parse tree memory usage is present in stored procedures that use the offending CREATE TABLE, CREATE INDEX and ALTER TABLE CREATE INDEX statements. To fix this issue, prepared statements and stored procedures using the offending statements are now reprepared/reparsed prior to second and subsequent executions so that memory access via pointers to invalidated/freed memory is avoided. This is a short term fix: a longer term fix will involve refactoring the initial preparation of the parse trees for the offending statements so that they can be used in multiple executions without having to reparse each time. Change-Id: Ic1601a8b9bddc754f17d3df8e86bc1be3c01cd30
1 parent 841c3c7 commit 920f373

10 files changed

+100
-39
lines changed

mysql-test/r/ps_ddl.result

+20-20
Original file line numberDiff line numberDiff line change
@@ -1332,7 +1332,7 @@ alter table t1 drop column b;
13321332
execute stmt;
13331333
alter table t1 drop column b;
13341334
execute stmt;
1335-
call p_verify_reprepare_count(0);
1335+
call p_verify_reprepare_count(3);
13361336
SUCCESS
13371337

13381338
drop table t1;
@@ -1818,7 +1818,7 @@ drop table t2;
18181818
execute stmt;
18191819
drop table t2;
18201820
execute stmt;
1821-
call p_verify_reprepare_count(0);
1821+
call p_verify_reprepare_count(2);
18221822
SUCCESS
18231823

18241824
execute stmt;
@@ -1828,13 +1828,13 @@ SUCCESS
18281828

18291829
execute stmt;
18301830
ERROR 42S01: Table 't2' already exists
1831-
call p_verify_reprepare_count(0);
1831+
call p_verify_reprepare_count(1);
18321832
SUCCESS
18331833

18341834
drop table t2;
18351835
create temporary table t2 (a int);
18361836
execute stmt;
1837-
call p_verify_reprepare_count(0);
1837+
call p_verify_reprepare_count(1);
18381838
SUCCESS
18391839

18401840
execute stmt;
@@ -1845,12 +1845,12 @@ SUCCESS
18451845
drop temporary table t2;
18461846
execute stmt;
18471847
ERROR 42S01: Table 't2' already exists
1848-
call p_verify_reprepare_count(0);
1848+
call p_verify_reprepare_count(1);
18491849
SUCCESS
18501850

18511851
drop table t2;
18521852
execute stmt;
1853-
call p_verify_reprepare_count(0);
1853+
call p_verify_reprepare_count(1);
18541854
SUCCESS
18551855

18561856
drop table t2;
@@ -1862,7 +1862,7 @@ SUCCESS
18621862

18631863
execute stmt;
18641864
Got one of the listed errors
1865-
call p_verify_reprepare_count(0);
1865+
call p_verify_reprepare_count(1);
18661866
SUCCESS
18671867

18681868
drop view t2;
@@ -1876,7 +1876,7 @@ select * from t2;
18761876
x
18771877
drop table t2;
18781878
execute stmt;
1879-
call p_verify_reprepare_count(0);
1879+
call p_verify_reprepare_count(1);
18801880
SUCCESS
18811881

18821882
drop table t2;
@@ -1889,7 +1889,7 @@ select * from t2;
18891889
x y
18901890
drop table t2;
18911891
execute stmt;
1892-
call p_verify_reprepare_count(0);
1892+
call p_verify_reprepare_count(1);
18931893
SUCCESS
18941894

18951895
drop table t1;
@@ -1900,7 +1900,7 @@ prepare stmt from "create temporary table if not exists t2 as select * from t1";
19001900
execute stmt;
19011901
drop table t2;
19021902
execute stmt;
1903-
call p_verify_reprepare_count(0);
1903+
call p_verify_reprepare_count(1);
19041904
SUCCESS
19051905

19061906
execute stmt;
@@ -1915,7 +1915,7 @@ a
19151915
execute stmt;
19161916
Warnings:
19171917
Note 1050 Table 't2' already exists
1918-
call p_verify_reprepare_count(0);
1918+
call p_verify_reprepare_count(1);
19191919
SUCCESS
19201920

19211921
select * from t2;
@@ -1942,7 +1942,7 @@ SUCCESS
19421942
execute stmt;
19431943
Warnings:
19441944
Note 1050 Table 't2' already exists
1945-
call p_verify_reprepare_count(0);
1945+
call p_verify_reprepare_count(1);
19461946
SUCCESS
19471947

19481948
drop table t1;
@@ -1957,19 +1957,19 @@ SUCCESS
19571957

19581958
drop table t2;
19591959
execute stmt;
1960-
call p_verify_reprepare_count(0);
1960+
call p_verify_reprepare_count(1);
19611961
SUCCESS
19621962

19631963
drop table t2;
19641964
drop table t1;
19651965
execute stmt;
19661966
ERROR 42S02: Table 'test.t1' doesn't exist
1967-
call p_verify_reprepare_count(0);
1967+
call p_verify_reprepare_count(1);
19681968
SUCCESS
19691969

19701970
execute stmt;
19711971
ERROR 42S02: Table 'test.t1' doesn't exist
1972-
call p_verify_reprepare_count(0);
1972+
call p_verify_reprepare_count(1);
19731973
SUCCESS
19741974

19751975
create table t1 (x char(17));
@@ -1979,7 +1979,7 @@ SUCCESS
19791979

19801980
drop table t2;
19811981
execute stmt;
1982-
call p_verify_reprepare_count(0);
1982+
call p_verify_reprepare_count(1);
19831983
SUCCESS
19841984

19851985
drop table t2;
@@ -1992,7 +1992,7 @@ select * from t2;
19921992
x y
19931993
drop table t2;
19941994
execute stmt;
1995-
call p_verify_reprepare_count(0);
1995+
call p_verify_reprepare_count(1);
19961996
SUCCESS
19971997

19981998
drop table t1;
@@ -2362,9 +2362,9 @@ drop table t1;
23622362
deallocate prepare stmt;
23632363
# Intermediate result: number of reprepares matches the number
23642364
# of tests
2365-
call p_verify_reprepare_count(17);
2366-
ERROR
2367-
Expected: 17, actual: 18
2365+
call p_verify_reprepare_count(18);
2366+
SUCCESS
2367+
23682368
#
23692369
# SQLCOM_ALTER_VIEW
23702370
#

mysql-test/t/ps_ddl.test

+18-18
Original file line numberDiff line numberDiff line change
@@ -1084,7 +1084,7 @@ execute stmt;
10841084

10851085
alter table t1 drop column b;
10861086
execute stmt;
1087-
call p_verify_reprepare_count(0);
1087+
call p_verify_reprepare_count(3);
10881088

10891089
drop table t1;
10901090

@@ -1526,30 +1526,30 @@ drop table t2;
15261526
execute stmt;
15271527
drop table t2;
15281528
execute stmt;
1529-
call p_verify_reprepare_count(0);
1529+
call p_verify_reprepare_count(2);
15301530
# Base table with name of table to be created exists
15311531
--error ER_TABLE_EXISTS_ERROR
15321532
execute stmt;
15331533
call p_verify_reprepare_count(1);
15341534
--error ER_TABLE_EXISTS_ERROR
15351535
execute stmt;
1536-
call p_verify_reprepare_count(0);
1536+
call p_verify_reprepare_count(1);
15371537
drop table t2;
15381538
# Temporary table with name of table to be created exists
15391539
create temporary table t2 (a int);
15401540
# Temporary table and base table are not in the same name space.
15411541
execute stmt;
1542-
call p_verify_reprepare_count(0);
1542+
call p_verify_reprepare_count(1);
15431543
--error ER_TABLE_EXISTS_ERROR
15441544
execute stmt;
15451545
call p_verify_reprepare_count(1);
15461546
drop temporary table t2;
15471547
--error ER_TABLE_EXISTS_ERROR
15481548
execute stmt;
1549-
call p_verify_reprepare_count(0);
1549+
call p_verify_reprepare_count(1);
15501550
drop table t2;
15511551
execute stmt;
1552-
call p_verify_reprepare_count(0);
1552+
call p_verify_reprepare_count(1);
15531553
drop table t2;
15541554
# View with name of table to be created exists
15551555
# Attention:
@@ -1564,7 +1564,7 @@ execute stmt;
15641564
call p_verify_reprepare_count(1);
15651565
--error ER_TABLE_EXISTS_ERROR,9999
15661566
execute stmt;
1567-
call p_verify_reprepare_count(0);
1567+
call p_verify_reprepare_count(1);
15681568
drop view t2;
15691569
drop table t1;
15701570
# Table to be used recreated (drop,create) with different layout
@@ -1574,7 +1574,7 @@ call p_verify_reprepare_count(1);
15741574
select * from t2;
15751575
drop table t2;
15761576
execute stmt;
1577-
call p_verify_reprepare_count(0);
1577+
call p_verify_reprepare_count(1);
15781578
drop table t2;
15791579
# Table to be used has a modified (alter table) layout
15801580
alter table t1 add column y decimal(10,3);
@@ -1583,7 +1583,7 @@ call p_verify_reprepare_count(1);
15831583
select * from t2;
15841584
drop table t2;
15851585
execute stmt;
1586-
call p_verify_reprepare_count(0);
1586+
call p_verify_reprepare_count(1);
15871587
drop table t1;
15881588
deallocate prepare stmt;
15891589
create table t1 (a int);
@@ -1592,12 +1592,12 @@ prepare stmt from "create temporary table if not exists t2 as select * from t1";
15921592
execute stmt;
15931593
drop table t2;
15941594
execute stmt;
1595-
call p_verify_reprepare_count(0);
1595+
call p_verify_reprepare_count(1);
15961596
execute stmt;
15971597
call p_verify_reprepare_count(1);
15981598
select * from t2;
15991599
execute stmt;
1600-
call p_verify_reprepare_count(0);
1600+
call p_verify_reprepare_count(1);
16011601
select * from t2;
16021602
drop table t2;
16031603
create temporary table t2 (a varchar(10));
@@ -1609,7 +1609,7 @@ create table t1 (x int);
16091609
execute stmt;
16101610
call p_verify_reprepare_count(1);
16111611
execute stmt;
1612-
call p_verify_reprepare_count(0);
1612+
call p_verify_reprepare_count(1);
16131613
drop table t1;
16141614
drop temporary table t2;
16151615
drop table t2;
@@ -1621,23 +1621,23 @@ execute stmt;
16211621
call p_verify_reprepare_count(0);
16221622
drop table t2;
16231623
execute stmt;
1624-
call p_verify_reprepare_count(0);
1624+
call p_verify_reprepare_count(1);
16251625
drop table t2;
16261626
# Table to be used does not exist
16271627
drop table t1;
16281628
--error ER_NO_SUCH_TABLE
16291629
execute stmt;
1630-
call p_verify_reprepare_count(0);
1630+
call p_verify_reprepare_count(1);
16311631
--error ER_NO_SUCH_TABLE
16321632
execute stmt;
1633-
call p_verify_reprepare_count(0);
1633+
call p_verify_reprepare_count(1);
16341634
# Table to be used recreated (drop,create) with different layout
16351635
create table t1 (x char(17));
16361636
execute stmt;
16371637
call p_verify_reprepare_count(1);
16381638
drop table t2;
16391639
execute stmt;
1640-
call p_verify_reprepare_count(0);
1640+
call p_verify_reprepare_count(1);
16411641
drop table t2;
16421642
# Table to be used has a modified (alter table) layout
16431643
alter table t1 add column y time;
@@ -1646,7 +1646,7 @@ call p_verify_reprepare_count(1);
16461646
select * from t2;
16471647
drop table t2;
16481648
execute stmt;
1649-
call p_verify_reprepare_count(0);
1649+
call p_verify_reprepare_count(1);
16501650
drop table t1;
16511651
drop table t2;
16521652
deallocate prepare stmt;
@@ -2057,7 +2057,7 @@ drop table t1;
20572057
deallocate prepare stmt;
20582058
--echo # Intermediate result: number of reprepares matches the number
20592059
--echo # of tests
2060-
call p_verify_reprepare_count(17);
2060+
call p_verify_reprepare_count(18);
20612061

20622062
--echo #
20632063
--echo # SQLCOM_ALTER_VIEW

sql/sp_instr.cc

+5-1
Original file line numberDiff line numberDiff line change
@@ -704,7 +704,11 @@ bool sp_lex_instr::validate_lex_and_execute_core(THD *thd, uint *nextp,
704704

705705
while (true) {
706706
DBUG_EXECUTE_IF("simulate_bug18831513", { invalidate(); });
707-
if (is_invalid() || (m_lex->has_udf() && !m_first_execution)) {
707+
if (is_invalid() ||
708+
(!m_first_execution &&
709+
(m_lex->has_udf() ||
710+
(m_lex->m_sql_cmd != nullptr &&
711+
m_lex->m_sql_cmd->reprepare_on_execute_required())))) {
708712
free_lex();
709713
LEX *lex = parse_expr(thd, thd->sp_runtime_ctx->sp);
710714

sql/sql_alter.cc

+12
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,18 @@ bool Sql_cmd_alter_table::execute(THD *thd) {
356356
return result;
357357
}
358358

359+
bool Sql_cmd_alter_table::reprepare_on_execute_required() const {
360+
// Expressions in key and partition clauses end up with being allocated on
361+
// differing (incompatible) MEM_ROOTs and thus need to be reprepared. The
362+
// incompatibility arises in the case of prepared statements as a parse tree
363+
// MEM_ROOT whose lifetime is associated with the lifetime of the prepared
364+
// statement ends up containing pointers to parse tree objects that have been
365+
// allocated from a MEM_ROOT with a lifetime of the prepared statement's
366+
// execution. It's benign (though wasteful) to reprepare other alter table
367+
// statements as well.
368+
return true;
369+
}
370+
359371
bool Sql_cmd_discard_import_tablespace::execute(THD *thd) {
360372
/* Verify that exactly one of the DISCARD and IMPORT flags are set. */
361373
assert((m_alter_info->flags & Alter_info::ALTER_DISCARD_TABLESPACE) ^

sql/sql_alter.h

+1
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,7 @@ class Sql_cmd_alter_table : public Sql_cmd_common_alter_table {
618618
using Sql_cmd_common_alter_table::Sql_cmd_common_alter_table;
619619

620620
bool execute(THD *thd) override;
621+
bool reprepare_on_execute_required() const override;
621622
};
622623

623624
/**

sql/sql_cmd.h

+8
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,14 @@ class Sql_cmd {
115115
*/
116116
virtual bool execute(THD *thd) = 0;
117117

118+
/**
119+
Some SQL commands currently require re-preparation on re-execution of a
120+
prepared statement or stored procedure. For example, a CREATE TABLE command
121+
containing an index expression.
122+
@return true if re-preparation is required, false otherwise.
123+
*/
124+
virtual bool reprepare_on_execute_required() const { return false; }
125+
118126
/**
119127
Command-specific reinitialization before execution of prepared statement
120128

sql/sql_cmd_ddl_table.cc

+24
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,18 @@ bool Sql_cmd_create_table::execute(THD *thd) {
462462
return res;
463463
}
464464

465+
bool Sql_cmd_create_table::reprepare_on_execute_required() const {
466+
// Expressions in key and partition clauses end up with being allocated on
467+
// differing (incompatible) MEM_ROOTs and thus need to be reprepared. The
468+
// incompatibility arises in the case of prepared statements as a parse tree
469+
// MEM_ROOT whose lifetime is associated with the lifetime of the prepared
470+
// statement ends up containing pointers to parse tree objects that have been
471+
// allocated from a MEM_ROOT with a lifetime of the prepared statement's
472+
// execution. It's benign (though wasteful) to reprepare other create table
473+
// statements as well.
474+
return true;
475+
}
476+
465477
const MYSQL_LEX_CSTRING *
466478
Sql_cmd_create_table::eligible_secondary_storage_engine() const {
467479
// Now check if the opened tables are available in a secondary
@@ -547,6 +559,18 @@ bool Sql_cmd_create_or_drop_index_base::execute(THD *thd) {
547559
return res;
548560
}
549561

562+
bool Sql_cmd_create_index::reprepare_on_execute_required() const {
563+
// Expressions in index/key clauses end up with being allocated on
564+
// differing (incompatible) MEM_ROOTs and thus need to be reprepared.
565+
// The incompatibility arises in the case of prepared statements as a parse
566+
// tree MEM_ROOT whose lifetime is associated with the lifetime of the
567+
// prepared statement ends up containing pointers to parse tree objects that
568+
// have been allocated from a MEM_ROOT with a lifetime of the prepared
569+
// statement's execution. It's benign (though wasteful) to reprepare other
570+
// create index statements as well.
571+
return true;
572+
}
573+
550574
bool Sql_cmd_cache_index::execute(THD *thd) {
551575
Table_ref *const first_table = thd->lex->query_block->get_table_list();
552576
if (check_table_access(thd, INDEX_ACL, first_table, true, UINT_MAX, false))

0 commit comments

Comments
 (0)