List:Commits« Previous MessageNext Message »
From:dlenev Date:May 9 2006 12:39pm
Subject:bk commit into 5.0 tree (dlenev:1.2139)
View as plain text  
Below is the list of changes that have just been committed into a local
5.0 repository of dlenev. When dlenev does a push these changes will
be propagated to the main repository and, within 24 hours after the
push, to the public repository.
For information on how to access the public repository
see http://dev.mysql.com/doc/mysql/en/installing-source-tree.html

ChangeSet
  1.2139 06/05/09 16:39:11 dlenev@stripped +6 -0
  Fix for bugs#12472/#15137 'CREATE TABLE ... SELECT ... which explicitly
  or implicitly uses stored function gives "Table not locked" error'
  
  CREATE TABLE ... SELECT ... statement which was explicitly or implicitly
  (through view) using stored function gave "Table not locked" error.
  
  The actual bug resides in the current locking scheme of CREATE TABLE SELECT
  code, which first opens and locks tables of the SELECT statement itself,
  and then, having SELECT tables locked, creates the .FRM, opens the .FRM and
  acquires lock on it. This scheme opens a possibility for a deadlock, which
  was present and ignored since version 3.23 or earlier. This scheme also
  conflicts with the invariant of the prelocking algorithm -- no table can
  be open and locked while there are tables locked in prelocked mode.
  
  The patch makes an exception for this invariant when doing CREATE TABLE ...
  SELECT, thus extending the possibility of a deadlock to the prelocked mode.
  We can't supply a better fix in 5.0.

  sql/sql_table.cc
    1.304 06/05/09 16:39:06 dlenev@stripped +0 -99
    Moved create_table_from_items() routine into sql_insert.cc, since it was not
    used anywhere outside of this file and contains logic which is specific for
    CREATE TABLE ... SELECT statement.

  sql/sql_insert.cc
    1.186 06/05/09 16:39:06 dlenev@stripped +147 -0
    Moved create_table_from_items() from sql_table.cc to sql_insert.cc as it was
    not used outside of sql_insert.cc and contains code which is specific for
    CREATE TABLE ... SELECT.
    Also now when we are executing CREATE TABLE ... SELECT ... statement which
    SELECT part requires execution in prelocked mode we ignore set of locked
    tables in order to get access to the table we just have created.
    We probably don't want to do this if we are under real LOCK TABLES since
    it will widen window for deadlock too much.

  sql/sql_base.cc
    1.331 06/05/09 16:39:06 dlenev@stripped +4 -1
    open_table():
      Added flag which allows open table ignoring set of locked tables and
      prelocked mode.

  sql/mysql_priv.h
    1.381 06/05/09 16:39:06 dlenev@stripped +2 -7
    Added flag which can be passed to open_table() routine in order to ignore
    set of locked tables and prelocked mode.
    We don't need declaration of create_table_from_items() any longer as it was
    moved into sql_insert.cc and made static.

  mysql-test/t/sp.test
    1.182 06/05/09 16:39:06 dlenev@stripped +23 -0
    Added tests for bugs#12472/#15137 'CREATE TABLE ... SELECT ... which
    explicitly or implicitly uses stored function gives "Table not locked" error'

  mysql-test/r/sp.result
    1.194 06/05/09 16:39:06 dlenev@stripped +25 -0
    Added tests for bugs#12472/#15137 'CREATE TABLE ... SELECT ... which
    explicitly or implicitly uses stored function gives "Table not locked" error'

# This is a BitKeeper patch.  What follows are the unified diffs for the
# set of deltas contained in the patch.  The rest of the patch, the part
# that BitKeeper cares about, is below these diffs.
# User:	dlenev
# Host:	jabberwock.site
# Root:	/home/dlenev/mysql-5.0-bg12472

