List:Commits« Previous MessageNext Message »
From:Dmitry Lenev Date:June 30 2009 12:40pm
Subject:bzr commit into mysql-6.1-fk branch (dlenev:2726)
View as plain text  
#At file:///home/dlenev/src/bzr/mysql-6.1-mil13/ based on revid:dlenev@stripped

 2726 Dmitry Lenev	2009-06-30
      Milestone 13 "DDL checks and changes: ALTER, CREATE INDEX, DROP INDEX words".
      
      Work in progress.
      
      Moved ALTER TABLE's code handling locking for foreign key names
      in LOCK TABLES mode to auxiliary functions.

    modified:
      mysql-test/r/foreign_key_all_engines_2.result
      mysql-test/t/foreign_key_all_engines_2.test
      sql/sql_table.cc
=== modified file 'mysql-test/r/foreign_key_all_engines_2.result'
--- a/mysql-test/r/foreign_key_all_engines_2.result	2009-06-25 08:43:04 +0000
+++ b/mysql-test/r/foreign_key_all_engines_2.result	2009-06-30 12:40:10 +0000
@@ -1576,6 +1576,21 @@ t2	CREATE TABLE `t2` (
 delete from t1 where a = 1;
 ERROR 23000: Foreign key error: constraint 'c': cannot change because foreign key refers to value '1'
 # 
+# Similar check for alter that tries to replace foreign key.
+alter table t2 drop constraint c,
+add constraint c foreign key (a) references t1 (no_such_column);
+ERROR 42000: Foreign key error: Constraint 'c': No such column 'no_such_column' in parent table
+# Check that metadata was not changed.
+show create table t2;
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `a` int(11) DEFAULT NULL,
+  KEY `c` (`a`),
+  CONSTRAINT `c` FOREIGN KEY (`a`) REFERENCES `t1` (`a`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+delete from t1 where a = 1;
+ERROR 23000: Foreign key error: constraint 'c': cannot change because foreign key refers to value '1'
+# 
 # Finally let us see what happens when one uses the same constraint
 # name twice.
 alter table t2 drop constraint c, drop constraint c;

=== modified file 'mysql-test/t/foreign_key_all_engines_2.test'
--- a/mysql-test/t/foreign_key_all_engines_2.test	2009-06-25 08:43:04 +0000
+++ b/mysql-test/t/foreign_key_all_engines_2.test	2009-06-30 12:40:10 +0000
@@ -1481,6 +1481,16 @@ show create table t2;
 --error ER_FK_CHILD_VALUE_EXISTS
 delete from t1 where a = 1;
 --echo # 
+--echo # Similar check for alter that tries to replace foreign key.
+--error ER_FK_PARENT_COLUMN_MISSING
+alter table t2 drop constraint c,
+               add constraint c foreign key (a) references t1 (no_such_column);
+--echo # Check that metadata was not changed.
+--replace_result $engine_type <engine_type>
+show create table t2;
+--error ER_FK_CHILD_VALUE_EXISTS
+delete from t1 where a = 1;
+--echo # 
 --echo # Finally let us see what happens when one uses the same constraint
 --echo # name twice.
 --error ER_CANT_DROP_FIELD_OR_KEY

=== modified file 'sql/sql_table.cc'
--- a/sql/sql_table.cc	2009-06-28 06:37:40 +0000
+++ b/sql/sql_table.cc	2009-06-30 12:40:10 +0000
@@ -2389,19 +2389,13 @@ drop_table_check_locks(THD *thd, TABLE_L
       LEX_STRING db_name= { table->db, table->db_length };
 
       if (! (fk_name= Foreign_key_name::create(thd->mem_root, db_name,
-                                               fk_c_s->name)))
+                                               fk_c_s->name)) ||
+          fk_names_locked->push_back(fk_name, thd->mem_root))
         return TRUE;
 
-      /*
-        TODO/FIXME: Check what happens with list contents in case of error.
-      */
-
       fk_name->find_ticket(&thd->mdl_context);
 
       DBUG_ASSERT(fk_name->get_mdl_request()->ticket);
-
-      if (fk_names_locked->push_back(fk_name, thd->mem_root))
-        return TRUE;
     }
   }
   return FALSE;
