From 707f067446939f8e00ac22fc1c07d62846737193 Mon Sep 17 00:00:00 2001 From: "anozdrin/alik@station." <> Date: Thu, 4 Oct 2007 17:19:14 +0400 Subject: [PATCH 1/2] Fix for BUG#31035: select from function, group by result crasher. This actually, fix for the patch for bug-27354. The problem with the patch was that Item_func_sp::used_tables() was updated, but Item_func_sp::const_item() was not. So, for Item_func_sp, we had the following inconsistency: - used_tables() returned RAND_TABLE, which means that the item can produce "random" results; - but const_item() returned TRUE, which means that the item is a constant one. The fix is to change Item_func_sp::const_item() behaviour: it must return TRUE (an item is a constant one) only if a stored function is deterministic and each of its arguments (if any) is a constant item. --- mysql-test/r/sp.result | 176 +++++++++++++++++++++++++ mysql-test/t/sp.test | 290 +++++++++++++++++++++++++++++++++++++++++ sql/item_func.cc | 13 +- 3 files changed, 477 insertions(+), 2 deletions(-) diff --git a/mysql-test/r/sp.result b/mysql-test/r/sp.result index a6c429a31fb..efc12b402ce 100644 --- a/mysql-test/r/sp.result +++ b/mysql-test/r/sp.result @@ -6367,4 +6367,180 @@ DROP TABLE t1; DROP PROCEDURE p1; DROP PROCEDURE p2; + +# +# Bug#31035. +# + +# +# - Prepare. +# + +DROP TABLE IF EXISTS t1; +DROP FUNCTION IF EXISTS f1; +DROP FUNCTION IF EXISTS f2; +DROP FUNCTION IF EXISTS f3; +DROP FUNCTION IF EXISTS f4; + +# +# - Create required objects. +# + +CREATE TABLE t1(c1 INT); + +INSERT INTO t1 VALUES (1), (2), (3); + +CREATE FUNCTION f1() +RETURNS INT +NOT DETERMINISTIC +RETURN 1; + +CREATE FUNCTION f2(p INT) +RETURNS INT +NOT DETERMINISTIC +RETURN 1; + +CREATE FUNCTION f3() +RETURNS INT +DETERMINISTIC +RETURN 1; + +CREATE FUNCTION f4(p INT) +RETURNS INT +DETERMINISTIC +RETURN 1; + +# +# - Check. +# + +SELECT f1() AS a FROM t1 GROUP BY a; +a +1 + +SELECT f2(@a) AS a FROM t1 GROUP BY a; +a +1 + +SELECT f3() AS a FROM t1 GROUP BY a; +a +1 + +SELECT f4(0) AS a FROM t1 GROUP BY a; +a +1 + +SELECT f4(@a) AS a FROM t1 GROUP BY a; +a +1 + +# +# - Cleanup. +# + +DROP TABLE t1; +DROP FUNCTION f1; +DROP FUNCTION f2; +DROP FUNCTION f3; +DROP FUNCTION f4; + +# +# Bug#31191. +# + +# +# - Prepare. +# + +DROP TABLE IF EXISTS t1; +DROP TABLE IF EXISTS t2; +DROP FUNCTION IF EXISTS f1; + +# +# - Create required objects. +# + +CREATE TABLE t1 ( +id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, +barcode INT(8) UNSIGNED ZEROFILL nOT NULL, +PRIMARY KEY (id), +UNIQUE KEY barcode (barcode) +); + +INSERT INTO t1 (id, barcode) VALUES (1, 12345678); +INSERT INTO t1 (id, barcode) VALUES (2, 12345679); + +CREATE TABLE test.t2 ( +id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, +barcode BIGINT(11) UNSIGNED ZEROFILL NOT NULL, +PRIMARY KEY (id) +); + +INSERT INTO test.t2 (id, barcode) VALUES (1, 12345106708); +INSERT INTO test.t2 (id, barcode) VALUES (2, 12345106709); + +CREATE FUNCTION f1(p INT(8)) +RETURNS BIGINT(11) UNSIGNED +READS SQL DATA +RETURN FLOOR(p/1000)*1000000 + 100000 + FLOOR((p MOD 1000)/10)*100 + (p MOD 10); + +# +# - Check. +# + +SELECT DISTINCT t1.barcode, f1(t1.barcode) +FROM t1 +INNER JOIN t2 +ON f1(t1.barcode) = t2.barcode +WHERE t1.barcode=12345678; +barcode f1(t1.barcode) +12345678 12345106708 + +# +# - Cleanup. +# + +DROP TABLE t1; +DROP TABLE t2; +DROP FUNCTION f1; + +# +# Bug#31226. +# + +# +# - Prepare. +# + +DROP TABLE IF EXISTS t1; +DROP FUNCTION IF EXISTS f1; + +# +# - Create required objects. +# + +CREATE TABLE t1(id INT); + +INSERT INTO t1 VALUES (1), (2), (3); + +CREATE FUNCTION f1() +RETURNS DATETIME +NOT DETERMINISTIC NO SQL +RETURN NOW(); + +# +# - Check. +# + +SELECT f1() FROM t1 GROUP BY 1; +f1() + + +# +# - Cleanup. +# + +DROP TABLE t1; +DROP FUNCTION f1; + End of 5.0 tests diff --git a/mysql-test/t/sp.test b/mysql-test/t/sp.test index 8dd99c2b2f1..6413cf2e89d 100644 --- a/mysql-test/t/sp.test +++ b/mysql-test/t/sp.test @@ -7354,4 +7354,294 @@ DROP TABLE t1; DROP PROCEDURE p1; DROP PROCEDURE p2; +########################################################################### + +# +# Bug#31035: select from function, group by result crasher. +# + +########################################################################### + +--echo + +--echo # +--echo # Bug#31035. +--echo # + +--echo + +--echo # +--echo # - Prepare. +--echo # + +--echo + +--disable_warnings +DROP TABLE IF EXISTS t1; +DROP FUNCTION IF EXISTS f1; +DROP FUNCTION IF EXISTS f2; +DROP FUNCTION IF EXISTS f3; +DROP FUNCTION IF EXISTS f4; +--enable_warnings + +--echo + +--echo # +--echo # - Create required objects. +--echo # + +--echo + +CREATE TABLE t1(c1 INT); + +--echo + +INSERT INTO t1 VALUES (1), (2), (3); + +--echo + +CREATE FUNCTION f1() + RETURNS INT + NOT DETERMINISTIC + RETURN 1; + +--echo + +CREATE FUNCTION f2(p INT) + RETURNS INT + NOT DETERMINISTIC + RETURN 1; + +--echo + +CREATE FUNCTION f3() + RETURNS INT + DETERMINISTIC + RETURN 1; + +--echo + +CREATE FUNCTION f4(p INT) + RETURNS INT + DETERMINISTIC + RETURN 1; + +--echo + +--echo # +--echo # - Check. +--echo # + +--echo + +# Not deterministic function, no arguments. + +SELECT f1() AS a FROM t1 GROUP BY a; + +--echo + +# Not deterministic function, non-constant argument. + +SELECT f2(@a) AS a FROM t1 GROUP BY a; + +--echo + +# Deterministic function, no arguments. + +SELECT f3() AS a FROM t1 GROUP BY a; + +--echo + +# Deterministic function, constant argument. + +SELECT f4(0) AS a FROM t1 GROUP BY a; + +--echo + +# Deterministic function, non-constant argument. + +SELECT f4(@a) AS a FROM t1 GROUP BY a; + +--echo + +--echo # +--echo # - Cleanup. +--echo # + +--echo + +DROP TABLE t1; +DROP FUNCTION f1; +DROP FUNCTION f2; +DROP FUNCTION f3; +DROP FUNCTION f4; + +--echo + +########################################################################### + +# +# Bug#31191: JOIN in combination with stored function crashes the server. +# + +########################################################################### + +--echo # +--echo # Bug#31191. +--echo # + +--echo + +--echo # +--echo # - Prepare. +--echo # + +--echo + +--disable_warnings +DROP TABLE IF EXISTS t1; +DROP TABLE IF EXISTS t2; +DROP FUNCTION IF EXISTS f1; +--enable_warnings + +--echo + +--echo # +--echo # - Create required objects. +--echo # + +--echo + +CREATE TABLE t1 ( + id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, + barcode INT(8) UNSIGNED ZEROFILL nOT NULL, + PRIMARY KEY (id), + UNIQUE KEY barcode (barcode) +); + +--echo + +INSERT INTO t1 (id, barcode) VALUES (1, 12345678); +INSERT INTO t1 (id, barcode) VALUES (2, 12345679); + +--echo + +CREATE TABLE test.t2 ( + id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, + barcode BIGINT(11) UNSIGNED ZEROFILL NOT NULL, + PRIMARY KEY (id) +); + +--echo + +INSERT INTO test.t2 (id, barcode) VALUES (1, 12345106708); +INSERT INTO test.t2 (id, barcode) VALUES (2, 12345106709); + +--echo + +CREATE FUNCTION f1(p INT(8)) + RETURNS BIGINT(11) UNSIGNED + READS SQL DATA + RETURN FLOOR(p/1000)*1000000 + 100000 + FLOOR((p MOD 1000)/10)*100 + (p MOD 10); + +--echo + +--echo # +--echo # - Check. +--echo # + +--echo + +SELECT DISTINCT t1.barcode, f1(t1.barcode) +FROM t1 +INNER JOIN t2 +ON f1(t1.barcode) = t2.barcode +WHERE t1.barcode=12345678; + +--echo + +--echo # +--echo # - Cleanup. +--echo # + +--echo + +DROP TABLE t1; +DROP TABLE t2; +DROP FUNCTION f1; + +--echo + +########################################################################### + +# +# Bug#31226: Group by function crashes mysql. +# + +########################################################################### + +--echo # +--echo # Bug#31226. +--echo # + +--echo + +--echo # +--echo # - Prepare. +--echo # + +--echo + +--disable_warnings +DROP TABLE IF EXISTS t1; +DROP FUNCTION IF EXISTS f1; +--enable_warnings + +--echo + +--echo # +--echo # - Create required objects. +--echo # + +--echo + +CREATE TABLE t1(id INT); + +--echo + +INSERT INTO t1 VALUES (1), (2), (3); + +--echo + +CREATE FUNCTION f1() + RETURNS DATETIME + NOT DETERMINISTIC NO SQL + RETURN NOW(); + +--echo + +--echo # +--echo # - Check. +--echo # + +--echo + +--replace_column 1 +SELECT f1() FROM t1 GROUP BY 1; + +--echo + +--echo # +--echo # - Cleanup. +--echo # + +--echo + +DROP TABLE t1; +DROP FUNCTION f1; + +--echo + +########################################################################### + --echo End of 5.0 tests diff --git a/sql/item_func.cc b/sql/item_func.cc index d03d497dfd0..f7103c22581 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -5583,8 +5583,13 @@ Item_func_sp::fix_fields(THD *thd, Item **ref) #endif /* ! NO_EMBEDDED_ACCESS_CHECKS */ } + if (!m_sp->m_chistics->detistic) - used_tables_cache |= RAND_TABLE_BIT; + { + used_tables_cache |= RAND_TABLE_BIT; + const_item_cache= FALSE; + } + DBUG_RETURN(res); } @@ -5592,6 +5597,10 @@ Item_func_sp::fix_fields(THD *thd, Item **ref) void Item_func_sp::update_used_tables() { Item_func::update_used_tables(); + if (!m_sp->m_chistics->detistic) - used_tables_cache |= RAND_TABLE_BIT; + { + used_tables_cache |= RAND_TABLE_BIT; + const_item_cache= FALSE; + } } From 305ebc1e21d72c54a2c3e7714b884b847da06efc Mon Sep 17 00:00:00 2001 From: "davi@moksha.local" <> Date: Thu, 4 Oct 2007 17:34:41 -0300 Subject: [PATCH 2/2] Bug#21587 FLUSH TABLES causes server crash when used with HANDLER statements This bug is a symptom of the way handler's tables are managed. The most different aspect, compared to the conventional behavior, is that the handler's tables are long lived, meaning that their lifetimes are not bounded by the duration of the command that opened them. For this effect the handler code uses its own list (handler_tables instead of open_tables) to hold open handler tables so that the tables won't be closed at the end of the command/statement. Besides the handler_tables list, there is a hash (handler_tables_hash) which is used to associate handler aliases to tables and to refresh the tables upon demand (flush tables). The current implementation doesn't work properly with refreshed tables -- more precisely when flush commands are issued by other initiators. This happens because when a handler open or read statement is being processed, the associated table has to be opened or locked and, for this matter, the open_tables and handler_tables lists are swapped so that the new table being opened is inserted into the handler_tables list. But when opening or locking the table, if the refresh version is different from the thread refresh version then all used tables in the open_tables list (now handler_tables) are refreshed. In the "refreshing" process the handler tables are flushed (closed) without being properly unlinked from the handler hash. The current implementation also fails to properly discard handlers of dropped tables, but this and other problems are going to be addressed in the fixes for bugs 31397 and 31409. The chosen approach tries to properly save and restore the table state so that no table is flushed during the table open and lock operations. The logic is almost the same as before with the list swapping, but with a working glue code. The test case for this bug is going to be committed into 5.1 because it requires a test feature only avaiable in 5.1 (wait_condition). --- sql/sql_handler.cc | 53 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 41 insertions(+), 12 deletions(-) diff --git a/sql/sql_handler.cc b/sql/sql_handler.cc index 9aefa71647e..89090c9fa63 100644 --- a/sql/sql_handler.cc +++ b/sql/sql_handler.cc @@ -65,11 +65,6 @@ static enum enum_ha_read_modes rkey_to_rnext[]= { RNEXT_SAME, RNEXT, RPREV, RNEXT, RPREV, RNEXT, RPREV, RPREV }; -#define HANDLER_TABLES_HACK(thd) { \ - TABLE *tmp=thd->open_tables; \ - thd->open_tables=thd->handler_tables; \ - thd->handler_tables=tmp; } - static int mysql_ha_flush_table(THD *thd, TABLE **table_ptr, uint mode_flags); @@ -187,6 +182,7 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen) char *db, *name, *alias; uint dblen, namelen, aliaslen, counter; int error; + TABLE *backup_open_tables, *backup_handler_tables; DBUG_ENTER("mysql_ha_open"); DBUG_PRINT("enter",("'%s'.'%s' as '%s' reopen: %d", tables->db, tables->table_name, tables->alias, @@ -215,18 +211,31 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen) } } + /* save open_ and handler_ tables state */ + backup_open_tables= thd->open_tables; + backup_handler_tables= thd->handler_tables; + + /* no pre-opened tables */ + thd->open_tables= NULL; + /* to avoid flushes */ + thd->handler_tables= NULL; + /* open_tables() will set 'tables->table' if successful. It must be NULL for a real open when calling open_tables(). */ DBUG_ASSERT(! tables->table); - HANDLER_TABLES_HACK(thd); /* for now HANDLER can be used only for real TABLES */ tables->required_type= FRMTYPE_TABLE; error= open_tables(thd, &tables, &counter, 0); - HANDLER_TABLES_HACK(thd); + /* restore the state and merge the opened table into handler_tables list */ + thd->handler_tables= thd->open_tables ? + thd->open_tables->next= backup_handler_tables, + thd->open_tables : backup_handler_tables; + thd->open_tables= backup_open_tables; + if (error) goto err; @@ -351,7 +360,7 @@ bool mysql_ha_read(THD *thd, TABLE_LIST *tables, ha_rows select_limit_cnt, ha_rows offset_limit_cnt) { TABLE_LIST *hash_tables; - TABLE *table; + TABLE *table, *backup_open_tables, *backup_handler_tables; MYSQL_LOCK *lock; List list; Protocol *protocol= thd->protocol; @@ -361,7 +370,7 @@ bool mysql_ha_read(THD *thd, TABLE_LIST *tables, uint num_rows; byte *key; uint key_len; - bool not_used; + bool need_reopen; DBUG_ENTER("mysql_ha_read"); DBUG_PRINT("enter",("'%s'.'%s' as '%s'", tables->db, tables->table_name, tables->alias)); @@ -375,6 +384,7 @@ bool mysql_ha_read(THD *thd, TABLE_LIST *tables, List_iterator it(list); it++; +retry: if ((hash_tables= (TABLE_LIST*) hash_search(&thd->handler_tables_hash, (byte*) tables->alias, strlen(tables->alias) + 1))) @@ -427,9 +437,28 @@ bool mysql_ha_read(THD *thd, TABLE_LIST *tables, } tables->table=table; - HANDLER_TABLES_HACK(thd); - lock= mysql_lock_tables(thd, &tables->table, 1, 0, ¬_used); - HANDLER_TABLES_HACK(thd); + /* save open_ and handler_ tables state */ + backup_open_tables= thd->open_tables; + backup_handler_tables= thd->handler_tables; + + /* no pre-opened tables */ + thd->open_tables= NULL; + /* to avoid flushes */ + thd->handler_tables= NULL; + + lock= mysql_lock_tables(thd, &tables->table, 1, + MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN, &need_reopen); + + /* restore previous context */ + thd->handler_tables= backup_handler_tables; + thd->open_tables= backup_open_tables; + + if (need_reopen) + { + mysql_ha_close_table(thd, tables); + hash_tables->table= NULL; + goto retry; + } if (!lock) goto err0; // mysql_lock_tables() printed error message already