--- 1.380/sql/mysql_priv.h	2006-03-30 17:14:51 +04:00
+++ 1.381/sql/mysql_priv.h	2006-05-09 16:39:06 +04:00
@@ -718,12 +718,6 @@
                         List<create_field> &fields, List<Key> &keys,
                         bool tmp_table, uint select_field_count);
 
-TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info,
-			       TABLE_LIST *create_table,
-			       List<create_field> *extra_fields,
-			       List<Key> *keys,
-			       List<Item> *items,
-                               MYSQL_LOCK **lock);
 bool mysql_alter_table(THD *thd, char *new_db, char *new_name,
                        HA_CREATE_INFO *create_info,
                        TABLE_LIST *table_list,
@@ -1312,10 +1306,11 @@
 
 MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **table, uint count,
                               uint flags, bool *need_reopen);
-/* mysql_lock_tables() flags bits */
+/* mysql_lock_tables() and open_table() flags bits */
 #define MYSQL_LOCK_IGNORE_GLOBAL_READ_LOCK      0x0001
 #define MYSQL_LOCK_IGNORE_FLUSH                 0x0002
 #define MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN        0x0004
+#define MYSQL_OPEN_IGNORE_LOCKED_TABLES         0x0008
 
 void mysql_unlock_tables(THD *thd, MYSQL_LOCK *sql_lock);
 void mysql_unlock_read_tables(THD *thd, MYSQL_LOCK *sql_lock);

--- 1.330/sql/sql_base.cc	2006-03-06 12:41:16 +03:00
+++ 1.331/sql/sql_base.cc	2006-05-09 16:39:06 +04:00
@@ -1076,6 +1076,8 @@
                           MYSQL_LOCK_IGNORE_FLUSH - Open table even if
                           someone has done a flush or namelock on it.
                           No version number checking is done.
+                          MYSQL_OPEN_IGNORE_LOCKED_TABLES - Open table
+                          ignoring set of locked tables and prelocked mode.
 
   IMPLEMENTATION
     Uses a cache of open tables to find a table not in use.
@@ -1135,7 +1137,8 @@
     }
   }
 