@@ -8731,6 +8725,267 @@ err:
 
 
 /**
+  Handle ALTER TABLE's locking for foreign key names under LOCK TABLES
+  by upgrading pre-acquired shared metadata locks on names of foreign
+  keys to be dropped and acquiring exclusive locks for names of foreign
+  keys to be created.
+
+  @param[in]   thd               Thread context.
+  @param[in]   table_list        Table list element for table being altered.
+  @param[in]   alter_info        Alter_info object containing information about
+                                 foreign keys to be dropped and added.
+  @param[out]  fk_names_drop     List of names of foreign keys which are going
+                                 to be dropped, shared metadata locks for which
+                                 were upgraded to exclusive locks.
+  @param[out]  fk_names_add      List of names of foreign keys which are going
+                                 to be added and for which exclusive metadata
+                                 locks were acquired.
+  @param[out]  fk_names_replace  List of names of foreign keys which are going
+                                 to be replaced with new foreign keys with the
+                                 same names, shared locks for which were
+                                 upgraded to exclusive locks.
+
+  @note In case of failure caller of this function is still responsible
+        for restoring original state of metadata locks by calling
+        alter_table_restore_fk_name_locks_under_lock_tables() function.
+
+  @retval FALSE  Success.
+  @retval TRUE   Failure (due to OOM or KILL statement).
+*/
+
+bool
+alter_table_lock_fk_names_under_lock_tables(THD *thd, TABLE_LIST *table_list,
+                                      Alter_info *alter_info,
+                                      List<Foreign_key_name> *fk_names_drop,
+                                      List<Foreign_key_name> *fk_names_add,
+                                      List<Foreign_key_name> *fk_names_replace)
+{
+  /*
+    For all foreign keys to be dropped find shared metadata locks on their
+    names which were pre-acquired at LOCK TABLES time and upgrade them to
+    exclusive locks. Deadlocks are impossible in this case as connection
+    which holds shared lock on foreign key name also holds TL_WRITE on its
+    child table and thus there can be only one such connection at the moment.
+  */
+  List_iterator<Alter_drop> drop_it(alter_info->drop_list);
+  Alter_drop *drop;
+
+  DBUG_ASSERT(fk_names_drop->is_empty());
+
+  while ((drop= drop_it++))
+  {
+    if (drop->type == Alter_drop::FOREIGN_KEY)
+    {
+      LEX_STRING db_name= { table_list->db, table_list->db_length };
+      LEX_STRING name= { (char *)drop->name, strlen(drop->name) };
+      Foreign_key_name *fk_name;
+      MDL_ticket *mdl_ticket;
+
+      if (! (fk_name= Foreign_key_name::create(thd->mem_root, db_name, name)))
+        return TRUE;
+
+      if (! fk_name->find_ticket(&thd->mdl_context))
+      {
+        /*
+          We have not prelocked this foreign key name at LOCK TABLES time.
+          This means that constraint with such name either does not exist
+          or belongs to table other than one being altered.
+          Don't do anything, this statement will end with an appropriate
+          error in any case.
+        */
+        continue;
+      }
+
+      mdl_ticket= fk_name->get_mdl_request()->ticket;
+
+      if (mdl_ticket->upgrade_shared_lock_to_exclusive())
+        return TRUE;
+
+      if (fk_names_drop->push_back(fk_name, thd->mem_root))
+      {
+        mdl_ticket->downgrade_exclusive_lock();
+        return TRUE;
+      }
+    }
+  }
+
+  /**
+    Now let us try to acquire exclusive metadata locks on names of foreign keys
+    to be created. We avoid deadlocks in this case by using try-lock method
+    instead of one which will wait until conflicting locks will go away.
+    We interpret existence of conflicting lock as existence of foreign key with
+    such name and emit a corresponding error in this case. Indeed, such approach
+    might give false positives if there are other DDL statements which tries to
+    do something with a foreign key with the same name, but since chance of this
+    is fairly low we accept this trade-off.
+
+    Note that the case when new foreign key replaces the old foreign key with
+    the same name has to be handled with special care.
+  */
+
+  List_iterator<Foreign_key_child> fk_it(alter_info->foreign_key_list);
+  Foreign_key_child *fk;
+
+  DBUG_ASSERT(fk_names_add->is_empty() && fk_names_replace->is_empty());
+
+  while ((fk= fk_it++))
+  {
+    Foreign_key_name *fk_name;
+
+    if (! fk->replaces_existing)
+    {
+      MDL_request *mdl_request;
+      MDL_ticket *save_mdl_ticket;
+      LEX_STRING db_name = { table_list->db, table_list->db_length };
+
+      if (! (fk_name= Foreign_key_name::create(thd->mem_root,
+                                               db_name, fk->name)))
+        return TRUE;
+
+      mdl_request= fk_name->get_mdl_request();
+      mdl_request->set_type(MDL_EXCLUSIVE);
+
+      if (thd->mdl_context.try_acquire_exclusive_lock(mdl_request))
+        return TRUE;
+
+      if (mdl_request->ticket == NULL)
+      {
+        my_error(ER_FK_CONSTRAINT_NAME_DUPLICATE, MYF(0), fk->name.str);
+        return TRUE;
+      }
+
+      /*
+        Remove metadata lock request from the context as memory on
+        which it was allocated would be gone at the end of statement.
+        Store pointer to ticket on stack as removing request from a
+        context clears MDL_request::ticket member.
+      */
+      save_mdl_ticket= mdl_request->ticket;
+      thd->mdl_context.remove_request(mdl_request);
+      mdl_request->ticket= save_mdl_ticket;
+
+      if (fk_names_add->push_back(fk_name, thd->mem_root))
+      {
+        thd->mdl_context.release_lock(save_mdl_ticket);
+        return TRUE;
+      }
+    }
+    else
+    {
+      List_iterator<Foreign_key_name> fk_name_drop_it(*fk_names_drop);
+
+      while ((fk_name= fk_name_drop_it++))
+        if (fk_name->is_equal(table_list->db, fk->name.str))
+          break;
+
+      if (fk_name)
+      {
+        fk_name_drop_it.remove();
+
+        if (fk_names_replace->push_back(fk_name, thd->mem_root))
+        {
+          fk_name->get_mdl_request()->ticket->downgrade_exclusive_lock();
+          return TRUE;
+        }
+      }
+      else
+      {
+        /*
+          Absence of foreign key name in 'fk_name_drop' list when
+          Foreign_key_child::replaces_existing is TRUE means that
+          either this ALTER TABLE statement tries to drop foreign
+          key which does not exist or belongs to different table
+          or that it tries to create two foreign keys with the
+          same name. Since in both these cases this statement will
+          end with an error we can safely continue.
+        */
+      }
+    }
+  }
+
+  return FALSE;
+}
+
+
+/**
+  Restore metadata locks on foreign key names after failing to perform ALTER
+  TABLE under LOCK TABLES (downgrade locks on names which were upgraded and
+  release locks which were acquired).
+
+  @param thd               Thread context.
+  @param fk_names_drop     List of names of foreign keys which were supposed to
+                           be dropped and locks for which should be downgraded.
+  @param fk_names_add      List of names of foreign keys which were supposed to
+                           be added and locks on which should be released.
+  @param fk_names_replace  List of names of foreign keys which were supposed to
+                           replaced old foreign keys, locks for which should be
+                           downgraded.
+*/
+
+void
+alter_table_restore_fk_name_locks_under_lock_tables(THD *thd,
+                                     List<Foreign_key_name> &fk_names_drop,
+                                     List<Foreign_key_name> &fk_names_add,
+                                     List<Foreign_key_name> &fk_names_replace)
+{
+  List_iterator<Foreign_key_name> fk_drop_it(fk_names_drop);
+  List_iterator<Foreign_key_name> fk_add_it(fk_names_add);
+  List_iterator<Foreign_key_name> fk_replace_it(fk_names_replace);
+  Foreign_key_name *fk_name;
+
+  while ((fk_name= fk_drop_it++))
+    fk_name->get_mdl_request()->ticket->downgrade_exclusive_lock();
+
+  while ((fk_name= fk_add_it++))
+    thd->mdl_context.release_lock(fk_name->get_mdl_request()->ticket);
+
+  while ((fk_name= fk_replace_it++))
+    fk_name->get_mdl_request()->ticket->downgrade_exclusive_lock();
+}
+
+
+/**
+  Adjust metadata locks on foreign key names after performing ALTER TABLE
+  under LOCK TABLES by releasing locks on names of dropped foreign keys
+  and downgrading locks on names of foreign keys which were created.
+
+  @param thd               Thread context.
+  @param fk_names_drop     List of names of foreign keys which were dropped
+                           and therefore locks for which should be released.
+  @param fk_names_add      List of names of foreign keys which were added
+                           and metadata locks for which should be downgraded
+                           from exclusive to shared.
+  @param fk_names_replace  List of names of foreign keys which replaced old
+                           foreign keys, locks for which should be downgraded.
+*/
+
+void
+alter_table_release_fk_name_locks_under_lock_tables(THD *thd,
+                                      List<Foreign_key_name> &fk_names_drop,
+                                      List<Foreign_key_name> &fk_names_add,
+                                      List<Foreign_key_name> &fk_names_replace)
+{
+  List_iterator<Foreign_key_name> fk_drop_it(fk_names_drop);
+  List_iterator<Foreign_key_name> fk_add_it(fk_names_add);
+  List_iterator<Foreign_key_name> fk_replace_it(fk_names_replace);
+  Foreign_key_name *fk_name;
+
+  /*
+    QQ: Does it makes sense to use release_all_locks_for_name() here ?
+    What is the point behind this function now ?
+  */
+  while ((fk_name= fk_drop_it++))
+    thd->mdl_context.release_lock(fk_name->get_mdl_request()->ticket);
+
+  while ((fk_name= fk_add_it++))
+    fk_name->get_mdl_request()->ticket->downgrade_exclusive_lock();
+
+  while ((fk_name= fk_replace_it++))
+    fk_name->get_mdl_request()->ticket->downgrade_exclusive_lock();
+}
+
+
+/**
   Check if table being altered participates in some foreign key as parent
   which will be preserved in its new version.
 
@@ -8820,7 +9075,7 @@ bool mysql_alter_table(THD *thd,char *ne
   bool partition_changed= FALSE;
 #endif
   TABLE_LIST *tab, *parent_tables;
-  List<Foreign_key_name> fk_names_add, fk_names_drop;
+  List<Foreign_key_name> fk_names_add, fk_names_drop, fk_names_replace;
   DBUG_ENTER("mysql_alter_table");
 
   /*
@@ -9313,97 +9568,13 @@ view_err:
 
   /* We have to do full alter table. */
 
