summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--mysql-test/r/ps.result11
-rw-r--r--mysql-test/t/ps.test15
-rw-r--r--sql/item.cc17
-rw-r--r--sql/item.h3
-rw-r--r--sql/item_cmpfunc.cc16
-rw-r--r--sql/sql_class.cc48
-rw-r--r--sql/sql_class.h31
-rw-r--r--sql/sql_parse.cc1
-rw-r--r--sql/sql_prepare.cc9
-rw-r--r--tests/client_test.c2
10 files changed, 127 insertions, 26 deletions
diff --git a/mysql-test/r/ps.result b/mysql-test/r/ps.result
index 12337d358e1..8e63e0cdf00 100644
--- a/mysql-test/r/ps.result
+++ b/mysql-test/r/ps.result
@@ -297,3 +297,14 @@ execute stmt;
'abc' like convert('abc' using utf8)
1
deallocate prepare stmt;
+create table t1 ( a bigint );
+prepare stmt from 'select a from t1 where a between ? and ?';
+set @a=1;
+execute stmt using @a, @a;
+a
+execute stmt using @a, @a;
+a
+execute stmt using @a, @a;
+a
+drop table t1;
+deallocate prepare stmt;
diff --git a/mysql-test/t/ps.test b/mysql-test/t/ps.test
index 4b63a7db95f..c961251255d 100644
--- a/mysql-test/t/ps.test
+++ b/mysql-test/t/ps.test
@@ -314,3 +314,18 @@ prepare stmt from "select 'abc' like convert('abc' using utf8)";
execute stmt;
execute stmt;
deallocate prepare stmt;
+
+#
+# BUG#5748 "Prepared statement with BETWEEN and bigint values crashes
+# mysqld". Just another place where an item tree modification must be
+# rolled back.
+#
+create table t1 ( a bigint );
+prepare stmt from 'select a from t1 where a between ? and ?';
+set @a=1;
+execute stmt using @a, @a;
+execute stmt using @a, @a;
+execute stmt using @a, @a;
+drop table t1;
+deallocate prepare stmt;
+
diff --git a/sql/item.cc b/sql/item.cc
index b7523478be3..1c4bfbe2c77 100644
--- a/sql/item.cc
+++ b/sql/item.cc
@@ -106,7 +106,7 @@ void Item::print_item_w_name(String *str)
Item_ident::Item_ident(const char *db_name_par,const char *table_name_par,
const char *field_name_par)
:orig_db_name(db_name_par), orig_table_name(table_name_par),
- orig_field_name(field_name_par), changed_during_fix_field(0),
+ orig_field_name(field_name_par),
db_name(db_name_par), table_name(table_name_par),
field_name(field_name_par), cached_field_index(NO_CACHED_FIELD_INDEX),
cached_table(0), depended_from(0)
@@ -120,7 +120,6 @@ Item_ident::Item_ident(THD *thd, Item_ident *item)
orig_db_name(item->orig_db_name),
orig_table_name(item->orig_table_name),
orig_field_name(item->orig_field_name),
- changed_during_fix_field(0),
db_name(item->db_name),
table_name(item->table_name),
field_name(item->field_name),
@@ -137,11 +136,6 @@ void Item_ident::cleanup()
table_name, orig_table_name,
field_name, orig_field_name));
Item::cleanup();
- if (changed_during_fix_field)
- {
- *changed_during_fix_field= this;
- changed_during_fix_field= 0;
- }
db_name= orig_db_name;
table_name= orig_table_name;
field_name= orig_field_name;
@@ -1340,10 +1334,10 @@ bool Item_field::fix_fields(THD *thd, TABLE_LIST *tables, Item **ref)
Item_ref *rf;
*ref= rf= new Item_ref(last->ref_pointer_array + counter,
- ref,
+ 0,
(char *)table_name,
(char *)field_name);
- register_item_tree_changing(ref);
+ thd->register_item_tree_change(ref, this, &thd->mem_root);
if (!rf)
return 1;
/*
@@ -1362,7 +1356,8 @@ bool Item_field::fix_fields(THD *thd, TABLE_LIST *tables, Item **ref)
if (last->having_fix_field)
{
Item_ref *rf;
- *ref= rf= new Item_ref(ref, *ref,
+ thd->register_item_tree_change(ref, *ref, &thd->mem_root);
+ *ref= rf= new Item_ref(ref, 0,
(where->db[0]?where->db:0),
(char *)where->alias,
(char *)field_name);
@@ -2003,7 +1998,7 @@ bool Item_ref::fix_fields(THD *thd,TABLE_LIST *tables, Item **reference)
Item_field* fld;
if (!((*reference)= fld= new Item_field(tmp)))
return 1;
- register_item_tree_changing(reference);
+ thd->register_item_tree_change(reference, this, &thd->mem_root);
mark_as_dependent(thd, last, thd->lex->current_select, fld);
return 0;
}
diff --git a/sql/item.h b/sql/item.h
index ce52705c341..6a22b57ee8e 100644
--- a/sql/item.h
+++ b/sql/item.h
@@ -317,7 +317,6 @@ class Item_ident :public Item
const char *orig_db_name;
const char *orig_table_name;
const char *orig_field_name;
- Item **changed_during_fix_field;
public:
const char *db_name;
const char *table_name;
@@ -340,8 +339,6 @@ public:
Item_ident(THD *thd, Item_ident *item);
const char *full_name() const;
void cleanup();
- void register_item_tree_changing(Item **ref)
- { changed_during_fix_field= ref; }
bool remove_dependence_processor(byte * arg);
};
diff --git a/sql/item_cmpfunc.cc b/sql/item_cmpfunc.cc
index bb1ea09d6bc..a2bee632017 100644
--- a/sql/item_cmpfunc.cc
+++ b/sql/item_cmpfunc.cc
@@ -145,7 +145,7 @@ void Item_func_not_all::print(String *str)
1 Item was replaced with an integer version of the item
*/
-static bool convert_constant_item(Field *field, Item **item)
+static bool convert_constant_item(THD *thd, Field *field, Item **item)
{
if ((*item)->const_item())
{
@@ -153,7 +153,10 @@ static bool convert_constant_item(Field *field, Item **item)
{
Item *tmp=new Item_int_with_ref(field->val_int(), *item);
if (tmp)
+ {
+ thd->register_item_tree_change(item, *item, &thd->mem_root);
*item=tmp;
+ }
return 1; // Item was replaced
}
}
@@ -164,6 +167,7 @@ static bool convert_constant_item(Field *field, Item **item)
void Item_bool_func2::fix_length_and_dec()
{
max_length= 1; // Function returns 0 or 1
+ THD *thd= current_thd;
/*
As some compare functions are generated after sql_yacc,
@@ -199,7 +203,6 @@ void Item_bool_func2::fix_length_and_dec()
!coll.set(args[0]->collation, args[1]->collation, TRUE))
{
Item* conv= 0;
- THD *thd= current_thd;
Item_arena *arena= thd->current_arena, backup;
strong= coll.strong;
weak= strong ? 0 : 1;
@@ -245,7 +248,7 @@ void Item_bool_func2::fix_length_and_dec()
Field *field=((Item_field*) args[0])->field;
if (field->can_be_compared_as_longlong())
{
- if (convert_constant_item(field,&args[1]))
+ if (convert_constant_item(thd, field,&args[1]))
{
cmp.set_cmp_func(this, tmp_arg, tmp_arg+1,
INT_RESULT); // Works for all types.
@@ -258,7 +261,7 @@ void Item_bool_func2::fix_length_and_dec()
Field *field=((Item_field*) args[1])->field;
if (field->can_be_compared_as_longlong())
{
- if (convert_constant_item(field,&args[0]))
+ if (convert_constant_item(thd, field,&args[0]))
{
cmp.set_cmp_func(this, tmp_arg, tmp_arg+1,
INT_RESULT); // Works for all types.
@@ -836,6 +839,7 @@ longlong Item_func_interval::val_int()
void Item_func_between::fix_length_and_dec()
{
max_length= 1;
+ THD *thd= current_thd;
/*
As some compare functions are generated after sql_yacc,
@@ -858,9 +862,9 @@ void Item_func_between::fix_length_and_dec()
Field *field=((Item_field*) args[0])->field;
if (field->can_be_compared_as_longlong())
{
- if (convert_constant_item(field,&args[1]))
+ if (convert_constant_item(thd, field,&args[1]))
cmp_type=INT_RESULT; // Works for all types.
- if (convert_constant_item(field,&args[2]))
+ if (convert_constant_item(thd, field,&args[2]))
cmp_type=INT_RESULT; // Works for all types.
}
}
diff --git a/sql/sql_class.cc b/sql/sql_class.cc
index 6cf01896b03..d38ad73a9f4 100644
--- a/sql/sql_class.cc
+++ b/sql/sql_class.cc
@@ -698,6 +698,54 @@ void THD::close_active_vio()
#endif
+struct Item_change_record: public ilink
+{
+ Item **place;
+ Item *old_value;
+ /* Placement new was hidden by `new' in ilink (TODO: check): */
+ static void *operator new(unsigned int size, void *mem) { return mem; }
+};
+
+
+/*
+ Register an item tree tree transformation, performed by the query
+ optimizer. We need a pointer to runtime_memroot because it may be !=
+ thd->mem_root (due to possible set_n_backup_item_arena called for thd).
+*/
+
+void THD::nocheck_register_item_tree_change(Item **place, Item *old_value,
+ MEM_ROOT *runtime_memroot)
+{
+ Item_change_record *change;
+ /*
+ Now we use one node per change, which adds some memory overhead,
+ but still is rather fast as we use alloc_root for allocations.
+ A list of item tree changes of an average query should be short.
+ */
+ void *change_mem= alloc_root(runtime_memroot, sizeof(*change));
+ if (change_mem == 0)
+ {
+ fatal_error();
+ return;
+ }
+ change= new (change_mem) Item_change_record;
+ change->place= place;
+ change->old_value= old_value;
+ change_list.push_back(change);
+}
+
+
+void THD::rollback_item_tree_changes()
+{
+ I_List_iterator<Item_change_record> it(change_list);
+ Item_change_record *change;
+ while ((change= it++))
+ *change->place= change->old_value;
+ /* We can forget about changes memory: it's allocated in runtime memroot */
+ change_list.empty();
+}
+
+
/*****************************************************************************
** Functions to provide a interface to select results
*****************************************************************************/
diff --git a/sql/sql_class.h b/sql/sql_class.h
index fbbb7fc7383..fa9c1511ed0 100644
--- a/sql/sql_class.h
+++ b/sql/sql_class.h
@@ -461,6 +461,8 @@ public:
inline bool is_stmt_prepare() const { return (int)state < (int)PREPARED; }
inline bool is_first_stmt_execute() const { return state == PREPARED; }
+ inline bool is_conventional_execution() const
+ { return state == CONVENTIONAL_EXECUTION; }
inline gptr alloc(unsigned int size) { return alloc_root(&mem_root,size); }
inline gptr calloc(unsigned int size)
{
@@ -641,6 +643,17 @@ private:
/*
+ A registry for item tree transformations performed during
+ query optimization. We register only those changes which require
+ a rollback to re-execute a prepared statement or stored procedure
+ yet another time.
+*/
+
+struct Item_change_record;
+typedef I_List<Item_change_record> Item_change_list;
+
+
+/*
For each client connection we create a separate thread with THD serving as
a thread/connection descriptor
*/
@@ -808,6 +821,14 @@ public:
Vio* active_vio;
#endif
/*
+ This is to track items changed during execution of a prepared
+ statement/stored procedure. It's created by
+ register_item_tree_change() in memory root of THD, and freed in
+ rollback_item_tree_changes(). For conventional execution it's always 0.
+ */
+ Item_change_list change_list;
+
+ /*
Current prepared Item_arena if there one, or 0
*/
Item_arena *current_arena;
@@ -1031,6 +1052,16 @@ public:
}
inline CHARSET_INFO *charset() { return variables.character_set_client; }
void update_charset();
+
+ void register_item_tree_change(Item **place, Item *old_value,
+ MEM_ROOT *runtime_memroot)
+ {
+ if (!current_arena->is_conventional_execution())
+ nocheck_register_item_tree_change(place, old_value, runtime_memroot);
+ }
+ void nocheck_register_item_tree_change(Item **place, Item *old_value,
+ MEM_ROOT *runtime_memroot);
+ void rollback_item_tree_changes();
};
/* Flags for the THD::system_thread (bitmap) variable */
diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc
index 34cad1b062d..c5ca19ecba8 100644
--- a/sql/sql_parse.cc
+++ b/sql/sql_parse.cc
@@ -4056,6 +4056,7 @@ void mysql_parse(THD *thd, char *inBuf, uint length)
}
thd->proc_info="freeing items";
thd->end_statement();
+ DBUG_ASSERT(thd->change_list.is_empty());
}
DBUG_VOID_RETURN;
}
diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc
index aa3301d540f..a638e74dc2f 100644
--- a/sql/sql_prepare.cc
+++ b/sql/sql_prepare.cc
@@ -1614,6 +1614,7 @@ int mysql_stmt_prepare(THD *thd, char *packet, uint packet_length,
cleanup_items(stmt->free_list);
close_thread_tables(thd);
free_items(thd->free_list);
+ thd->rollback_item_tree_changes();
thd->free_list= 0;
thd->current_arena= thd;
@@ -1870,8 +1871,7 @@ static void execute_stmt(THD *thd, Prepared_statement *stmt,
transformations of the query tree (i.e. negations elimination).
This should be done permanently on the parse tree of this statement.
*/
- if (stmt->state == Item_arena::PREPARED)
- thd->current_arena= stmt;
+ thd->current_arena= stmt;
if (!(specialflag & SPECIAL_NO_PRIOR))
my_pthread_setprio(pthread_self(),QUERY_PRIOR);
@@ -1884,11 +1884,10 @@ static void execute_stmt(THD *thd, Prepared_statement *stmt,
free_items(thd->free_list);
thd->free_list= 0;
if (stmt->state == Item_arena::PREPARED)
- {
- thd->current_arena= thd;
stmt->state= Item_arena::EXECUTED;
- }
+ thd->current_arena= thd;
cleanup_items(stmt->free_list);
+ thd->rollback_item_tree_changes();
reset_stmt_params(stmt);
close_thread_tables(thd); // to close derived tables
thd->set_statement(&thd->stmt_backup);
diff --git a/tests/client_test.c b/tests/client_test.c
index 06a655cd3fb..ee8bc28165c 100644
--- a/tests/client_test.c
+++ b/tests/client_test.c
@@ -8442,7 +8442,7 @@ static void test_subqueries_ref()
int rc, i;
const char *query= "SELECT a as ccc from t1 where a+1=(SELECT 1+ccc from t1 where ccc+1=a+1 and a=1)";
- myheader("test_subquery_ref");
+ myheader("test_subqueries_ref");
rc= mysql_query(mysql, "DROP TABLE IF EXISTS t1");
myquery(rc);