-  if (thd->locked_tables || thd->prelocked_mode)
+  if (!(flags & MYSQL_OPEN_IGNORE_LOCKED_TABLES) &&
+      (thd->locked_tables || thd->prelocked_mode))
   {						// Using table locks
     TABLE *best_table= 0;
     int best_distance= INT_MIN;

--- 1.185/sql/sql_insert.cc	2006-03-29 14:54:59 +04:00
+++ 1.186/sql/sql_insert.cc	2006-05-09 16:39:06 +04:00
@@ -2434,6 +2434,153 @@
   CREATE TABLE (SELECT) ...
 ***************************************************************************/
 
+/*
+  Create table from lists of fields and items (or open existing table
+  with same name).
+
+  SYNOPSIS
+    create_table_from_items()
+      thd          in     Thread object
+      create_info  in     Create information (like MAX_ROWS, ENGINE or
+                          temporary table flag)
+      create_table in     Pointer to TABLE_LIST object providing database
+                          and name for table to be created or to be open
+      extra_fields in/out Initial list of fields for table to be created
+      keys         in     List of keys for table to be created
+      items        in     List of items which should be used to produce rest
+                          of fields for the table (corresponding fields will
+                          be added to the end of 'extra_fields' list)
+      lock         out    Pointer to the MYSQL_LOCK object for table created
+                          (open) will be returned in this parameter. Since
+                          this table is not included in THD::lock caller is
+                          responsible for explicitly unlocking this table.
+
+  NOTES
+    If 'create_info->options' bitmask has HA_LEX_CREATE_IF_NOT_EXISTS
+    flag and table with name provided already exists then this function will
+    simply open existing table.
+    Also note that create, open and lock sequence in this function is not
+    atomic and thus contains gap for deadlock and can cause other troubles.
+    Since this function contains some logic specific to CREATE TABLE ... SELECT
+    it should be changed before it can be used in other contexts.
+
+  RETURN VALUES
+    non-zero  Pointer to TABLE object for table created or opened
+    0         Error
+*/
+
+static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info,
+                                      TABLE_LIST *create_table,
+                                      List<create_field> *extra_fields,
+                                      List<Key> *keys, List<Item> *items,
+                                      MYSQL_LOCK **lock)
+{
+  TABLE tmp_table;		// Used during 'create_field()'
+  TABLE *table= 0;
+  uint select_field_count= items->elements;
+  /* Add selected items to field list */
+  List_iterator_fast<Item> it(*items);
+  Item *item;
+  Field *tmp_field;
+  bool not_used;
+  DBUG_ENTER("create_table_from_items");
+
+  tmp_table.alias= 0;
+  tmp_table.timestamp_field= 0;
+  tmp_table.s= &tmp_table.share_not_to_be_used;
+  tmp_table.s->db_create_options=0;
+  tmp_table.s->blob_ptr_size= portable_sizeof_char_ptr;
+  tmp_table.s->db_low_byte_first= test(create_info->db_type == DB_TYPE_MYISAM ||
+                                       create_info->db_type == DB_TYPE_HEAP);
+  tmp_table.null_row=tmp_table.maybe_null=0;
+
+  while ((item=it++))
+  {
+    create_field *cr_field;
+    Field *field;
+    if (item->type() == Item::FUNC_ITEM)
+      field=item->tmp_table_field(&tmp_table);
+    else
+      field=create_tmp_field(thd, &tmp_table, item, item->type(),
+                             (Item ***) 0, &tmp_field, 0, 0, 0, 0, 0);
+    if (!field ||
+	!(cr_field=new create_field(field,(item->type() == Item::FIELD_ITEM ?
+					   ((Item_field *)item)->field :
+					   (Field*) 0))))
+      DBUG_RETURN(0);
+    if (item->maybe_null)
+      cr_field->flags &= ~NOT_NULL_FLAG;
+    extra_fields->push_back(cr_field);
+  }
+  /*
+    create and lock table
+
+    We don't log the statement, it will be logged later.
+
+    If this is a HEAP table, the automatic DELETE FROM which is written to the
+    binlog when a HEAP table is opened for the first time since startup, must
+    not be written: 1) it would be wrong (imagine we're in CREATE SELECT: we
+    don't want to delete from it) 2) it would be written before the CREATE
+    TABLE, which is a wrong order. So we keep binary logging disabled when we
+    open_table().
+    NOTE: By locking table which we just have created (or for which we just have
+    have found that it already exists) separately from other tables used by the
+    statement we create potential window for deadlock.
+    TODO: create and open should be done atomic !
+  */
+  {
+    tmp_disable_binlog(thd);
+    if (!mysql_create_table(thd, create_table->db, create_table->table_name,
+                            create_info, *extra_fields, *keys, 0,
+                            select_field_count))
+    {
+      /*
+        If we are here in prelocked mode we either create temporary table
+        or prelocked mode is caused by the SELECT part of this statement.
+      */
+      DBUG_ASSERT(!thd->prelocked_mode ||
+                  create_info->options & HA_LEX_CREATE_TMP_TABLE ||
+                  thd->lex->requires_prelocking());
+
+      /*
+        NOTE: We don't want to ignore set of locked tables here if we are
+              under explicit LOCK TABLES since it will open gap for deadlock
+              too wide (and also is not backward compatible).
+      */
+      if (! (table= open_table(thd, create_table, thd->mem_root, (bool*) 0,
+                               (MYSQL_LOCK_IGNORE_FLUSH |
+                                ((thd->prelocked_mode == PRELOCKED) ?
+                                 MYSQL_OPEN_IGNORE_LOCKED_TABLES:0)))))
+        quick_rm_table(create_info->db_type, create_table->db,
+                       table_case_name(create_info, create_table->table_name));
+    }
+    reenable_binlog(thd);
+    if (!table)                                   // open failed
+      DBUG_RETURN(0);
+  }
+
+  /*
+    FIXME: What happens if trigger manages to be created while we are
+           obtaining this lock ? May be it is sensible just to disable
+           trigger execution in this case ? Or will MYSQL_LOCK_IGNORE_FLUSH
+           save us from that ?
+  */
+  table->reginfo.lock_type=TL_WRITE;
+  if (! ((*lock)= mysql_lock_tables(thd, &table, 1,
+                                    MYSQL_LOCK_IGNORE_FLUSH, &not_used)))
+  {
+    VOID(pthread_mutex_lock(&LOCK_open));
+    hash_delete(&open_cache,(byte*) table);
+    VOID(pthread_mutex_unlock(&LOCK_open));
+    quick_rm_table(create_info->db_type, create_table->db,
+		   table_case_name(create_info, create_table->table_name));
+    DBUG_RETURN(0);
+  }
+  table->file->extra(HA_EXTRA_WRITE_CACHE);
+  DBUG_RETURN(table);
+}
+
+
 int
 select_create::prepare(List<Item> &values, SELECT_LEX_UNIT *u)
 {

--- 1.303/sql/sql_table.cc	2006-03-30 17:14:51 +04:00
+++ 1.304/sql/sql_table.cc	2006-05-09 16:39:06 +04:00
@@ -1761,105 +1761,6 @@
 
 
 /****************************************************************************
-** Create table from a list of fields and items
-****************************************************************************/
-
-TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info,
-			       TABLE_LIST *create_table,
-			       List<create_field> *extra_fields,
-			       List<Key> *keys,
-			       List<Item> *items,
-			       MYSQL_LOCK **lock)
-{
-  TABLE tmp_table;		// Used during 'create_field()'
-  TABLE *table= 0;
-  uint select_field_count= items->elements;
-  /* Add selected items to field list */
-  List_iterator_fast<Item> it(*items);
-  Item *item;
-  Field *tmp_field;
-  bool not_used;
-  DBUG_ENTER("create_table_from_items");
-
-  tmp_table.alias= 0;
-  tmp_table.timestamp_field= 0;
-  tmp_table.s= &tmp_table.share_not_to_be_used;
-  tmp_table.s->db_create_options=0;
-  tmp_table.s->blob_ptr_size= portable_sizeof_char_ptr;
-  tmp_table.s->db_low_byte_first= test(create_info->db_type == DB_TYPE_MYISAM ||
-                                       create_info->db_type == DB_TYPE_HEAP);
-  tmp_table.null_row=tmp_table.maybe_null=0;
-
-  while ((item=it++))
-  {
-    create_field *cr_field;
-    Field *field;
-    if (item->type() == Item::FUNC_ITEM)
-      field=item->tmp_table_field(&tmp_table);
-    else
-      field=create_tmp_field(thd, &tmp_table, item, item->type(),
-                             (Item ***) 0, &tmp_field, 0, 0, 0, 0, 0);
-    if (!field ||
-	!(cr_field=new create_field(field,(item->type() == Item::FIELD_ITEM ?
-					   ((Item_field *)item)->field :
-					   (Field*) 0))))
-      DBUG_RETURN(0);
-    if (item->maybe_null)
-      cr_field->flags &= ~NOT_NULL_FLAG;
-    extra_fields->push_back(cr_field);
-  }
-  /*
-    create and lock table
-
-    We don't log the statement, it will be logged later.
-
-    If this is a HEAP table, the automatic DELETE FROM which is written to the
-    binlog when a HEAP table is opened for the first time since startup, must
-    not be written: 1) it would be wrong (imagine we're in CREATE SELECT: we
-    don't want to delete from it) 2) it would be written before the CREATE
-    TABLE, which is a wrong order. So we keep binary logging disabled when we
-    open_table().
-    TODO: create and open should be done atomic !
-  */
-  {
-    tmp_disable_binlog(thd);
-    if (!mysql_create_table(thd, create_table->db, create_table->table_name,
-                            create_info, *extra_fields, *keys, 0,
-                            select_field_count))
-    {
-      if (! (table= open_table(thd, create_table, thd->mem_root, (bool*) 0,
-                               MYSQL_LOCK_IGNORE_FLUSH)))
-        quick_rm_table(create_info->db_type, create_table->db,
-                       table_case_name(create_info, create_table->table_name));
-    }
-    reenable_binlog(thd);
-    if (!table)                                   // open failed
-      DBUG_RETURN(0);
-  }
-
-  /*
-    FIXME: What happens if trigger manages to be created while we are
-           obtaining this lock ? May be it is sensible just to disable
-           trigger execution in this case ? Or will MYSQL_LOCK_IGNORE_FLUSH
-           save us from that ?
-  */
-  table->reginfo.lock_type=TL_WRITE;
-  if (! ((*lock)= mysql_lock_tables(thd, &table, 1,
-                                    MYSQL_LOCK_IGNORE_FLUSH, &not_used)))
-  {
-    VOID(pthread_mutex_lock(&LOCK_open));
-    hash_delete(&open_cache,(byte*) table);
-    VOID(pthread_mutex_unlock(&LOCK_open));
-    quick_rm_table(create_info->db_type, create_table->db,
-		   table_case_name(create_info, create_table->table_name));
-    DBUG_RETURN(0);
-  }
-  table->file->extra(HA_EXTRA_WRITE_CACHE);
-  DBUG_RETURN(table);
-}
-
-
-/****************************************************************************
 ** Alter a table definition
 ****************************************************************************/
 