-  /*
-    FIXME/TODO: - Consider doing below opt_fk_all_engines only ?
-                - Move code to separate function.
-                - Check what happens in case of error.
-  */
-
-  if (thd->locked_tables_mode == LTM_LOCK_TABLES ||
-      thd->locked_tables_mode == LTM_PRELOCKED_UNDER_LOCK_TABLES)
-  {
-    List_iterator<Alter_drop> drop_it(alter_info->drop_list);
-    Alter_drop *drop;
-
-    while ((drop= drop_it++))
-    {
-      if (drop->type == Alter_drop::FOREIGN_KEY)
-      {
-        LEX_STRING db_name= { table_list->db, table_list->db_length };
-        LEX_STRING name= { (char *)drop->name, strlen(drop->name) };
-        Foreign_key_name *fk_name;
-
-        if (! (fk_name= Foreign_key_name::create(thd->mem_root, db_name, name)))
-          DBUG_RETURN(TRUE);
-
-        if (! fk_name->find_ticket(&thd->mdl_context))
-        {
-          /*
-            We have not prelocked this foreign key name at LOCK TABLES time.
-            This means that constraint with such name either does not exist
-            or belongs to table other than one being altered.
-            Don't do anything, this statement will end with an appropriate
-            error in any case.
-          */
-          continue;
-        }
-
-        if (fk_name->get_mdl_request()->ticket->upgrade_shared_lock_to_exclusive() ||
-            fk_names_drop.push_back(fk_name, thd->mem_root))
-          DBUG_RETURN(TRUE);
-      }
-    }
-
-    List_iterator<Foreign_key_child> fk_it(alter_info->foreign_key_list);
-    Foreign_key_child *fk;
-
-    while ((fk= fk_it++))
-    {
-      Foreign_key_name *fk_name;
-      MDL_request *mdl_request;
-      LEX_STRING db_name = { table_list->db, table_list->db_length };
-
-      if (! fk->replaces_existing)
-      {
-
-        if (! (fk_name= Foreign_key_name::create(thd->mem_root,
-                                               db_name, fk->name)) ||
-            fk_names_add.push_back(fk_name, thd->mem_root))
-          DBUG_RETURN(TRUE);
-
-        mdl_request= fk_name->get_mdl_request();
-        mdl_request->set_type(MDL_EXCLUSIVE);
-
-        if (thd->mdl_context.try_acquire_exclusive_lock(mdl_request))
-          DBUG_RETURN(TRUE);
-
-        if (mdl_request->ticket == NULL)
-        {
-          my_error(ER_FK_CONSTRAINT_NAME_DUPLICATE, MYF(0), fk->name.str);
-          DBUG_RETURN(TRUE);
-        }
-        MDL_ticket *ticket= mdl_request->ticket;
-        thd->mdl_context.remove_request(mdl_request);
-        mdl_request->ticket= ticket;
-      }
-      else
-      {
-        List_iterator<Foreign_key_name> fk_name_drop_it(fk_names_drop);
-
-        while ((fk_name= fk_name_drop_it++))
-          if (fk_name->is_equal(table_list->db, fk->name.str))
-            break;
-
-        if (fk_name)
-        {
-          fk_name_drop_it.remove();
-
-          if (fk_names_add.push_back(fk_name, thd->mem_root))
-            DBUG_RETURN(TRUE);
-        }
-      }
-    }
-  }
+  if (opt_fk_all_engines &&
+      (thd->locked_tables_mode == LTM_LOCK_TABLES ||
+       thd->locked_tables_mode == LTM_PRELOCKED_UNDER_LOCK_TABLES) &&
+      alter_table_lock_fk_names_under_lock_tables(thd, table_list, alter_info,
+                                                  &fk_names_drop, &fk_names_add,
+                                                  &fk_names_replace))
+    goto err;
 
 #ifdef WITH_PARTITION_STORAGE_ENGINE
   if (prep_alter_part_table(thd, table, alter_info, create_info, old_db_type,
@@ -9900,7 +10071,8 @@ end_online:
     thd->mdl_context.remove_request(&target_mdl_request);
   }
 
-  if (thd->locked_tables_mode)
+  if (thd->locked_tables_mode == LTM_LOCK_TABLES ||
+      thd->locked_tables_mode == LTM_PRELOCKED_UNDER_LOCK_TABLES)
   {
     if ((new_name != table_name || new_db != db))
     {
@@ -9909,27 +10081,10 @@ end_online:
     else
       mdl_ticket->downgrade_exclusive_lock();
 
-    /*
-      TODO/FIXME: Add comment, consider doing this only for opt_fk_all_engines,
-                  double check code. Consider what will happen on error path.
-    */
-    List_iterator<Foreign_key_name> fk_add_it(fk_names_add);
-    List_iterator<Foreign_key_name> fk_drop_it(fk_names_drop);
-    Foreign_key_name *fk_name;
-    MDL_request *mdl_request;
-
-    while ((fk_name= fk_add_it++))
-    {
-      mdl_request= fk_name->get_mdl_request();
-      mdl_request->ticket->downgrade_exclusive_lock();
-    }
-
-    while ((fk_name= fk_drop_it++))
-    {
-      thd->mdl_context.release_all_locks_for_name(fk_name->get_mdl_request()->
-                                                  ticket);
-    }
-
+    if (opt_fk_all_engines)
+      alter_table_release_fk_name_locks_under_lock_tables(thd, fk_names_drop,
+                                                          fk_names_add,
+                                                          fk_names_replace);
   }
 
 end_temporary:
@@ -9993,6 +10148,12 @@ err:
     thd->mdl_context.release_lock(target_mdl_request.ticket);
     thd->mdl_context.remove_request(&target_mdl_request);
   }
+  if (opt_fk_all_engines &&
+      (thd->locked_tables_mode == LTM_LOCK_TABLES ||
+       thd->locked_tables_mode == LTM_PRELOCKED_UNDER_LOCK_TABLES))
+    alter_table_restore_fk_name_locks_under_lock_tables(thd, fk_names_drop,
+                                                        fk_names_add,
+                                                        fk_names_replace);
   DBUG_RETURN(TRUE);
 
 err_with_mdl:
@@ -10009,6 +10170,17 @@ err_with_mdl:
     thd->mdl_context.remove_request(&target_mdl_request);
   }
   thd->mdl_context.release_all_locks_for_name(mdl_ticket);
+
+  /*
+    QQ: May be it makes sense to do something special about FK names in
+        this case instead of restoring original locks?
+  */
+  if (opt_fk_all_engines &&
+      (thd->locked_tables_mode == LTM_LOCK_TABLES ||
+       thd->locked_tables_mode == LTM_PRELOCKED_UNDER_LOCK_TABLES))
+    alter_table_restore_fk_name_locks_under_lock_tables(thd, fk_names_drop,
+                                                        fk_names_add,
+                                                        fk_names_replace);
   DBUG_RETURN(TRUE);
 }
 /* mysql_alter_table */


Attachment: [text/bzr-bundle] bzr/dlenev@mysql.com-20090630124010-r0q3g6g22dhs1qd7.bundle
Thread
bzr commit into mysql-6.1-fk branch (dlenev:2726)Dmitry Lenev30 Jun