--- 1.193/mysql-test/r/sp.result	2006-03-28 16:18:44 +04:00
+++ 1.194/mysql-test/r/sp.result	2006-05-09 16:39:06 +04:00
@@ -4837,4 +4837,29 @@
 b	3
 a	1
 delete from t1|
+drop function if exists bug12472|
+create function bug12472() returns int return (select count(*) from t1)|
+create table t3 as select bug12472() as i|
+show create table t3|
+Table	Create Table
+t3	CREATE TABLE `t3` (
+  `i` int(11) default NULL
+) ENGINE=MyISAM DEFAULT CHARSET=latin1
+select * from t3|
+i
+0
+drop table t3|
+create view v1 as select bug12472() as j|
+create table t3 as select * from v1|
+show create table t3|
+Table	Create Table
+t3	CREATE TABLE `t3` (
+  `j` bigint(11) default NULL
+) ENGINE=MyISAM DEFAULT CHARSET=latin1
+select * from t3|
+j
+0
+drop table t3|
+drop view v1|
+drop function bug12472|
 drop table t1,t2;

--- 1.181/mysql-test/t/sp.test	2006-03-28 16:17:15 +04:00
+++ 1.182/mysql-test/t/sp.test	2006-05-09 16:39:06 +04:00
@@ -5684,6 +5684,29 @@
 
 
 #
+# BUG#12472/BUG#15137 'CREATE TABLE ... SELECT ... which explicitly or
+# implicitly uses stored function gives "Table not locked" error'.
+#
+--disable_warnings
+drop function if exists bug12472|
+--enable_warnings
+create function bug12472() returns int return (select count(*) from t1)|
+# Check case when function is used directly
+create table t3 as select bug12472() as i|
+show create table t3|
+select * from t3|
+drop table t3|
+# Check case when function is used indirectly through view
+create view v1 as select bug12472() as j|
+create table t3 as select * from v1|
+show create table t3|
+select * from t3|
+drop table t3|
+drop view v1|
+drop function bug12472|
+
+
+#
 # BUG#NNNN: New bug synopsis
 #
 #--disable_warnings
Thread
bk commit into 5.0 tree (dlenev:1.2139)dlenev9 May