List:Commits« Previous MessageNext Message »
From:Dmitry Lenev Date:July 31 2009 3:10pm
Subject:bzr commit into mysql-6.1-fk branch (dlenev:2725) WL#148
View as plain text  
#At file:///home/dlenev/src/bzr/mysql-6.1-mil13-2/ based on revid:dlenev@stripped6-plxfo3odggj2jdd3

 2725 Dmitry Lenev	2009-07-31
      WL#148 "Foreign keys".
      
      Milestone 13 "DDL checks and changes: ALTER, CREATE INDEX,
      DROP INDEX words".
      
      Implemented support for new foreign keys in ALTER TABLE,
      CREATE INDEX and DROP INDEX statements. Added test coverage
      for this support.
     @ mysql-test/include/mtr_warnings.sql
        Added table t3 to the list of tables for which warnings about absence
        from InnoDB internal dictionary is supressed. New tests which simulate
        failure to install new version of table during ALTER TABLE trigger this
        warning for this table (which is expected).
     @ mysql-test/lib/v1/mtr_report.pl
        Added table t3 to the list of tables for which warnings about absence
        from InnoDB internal dictionary is supressed. New tests which simulate
        failure to install new version of table during ALTER TABLE trigger this
        warning for this table (which is expected).
     @ mysql-test/r/foreign_key.result
        Added test case for bug #46124  "Foreign keys: crash if alter table
        drop foreign key".
     @ mysql-test/r/foreign_key_all_engines.result
        Added coverage for the 13th milestone of WL#148 "Foreign keys" ("DDL
        checks and changes: ALTER, CREATE INDEX, DROP INDEX") and for several
        bugs which were reported during QA for this milestone.
        Also extended test coverage for earlier milestones.
     @ mysql-test/r/foreign_key_all_engines_2.result
        Added part of coverage for the 13th milestone of WL#148 "Foreign
        keys" ("DDL checks and changes: ALTER, CREATE INDEX, DROP INDEX")
        which uses InnoDB or Partitioning engine. Also added test case
        for bug #46125 "Foreign keys: reorganizing partitions causes
        dangling reference".
     @ mysql-test/r/foreign_key_all_engines_3.result
        Added part of coverage for the 13th milestone of WL#148 "Foreign
        keys" ("DDL checks and changes: ALTER, CREATE INDEX, DROP INDEX")
        which requires usage of debug build of the server.
     @ mysql-test/r/innodb_mysql.result
        Added test for bug #39932 "create table fails if column for FK is in
        different case than in corr index".
     @ mysql-test/t/foreign_key.test
        Added test case for bug #46124  "Foreign keys: crash if alter table
        drop foreign key".
     @ mysql-test/t/foreign_key_all_engines.test
        Added coverage for the 13th milestone of WL#148 "Foreign keys" ("DDL
        checks and changes: ALTER, CREATE INDEX, DROP INDEX") and for several
        bugs which were reported during QA for this milestone.
        Also extended test coverage for earlier milestones.
     @ mysql-test/t/foreign_key_all_engines_2.test
        Added part of coverage for the 13th milestone of WL#148 "Foreign
        keys" ("DDL checks and changes: ALTER, CREATE INDEX, DROP INDEX")
        which uses InnoDB or Partitioning engine. Also added test case
        for bug #46125 "Foreign keys: reorganizing partitions causes
        dangling reference".
     @ mysql-test/t/foreign_key_all_engines_3.test
        Added part of coverage for the 13th milestone of WL#148 "Foreign
        keys" ("DDL checks and changes: ALTER, CREATE INDEX, DROP INDEX")
        which requires usage of debug build of the server.
     @ mysql-test/t/innodb_mysql.test
        Added test for bug #39932 "create table fails if column for FK is in
        different case than in corr index".
     @ sql/fk.cc
        Added Fk_constraint_list and Foreign_key_child_rcontext methods for
        preparing these contexts for integrity checks which are performed
        during ALTER TABLE for constraints being added.
     @ sql/fk.h
        Added Fk_constraint_list methods and members for preparing this context
        and carrying out integrity checks which are performed during ALTER TABLE
        for constraints being added.
     @ sql/fk_dd.cc
        - Store in .FRM information that foreign key is self-referencing as a flag
          (we also no longer store child/parent table names for such foreign keys).
          By pre-calculating value of this flag in parser and storing it in .FRM
          we are able to get rid of some string comparisons when determining if
          foreign key is self-referencing.
        - Store in foreign key's description in .FRM of parent table name of
          primary/unique constraint which participates in this foreign key.
          Thanks to this we can easily check which primary/unique key should
          not be dropped when parent table is altered.
        - Changed Foreign_key_name to avoid storing extra copies of database and
          foreign key name by using those which are stored in MDL_request object.
          Added Foreign_key_name::find_ticket() method which allows to find ticket
          for pre-obtained metadata lock for the foreign key name.
        - Changed fk_add_constraint_names_to_lock() to add locks for names of
          foreign key mentioned in ALTER's DROP FOREIGN KEY/CONSTRAINT clause.
        - Moved code which checks that deprececated "FOREIGN KEY <name>" syntax
          is used and emits appropriate warning to separate function so it can
          be used in ALTER TABLE (doing this in mysql_prepare_create_table() is
          not an option this function is called several times during ALTER TABLE
          execution and therefore would generate extra warnings).
          Extended this function to generate warnings for ALTER's deprecated
          "DROP FOREIGN KEY <name>" clause.
        - Adjusted fk_check_constraint_added() to not emit error that constraint
          name is occupied when new foreign key is created as replacement for
          existing foreign key.
        - Introduced Foreign_key_parent::check_if_can_change_engine() method
          and fk_check_if_can_change_engine() which allow to check if existing
          foreign keys are compatible with new engine for table being altered.
        - Added fk_check_if_supporting_key_exists() function which checks if
          new definition of table being altered contains supporting keys for
          existing foreign keys.
          Also added Foreign_key_child::is_supporting_key() family of methods
          to be able easily determine key can serve as supporting for the
          foreign key.
        - Adjusted various prelocking-related functions to use Query_tables_list
          object instead of full-blown LEX.
        - With introduction of Prelocking_strategy class we now add names of
          foreign keys to be prelocked at LOCK TABLE time to prelocking set
          and acquire these metadata locks as part of execution of generic
          prelocking algorithm. Hence Foreign_key_share_list::add_fk_names()
          method and changes to fk_take_mdl_on_fk_name() routine.
        - Added Foreign_key_share_list::add_parent_tables() method which adds
          parent tables for foreign keys to be dropped to the statement's
          table list for prelocking with TL_WRITE_ALLOW_READ as lock type.
     @ sql/fk_dd.h
        - Changed Foreign_key_name to avoid storing extra copies of database and
          foreign key name by using those which are stored in MDL_request object.
          Added Foreign_key_name::find_ticket() method which allows to find ticket
          for pre-obtained metadata lock for the foreign key name.
        - Changed fk_add_constraint_names_to_lock() to add locks for names of
          foreign key mentioned in ALTER's DROP FOREIGN KEY/CONSTRAINT clause.
        - With introduction of Prelocking_strategy class we now add names of
          foreign keys to be prelocked at LOCK TABLE time to prelocking set
          and acquire these metadata locks as part of execution of generic
          prelocking algorithm. Hence Foreign_key_share_list::add_fk_names()
          method and changes to fk_take_mdl_on_fk_name() routine.
        - Moved code which checks that deprececated "FOREIGN KEY <name>" syntax
          is used and emits appropriate warning to separate function so it can
          be used in ALTER TABLE (doing this in mysql_prepare_create_table() is
          not an option this function is called several times during ALTER TABLE
          execution and therefore would generate extra warnings).
          Extended this function to generate warnings for ALTER's deprecated
          "DROP FOREIGN KEY <name>" clause.
        - Introduced Foreign_key_parent::check_if_can_change_engine() method
          and fk_check_if_can_change_engine() which allow to check if existing
          foreign keys are compatible with new engine for table being altered.
        - Added fk_check_if_supporting_key_exists() function which checks if
          new definition of table being altered contains supporting keys for
          existing foreign keys.
          Also added Foreign_key_child::is_supporting_key() family of methods
          to be able easily determine key can serve as supporting for the
          foreign key.
        - Store in Foreign_key_share and in information that foreign key is
          self-referencing as a flag. By pre-calculating value of this flag
          in parser and storing it in .FRM we are able to get rid of some
          string comparisons when determining if foreign key is self-referencing.
        - Store in Foreign_key_share name of primary/unique constraint which
          participates in this foreign key. Thanks to this we can easily check
          which primary/unique key should not be dropped when parent table is
          altered.
     @ sql/mdl.h
        Made MDL_context::find_ticket() usable outside of MDL subsystem.
     @ sql/mysql_priv.h
        - Introduced alter_table_adjust_tables() routine which is used for
          adjusting table list of ALTER TABLE statement to take into account
          value of --foreign-key-all-engines option.
        - Introduced Prelocking_strategy class to enable easier parametrization
          of the way how prelocking algorithm works. Adjusted open_tables() and
          open_and_lock_tables_derived() signatures to accept instance of this
          class and provided backwards compatible versions of this functions.
        - Got rid of MYSQL_OPEN_TAKE_FK_NAMES_MDL flag as now the same effect
          is achieved by using Lock_tables_prelocking_strategy strategy.
     @ sql/share/errmsg.txt
        Added error and warning messages to be emitted by foreign key related
        checks in ALTER TABLE.
     @ sql/sp.cc
        - With introduction of Prelocking_strategy class logic from
          sp_get_prelocking_info() procedure has moved to
          DML_prelocking_strategy::handle_routine() method.
          Also as part of related changes iteration over sroutines list
          which happened in sp_cache_routines_and_add_tables() and
          sp_cache_routines_and_add_tables_aux() was moved to more generic
          open_routines() function. Routine-specific part of the former
          became sp_cache_routine().
          Logic from sp_cache_routines_and_add_tables_for_view() has
          moved to DML_prelocking_strategy::handle_view() method.
          And logic from sp_cache_routines_and_add_tables_for_triggers()
          has moved Table_triggers_list::add_tables_and_routines_for_triggers()
          method.
        - Adjusted various prelocking-related functions to use Query_tables_list
          object instead of full-blown LEX.
     @ sql/sp.h
        - With introduction of Prelocking_strategy class logic from
          sp_get_prelocking_info() procedure has moved to
          DML_prelocking_strategy::handle_routine() method.
          Also as part of related changes iteration over sroutines list
          which happened in sp_cache_routines_and_add_tables() and
          sp_cache_routines_and_add_tables_aux() was moved to more generic
          open_routines() function. Routine-specific part of the former
          became sp_cache_routine().
          Logic from sp_cache_routines_and_add_tables_for_view() has
          moved to DML_prelocking_strategy::handle_view() method.
          And logic from sp_cache_routines_and_add_tables_for_triggers()
          has moved Table_triggers_list::add_tables_and_routines_for_triggers()
          method.
        - Adjusted various prelocking-related functions to use Query_tables_list
          object instead of full-blown LEX.
     @ sql/sp_head.h
        - Added TYPE_ENUM_FOREIGN_KEY_NAME to denote new object to prelock -
          a name of foreign key.
        - Adjusted prelocking-related method to use Query_tables_list object
          instead of full-blown LEX.
     @ sql/sql_base.cc
        - Thanks to making MDL_context::find_ticket() public we no longer
          need to keep information about prelocked names of foreign keys
          in Locked_tables_list context.
        - Made Locked_tables_list::unlink_from_list() usable in
          LTM_PRELOCKED_UNDER_LOCK_TABLES mode.
        - Introduced Prelocking_strategy class to enable easier parametrization
          of the way how prelocking algorithm works. Now all iteration over
          prelocking set/list happens in open_routines()/open_tables() pair
          of functions and all decisions about how prelocking-list should
          be extended (and if prelocking mode should be turned on) are made
          within methods of this class.
          As result, special handling of views, triggers and foreign keys for
          DML statement has moved from open_tables() to methods of
          DML_prelocking_strategy.
          Special handling of foreign key names for LOCK TABLES statement has
          moved to Lock_tables_prelocking_strategy.
          And special handling of parent tables for foreign keys to be dropped
          during ALTER TABLES is encapsulated in Alter_table_prelocking_strategy.
     @ sql/sql_class.cc
        - Use case-insensitive comparison for field names in Key_part_spec's
          operator==() as it happens in other places where we compare field
          names. This allows to properly detect parent or supporting key in
          cases when foreign key specification uses field name in case other
          than one which was used for field creation.
        - Adjust Foreign_key_parent to also store information whether foreign
          key it represents already exists and the name of parent constraint
          participating in it. Introduced flag to simplify checking if this
          foreign key is self-referencing to be used for both descriptions of
          foreign key in child and in parent.
        - Finally added flag to Foreign_key_child to distinguish foreign keys
          which replace existing ones.
     @ sql/sql_class.h
        - Added flag to Alter_drop to be able distinguish deprecated ALTER's
          "DROP FOREIGN KEY <name>" clauses and emit an appropriate warning.
        - Adjust Foreign_key_parent to also store information whether foreign
          key it represents already exists and the name of parent constraint
          participating in it. Introduced flag to simplify checking if this
          foreign key is self-referencing to be used for both descriptions of
          foreign key in child and in parent.
        - Added flag to Foreign_key_child to distinguish foreign keys which
          replace existing ones.
        - Introduced Prelocking_strategy class to enable easier parametrization
          of the way how prelocking algorithm works. Now all iteration over
          prelocking set/list happens in open_routines()/open_tables() pair
          of functions and all decisions about how prelocking-list should
          be extended (and if prelocking mode should be turned on) are made
          within methods of this class.
          Added strategies for DML statements, LOCK TABLES and ALTER TABLE.
        - Thanks to making MDL_context::find_ticket() public we no longer
          need to keep information about prelocked names of foreign keys
          in Locked_tables_list context.
     @ sql/sql_lex.cc
        Query_tables_list::has_own_foreign_keys_to_lock was removed
        as no longer necessary.
     @ sql/sql_lex.h
        - Query_tables_list::has_own_foreign_keys_to_lock was removed
          as no longer necessary.
        - Added Query_tables_list::last_table() method to be able easily
          access last table in the statement's table list.
     @ sql/sql_parse.cc
        - Moved code which checks that deprececated "FOREIGN KEY <name>" syntax
          is used and emits appropriate warning to separate function so it can
          be used in ALTER TABLE. Extended this function to generate warnings
          for ALTER's deprecated "DROP FOREIGN KEY <name>" clause.
        - Changed fk_add_constraint_names_to_lock() to add locks for names of
          foreign key mentioned in ALTER's DROP FOREIGN KEY/CONSTRAINT clause.
        - Query_tables_list::foreign_keys_to_lock is now reset in
          reinit_stmt_before_use().
        - When opening and locking tables in LOCK TABLES statement use
          Lock_tables_prelocking_strategy which allows to pre-lock names
          of foreign keys which can be changed under such LOCK TABLES.
        - Introduced alter_table_adjust_tables() routine which is used for
          adjusting table list of ALTER TABLE statement to take into account
          value of --foreign-key-all-engines option.
     @ sql/sql_partition.cc
        Adjusted code handling fast changes to partitioning to detect and emit
        an appropriate error in cases when such changes lead to removal of rows
        for tables which serve as parent in a foreign key relationship and
        therefore orphans could be created.
     @ sql/sql_prepare.cc
        Moved resetting of Query_table_list::foreign_keys_to_lock to
        reinit_stmt_before_use().
     @ sql/sql_table.cc
        - Changed Foreign_key_ddl_rcontext methods and code that uses them
          to perform addition of foreign keys to descriptions of parent tables
          after mysql_prepare_create_table() was called for child table.
          This is necessary since now foreign key descriptions for parent tables
          have to contain names of primary/unique constraints participating in
          them, and they become known only in mysql_prepare_create_table().
          For self-referencing foreign keys creation of foreign key description
          for parent is now responsibility of mysql_prepare_create_table().
        - Added Foreign_key_ddl_rcontext::prepare_alter_table() method and
          alter_table_prepare_parent_list()/alter_table_check_parent_locks()
          auxiliary functions for preparing this context and list of parent
          tables for ALTER TABLE operation.
        - Changed Foreign_key_ddl_rcontext class and methods to support
          simultaneous dropping and creation of .CNS files within same
          operation.
        - Got rid of string comparisons to determine if constraint is
          self-referencing, now we use pre-calculated flag instead.
        - Now we use Foreign_key_name/MDL_context::find_ticket() instead of
          similar Locked_tables_list method since the former allow to avoid
          storing information about prelocked foreign key names anywhere
          outside MDL subsystem.
        - Changed compare_tables() to ignore foreign keys created or dropped
          in new mode since these operations do not involve changes to table
          structure (but creation of supporting indexes does).
        - Introduced auxiliary functions for upgrading, downgrading and releasing
          metadata locks on table being altered and all parent tables affected.
        - Changed mysql_fast_or_online_alter_table() to do proper metadata lock
          upgrade and modifications of .FRMs for parent tables changed by this
          ALTER TABLE.
        - Changed mysql_prepare_alter_table() to support addition and dropping
          of new foreign keys. Added checks for other changes which are
          prohibited in the presence of foreign keys. Added some auxiliary
          functions to implement these checks.
        - Added alter_table_*_fk_names_under_lock_tables() family of functions
          which perform metadata locking for names of foreign key during ALTER
          TABLE which is carried out under LOCK TABLES. For foreign keys being
          dropped we upgrade to exclusive metadata locks pre-acquired at
          LOCK TABLES time. For foreign keys being created we use try to acquire
          new exclusive lock on name. Things get a bit more complicated with
          addition of foreign keys which replace existing foreign keys
          (For non-LOCK TABLES case we simply acquire metadata locks for names
           of foreign keys created or dropped at open_tables() time).
        - mysql_alter_table():
          * Now performs proper locking and metadata locks hadling for parent
            tables and foreign key names which are affected by this ALTER.
          * Prepares and installs new .FRMs for parent tables affected by it.
          * Performs those checks if this alter compatible with existing foreign
            keys which cannot be carried out in mysql_prepare_alter_table().
          * Ensures that for newly created foreign keys integrity checks are
            performed unless FOREIGN_KEY_CHECKS system variable is set to 0.
        - Changed copy_data_between_tables() to perform integrity checks for
          foreign keys added by this ALTER TABLE.
     @ sql/sql_trigger.cc
        Moved logic from sp_cache_routines_and_add_tables_for_triggers()
        to Table_triggers_list::add_tables_and_routines_for_triggers()
        method.
     @ sql/sql_trigger.h
        Moved logic from sp_cache_routines_and_add_tables_for_triggers()
        to Table_triggers_list::add_tables_and_routines_for_triggers()
        method.
     @ sql/sql_yacc.yy
        - Changed grammar to not support "DROP FOREIGN KEY" clause without
          name specification. Supporting such syntax does not make any
          sense and disabling it allows to avoid extra checks during ALTER's
          execution and reduces number of shift/reduce conflicts.
        - Changed rules for ALTER TABLE and CREATE/DROP INDEX to specify correct
          type of lock for table being changed right in the parser.
        - Changed rules for various partitioning clauses to exclude all tables
          used in them from statement's table list (we don't support subqueries
          in partitioning clauses anyway).
        - Changed rule for MERGE tables create table specification to use more
          clean way for removal tables used by UNION clause from statement's
          table list.
        - Changed rule for ALTER TABLE to adjust list of statement's tables
          taking into account --foreign-key-all-engines mode.
        - Added flag to Alter_drop to be able distinguish deprecated ALTER's
          "DROP FOREIGN KEY <name>" clauses and emit an appropriate warning.
     @ sql/table.h
        Foreign_key_share:
        - add_tables_for_fks() now takes Query_tables_list as a parameter instead
          of full-blown LEX.
        - Added add_fk_names() method adding names of foreign keys for which this
          table serves as child to the prelocking set (to be used for pre-acquiring
          metadata locks on foreign key names at LOCK TABLES time).
        - Added add_parent_tables() methods which adds parent tables for foreign
          keys to be dropped by the ALTER TABLE statement to the prelocking list.
     @ storage/falcon/ha_falcon.cpp
        Enabled addition or dropping foreign keys to Falcon table as fast/online
        operation.
     @ storage/innobase/handler/ha_innodb.cc
        Inform row_rename_table_for_mysql() that it in --foreign-key-all-engines
        mode it should not try parsing ALTER TABLE statement and try dropping
        foreign keys which are mentioned in DROP FOREIGN KEY clauses.
     @ storage/innobase/include/row0mysql.h
        Added parameter to row_rename_table_for_mysql() function which allows
        to disable parsing and execution of ALTER's DROP FOREIGN KEY clauses
        which happens in it for native foreign key implementation.
     @ storage/innobase/row/row0mysql.c
        Added parameter to row_rename_table_for_mysql() function which allows
        to disable parsing and execution of ALTER's DROP FOREIGN KEY clauses
        which happens in it for native foreign key implementation.

    modified:
      mysql-test/include/mtr_warnings.sql
      mysql-test/lib/v1/mtr_report.pl
      mysql-test/r/foreign_key.result
      mysql-test/r/foreign_key_all_engines.result
      mysql-test/r/foreign_key_all_engines_2.result
      mysql-test/r/foreign_key_all_engines_3.result
      mysql-test/r/innodb_mysql.result
      mysql-test/t/foreign_key.test
      mysql-test/t/foreign_key_all_engines.test
      mysql-test/t/foreign_key_all_engines_2.test
      mysql-test/t/foreign_key_all_engines_3.test
      mysql-test/t/innodb_mysql.test
      sql/fk.cc
      sql/fk.h
      sql/fk_dd.cc
      sql/fk_dd.h
      sql/mdl.h
      sql/mysql_priv.h
      sql/share/errmsg.txt
      sql/sp.cc
      sql/sp.h
      sql/sp_head.h
      sql/sql_base.cc
      sql/sql_class.cc
      sql/sql_class.h
      sql/sql_lex.cc
      sql/sql_lex.h
      sql/sql_parse.cc
      sql/sql_partition.cc
      sql/sql_prepare.cc
      sql/sql_table.cc
      sql/sql_trigger.cc
      sql/sql_trigger.h
      sql/sql_yacc.yy
      sql/table.h
      storage/falcon/ha_falcon.cpp
      storage/innobase/handler/ha_innodb.cc
      storage/innobase/include/row0mysql.h
      storage/innobase/row/row0mysql.c
=== modified file 'mysql-test/include/mtr_warnings.sql'
--- a/mysql-test/include/mtr_warnings.sql	2009-01-27 14:53:22 +0000
+++ b/mysql-test/include/mtr_warnings.sql	2009-07-31 15:10:25 +0000
@@ -141,7 +141,11 @@ INSERT INTO global_suppressions VALUES
  /* innodb foreign key tests that fail in ALTER or RENAME produce this */
  ("InnoDB: Error: in ALTER TABLE `test`.`t[12]`"),
  ("InnoDB: Error: in RENAME TABLE table `test`.`t1`"),
- ("InnoDB: Error: table `test`.`t[12]` does not exist in the InnoDB internal"),
+ /* 
+   In addition to innodb foreign key tests this error is produced by tests
+   which simulate failure to install new version of table during ALTER TABLE.
+  */
+ ("InnoDB: Error: table `test`.`t[123]` does not exist in the InnoDB internal"),
 
  /* Test case for Bug#14233 produces the following warnings: */
  ("Stored routine 'test'.'bug14233_1': invalid value in column mysql.proc"),

=== modified file 'mysql-test/lib/v1/mtr_report.pl'
--- a/mysql-test/lib/v1/mtr_report.pl	2009-02-05 08:59:29 +0000
+++ b/mysql-test/lib/v1/mtr_report.pl	2009-07-31 15:10:25 +0000
@@ -356,7 +356,10 @@ sub mtr_report_stats ($) {
                 # innodb foreign key tests that fail in ALTER or RENAME produce this
                 /InnoDB: Error: in ALTER TABLE `test`.`t[12]`/ or
                 /InnoDB: Error: in RENAME TABLE table `test`.`t1`/ or
-                /InnoDB: Error: table `test`.`t[12]` does not exist in the InnoDB internal/ or
+                # In addition to innodb foreign key tests this error is
+                # produced by any tests which simulate failure to install
+                # new version of table during ALTER TABLE.
+                /InnoDB: Error: table `test`.`t[123]` does not exist in the InnoDB internal/ or
 
                 # Test case for Bug#14233 produces the following warnings:
                 /Stored routine 'test'.'bug14233_1': invalid value in column mysql.proc/ or

=== modified file 'mysql-test/r/foreign_key.result'
--- a/mysql-test/r/foreign_key.result	2009-05-27 09:00:35 +0000
+++ b/mysql-test/r/foreign_key.result	2009-07-31 15:10:25 +0000
@@ -124,3 +124,18 @@ create database mysqltest;
 # DROP DATABASE should succeed.
 drop database mysqltest;
 use test;
+#
+# Additional test for bug #46124  "Foreign keys: crash if alter table
+# drop foreign key".
+# 
+# Check that even in --foreign-key-all-engines=0 mode we reject DROP
+# FOREIGN KEY clause without foreign key name which crashed server in
+# --foreign-key-all-engines=1 mode.
+drop table if exists t1, t2;
+alter table t1 drop foreign key;
+ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' at line 1
+create table t1 (pk int primary key);
+create table t2 (fk int references t1 (pk));
+alter table t2 drop foreign key;
+ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' at line 1
+drop tables t1, t2;

=== modified file 'mysql-test/r/foreign_key_all_engines.result'
--- a/mysql-test/r/foreign_key_all_engines.result	2009-06-01 11:37:20 +0000
+++ b/mysql-test/r/foreign_key_all_engines.result	2009-07-31 15:10:25 +0000
@@ -317,9 +317,9 @@ set @@rand_seed1=10000000,@@rand_seed2=1
 drop tables if exists t1, t2, t3, v1;
 drop view if exists v1;
 #
-# Tests for various checks that happen during foreign key creation
+# Tests for various checks that happen during foreign key creation.
 #
-# Checks for child and parent's tables types
+# Checks for child and parent table types.
 create table t1 (pk int primary key);
 create temporary table t2 (fk int references t1 (pk));
 ERROR 42000: Foreign key error: Constraint 'fk_t2_11f06': Child table is temporary
@@ -332,11 +332,31 @@ create table t1 (pk int primary key);
 create view v1 as select * from t1;
 create table t2 (fk int references v1 (pk));
 ERROR 42000: Foreign key error: Constraint 'fk_t2_hu4cw': Parent table is a view
+drop view v1;
 drop table t1;
+#
+# A slightly more complicated example when the view uses
+# stored functions.
+#
+create table t1 (pk int primary key);
+create table t2 (a int);
+create table t3 (a int);
+create trigger t2_ai after insert on t2 for each row insert into t3 (a)
+values (new.a);
+create procedure p1(p_a int) insert into t2 (a) values (p_a);
+create function f1(p_a int) returns int begin call p1(p_a); return p_a; end|
+create view v1 as select f1(pk) as pk from t1;
+create table t4 (fk int, constraint c foreign key (fk) references v1 (pk));
+ERROR 42000: Foreign key error: Constraint 'c': Parent table is a view
+drop table t1, t2, t3;
 drop view v1;
+drop procedure p1;
+drop function f1;
+#
 # It should be impossible to create a foreign key referencing
 # I_S tables. So statement below should emit reasonable error
 # like ER_FK_PARENT_VIEW or ER_DBACCESS_DENIED_ERROR.
+#
 create table t2 (fk varchar(64)
 references information_schema.tables (table_name));
 ERROR 42000: Access denied for user 'root'@'localhost' to database 'information_schema'
@@ -485,6 +505,45 @@ t3	CREATE TABLE `t3` (
   KEY `c` (`fk`)
 ) ENGINE=<engine_type> DEFAULT CHARSET=latin1
 drop table t3, t2, t1;
+# 
+# Coverage for use of prepared statemetns and foreign keys.
+# We need to check that foreign keys with auto-generated names
+# work okay during re-execution.
+#
+create table t1 (pk int primary key);
+prepare stmt from "create table  t2 (fk int, fk1 int references t1 (pk),
+constraint c foreign key (fk) references t1 (pk))";
+execute stmt;
+show create table t2;
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `fk` int(11) DEFAULT NULL,
+  `fk1` int(11) DEFAULT NULL CONSTRAINT `fk_t2_1ennw` REFERENCES `t1` (`pk`),
+  KEY `fk_t2_1ennw` (`fk1`),
+  KEY `c` (`fk`),
+  CONSTRAINT `c` FOREIGN KEY (`fk`) REFERENCES `t1` (`pk`)
+) ENGINE=Falcon DEFAULT CHARSET=latin1
+execute stmt;
+ERROR 42000: Foreign key error: Constraint 'c': Duplicate constraint name
+drop table t2;
+execute stmt;
+show create table t2;
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `fk` int(11) DEFAULT NULL,
+  `fk1` int(11) DEFAULT NULL CONSTRAINT `fk_t2_yf4ul` REFERENCES `t1` (`pk`),
+  KEY `fk_t2_yf4ul` (`fk1`),
+  KEY `c` (`fk`),
+  CONSTRAINT `c` FOREIGN KEY (`fk`) REFERENCES `t1` (`pk`)
+) ENGINE=Falcon DEFAULT CHARSET=latin1
+flush table t1, t2;
+execute stmt;
+ERROR 42000: Foreign key error: Constraint 'c': Duplicate constraint name
+flush table t1, t2;
+drop table t2;
+execute stmt;
+drop table t1, t2;
+deallocate prepare stmt;
 #
 # Basic coverage for the 8th milestone of WL#148 "Foreign keys"
 # ("DML words: INSERT, UPDATE, DELETE, no EOS checks").
@@ -1263,10 +1322,9 @@ pk	fk
 update t1 set pk= if(pk = 2, 2, 3), fk= if(fk = 3, 4, 2);
 ERROR 23000: Foreign key error: constraint 'fk_t1_11f06': no matching key for value '4', it is not in parent table
 drop table t1;
-# TODO: Implement coverage for UPDATE where matching PK value is
-# added by cascading action once milestone 13 "DDL checks and changes:
-# ALTER, CREATE INDEX, DROP INDEX words" is complete.
-# 
+# TODO: Enable and extend coverage for UPDATE where matching PK value
+# is added by cascading action once Bug #46474 "Foreign keys: some
+# forms of cascading changes are disallowed" is fixed.
 # TODO: Implement coverage for multi-UPDATE adding dangling FK value
 # to the child and then adding matching PK value to the parent once
 # milestone 12 "MULTI-DELETE, MULTI-UPDATE" is complete.
@@ -1536,173 +1594,1836 @@ Created_tmp_disk_tables	2
 set @@max_heap_table_size= @old_max_heap_table_size;
 drop table t1;
 #
-# Coverage for the 14th milestone of WL#148 "Foreign keys"
-# ("DDL checks and changes: DROP, TRUNCATE, RENAME").
+# Coverage for the 13th milestone of WL#148 "Foreign keys"
+# ("DDL checks and changes: ALTER, CREATE INDEX, DROP INDEX").
 #
-drop tables if exists t1, t2, t3, t4, t1_2, t2_2, t3_2, t1_3, t2_3, t3_3;
+drop tables if exists t1, t2, t3, t4, t5, v1;
 drop database if exists mysqltest;
+#
+# Let us test how ALTER TABLE ... ADD CONSTRAINT works.
 # 
-# DROP TABLES.
-# 
-# Let us check that we disallow dropping of parent tables
-# without dropping child tables.
 create table t1 (pk int primary key);
-create table t2 (fk int constraint c references t1 (pk));
-drop table t1;
-ERROR 23000: Foreign key error: constraint 'c': you cannot use 'DROP TABLE' statement because table 't1' has a foreign key that refers to it
-# But dropping a child without the parent should be allowed.
-drop table t2;
-# Also dropping the child and the parent simultaneously should be fine.
-create table t2 (fk int references t1 (pk));
+create table t2 (fk int);
+insert into t1 values (1);
+insert into t2 values (1);
+#
+# Add a constraint and check that metadata was properly updated.
+#
+alter table t2 add constraint c foreign key (fk) references t1 (pk);
+show create table t2;
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `fk` int(11) DEFAULT NULL,
+  KEY `c` (`fk`),
+  CONSTRAINT `c` FOREIGN KEY (`fk`) REFERENCES `t1` (`pk`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+delete from t1 where pk = 1;
+ERROR 23000: Foreign key error: constraint 'c': cannot change because foreign key refers to value '1'
+create table t3 (pk int primary key, fk int constraint c references t3 (pk));
+ERROR 42000: Foreign key error: Constraint 'c': Duplicate constraint name
+#
+# Now try to add constraint with the same name.
+#
+alter table t2 add constraint c foreign key (fk) references t1 (pk);
+ERROR 42000: Foreign key error: Constraint 'c': Duplicate constraint name
 drop tables t1, t2;
-# Dropping a table with a self-reference also should be OK.
-create table t1 (pk int primary key, fk int references t1 (pk));
-drop table t1;
-# Now let us check that dropping a child without without a parent
-# leaves no traces in the parent description (this is a test for
-# Bug #42063 "Foreign keys: constraint survives after table 
-# is dropped").
+#
+# Now similar test for constraint with auto-generated name.
+#
+# Let us make the name repeatable first.
+#
+set @@rand_seed1=10000000,@@rand_seed2=1000000;
 create table t1 (pk int primary key);
-create table t2 (fk int references t1 (pk));
+create table t2 (fk int);
 insert into t1 values (1);
-drop table t2;
-# This update should succeed and should not emit any errors
-update t1 set pk = 0;
+insert into t2 values (1);
+#
+# Add constraint and check that metadata was properly updated.
+#
+alter table t2 add constraint foreign key (fk) references t1 (pk);
+show create table t2;
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `fk` int(11) DEFAULT NULL,
+  KEY `fk_t2_11f06` (`fk`),
+  CONSTRAINT `fk_t2_11f06` FOREIGN KEY (`fk`) REFERENCES `t1` (`pk`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+delete from t1 where pk = 1;
+ERROR 23000: Foreign key error: constraint 'fk_t2_11f06': cannot change because foreign key refers to value '1'
+create table t3 (pk int primary key, fk int constraint fk_t2_11f06 references t3 (pk));
+ERROR 42000: Foreign key error: Constraint 'fk_t2_11f06': Duplicate constraint name
+drop tables t1, t2;
+#
+# Let us try to add a self-referencing foreign key.
+# 
+create table t1 (pk int primary key, fk int);
+insert into t1 values (1, 1), (2, 1);
+alter table t1 add constraint c foreign key (fk) references t1 (pk);
+show create table t1;
+Table	Create Table
+t1	CREATE TABLE `t1` (
+  `pk` int(11) NOT NULL,
+  `fk` int(11) DEFAULT NULL,
+  PRIMARY KEY (`pk`),
+  KEY `c` (`fk`),
+  CONSTRAINT `c` FOREIGN KEY (`fk`) REFERENCES `t1` (`pk`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+delete from t1 where pk = 1;
+ERROR 23000: Foreign key error: constraint 'c': cannot change because foreign key refers to value '1'
+create table t2 (pk int primary key, fk int constraint c references t2 (pk));
+ERROR 42000: Foreign key error: Constraint 'c': Duplicate constraint name
 drop table t1;
-# A bit more complex scenario in which we drop two tables
-# with foreign keys.
+#
+# Now let us try to add several constraints at the same time.
+#
 create table t1 (pk int primary key);
 create table t2 (pk int primary key);
-create table t3 (fk int references t1 (pk));
-create table t4 (fk int references t2 (pk));
-drop tables t3, t4;
-# Now a scenario with more complex dependencies
-drop table t2;
-create table t2 (pk int primary key, fk int references t1 (pk));
-create table t4 (pk int primary key, fk int references t1 (pk));
-create table t3 (fk1 int references t1 (pk), fk2 int references t2 (pk),
-fk3 int references t4 (pk));
-drop tables t4, t3, t2;
-# An even more complex scenario involving 3 clusters of
-# differently connected tables.
-create table t2 (fk int references t1 (pk));
-create table t3 (fk int references t1 (pk));
-create table t1_2 (pk int primary key);
-create table t2_2 (pk int primary key);
-create table t3_2 (fk1 int references t1_2 (pk), fk2 int references t2_2 (pk));
-create table t1_3 (pk int primary key);
-create table t2_3 (pk int primary key, fk int references t1_3 (pk));
-create table t3_3 (fk int references t2_3 (pk));
-# Let us try to drop all child tables in some arbitrary order.
-drop tables t2, t2_3, t3, t3_2, t3_3;
-# Re-create children and try to drop all tables at once.
-create table t2 (fk int references t1 (pk));
-create table t3 (fk int references t1 (pk));
-create table t3_2 (fk1 int references t1_2 (pk), fk2 int references t2_2 (pk));
-create table t2_3 (pk int primary key, fk int references t1_3 (pk));
-create table t3_3 (fk int references t2_3 (pk));
-drop tables t1, t1_2, t1_3, t2, t2_2, t2_3, t3, t3_2, t3_3;
-# TODO: Add coverage for dropping tables with circular references once
-# milestone 13 "DDL checks and changes: ALTER, CREATE INDEX, DROP INDEX
-# words" is complete.
-# Let us also check scenarios in which there is a temporary
-# table shadowing one of the tables participating in the constraint.
+create table t3 (pk int primary key);
+create table t4 (fk1 int, fk2 int);
+insert into t1 values (1);
+insert into t2 values (1);
+insert into t3 values (1);
+insert into t4 values (1, 1);
+alter table t4 add constraint c1 foreign key (fk1) references t1 (pk),
+add constraint c2 foreign key (fk2) references t2 (pk)
+on delete restrict on update restrict,
+add column fk3 int default 1,
+add constraint c3 foreign key (fk3) references t3 (pk)
+on update cascade;
+show create table t4;
+Table	Create Table
+t4	CREATE TABLE `t4` (
+  `fk1` int(11) DEFAULT NULL,
+  `fk2` int(11) DEFAULT NULL,
+  `fk3` int(11) DEFAULT '1',
+  KEY `c1` (`fk1`),
+  KEY `c2` (`fk2`),
+  KEY `c3` (`fk3`),
+  CONSTRAINT `c1` FOREIGN KEY (`fk1`) REFERENCES `t1` (`pk`),
+  CONSTRAINT `c2` FOREIGN KEY (`fk2`) REFERENCES `t2` (`pk`) ON DELETE RESTRICT ON UPDATE RESTRICT,
+  CONSTRAINT `c3` FOREIGN KEY (`fk3`) REFERENCES `t3` (`pk`) ON UPDATE CASCADE
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+delete from t1 where pk = 1;
+ERROR 23000: Foreign key error: constraint 'c1': cannot change because foreign key refers to value '1'
+delete from t2 where pk = 1;
+ERROR 23000: Foreign key error: constraint 'c2': cannot change because foreign key refers to value '1'
+delete from t3 where pk = 1;
+ERROR 23000: Foreign key error: constraint 'c3': cannot change because foreign key refers to value '1'
+create table t5 (pk int primary key, fk int constraint c1 references t5 (pk));
+ERROR 42000: Foreign key error: Constraint 'c1': Duplicate constraint name
+create table t5 (pk int primary key, fk int constraint c2 references t5 (pk));
+ERROR 42000: Foreign key error: Constraint 'c2': Duplicate constraint name
+create table t5 (pk int primary key, fk int constraint c3 references t5 (pk));
+ERROR 42000: Foreign key error: Constraint 'c3': Duplicate constraint name
+drop table t4;
+#
+# And now let us add them in sequence.
+#
+create table t4 (fk1 int, fk2 int);
+insert into t4 values (1, 1);
+alter table t4 add constraint c1 foreign key (fk1) references t1 (pk);
+alter table t4 add constraint c2 foreign key (fk2) references t2 (pk)
+on delete restrict on update restrict;
+alter table t4 add column fk3 int default 1,
+add constraint c3 foreign key (fk3) references t3 (pk)
+on update cascade;
+show create table t4;
+Table	Create Table
+t4	CREATE TABLE `t4` (
+  `fk1` int(11) DEFAULT NULL,
+  `fk2` int(11) DEFAULT NULL,
+  `fk3` int(11) DEFAULT '1',
+  KEY `c1` (`fk1`),
+  KEY `c2` (`fk2`),
+  KEY `c3` (`fk3`),
+  CONSTRAINT `c1` FOREIGN KEY (`fk1`) REFERENCES `t1` (`pk`),
+  CONSTRAINT `c2` FOREIGN KEY (`fk2`) REFERENCES `t2` (`pk`) ON DELETE RESTRICT ON UPDATE RESTRICT,
+  CONSTRAINT `c3` FOREIGN KEY (`fk3`) REFERENCES `t3` (`pk`) ON UPDATE CASCADE
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+delete from t1 where pk = 1;
+ERROR 23000: Foreign key error: constraint 'c1': cannot change because foreign key refers to value '1'
+delete from t2 where pk = 1;
+ERROR 23000: Foreign key error: constraint 'c2': cannot change because foreign key refers to value '1'
+delete from t3 where pk = 1;
+ERROR 23000: Foreign key error: constraint 'c3': cannot change because foreign key refers to value '1'
+create table t5 (pk int primary key, fk int constraint c1 references t5 (pk));
+ERROR 42000: Foreign key error: Constraint 'c1': Duplicate constraint name
+create table t5 (pk int primary key, fk int constraint c2 references t5 (pk));
+ERROR 42000: Foreign key error: Constraint 'c2': Duplicate constraint name
+create table t5 (pk int primary key, fk int constraint c3 references t5 (pk));
+ERROR 42000: Foreign key error: Constraint 'c3': Duplicate constraint name
+drop tables t1, t2, t3, t4;
+#
+# Let us also check that when adding constraints we correctly
+# change metadata of the parent table.
+#
 create table t1 (pk int primary key);
-create table t2 (fk int constraint c references t1 (pk));
-# Attempt to drop the parent without dropping the child should
-# lead to an error.
-create temporary table t2 (i int);
-drop tables t1, t2;
-ERROR 23000: Foreign key error: constraint 'c': you cannot use 'DROP TABLE' statement because table 't1' has a foreign key that refers to it
-drop temporary table t2;
-# When dropping the child we should not be confused by the
-# presence of a temporary table, that is shadowing the parent.
-create temporary table t1 (i int);
-drop table t2;
-# Check that parent .FRM was properly modified.
-drop temporary table t1;
-# The below statements should succeed.
+create table t2 (pk int primary key, fk int constraint c1 references t1 (pk));
+create table t3 (fk int);
 insert into t1 values (1);
-update t1 set pk= 0;
-# Finally a bit more complicated scenario in which we
-# are dropping the temporary table and modify .FRM of
-# the parent table which is shadowed by this temporary table.
-create table t2 (fk int references t1 (pk));
-create temporary table t1 (i int);
-drop tables t1, t2;
-drop table t1;
-# Until bug #44230 "Altering Falcon table under LOCK TABLES fails with
-# "Can't lock file" error" is fixed tests for DROP TABLE under LOCK
-# TABLES have to reside in foreign_key_all_engines_2.test.
-# Let us check that we are able to drop a table participating in a 
-# foreign key even if the other table in this relationship does
-# not exist (i.e. if the data-dictionary is inconsistent).
-# First let us drop a child with non-existing parent.
-create table t1 (pk int primary key) engine=myisam;
-create table t2 (fk int references t1 (pk)
-on delete restrict on update restrict) engine=myisam;
-# Now let us simulate manual dropping of parent table.
-flush table t1;
-# Dropping of child should succeed.
-drop table t2;
-# Perform a similar test for parent table.
-create table t1 (pk int primary key) engine=myisam;
-create table t2 (fk int references t1 (pk)
-on delete restrict on update restrict) engine=myisam;
-# Simulate manual dropping of table t2.
-flush table t2;
-# The below statement should succeed. We have to mention both tables
-# in it as otherwise error about attempt to drop parent table without
-# child table will be reported.
-drop tables if exists t1, t2;
-Warnings:
-Note	1051	Unknown table 't2'
-# Test that CREATE and DROP TABLE are able to modify .FRMs of parent
-# table even if this table was created under more relaxed sql_mode and
-# thus, for example, straightforward attempt to alter such table might
-# fail. We want to allow creation/dropping of foreign keys in such
-# circumstances since, after all, the fact that we modify .FRM of
-# the parent in the process is just an implementation detail.
-set @old_sql_mode:= @@sql_mode;
-set sql_mode='ALLOW_INVALID_DATES';
-# Create a parent-to-be with defaults which prohibit its re-creation
-# or normal altering in 'traditional' mode.
-create table t1 (pk int primary key,
-a datetime default '2009-03-30 00:00:00',
-b datetime default '2009-03-00 00:00:00',
-c datetime default '0000-00-00 00:00:00');
-set sql_mode= 'TRADITIONAL';
-# The below CREATE and DROP TABLE should succeed.
-create table t2 (fk int references t1 (pk));
-drop table t2;
-set sql_mode= @old_sql_mode;
-drop table t1;
+insert into t2 values (1, 1);
+insert into t3 values (1);
+alter table t3 add constraint c2 foreign key (fk) references t2 (pk);
+show create table t3;
+Table	Create Table
+t3	CREATE TABLE `t3` (
+  `fk` int(11) DEFAULT NULL,
+  KEY `c2` (`fk`),
+  CONSTRAINT `c2` FOREIGN KEY (`fk`) REFERENCES `t2` (`pk`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+show create table t2;
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `pk` int(11) NOT NULL,
+  `fk` int(11) DEFAULT NULL CONSTRAINT `c1` REFERENCES `t1` (`pk`),
+  PRIMARY KEY (`pk`),
+  KEY `c1` (`fk`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+delete from t2 where pk = 1;
+ERROR 23000: Foreign key error: constraint 'c2': cannot change because foreign key refers to value '1'
+delete from t1 where pk = 1;
+ERROR 23000: Foreign key error: constraint 'c1': cannot change because foreign key refers to value '1'
+create table t4 (pk int primary key, fk int constraint c1 references t4 (pk));
+ERROR 42000: Foreign key error: Constraint 'c1': Duplicate constraint name
+create table t4 (pk int primary key, fk int constraint c2 references t4 (pk));
+ERROR 42000: Foreign key error: Constraint 'c2': Duplicate constraint name
+drop tables t1, t2, t3;
+# 
+# It's time for tables with a loop of constraints.
 #
-# DROP DATABASE
+create table t1 (pk int primary key, fk int);
+create table t2 (pk int primary key, fk int constraint c1 references t1 (pk));
+insert into t1 values (1, 1);
+insert into t2 values (1, 1);
+alter table t1 add constraint c2 foreign key (fk) references t2 (pk);
+show create table t1;
+Table	Create Table
+t1	CREATE TABLE `t1` (
+  `pk` int(11) NOT NULL,
+  `fk` int(11) DEFAULT NULL,
+  PRIMARY KEY (`pk`),
+  KEY `c2` (`fk`),
+  CONSTRAINT `c2` FOREIGN KEY (`fk`) REFERENCES `t2` (`pk`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+show create table t2;
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `pk` int(11) NOT NULL,
+  `fk` int(11) DEFAULT NULL CONSTRAINT `c1` REFERENCES `t1` (`pk`),
+  PRIMARY KEY (`pk`),
+  KEY `c1` (`fk`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+delete from t2 where pk = 1;
+ERROR 23000: Foreign key error: constraint 'c2': cannot change because foreign key refers to value '1'
+delete from t1 where pk = 1;
+ERROR 23000: Foreign key error: constraint 'c1': cannot change because foreign key refers to value '1'
+create table t3 (pk int primary key, fk int constraint c1 references t3 (pk));
+ERROR 42000: Foreign key error: Constraint 'c1': Duplicate constraint name
+create table t3 (pk int primary key, fk int constraint c2 references t3 (pk));
+ERROR 42000: Foreign key error: Constraint 'c2': Duplicate constraint name
+drop table t1, t2;
 #
-# From the foreign keys point of view DROP DATABASE is simply a
-# special form of DROP TABLES.
+# Test that when a constraint is added we also check that there
+# is a matching parent value for values in each child record.
 # 
-# It should be impossible to drop a database containing tables which
-# are referenced by tables from other databases.
-create database mysqltest;
-create table mysqltest.t1 (pk int primary key);
-create table test.t2 (fk int constraint c references mysqltest.t1 (pk));
-drop database mysqltest;
-ERROR 23000: Foreign key error: constraint 'c': you cannot use 'DROP DATABASE' statement because table 't1' has a foreign key that refers to it
-# It should be possible to drop a database which tables are not
-# referenced from other databases.
-drop table test.t2;
-create table mysqltest.t2 (fk int references mysqltest.t1 (pk));
-drop database mysqltest;
-# Also it should be possible to drop a database which is not
-# referenced from other databases, but itself constains tables
-# referencing to other databases.
+# First, the case with one constraint referencing to some other table.
+#
 create table t1 (pk int primary key);
-create database mysqltest;
-create table mysqltest.t2 (fk int references test.t1 (pk));
-drop database mysqltest;
-drop table t1;
+create table t2 (fk int);
+insert into t2 values (1);
+alter table t2 add constraint c foreign key (fk) references t1 (pk);
+ERROR 23000: Foreign key error: constraint 'c': no matching key for value '1', it is not in parent table
+show create table t2;
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `fk` int(11) DEFAULT NULL
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+# 
+# Now the case with two constraints only one of which fails the check.
+#
+insert into t1 values (1);
+alter table t2 add column fk2 int;
+update t2 set fk2= 2;
+alter table t2 add constraint c1 foreign key (fk) references t1 (pk),
+add constraint c2 foreign key (fk2) references t1 (pk);
+ERROR 23000: Foreign key error: constraint 'c2': no matching key for value '2', it is not in parent table
+show create table t2;
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `fk` int(11) DEFAULT NULL,
+  `fk2` int(11) DEFAULT NULL
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+#
+# Test that this check honors FOREIGN_KEY_CHECKS=0.
+#
+set foreign_key_checks= 0;
+alter table t2 add constraint c1 foreign key (fk2) references t1 (pk);
+show create table t2;
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `fk` int(11) DEFAULT NULL,
+  `fk2` int(11) DEFAULT NULL,
+  KEY `c1` (`fk2`),
+  CONSTRAINT `c1` FOREIGN KEY (`fk2`) REFERENCES `t1` (`pk`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+set foreign_key_checks= 1;
+#
+# Also let us test that we perform the check for newly added
+# constraints only.
+#
+alter table t2 add constraint c2 foreign key (fk) references t1 (pk);
+show create table t2;
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `fk` int(11) DEFAULT NULL,
+  `fk2` int(11) DEFAULT NULL,
+  KEY `c1` (`fk2`),
+  KEY `c2` (`fk`),
+  CONSTRAINT `c1` FOREIGN KEY (`fk2`) REFERENCES `t1` (`pk`),
+  CONSTRAINT `c2` FOREIGN KEY (`fk`) REFERENCES `t1` (`pk`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+drop tables t1, t2;
+#
+# Now the case with a transactional table and a self-referencing key.
+#
+create table t1 (pk int primary key, fk1 int, fk2 int);
+insert into t1 values (1, 2, 4), (2, 2, 2), (3, 2, 4);
+alter table t1 add constraint c foreign key (fk2) references t1 (pk);
+ERROR 23000: Foreign key error: constraint 'c': no matching key for value '4', it is not in parent table
+#
+# The below statement should succeed because EOS check will happen.
+#
+alter table t1 add constraint c foreign key (fk1) references t1 (pk);
+show create table t1;
+Table	Create Table
+t1	CREATE TABLE `t1` (
+  `pk` int(11) NOT NULL,
+  `fk1` int(11) DEFAULT NULL,
+  `fk2` int(11) DEFAULT NULL,
+  PRIMARY KEY (`pk`),
+  KEY `c` (`fk1`),
+  CONSTRAINT `c` FOREIGN KEY (`fk1`) REFERENCES `t1` (`pk`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+drop table t1;
+#
+# A bit more complex case in which we add simultaneously two
+# constraints which might interact with each other
+# if implemented wrong.
+#
+create table t1 (pk int primary key, a int not null, b int not null,
+c int, d int, e int not null, f int not null,
+unique (a, b), unique (e, f));
+insert into t1 values (1, 1, 2, 1, 1, 1, 2), (2, 1, 1, 1, 1, 2, 1);
+alter table t1 add constraint c1 foreign key (d, e) references t1 (a, b),
+add constraint c2 foreign key (b, c) references t1 (e, f);
+ERROR 23000: Foreign key error: constraint 'c2': no matching key for value '1-1', it is not in parent table
+drop table t1;
+#
+# Another case which emphasizes why it is important to use new
+# version of the table being altered for the checks.
+#
+create table t1 (fk int);
+insert into t1 values (1);
+alter table t1 add column pk int primary key default 1,
+add constraint c foreign key (fk) references t1 (pk);
+drop table t1;
+#
+# Now test of a non-transactional table and a self-referencing key.
+#
+create table t1 (pk int primary key, fk1 int, fk2 int, fk3 int) engine=myisam;
+insert into t1 values (1, 1, 2, 3), (2, 1, 2, 3);
+#
+# The below ALTER should succeed as we allow self-referencing
+# rows even for non-transactional tables.
+#
+alter table t1 add constraint c1 foreign key (fk1) references t1 (pk)
+on delete restrict on update restrict;
+#
+# The below ALTER should fail as we decided that for the sake of
+# consistency of behavior we won't enable EOS checks if table being
+# altered is non-transactional (event though it is theoretically
+# possible). This might change in future.
+#
+alter table t1 add constraint c2 foreign key (fk2) references t1 (pk)
+on delete restrict on update restrict;
+ERROR 23000: Foreign key error: constraint 'c2': no matching key for value '2', it is not in parent table
+#
+# Finally the below ALTER should always fail as otherwise we
+# would get an orphan foreign key value.
+#
+alter table t1 add constraint c3 foreign key (fk3) references t1 (pk)
+on delete restrict on update restrict;
+ERROR 23000: Foreign key error: constraint 'c3': no matching key for value '3', it is not in parent table
+show create table t1;
+Table	Create Table
+t1	CREATE TABLE `t1` (
+  `pk` int(11) NOT NULL,
+  `fk1` int(11) DEFAULT NULL,
+  `fk2` int(11) DEFAULT NULL,
+  `fk3` int(11) DEFAULT NULL,
+  PRIMARY KEY (`pk`),
+  KEY `c1` (`fk1`),
+  CONSTRAINT `c1` FOREIGN KEY (`fk1`) REFERENCES `t1` (`pk`) ON DELETE RESTRICT ON UPDATE RESTRICT
+) ENGINE=MyISAM DEFAULT CHARSET=latin1
+drop table t1;
+#
+# Let us also have a case in which ADD CONSTRAINT can be
+# performed as a "fast" alter table operation, i.e. when
+# supporting key already exists and FOREIGN_KEY_CHECKS=0.
+#
+# By using --enable_info and verifying that number of affected
+# rows is 0 we check that this ALTER TABLE is really carried
+# out as "fast/online" operation.
+#
+create table t1 (pk int primary key);
+insert into t1 values (1);
+create table t2 (fk int, key(fk));
+insert into t2 values (1);
+set foreign_key_checks= 0;
+alter table t2 add constraint c foreign key (fk) references t1 (pk);
+affected rows: 0
+info: Records: 0  Duplicates: 0  Warnings: 0
+set foreign_key_checks= 1;
+show create table t2;
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `fk` int(11) DEFAULT NULL,
+  KEY `fk` (`fk`),
+  CONSTRAINT `c` FOREIGN KEY (`fk`) REFERENCES `t1` (`pk`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+delete from t1 where pk = 1;
+ERROR 23000: Foreign key error: constraint 'c': cannot change because foreign key refers to value '1'
+create table t3 (pk int primary key, fk int constraint c references t3 (pk));
+ERROR 42000: Foreign key error: Constraint 'c': Duplicate constraint name
+drop tables t1, t2;
+#
+# ADD FOREIGN KEY clause should be supported as well
+# (One instance of an appropriate warning should be emitted).
+#
+create table t1 (pk int primary key, fk int);
+insert into t1 values (1, 1), (2, 1);
+alter table t1 add foreign key c (fk) references t1 (pk);
+Warnings:
+Warning	1780	Foreign key warning: Constraint 'c': Syntax 'FOREIGN KEY [id]' is old style, changing to CONSTRAINT [id]
+show create table t1;
+Table	Create Table
+t1	CREATE TABLE `t1` (
+  `pk` int(11) NOT NULL,
+  `fk` int(11) DEFAULT NULL,
+  PRIMARY KEY (`pk`),
+  KEY `c` (`fk`),
+  CONSTRAINT `c` FOREIGN KEY (`fk`) REFERENCES `t1` (`pk`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+delete from t1 where pk = 1;
+ERROR 23000: Foreign key error: constraint 'c': cannot change because foreign key refers to value '1'
+create table t2 (pk int primary key, fk int constraint c references t2 (pk));
+ERROR 42000: Foreign key error: Constraint 'c': Duplicate constraint name
+drop table t1;
+#
+# Now let us check that during ALTER TABLE ... ADD CONSTRAINT we
+# perform all the checks which are performed when foreign key is
+# created by CREATE TABLE. Basically this is test coverage for
+# milestone 7 ("DDL checks and changes CREATE, CREATE TABLE LIKE")
+# which was changed to use ALTER TABLE.
+#
+# Let us begin with various checks for constraint name legality.
+#
+create table t1 (pk int primary key);
+create table t2 (fk int);
+alter table t2 add constraint `` foreign key (fk) references t1 (pk);
+ERROR 42000: Foreign key error: Constraint '': Illegal constraint name
+alter table t2 add constraint ` ` foreign key (fk) references t1 (pk);
+ERROR 42000: Foreign key error: Constraint ' ': Illegal constraint name
+alter table t2 add constraint `c ` foreign key (fk) references t1 (pk);
+ERROR 42000: Foreign key error: Constraint 'c ': Illegal constraint name
+alter table t2 add constraint CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
+foreign key (fk)  references t1 (pk);
+ERROR 42000: Foreign key error: Constraint 'CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC': Illegal constraint name
+alter table t2 add constraint `PRIMARY` foreign key (fk) references t1 (pk);
+ERROR 42000: Foreign key error: Constraint 'PRIMARY': Illegal constraint name
+#
+# Check for duplicate constraint names within the same statement.
+#
+alter table t2 add constraint c foreign key (fk) references t1 (pk),
+add fk2 int constraint c references t1 (pk);
+ERROR 42000: Foreign key error: Constraint 'c': Duplicate constraint name
+#
+# Check for duplicate constraint names created by different
+# statements.
+#
+alter table t2 add constraint c foreign key (fk) references t1 (pk);
+alter table t2 add fk2 int constraint c references t1 (pk);
+ERROR 42000: Foreign key error: Constraint 'c': Duplicate constraint name
+create table t3 (fk int);
+alter table t2 add constraint c foreign key (fk) references t1 (pk);
+ERROR 42000: Foreign key error: Constraint 'c': Duplicate constraint name
+#
+# OTOH it should be possible to create constraint with the same name
+# in other database.
+#
+create database mysqltest;
+use mysqltest;
+create table t3 (fk int);
+alter table t3 add constraint c foreign key (fk) references test.t1(pk);
+use test;
+drop database mysqltest;
+drop tables t2, t3;
+# 
+# Checks for child and parent table types.
+#
+create temporary table t2 (fk int);
+alter table t2 add constraint c foreign key (fk) references t1 (pk);
+ERROR 42000: Foreign key error: Constraint 'c': Child table is temporary
+drop temporary table t2;
+drop table t1;
+create temporary table t1 (pk int primary key);
+create table t2 (fk int);
+alter table t2 add constraint c foreign key (fk) references t1 (pk);
+ERROR 42000: Foreign key error: Constraint 'c': Parent table is temporary
+drop temporary table t1;
+create table t1 (pk int primary key);
+create view v1 as select * from t1;
+alter table t2 add constraint c foreign key (fk) references v1 (pk);
+ERROR 42000: Foreign key error: Constraint 'c': Parent table is a view
+drop view v1;
+drop table t1;
+alter table t2 add fk2 varchar(64) constraint c
+references information_schema.tables (table_name);
+ERROR 42000: Access denied for user 'root'@'localhost' to database 'information_schema'
+alter table t2 add fk2 char(16) constraint c references mysql.user (user);
+ERROR HY000: Foreign key error: Constraint 'c': Parent table is in 'mysql' database
+drop table t2;
+#
+# Check for columns of child and parent tables.
+#
+create table t1 (pk1 int, pk2 int, primary key (pk1, pk2));
+create table t2 (fk int);
+alter table t2 add constraint c foreign key (no_such_column) references t1 (pk);
+ERROR 42000: Foreign key error: Constraint 'c': No such column 'no_such_column' in child table
+alter table t2 add constraint c foreign key (fk, fk) references t1 (pk1, pk2);
+ERROR 42000: Foreign key error: Constraint 'c': Child column 'fk' is duplicated
+alter table t2 add constraint c foreign key (fk) references t1 (no_such_column);
+ERROR 42000: Foreign key error: Constraint 'c': No such column 'no_such_column' in parent table
+alter table t2 add fk1 int, add fk2 int, add fk3 int,
+add constraint c foreign key (fk1, fk2, fk3)
+references t1 (pk1, pk2, pk2);
+ERROR 42000: Foreign key error: Constraint 'c': Parent column 'pk2' is duplicated
+drop tables t1, t2;
+#
+# Checks for types of columns of child and parent tables.
+#
+create table t1 (pk enum('a', 'b') primary key);
+create table t2 (fk enum('b', 'a'));
+alter table t2 add constraint c foreign key (fk) references t1 (pk);
+ERROR 42000: Foreign key error: Constraint 'c': data type of 'fk' column is illegal for foreign key
+drop tables t1, t2;
+create table t1 (pk set('a', 'b') primary key);
+create table t2 (fk set('b', 'a'));
+alter table t2 add constraint c foreign key (fk) references t1 (pk);
+ERROR 42000: Foreign key error: Constraint 'c': data type of 'fk' column is illegal for foreign key
+drop tables t1, t2;
+create table t1 (pk timestamp primary key);
+create table t2 (fk timestamp);
+alter table t2 add constraint c foreign key (fk) references t1 (pk);
+ERROR 42000: Foreign key error: Constraint 'c': data type of 'fk' column is illegal for foreign key
+drop tables t2, t1;
+create table t1 (a int unique);
+create table t2 (fk int);
+alter table t2 add constraint c foreign key (fk) references t1 (a);
+ERROR 42000: Foreign key error: Constraint 'c': Parent key has nullable column 'a'
+drop table t1;
+#
+# Parent column without explicit NOT NULL attribute but part
+# of PK should be OK.
+#
+create table t1 (pk int, primary key (pk));
+alter table t2 add constraint c foreign key (fk) references t1 (pk);
+drop tables t1, t2;
+create table t1 (a int unsigned not null unique,
+b decimal(10,5) not null unique,
+c char(10) not null unique,
+d datetime not null unique);
+create table t2 (fk_bu bigint unsigned, fk_i int,
+fk_du_10_5 decimal(10,5) unsigned,
+fk_d_9_5 decimal(9,5), fk_d_10_4 decimal(10,4),
+fk_c_10_utf8 char(10) character set utf8, fk_c_9 char(9));
+alter table t2 add constraint c foreign key (fk_bu) references t1 (a);
+ERROR 42000: Foreign key error: Constraint 'c': Type of parent column 'a' is not same as type of child column 'fk_bu'
+alter table t2 add constraint c foreign key (fk_i) references t1 (a);
+ERROR 42000: Foreign key error: Constraint 'c': Type of parent column 'a' is not same as type of child column 'fk_i'
+alter table t2 add constraint c foreign key (fk_du_10_5) references t1 (b);
+ERROR 42000: Foreign key error: Constraint 'c': Type of parent column 'b' is not same as type of child column 'fk_du_10_5'
+alter table t2 add constraint c foreign key (fk_d_9_5) references t1 (b);
+ERROR 42000: Foreign key error: Constraint 'c': Type of parent column 'b' is not same as type of child column 'fk_d_9_5'
+alter table t2 add constraint c foreign key (fk_d_10_4) references t1 (b);
+ERROR 42000: Foreign key error: Constraint 'c': Type of parent column 'b' is not same as type of child column 'fk_d_10_4'
+alter table t2 add constraint c foreign key (fk_c_10_utf8) references t1 (c);
+ERROR 42000: Foreign key error: Constraint 'c': Type of parent column 'c' is not same as type of child column 'fk_c_10_utf8'
+alter table t2 add constraint c foreign key (fk_c_9) references t1 (c);
+ERROR 42000: Foreign key error: Constraint 'c': Type of parent column 'c' is not same as type of child column 'fk_c_9'
+drop tables t1, t2;
+#
+# Check that one can't specify SET NULL clause if some of child
+# columns are defined as NOT NULL.
+#
+create table t1 (pk int primary key);
+create table t2 (fk int not null);
+alter table t2 add constraint c foreign key (fk) references t1 (pk) 
+on delete set null;
+ERROR 42000: Foreign key error: Constraint 'c': SET NULL for a NOT NULL column 'fk'
+alter table t2 add constraint c foreign key (fk) references t1 (pk) 
+on update set null;
+ERROR 42000: Foreign key error: Constraint 'c': SET NULL for a NOT NULL column 'fk'
+drop tables t1, t2;
+#
+# Check that we also detect implict NOT NULL specification.
+#
+create table t1 (pk1 int, pk2 int, primary key (pk1, pk2));
+create table t2 (a int, b int, c int, primary key (a, b));
+alter table t2 add constraint c foreign key (b, c) references t1 (pk1, pk2)
+on delete set null;
+ERROR 42000: Foreign key error: Constraint 'c': SET NULL for a NOT NULL column 'b'
+alter table t2 add constraint c foreign key (b, c) references t1 (pk1, pk2)
+on update set null;
+ERROR 42000: Foreign key error: Constraint 'c': SET NULL for a NOT NULL column 'b'
+drop tables t1, t2;
+#
+# Check that parent columns are part of primary key or unique
+# constraint.
+#
+create table t1 (a int not null);
+create table t2 (fk int);
+alter table t2 add constraint c foreign key (fk) references t1 (a);
+ERROR 42000: Foreign key error: Constraint 'c': Parent columns don't correspond to a PRIMARY KEY or UNIQUE constraint
+drop tables t1, t2;
+create table t1 (a int not null, b int not null, key (a, b));
+create table t2 (a int, b int);
+alter table t2 add constraint c foreign key (a, b) references t1 (a, b);
+ERROR 42000: Foreign key error: Constraint 'c': Parent columns don't correspond to a PRIMARY KEY or UNIQUE constraint
+drop table t1;
+create table t1 (a int not null, b int not null, c int not null,
+unique (a, b, c));
+alter table t2 add constraint c foreign key (a, b) references t1 (a, b);
+ERROR 42000: Foreign key error: Constraint 'c': Parent columns don't correspond to a PRIMARY KEY or UNIQUE constraint
+drop tables t1, t2;
+#
+# Check that we prohibit foreign keys in which one table is
+# transactional and another is not (see bug #41692 "Foreign keys:
+# failure if mixed engines").
+#
+create table t1 (pk int primary key) engine=myisam;
+create table t2 (fk int);
+alter table t2 add constraint c foreign key (fk) references t1 (pk);
+ERROR 42000: Foreign key error: Constraint 'c': Mixing transactional and non-transactional engines in one foreign key is not allowed
+drop tables t1, t2;
+create table t1 (pk int primary key);
+create table t2 (fk int) engine=myisam;
+alter table t2 add constraint c foreign key (fk) references t1 (pk);
+ERROR 42000: Foreign key error: Constraint 'c': Mixing transactional and non-transactional engines in one foreign key is not allowed
+drop tables t1, t2;
+#
+# Check for action types incompatible with non-transactional engines.
+# Non-transactional tables are incompatible with NO ACTION/CASCADE/
+# SET NULL/SET DEFAULT actions.
+#
+create table t1 (pk int primary key) engine=myisam;
+create table t2 (fk int) engine=myisam;
+alter table t2 add constraint c foreign key (fk) references t1 (pk)
+on delete cascade;
+ERROR 42000: Foreign key error: Constraint 'c': Engine of table 't2' is non-transactional and CASCADE/SET NULL/SET DEFAULT is specified
+alter table t2 add constraint c foreign key (fk) references t1 (pk)
+on delete set null;
+ERROR 42000: Foreign key error: Constraint 'c': Engine of table 't2' is non-transactional and CASCADE/SET NULL/SET DEFAULT is specified
+alter table t2 add constraint c foreign key (fk) references t1 (pk)
+on delete set default;
+ERROR 42000: Foreign key error: Constraint 'c': Engine of table 't2' is non-transactional and CASCADE/SET NULL/SET DEFAULT is specified
+alter table t2 add constraint c foreign key (fk) references t1 (pk)
+on update cascade;
+ERROR 42000: Foreign key error: Constraint 'c': Engine of table 't2' is non-transactional and CASCADE/SET NULL/SET DEFAULT is specified
+alter table t2 add constraint c foreign key (fk) references t1 (pk)
+on update set null;
+ERROR 42000: Foreign key error: Constraint 'c': Engine of table 't2' is non-transactional and CASCADE/SET NULL/SET DEFAULT is specified
+alter table t2 add constraint c foreign key (fk) references t1 (pk)
+on update set default;
+ERROR 42000: Foreign key error: Constraint 'c': Engine of table 't2' is non-transactional and CASCADE/SET NULL/SET DEFAULT is specified
+alter table t2 add constraint c foreign key (fk) references t1 (pk)
+on delete no action;
+ERROR 42000: Foreign key error: Constraint 'c': Engine of table 't2' is non-transactional and NO ACTION is specified
+alter table t2 add constraint c foreign key (fk) references t1 (pk)
+on update no action;
+ERROR 42000: Foreign key error: Constraint 'c': Engine of table 't2' is non-transactional and NO ACTION is specified
+# Creating foreign key with both RESTRICT actions should be OK
+alter table t2 add constraint c foreign key (fk) references t1 (pk)
+on delete restrict on update restrict;
+drop table t1, t2;
+#
+# Now let us test how ALTER TABLE ... DROP CONSTRAINT works.
+#
+create table t1 (pk int primary key);
+create table t2 (fk int constraint c references t1 (pk));
+insert into t1 values (1);
+insert into t2 values (1);
+#
+# First, let us drop the constraint and check that metadata
+# was properly updated.
+#
+alter table t2 drop constraint c;
+show create table t2;
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `fk` int(11) DEFAULT NULL,
+  KEY `c` (`fk`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+delete from t1 where pk = 1;
+#
+# It should be possible to create a different
+# constraint with name 'c' now.
+#
+create table t3 (pk int primary key, fk int constraint c references t3 (pk));
+drop table t3;
+#
+# Now let us try to drop a constraint which does not exist.
+#
+alter table t2 drop constraint c;
+ERROR 42000: Foreign key error: Constraint 'c': there is no such constraint on this table
+#
+# Let us see how DROP CONSTRAINT works for a table with a
+# couple of constraints.
+#
+insert into t1 values (1);
+create table t3 (pk int primary key);
+insert into t3 values (1);
+alter table t2 add constraint c1 foreign key (fk) references t1 (pk),
+add fk2 int constraint c2 references t3 (pk);
+insert into t2 values (1, 1);
+alter table t2 drop constraint c1;
+show create table t2;
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `fk` int(11) DEFAULT NULL,
+  `fk2` int(11) DEFAULT NULL CONSTRAINT `c2` REFERENCES `t3` (`pk`),
+  KEY `c` (`fk`),
+  KEY `c2` (`fk2`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+delete from t1 where pk = 1;
+create table t4 (pk int primary key, fk int constraint c1 references t4(pk));
+drop table t4;
+delete from t3 where pk = 1;
+ERROR 23000: Foreign key error: constraint 'c2': cannot change because foreign key refers to value '1'
+create table t4 (pk int primary key, fk int constraint c2 references t4 (pk));
+ERROR 42000: Foreign key error: Constraint 'c2': Duplicate constraint name
+drop tables t1, t2, t3;
+#
+# Check that we properly update metadata when dropping a constraint
+# with the same name as other constraint (e.g. when we have several
+# databases).
+#
+create table t1 (pk int primary key);
+insert into t1 values (1), (2);
+create table t2 (fk int constraint c references t1 (pk));
+insert into t2 values (2);
+create database mysqltest;
+use mysqltest;
+create table t2 (fk int constraint c references test.t1 (pk));
+insert into t2 values (1);
+use test;
+alter table t2 drop constraint c;
+#
+# Foreign key on test.t2 should be gone.
+#
+show create table t2;
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `fk` int(11) DEFAULT NULL,
+  KEY `c` (`fk`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+delete from t1 where pk = 2;
+create table t3 (pk int primary key, fk int constraint c references t3 (pk));
+drop table t3;
+#
+# But foreign key on mysqltest.t2 should be there!
+#
+show create table mysqltest.t2;
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `fk` int(11) DEFAULT NULL CONSTRAINT `c` REFERENCES `test`.`t1` (`pk`),
+  KEY `c` (`fk`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+delete from t1 where pk = 1;
+ERROR 23000: Foreign key error: constraint 'c': cannot change because foreign key refers to value '1'
+create table mysqltest.t3 (pk int primary key,
+fk int constraint c references mysqltest.t3 (pk));
+ERROR 42000: Foreign key error: Constraint 'c': Duplicate constraint name
+drop tables t1, t2, mysqltest.t2;
+# 
+# Now similar test for table with self-reference.
+#
+create table t1 (pk int primary key, fk int constraint c references t1 (pk));
+insert into t1 values (1, 1), (2, 2), (3, 2);
+use mysqltest;
+create table t1 (fk int constraint c references test.t1 (pk));
+insert into t1 values (1);
+use test;
+alter table t1 drop constraint c;
+#
+# Again test.c constraint should be completely gone.
+#
+show create table t1;
+Table	Create Table
+t1	CREATE TABLE `t1` (
+  `pk` int(11) NOT NULL,
+  `fk` int(11) DEFAULT NULL,
+  PRIMARY KEY (`pk`),
+  KEY `c` (`fk`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+delete from t1 where pk = 2;
+create table t2 (pk int primary key, fk int constraint c references t2 (pk));
+drop table t2;
+#
+# Again mysqltest.c constraint should be still there.
+#
+show create table mysqltest.t1;
+Table	Create Table
+t1	CREATE TABLE `t1` (
+  `fk` int(11) DEFAULT NULL CONSTRAINT `c` REFERENCES `test`.`t1` (`pk`),
+  KEY `c` (`fk`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+delete from t1 where pk = 1;
+ERROR 23000: Foreign key error: constraint 'c': cannot change because foreign key refers to value '1'
+create table mysqltest.t2 (pk int primary key,
+fk int constraint c references mysqltest.t2 (pk));
+ERROR 42000: Foreign key error: Constraint 'c': Duplicate constraint name
+drop table t1, mysqltest.t1;
+drop database mysqltest;
+# 
+# Let us try to drop constraint which belongs to other table.
+#
+create table t1 (pk int primary key);
+insert into t1 values (1), (2);
+create table t2 (fk int constraint c1 references t1 (pk));
+insert into t2 values (1);
+create table t3 (fk int constraint c2 references t1 (pk));
+insert into t3 values (2);
+alter table t2 drop constraint c2;
+ERROR 42000: Foreign key error: Constraint 'c2': there is no such constraint on this table
+#
+# Check that c2 is still there.
+#
+show create table t3;
+Table	Create Table
+t3	CREATE TABLE `t3` (
+  `fk` int(11) DEFAULT NULL CONSTRAINT `c2` REFERENCES `t1` (`pk`),
+  KEY `c2` (`fk`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+delete from t1 where pk = 2;
+ERROR 23000: Foreign key error: constraint 'c2': cannot change because foreign key refers to value '2'
+create table t4 (pk int primary key, fk int constraint c2 references t4 (pk));
+ERROR 42000: Foreign key error: Constraint 'c2': Duplicate constraint name
+alter table t1 drop constraint c1;
+ERROR 42000: Foreign key error: Constraint 'c1': there is no such constraint on this table
+#
+# Check that c1 is still there.
+#
+show create table t2;
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `fk` int(11) DEFAULT NULL CONSTRAINT `c1` REFERENCES `t1` (`pk`),
+  KEY `c1` (`fk`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+delete from t1 where pk = 1;
+ERROR 23000: Foreign key error: constraint 'c1': cannot change because foreign key refers to value '1'
+create table t4 (pk int primary key, fk int constraint c1 references t4 (pk));
+ERROR 42000: Foreign key error: Constraint 'c1': Duplicate constraint name
+drop tables t1, t2, t3;
+#
+# Now let us see what happens when we drop two constraints which
+# reference the same table.
+#
+create table t1 (pk int primary key);
+insert into t1 values (1), (2);
+create table t2 (fk1 int constraint c1 references t1 (pk),
+fk2 int constraint c2 references t1 (pk));
+insert into t2 values (1, 2);
+alter table t2 drop constraint c1, drop constraint c2;
+#
+# Check that both c1 and c2 are gone.
+#
+show create table t2;
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `fk1` int(11) DEFAULT NULL,
+  `fk2` int(11) DEFAULT NULL,
+  KEY `c1` (`fk1`),
+  KEY `c2` (`fk2`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+delete from t1;
+create table t3 (pk int primary key, fk int constraint c1 references t3 (pk));
+drop table t3;
+create table t3 (pk int primary key, fk int constraint c2 references t3 (pk));
+drop tables t1, t2, t3;
+#
+# Now let us test dropping of two constraints referencing two different
+# tables.
+#
+create table t1 (pk int primary key);
+insert into t1 values (1);
+create table t2 (pk int primary key);
+insert into t2 values (1);
+create table t3 (fk1 int constraint c1 references t1 (pk),
+fk2 int constraint c2 references t2 (pk));
+insert into t3 values (1, 1);
+alter table t3 drop constraint c1, drop constraint c2;
+#
+# Check that both c1 and c2 are gone.
+#
+show create table t3;
+Table	Create Table
+t3	CREATE TABLE `t3` (
+  `fk1` int(11) DEFAULT NULL,
+  `fk2` int(11) DEFAULT NULL,
+  KEY `c1` (`fk1`),
+  KEY `c2` (`fk2`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+delete from t1 where pk = 1;
+delete from t2 where pk = 1;
+create table t4 (pk int primary key, fk int constraint c1 references t4 (pk));
+drop table t4;
+create table t4 (pk int primary key, fk int constraint c2 references t4 (pk));
+drop tables t1, t2, t3, t4;
+#
+# Additional test for dropping a constraint that participates
+# in a loop.
+#
+create table t1 (pk int primary key, fk int);
+insert into t1 values (1, 1), (2, 2);
+create table t2 (pk int primary key, fk int constraint c1 references t1 (pk));
+insert into t2 values (1, 1), (2, 1);
+alter table t1 add constraint c2 foreign key (fk) references t2 (pk);
+#
+# Now let us drop c2 and check that it is completely gone.
+#
+alter table t1 drop constraint c2;
+show create table t1;
+Table	Create Table
+t1	CREATE TABLE `t1` (
+  `pk` int(11) NOT NULL,
+  `fk` int(11) DEFAULT NULL,
+  PRIMARY KEY (`pk`),
+  KEY `c2` (`fk`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+delete from t2 where pk = 2;
+create table t3 (pk int primary key, fk int constraint c2 references t3 (pk));
+drop table t3;
+#
+# And c1 should still be there.
+#
+show create table t2;
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `pk` int(11) NOT NULL,
+  `fk` int(11) DEFAULT NULL CONSTRAINT `c1` REFERENCES `t1` (`pk`),
+  PRIMARY KEY (`pk`),
+  KEY `c1` (`fk`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+delete from t1 where pk = 1;
+ERROR 23000: Foreign key error: constraint 'c1': cannot change because foreign key refers to value '1'
+create table t3 (pk int primary key, fk int constraint c1 references t3 (pk));
+ERROR 42000: Foreign key error: Constraint 'c1': Duplicate constraint name
+drop tables t1, t2;
+#
+# Let us also check that DROP CONSTRAINT is actually a "fast"
+# ALTER TABLE operation, i.e. does not require a full table copy.
+# 
+# We do that by using --enable_info and verifying that number of
+# affected rows is 0.
+#
+create table t1 (pk int primary key);
+create table t2 (fk int constraint c references t1 (pk));
+insert into t1 values (1);
+insert into t2 values (1);
+alter table t2 drop constraint c;
+affected rows: 0
+info: Records: 0  Duplicates: 0  Warnings: 0
+show create table t2;
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `fk` int(11) DEFAULT NULL,
+  KEY `c` (`fk`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+drop tables t1, t2;
+# 
+# Check that we accept and properly interpret DROP FOREIGN KEY clause.
+# We should also emit one instance of appropriate warning in this case.
+#
+create table t1 (pk int primary key, fk int constraint c references t1 (pk));
+insert into t1 values (1, 1), (2, 1);
+alter table t1 drop foreign key c;
+Warnings:
+Warning	1780	Foreign key warning: Constraint 'c': Syntax 'FOREIGN KEY [id]' is old style, changing to CONSTRAINT [id]
+show create table t1;
+Table	Create Table
+t1	CREATE TABLE `t1` (
+  `pk` int(11) NOT NULL,
+  `fk` int(11) DEFAULT NULL,
+  PRIMARY KEY (`pk`),
+  KEY `c` (`fk`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+delete from t1 where pk = 1;
+create table t2 (pk int primary key, fk int constraint c references t2 (pk));
+drop tables t1, t2;
+# 
+# Now let us test how ADD CONSTRAINT and DROP CONSTRAINT clauses
+# work together.
+#
+# Similarly to ADD/DROP COLUMN we first process all DROP clauses
+# and then all ADD clauses. This means that order in which these
+# clauses are used in ALTER TABLE statement doesn't really matter.
+#
+create table t1 (pk int primary key);
+create table t2 (fk int);
+alter table t2 add constraint c foreign key (fk) references t1 (pk),
+drop constraint c;
+ERROR 42000: Foreign key error: Constraint 'c': there is no such constraint on this table
+alter table t2 add constraint c foreign key (fk) references t1 (pk);
+alter table t2 drop constraint c,
+add constraint c foreign key (fk) references t1 (pk)
+on delete cascade;
+show create table t2;
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `fk` int(11) DEFAULT NULL,
+  KEY `c` (`fk`),
+  CONSTRAINT `c` FOREIGN KEY (`fk`) REFERENCES `t1` (`pk`) ON DELETE CASCADE
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+alter table t2 add constraint c foreign key (fk) references t1 (pk),
+drop constraint c;
+show create table t2;
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `fk` int(11) DEFAULT NULL,
+  KEY `c` (`fk`),
+  CONSTRAINT `c` FOREIGN KEY (`fk`) REFERENCES `t1` (`pk`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+drop tables t1, t2;
+#
+# Let us check how different clauses of ALTER TABLE interact with
+# foreign keys.
+#
+# ALTER TABLE .., ADD COLUMN should work fine for both parent and
+# child tables.
+#
+create table t1 (pk int primary key);
+create table t2 (fk int constraint c references t1 (pk));
+alter table t2 add column a int;
+show create table t2;
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `fk` int(11) DEFAULT NULL CONSTRAINT `c` REFERENCES `t1` (`pk`),
+  `a` int(11) DEFAULT NULL,
+  KEY `c` (`fk`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+alter table t1 add column b int;
+show create table t1;
+Table	Create Table
+t1	CREATE TABLE `t1` (
+  `pk` int(11) NOT NULL,
+  `b` int(11) DEFAULT NULL,
+  PRIMARY KEY (`pk`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+#
+# ALTER TABLE ... ADD KEY should also work fine even if new key covers
+# columns participating in a foreign key. 
+#
+alter table t2 add key (fk, a);
+show create table t2;
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `fk` int(11) DEFAULT NULL CONSTRAINT `c` REFERENCES `t1` (`pk`),
+  `a` int(11) DEFAULT NULL,
+  KEY `c` (`fk`),
+  KEY `fk` (`fk`,`a`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+alter table t1 add key (pk, b);
+show create table t1;
+Table	Create Table
+t1	CREATE TABLE `t1` (
+  `pk` int(11) NOT NULL,
+  `b` int(11) DEFAULT NULL,
+  PRIMARY KEY (`pk`),
+  KEY `pk` (`pk`,`b`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+drop tables t1, t2;
+#
+# There is one subtle moment: when we create PRIMARY KEY or
+# UNIQUE constraint on a parent table we should ensure that this
+# action won't delete any rows. Thus IGNORE clause of ALTER TABLE
+# should be ignored in this case [sic!].
+#
+# Until bug #45885 "Primary key added to Falcon table allows
+# non-unique values" this part of the test uses InnoDB and resides
+# in foreign_key_all_engines_2.test.
+#
+# But it should be OK to remove rows in cases when foreign key
+# is added in the same ALTER TABLE statement.
+#
+create table t1 (a int primary key, b int , c int);
+insert into t1 values (1, 1, 1), (2, 1, 1);
+alter ignore table t1 add constraint c foreign key (b) references t1 (a),
+add constraint u unique (c);
+#
+# Removal of rows in cases when foreign key is dropped is also OK.
+#
+alter table t1 drop key u;
+insert into t1 values (2, 1, 1);
+alter ignore table t1 drop constraint c, add constraint u unique (b);
+drop tables t1;
+#
+# ALTER TABLE ... CHANGE/MODIFY COLUMN is disallowed if it affects
+# column participating in an existing foreign key.
+#
+create table t1 (a int primary key, b int);
+create table t2 (a int constraint c references t1 (a), b int);
+alter table t2 change column a a bigint;
+ERROR HY000: Foreign key error: Constraint 'c': can't do CHANGE/MODIFY for column 'a' since it participates in a relationship
+alter table t2 modify column a bigint;
+ERROR HY000: Foreign key error: Constraint 'c': can't do CHANGE/MODIFY for column 'a' since it participates in a relationship
+alter table t1 change column a a bigint;
+ERROR HY000: Foreign key error: Constraint 'c': can't do CHANGE/MODIFY for column 'a' since it participates in a relationship
+alter table t1 modify column a bigint;
+ERROR HY000: Foreign key error: Constraint 'c': can't do CHANGE/MODIFY for column 'a' since it participates in a relationship
+#
+# Changing column which does not participate in such foreign key
+# should be fine.
+#
+alter table t2 change column b b bigint;
+alter table t1 modify column b bigint;
+#
+# Also it should be OK to change column which participates
+# in newly created foreign key.
+#
+alter table t2 drop constraint c;
+alter table t1 modify a bigint;
+alter table t2 add constraint c foreign key (a) references t1 (a),
+modify a bigint;
+drop tables t1, t2;
+create table t1 (pk int primary key, fk int);
+alter table t1 modify column pk bigint,
+modify column fk bigint,
+add constraint c foreign key (fk) references t1 (pk);
+drop table t1;
+#
+# And finally, let us have a test case which will show that even
+# changing columns which do not participate in foreign key can be
+# important from their point of view.
+#
+create table t1 (a int primary key, b char(4) unique);
+insert into t1 values (1, 'aaaa'), (2, 'aaab');
+create table t2 (fk int constraint c references t1 (a));
+insert into t2 values (1), (2);
+alter ignore table t1 modify b char(3);
+ERROR 23000: Duplicate entry 'aaa' for key 'b'
+drop tables t1, t2;
+#
+# ALTER TABLE ... DROP COLUMN affecting one of foreign key columns
+# is disallowed too.
+#
+create table t1 (a int not null, b int not null, primary key(a, b));
+create table t2 (a int, b int, constraint c foreign key (a, b)
+references t1 (a, b));
+alter table t1 drop column b;
+ERROR HY000: Foreign key error: Constraint 'c': can't do DROP for column 'b' since it participates in a relationship
+alter table t2 drop column b;
+ERROR HY000: Foreign key error: Constraint 'c': can't do DROP for column 'b' since it participates in a relationship
+drop tables t1, t2;
+#
+# ALTER TABLE ... DROP PRIMARY KEY/KEY should be disallowed if it
+# tries to drop parent key of a foreign key relationship. Also it
+# it should not be allowed to drop supporting index (if there are
+# no other suitable indexes).
+#
+create table t1 (pk int primary key);
+create table t2 (fk int constraint c references t1 (pk), key c (fk));
+alter table t1 drop primary key;
+ERROR 42000: Foreign key error: constraint 'c': references table, so you cannot drop unique index it depends on
+alter table t2 drop key c;
+ERROR 42000: Foreign key error: constraint 'c': you cannot drop an index that the child key depends on
+#
+# PRIMARY/UNIQUE KEY serving as a parent key should be undroppable
+# even if there is another UNIQUE key covering the same columns.
+#
+alter table t1 add unique u (pk);
+alter table t1 drop primary key;
+ERROR 42000: Foreign key error: constraint 'c': references table, so you cannot drop unique index it depends on
+#
+# OTOH it should be possible to drop this other unique index,
+# since it is not used as parent key.
+#
+alter table t1 drop key u;
+#
+# Also it should be possible to drop supporting index if there
+# is another suitable index.
+#
+alter table t2 add key d (fk);
+alter table t2 drop key c;
+drop tables t1, t2;
+create table t1 (pk int not null unique);
+create table t2 (fk int constraint c references t1 (pk));
+alter table t1 drop key pk;
+ERROR 42000: Foreign key error: constraint 'c': references table, so you cannot drop unique index it depends on
+#
+# And again PRIMARY/UNIQUE KEY with same columns as the parent key
+# but not used as parent key should be droppable and the parent key
+# should be undroppable.
+#
+alter table t1 add primary key (pk);
+alter table t1 drop key pk;
+ERROR 42000: Foreign key error: constraint 'c': references table, so you cannot drop unique index it depends on
+alter table t1 drop primary key;
+#
+# Dropping of unrelated keys should be allowed as well.
+#
+alter table t1 add column a int, add unique (a);
+alter table t1 drop key a;
+alter table t2 add column a int, add key (a);
+alter table t2 drop key a;
+drop tables t1, t2;
+#
+# ALTER TABLE ... DISABLE KEYS should be disallowed on tables
+# which participate in foreign keys as a child (since it will
+# disable supporting index). We can allow this statement on
+# parent tables as it won't disable unique indexes anyway.
+# ALTER TABLE ... ENABLE KEYS won't do any harm so it should
+# be allowed for both parent and child tables.
+#
+# We use MyISAM for this test as other engines are unlikely to
+# support these clauses.
+#
+create table t1 (pk int primary key, a int) engine=myisam;
+create table t2 (fk int constraint c references t1 (pk)
+on delete restrict on update restrict,
+a int) engine=myisam;
+#
+# Let us check that DISABLE KEYS in on the child table is
+# disallowed for simple/fast/full variants of ALTER TABLE.
+#
+alter table t2 disable keys;
+ERROR HY000: Foreign key error: you cannot use 'ALTER TABLE ... DISABLE KEYS' statement because 't2' table is in a relationship
+alter table t2 disable keys, alter column a set default 0;
+ERROR HY000: Foreign key error: you cannot use 'ALTER TABLE ... DISABLE KEYS' statement because 't2' table is in a relationship
+alter table t2 disable keys, add column b int;
+ERROR HY000: Foreign key error: you cannot use 'ALTER TABLE ... DISABLE KEYS' statement because 't2' table is in a relationship
+#
+# Same statements for parent table should succeed.
+#
+alter table t1 disable keys;
+alter table t1 disable keys, alter column a set default 0;
+alter table t1 disable keys, add column b int;
+#
+# Although disabling keys with dropping foreign keys should be OK.
+#
+alter table t2 drop constraint c, disable keys;
+#
+# Let us also check that we disallow creation of foreign keys
+# on tables with disabled foreign keys.
+#
+alter table t2 add constraint c foreign key (fk) references t1 (pk)
+on delete restrict on update restrict;
+ERROR HY000: Foreign key error: constraint 'c': no supporting index on child table
+#
+# Finally let us check that enabling indexes is fine on both child
+# and parent tables and for all internal ALTER variants.
+#
+alter table t2 enable keys;
+alter table t2 enable keys, alter column a set default 1;
+alter table t2 enable keys, add column c int;
+alter table t1 enable keys;
+alter table t1 enable keys, alter column a set default 1;
+alter table t1 enable keys, add column c int;
+drop tables t1, t2;
+#
+# ALTER TABLE ... DROP/SET DEFAULT is disallowed when it is going
+# to affect one of child columns participating in a foreign key
+# with ON DELETE/UPDATE SET DEFAULT clauses.
+#
+create table t1 (pk int primary key);
+create table t2 (fk1 int default 1 constraint c1 references t1 (pk) on update set default,
+fk2 int constraint c2 references t1 (pk));
+alter table t2 alter column fk1 drop default;
+ERROR 42000: Foreign key error: constraint 'c1': SET/DROP DEFAULT illegal while foreign key exists with SET DEFAULT
+alter table t2 alter column fk1 set default 2;
+ERROR 42000: Foreign key error: constraint 'c1': SET/DROP DEFAULT illegal while foreign key exists with SET DEFAULT
+#
+# Changing defaults for parent columns of child columns of foreign
+# keys without ON DELETE/UPDATE SET DEFAULT clauses should be OK.
+#
+alter table t2 alter column fk2 drop default;
+alter table t2 alter column fk2 set default 2;
+alter table t1 alter column pk set default 2;
+alter table t1 alter column pk drop default;
+#
+# And of course dropping the DEFAULT and the foreign key
+# simultaneously also works.
+#
+alter table t2 drop constraint c1, alter column fk1 drop default;
+drop tables t1, t2;
+#
+# ALTER TABLE ... RENAME is disallowed in the presence of foreign keys
+# (even if they are created by the same alter).
+#
+create table t1 (pk int primary key);
+create table t2 (fk int constraint c references t1 (pk));
+alter table t2 rename to t3;
+ERROR HY000: Foreign key error: you cannot use 'ALTER TABLE ... RENAME TABLE' statement because 't2' table is in a relationship
+alter table t1 rename to t3;
+ERROR HY000: Foreign key error: you cannot use 'ALTER TABLE ... RENAME TABLE' statement because 't1' table is in a relationship
+alter table t2 rename to t3, add column a int;
+ERROR HY000: Foreign key error: you cannot use 'ALTER TABLE ... RENAME TABLE' statement because 't2' table is in a relationship
+alter table t1 rename to t3, add column a int;

+ERROR HY000: Foreign key error: you cannot use 'ALTER TABLE ... RENAME TABLE' statement because 't1' table is in a relationship
+# But renaming and dropping foreign keys should be OK.
+alter table t2 drop constraint c, rename to t3;
+alter table t3 rename to t2,
+add constraint c foreign key (fk) references t1 (pk);
+ERROR HY000: Foreign key error: you cannot use 'ALTER TABLE ... RENAME TABLE' statement because 't3' table is in a relationship
+drop tables t1, t3;
+#
+# ALTER TABLE ... CONVERT TO CHARSET is also prohibited in the presence
+# of foreign keys since it can affect columns partiticipating in them.
+#
+create table t1 (pk varchar(10) character set latin1 primary key);
+create table t2 (fk varchar(10) character set latin1
+constraint c references t1 (pk));
+alter table t2 convert to character set utf8;
+ERROR HY000: Foreign key error: you cannot use 'ALTER TABLE ... CONVERT TO CHARSET' statement because 't2' table is in a relationship
+alter table t1 convert to character set utf8;
+ERROR HY000: Foreign key error: you cannot use 'ALTER TABLE ... CONVERT TO CHARSET' statement because 't1' table is in a relationship
+#
+# OTOH it should be possible to use this clause if all foreign keys
+# are dropped within same statement.
+#
+alter table t2 drop constraint c, convert to character set utf8;
+#
+# Or if foreign keys are added within same statement.
+#
+alter table t2 add constraint c foreign key (fk) references t1 (pk),
+convert to character set latin1;
+drop tables t1, t2;
+#
+# ALTER TABLE ... ORDER BY should work fine for both child
+# and parent tables, as it does not change row contents.
+#
+# We use MyISAM for this test as we know that this engine
+# does not support clustered indexes so using ORDER BY
+# makes some sense for it.
+#
+create table t1 (pk int primary key) engine=myisam;
+insert into t1 values (1), (3), (2);
+create table t2 (fk int constraint c references t1 (pk)
+on delete restrict on update restrict) engine=myisam;
+insert into t2 values (3), (2), (1);
+alter table t1 order by pk;
+alter table t2 order by fk;
+select * from t1;
+pk
+1
+2
+3
+select * from t2;
+fk
+1
+2
+3
+drop tables t1, t2;
+#
+# Now let see how ALTER TABLE that changes some table options
+# works in the presence of foreign keys.
+#
+# When doing ALTER TABLE ... ENGINE= ... we should check that
+# new engine is compatible with existing foreign keys.
+# E.g. for non-self-referencing foreing key it should be disallowed
+# to switch engine from non-transactional to transactional and vice
+# versa as it will create a foreign key between transactional and
+# non-transactional table which is disallowed. It should also be
+# disallowed to change table engine to non-transactional if table
+# participates in foreign keys which have something else than
+# RESTRICT as cascading actions (again this is because we disallow
+# of such foreign keys for such tables).
+#
+create table t1 (pk int primary key);
+create table t2 (fk int constraint c references t1 (pk) on update restrict
+on delete restrict);
+alter table t1 engine= myisam;
+ERROR 42000: Foreign key error: Constraint 'c': Mixing transactional and non-transactional engines in one foreign key is not allowed
+alter table t2 engine= myisam;
+ERROR 42000: Foreign key error: Constraint 'c': Mixing transactional and non-transactional engines in one foreign key is not allowed
+drop tables t1, t2;
+create table t1 (pk int primary key) engine=myisam;
+create table t2 (fk int constraint c references t1 (pk) on update restrict
+on delete restrict) engine=myisam;
+alter table t1 engine= <engine_type>;
+ERROR 42000: Foreign key error: Constraint 'c': Mixing transactional and non-transactional engines in one foreign key is not allowed
+alter table t2 engine= <engine_type>;
+ERROR 42000: Foreign key error: Constraint 'c': Mixing transactional and non-transactional engines in one foreign key is not allowed
+drop tables t1, t2;
+#
+# It is possible to change engine from transactional to non-transactional
+# and vice versa for tables with only self-referencing foreign keys.
+#
+create table t1 (pk int primary key,
+fk int constraint c references t1 (pk) on update restrict
+on delete restrict);
+alter table t1 engine= myisam;
+alter table t1 engine= <engine_type>;
+drop table t1;
+#
+# ... unless the foreign key has something else than RESTRICT
+# as one of cascading actions (the default is NO ACTION).
+#
+create table t1 (pk int primary key,
+fk int constraint c references t1 (pk));
+alter table t1 engine= myisam;
+ERROR 42000: Foreign key error: Constraint 'c': Engine of table 't1' is non-transactional and NO ACTION is specified
+alter table t1 drop constraint c,
+add constraint c foreign key (fk) references t1 (pk)
+on update cascade on delete cascade;
+alter table t1 engine= myisam;
+ERROR 42000: Foreign key error: Constraint 'c': Engine of table 't1' is non-transactional and CASCADE/SET NULL/SET DEFAULT is specified
+drop table t1;
+#
+# Let us also try setting some harmless (from foreign key POV)
+# option, e.g. COMMENT. ALTER TABLE setting such options should
+# work normally for tables with foreign keys.
+#
+create table t1 (pk int primary key);
+create table t2 (fk int constraint c references t1 (pk));
+alter table t2 comment='A comment';
+show create table t2;
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `fk` int(11) DEFAULT NULL CONSTRAINT `c` REFERENCES `t1` (`pk`),
+  KEY `c` (`fk`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1 COMMENT='A comment'
+alter table t1 comment='Another comment';
+show create table t1;
+Table	Create Table
+t1	CREATE TABLE `t1` (
+  `pk` int(11) NOT NULL,
+  PRIMARY KEY (`pk`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1 COMMENT='Another comment'
+drop tables t1, t2;
+# 
+# For tests covering interaction of ALTER TABLE with partitioning clauses
+# and table with foreign keys see foreign_key_all_engines_2.test.
+#
+#
+# Let us have basic coverage for the order in which ALTER TABLE's
+# clauses are processed. The idea is that all DROP clauses are
+# processed before corresponding ADD clauses. We also process
+# DROP CONSTRAINT/FOREIGN KEY clauses before COLUMN/other KEY
+# clauses in order to be able to change properties of columns/keys
+# participating in foreign keys even though doing this directly
+# is prohibited.
+#
+create table t1 (pk int primary key);
+insert into t1 values (1), (2);
+create table t2 (fk int constraint c references t1 (pk));
+insert into t2 values (NULL), (2);
+#
+# Let us show that dropping foreign key, changing column and
+# then re-adding foreign key is safe because in the process
+# orphan child rows introduced by column change are detected.
+#
+alter table t2 drop constraint c,
+modify column fk int not null,
+add constraint c foreign key (fk) references t1 (pk);
+ERROR 23000: Foreign key error: constraint 'c': no matching key for value '0', it is not in parent table
+#
+# But if there are no orphans as result the same statement is OK.
+#
+insert into t1 values (0);
+alter table t2 drop constraint c,
+modify column fk int not null,
+add constraint c foreign key (fk) references t1 (pk);
+Warnings:
+Warning	1265	Data truncated for column 'fk' at row 1
+show create table t2;
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `fk` int(11) NOT NULL,
+  KEY `c` (`fk`),
+  CONSTRAINT `c` FOREIGN KEY (`fk`) REFERENCES `t1` (`pk`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+drop tables t1, t2;
+#
+# Also when ADD CONSTRAINT is mixed with other ALTER TABLE's
+# clauses checks that are performed during creation of foreign key
+# should be based on table's final state and not some stale version
+# (in other words it should look as if ADD CONSTRAINT was processed
+# after all other ALTER TABLE's clauses).
+#
+create table t1 (pk int primary key, fk int);
+alter table t1 add constraint c foreign key (fk) references t1 (pk),
+drop primary key;
+ERROR 42000: Foreign key error: Constraint 'c': Parent columns don't correspond to a PRIMARY KEY or UNIQUE constraint
+alter table t1 drop primary key;
+alter table t1 add constraint c foreign key (fk) references t1 (pk),
+add primary key (pk);
+drop table t1;
+#
+# Even though CREATE INDEX is just another form of ALTER TABLE
+# let us have coverage which shows how it works in the presence
+# of foreign keys.
+#
+# Adding index which covers columns participating in a foreign
+# key is OK.
+create table t1 (pk int primary key, a int);
+create table t2 (fk int constraint c references t1 (pk), b int);
+create index b on t2 (fk, b);
+show create table t2;
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `fk` int(11) DEFAULT NULL CONSTRAINT `c` REFERENCES `t1` (`pk`),
+  `b` int(11) DEFAULT NULL,
+  KEY `c` (`fk`),
+  KEY `b` (`fk`,`b`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+create index a on t1 (pk, a);
+show create table t1;
+Table	Create Table
+t1	CREATE TABLE `t1` (
+  `pk` int(11) NOT NULL,
+  `a` int(11) DEFAULT NULL,
+  PRIMARY KEY (`pk`),
+  KEY `a` (`pk`,`a`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+drop tables t1, t2;
+#
+# CREATE INDEX does not support IGNORE clause so removal of rows from
+# a parent table which can happen when one adds UNIQUE index on it
+# will always result in an error and won't create orphans.
+#
+# Until bug #45885 "Primary key added to Falcon table allows
+# non-unique values" this part of the test uses InnoDB and resides
+# in foreign_key_all_engines_2.test.
+#
+# Let us also test how DROP INDEX (yet another variant of ALTER TABLE)
+# interacts with foreign keys.
+#
+# It is disallowed to drop parent key for a foreign key relationship.
+# Also it is prohibited to drop last suitable supporting index for it.
+#
+create table t1 (pk int primary key);
+create table t2 (fk int constraint c references t1 (pk), key c (fk));
+drop index `primary` on t1;
+ERROR 42000: Foreign key error: constraint 'c': references table, so you cannot drop unique index it depends on
+drop index c on t2;
+ERROR 42000: Foreign key error: constraint 'c': you cannot drop an index that the child key depends on
+#
+# It should be OK to drop supporting index if there is another
+# suitable one.
+#
+create index d on t2 (fk);
+drop index c on t2;
+drop tables t1, t2;
+create table t1 (pk int not null unique);
+create table t2 (fk int constraint c references t1 (pk));
+drop index pk on t1;
+ERROR 42000: Foreign key error: constraint 'c': references table, so you cannot drop unique index it depends on
+#
+# Unlike for supporting indexes the original parent key is undroppable
+# even if there is another suitable key. 
+# It is OK to drop this another key.
+#
+create unique index u on t1 (pk);
+drop index pk on t1;
+ERROR 42000: Foreign key error: constraint 'c': references table, so you cannot drop unique index it depends on
+drop index u on t1;
+#
+# But it is OK to drop an unrelated key.
+#
+alter table t1 add column a int, add unique (a);
+drop index a on t1;
+alter table t2 add column a int, add key (a);
+drop index a on t2;
+drop tables t1, t2;
+# 
+# Coverage for use of prepared statemetns and ALTER on table
+# with foreign keys.
+# We need to check that foreign keys with auto-generated names
+# work okay during re-execution.
+#
+create table t1 (pk int primary key);
+create table  t2 (fk int, fk1 int);
+prepare stmt from "alter table t2 add constraint c foreign key (fk)
+references t1 (pk), add constraint foreign key (fk1) references t1 (pk)";
+execute stmt;
+show create table t2;
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `fk` int(11) DEFAULT NULL,
+  `fk1` int(11) DEFAULT NULL,
+  KEY `c` (`fk`),
+  KEY `fk_t2_46vh3` (`fk1`),
+  CONSTRAINT `c` FOREIGN KEY (`fk`) REFERENCES `t1` (`pk`),
+  CONSTRAINT `fk_t2_46vh3` FOREIGN KEY (`fk1`) REFERENCES `t1` (`pk`)
+) ENGINE=Falcon DEFAULT CHARSET=latin1
+execute stmt;
+ERROR 42000: Foreign key error: Constraint 'c': Duplicate constraint name
+drop table t2;
+create table  t2 (fk int, fk1 int);
+execute stmt;
+show create table t2;
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `fk` int(11) DEFAULT NULL,
+  `fk1` int(11) DEFAULT NULL,
+  KEY `c` (`fk`),
+  KEY `fk_t2_4lzdd` (`fk1`),
+  CONSTRAINT `c` FOREIGN KEY (`fk`) REFERENCES `t1` (`pk`),
+  CONSTRAINT `fk_t2_4lzdd` FOREIGN KEY (`fk1`) REFERENCES `t1` (`pk`)
+) ENGINE=Falcon DEFAULT CHARSET=latin1
+flush table t1, t2;
+execute stmt;
+ERROR 42000: Foreign key error: Constraint 'c': Duplicate constraint name
+flush table t1, t2;
+drop table t2;
+create table  t2 (fk int, fk1 int);
+execute stmt;
+drop table t1, t2;
+deallocate prepare stmt;
+#
+# Test the situation when we try to add a foreign key on 
+# a view that uses stored functions.
+#
+create table t1 (pk int primary key);
+create table t2 (a int);
+create table t3 (a int);
+create table t4 (fk int);
+create trigger t2_ai after insert on t2 for each row insert into t3 (a)
+values (new.a);
+create procedure p1(p_a int) insert into t2 (a) values (p_a);
+create function f1(p_a int) returns int begin call p1(p_a); return p_a; end|
+create view v1 as select f1(pk) as pk from t1;
+alter table t4 add constraint c foreign key (fk) references v1 (pk);
+ERROR 42000: Foreign key error: Constraint 'c': Parent table is a view
+lock table t4 write, v1 write;
+alter table t4 add constraint c foreign key (fk) references v1 (pk);
+ERROR 42000: Foreign key error: Constraint 'c': Parent table is a view
+drop table t1, t2, t3, t4;
+unlock tables;
+drop view v1;
+drop procedure p1;
+drop function f1;
+#
+# Coverage for the 14th milestone of WL#148 "Foreign keys"
+# ("DDL checks and changes: DROP, TRUNCATE, RENAME").
+#
+drop tables if exists t1, t2, t3, t4, t1_2, t2_2, t3_2, t1_3, t2_3, t3_3;
+drop database if exists mysqltest;
+# 
+# DROP TABLES.
+# 
+# Let us check that we disallow dropping of parent tables
+# without dropping child tables.
+create table t1 (pk int primary key);
+create table t2 (fk int constraint c references t1 (pk));
+drop table t1;
+ERROR 23000: Foreign key error: constraint 'c': you cannot use 'DROP TABLE' statement because table 't1' has a foreign key that refers to it
+# But dropping a child without the parent should be allowed.
+drop table t2;
+# Also dropping the child and the parent simultaneously should be fine.
+create table t2 (fk int references t1 (pk));
+drop tables t1, t2;
+# Dropping a table with a self-reference also should be OK.
+create table t1 (pk int primary key, fk int references t1 (pk));
+drop table t1;
+# Now let us check that dropping a child without without a parent
+# leaves no traces in the parent description (this is a test for
+# Bug #42063 "Foreign keys: constraint survives after table 
+# is dropped").
+create table t1 (pk int primary key);
+create table t2 (fk int references t1 (pk));
+insert into t1 values (1);
+drop table t2;
+# This update should succeed and should not emit any errors
+update t1 set pk = 0;
+drop table t1;
+# A bit more complex scenario in which we drop two tables
+# with foreign keys.
+create table t1 (pk int primary key);
+create table t2 (pk int primary key);
+create table t3 (fk int references t1 (pk));
+create table t4 (fk int references t2 (pk));
+drop tables t3, t4;
+# Now a scenario with more complex dependencies
+drop table t2;
+create table t2 (pk int primary key, fk int references t1 (pk));
+create table t4 (pk int primary key, fk int references t1 (pk));
+create table t3 (fk1 int references t1 (pk), fk2 int references t2 (pk),
+fk3 int references t4 (pk));
+drop tables t4, t3, t2;
+# An even more complex scenario involving 3 clusters of
+# differently connected tables.
+create table t2 (fk int references t1 (pk));
+create table t3 (fk int references t1 (pk));
+create table t1_2 (pk int primary key);
+create table t2_2 (pk int primary key);
+create table t3_2 (fk1 int references t1_2 (pk), fk2 int references t2_2 (pk));
+create table t1_3 (pk int primary key);
+create table t2_3 (pk int primary key, fk int references t1_3 (pk));
+create table t3_3 (fk int references t2_3 (pk));
+# Let us try to drop all child tables in some arbitrary order.
+drop tables t2, t2_3, t3, t3_2, t3_3;
+# Re-create children and try to drop all tables at once.
+create table t2 (fk int references t1 (pk));
+create table t3 (fk int references t1 (pk));
+create table t3_2 (fk1 int references t1_2 (pk), fk2 int references t2_2 (pk));
+create table t2_3 (pk int primary key, fk int references t1_3 (pk));
+create table t3_3 (fk int references t2_3 (pk));
+drop tables t1, t1_2, t1_3, t2, t2_2, t2_3, t3, t3_2, t3_3;
+# Now let us test dropping of tables with circular references.
+# Let us check the most trivial scenario first. 
+create table t1 (pk int primary key, fk int);
+create table t2 (pk int primary key, fk int constraint a references t1 (pk));
+alter table t1 add constraint b foreign key (fk) references t2 (pk);
+# It should not possible to drop only one of tables.
+drop table t1;
+ERROR 23000: Foreign key error: constraint 'a': you cannot use 'DROP TABLE' statement because table 't1' has a foreign key that refers to it
+drop table t2;
+ERROR 23000: Foreign key error: constraint 'b': you cannot use 'DROP TABLE' statement because table 't2' has a foreign key that refers to it
+# But dropping both of them at once should be fine.
+drop tables t1, t2;
+# Now let us check more complicated scenario with circular references.
+create table t1 (pk int primary key);
+create table t2 (pk int primary key, fk int);
+create table t3 (pk int primary key,
+fk1 int constraint a references t1 (pk),
+fk2 int constraint b references t2 (pk));
+create table t4 (pk int primary key, fk int constraint c references t3 (pk));
+alter table t2 add constraint d foreign key (fk) references t4 (pk);
+# Again minimal subset of tables to be dropped should fully
+# include reference cycle. Let us check that we can't drop
+# some of the smaller subsets.
+drop table t2, t4;
+ERROR 23000: Foreign key error: constraint 'b': you cannot use 'DROP TABLE' statement because table 't2' has a foreign key that refers to it
+drop table t2, t3;
+ERROR 23000: Foreign key error: constraint 'c': you cannot use 'DROP TABLE' statement because table 't3' has a foreign key that refers to it
+drop table t4, t3;
+ERROR 23000: Foreign key error: constraint 'd': you cannot use 'DROP TABLE' statement because table 't4' has a foreign key that refers to it
+drop table t1, t2, t3;
+ERROR 23000: Foreign key error: constraint 'c': you cannot use 'DROP TABLE' statement because table 't3' has a foreign key that refers to it
+# Subset in the below statement fully includes cycle and
+# therefore it should be possible to drop it.
+drop tables t2, t3, t4;
+drop table t1;
+# Let us also check scenarios in which there is a temporary
+# table shadowing one of the tables participating in the constraint.
+#
+create table t1 (pk int primary key);
+create table t2 (fk int constraint c references t1 (pk));
+# Attempt to drop the parent without dropping the child should
+# lead to an error.
+create temporary table t2 (i int);
+drop tables t1, t2;
+ERROR 23000: Foreign key error: constraint 'c': you cannot use 'DROP TABLE' statement because table 't1' has a foreign key that refers to it
+drop temporary table t2;
+#
+# When dropping the child we should not be confused by the
+# presence of a temporary table, that is shadowing the parent.
+#
+create temporary table t1 (i int);
+drop table t2;
+#
+# Check that parent .FRM was properly modified.
+#
+drop temporary table t1;
+#
+# The below statements should succeed.
+#
+insert into t1 values (1);
+update t1 set pk= 0;
+#
+# Finally a bit more complicated scenario in which we
+# are dropping the temporary table and modify .FRM of
+# the parent table which is shadowed by this temporary table.
+#
+create table t2 (fk int references t1 (pk));
+create temporary table t1 (i int);
+drop tables t1, t2;
+drop table t1;
+# Until bug #44230 "Altering Falcon table under LOCK TABLES fails with
+# "Can't lock file" error" is fixed tests for DROP TABLE under LOCK
+# TABLES have to reside in foreign_key_all_engines_2.test.
+# Let us check that we are able to drop a table participating in a 
+# foreign key even if the other table in this relationship does
+# not exist (i.e. if the data-dictionary is inconsistent).
+# First let us drop a child with non-existing parent.
+create table t1 (pk int primary key) engine=myisam;
+create table t2 (fk int references t1 (pk)
+on delete restrict on update restrict) engine=myisam;
+# Now let us simulate manual dropping of parent table.
+flush table t1;
+# Dropping of child should succeed.
+drop table t2;
+# Perform a similar test for parent table.
+create table t1 (pk int primary key) engine=myisam;
+create table t2 (fk int references t1 (pk)
+on delete restrict on update restrict) engine=myisam;
+# Simulate manual dropping of table t2.
+flush table t2;
+# The below statement should succeed. We have to mention both tables
+# in it as otherwise error about attempt to drop parent table without
+# child table will be reported.
+drop tables if exists t1, t2;
+Warnings:
+Note	1051	Unknown table 't2'
+# Test that CREATE and DROP TABLE are able to modify .FRMs of parent
+# table even if this table was created under more relaxed sql_mode and
+# thus, for example, straightforward attempt to alter such table might
+# fail. We want to allow creation/dropping of foreign keys in such
+# circumstances since, after all, the fact that we modify .FRM of
+# the parent in the process is just an implementation detail.
+#
+set @old_sql_mode:= @@sql_mode;
+set sql_mode='ALLOW_INVALID_DATES';
+# Create a parent-to-be with defaults which prohibit its re-creation
+# or normal altering in 'traditional' mode.
+create table t1 (pk int primary key,
+a datetime default '2009-03-30 00:00:00',
+b datetime default '2009-03-00 00:00:00',
+c datetime default '0000-00-00 00:00:00');
+set sql_mode= 'TRADITIONAL';
+# The below CREATE and DROP TABLE should succeed.
+create table t2 (fk int references t1 (pk));
+drop table t2;
+set sql_mode= @old_sql_mode;
+drop table t1;
+#
+# DROP DATABASE
+#
+# From the foreign keys point of view DROP DATABASE is simply a
+# special form of DROP TABLES.
+# 
+# It should be impossible to drop a database containing tables which
+# are referenced by tables from other databases.
+create database mysqltest;
+create table mysqltest.t1 (pk int primary key);
+create table test.t2 (fk int constraint c references mysqltest.t1 (pk));
+drop database mysqltest;
+ERROR 23000: Foreign key error: constraint 'c': you cannot use 'DROP DATABASE' statement because table 't1' has a foreign key that refers to it
+# It should be possible to drop a database which tables are not
+# referenced from other databases.
+drop table test.t2;
+create table mysqltest.t2 (fk int references mysqltest.t1 (pk));
+drop database mysqltest;
+# Also it should be possible to drop a database which is not
+# referenced from other databases, but itself constains tables
+# referencing to other databases.
+create table t1 (pk int primary key);
+create database mysqltest;
+create table mysqltest.t2 (fk int references test.t1 (pk));
+drop database mysqltest;
+drop table t1;
 # Test DROP DATABASE which has foreign keys and
 # damaged tables lying around.
 create database mysql_test;
@@ -1964,6 +3685,31 @@ unique_constraint_name
 j
 drop table t1;
 #
+# Additional test for bug #39932 "create table fails if column for FK
+# is in different case than in corr index".
+#
+# See --foreign-key-all-engines=0 version of it in innodb_mysql.test.
+#
+drop tables if exists t1, t2;
+create table t1 (pk int primary key);
+#
+# Even though the below statement uses uppercased field names in
+# foreign key definition it still should be able to find explicitly
+# created supporting index and primary key on parent column. So it
+# should succeed and should not create any additional supporting
+# indexes.
+#
+create table t2 (fk int, key x (fk),
+constraint x foreign key (FK) references t1 (PK));
+show create table t2;
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `fk` int(11) DEFAULT NULL,
+  KEY `x` (`fk`),
+  CONSTRAINT `x` FOREIGN KEY (`FK`) REFERENCES `t1` (`PK`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+drop tables t1, t2;
+#
 # Test for bug #41687 "Foreign keys: crash if varbinary or varchar or
 #                      datetime or decimal".
 #
@@ -2477,3 +4223,176 @@ ERROR 23000: Duplicate entry '1' for key
 delete from t1 where pk = 1;
 ERROR 23000: Duplicate entry '1' for key 'PRIMARY'
 drop tables t1, t2, t3, t4;
+#
+# Test for bug #45985 "Foreign keys: crash if drop duplicate index"
+#
+# Server crashed when user tried to drop multi-column index which
+# might have been used by a foreign key (the fact that index was
+# duplicate was not significant).
+drop table if exists t1, t2;
+create table t1 (s1 int not null, s2 int not null,
+unique i1 (s1, s2));
+create table t2 (s1 int, s2 int,
+key j1 (s1, s2),
+constraint c foreign key (s1, s2) references t1 (s1, s2));
+drop index i1 on t1;
+ERROR 42000: Foreign key error: constraint 'c': references table, so you cannot drop unique index it depends on
+drop index j1 on t2;
+ERROR 42000: Foreign key error: constraint 'c': you cannot drop an index that the child key depends on
+# Also test situation which were originally described in the bug report.
+create unique index i2 on t1 (s1, s2);
+create index j2 on t2 (s1, s2);
+# It is OK to drop 'i2' since it is different unique key which is used
+# as a parent key.
+drop index i2 on t1;
+# And it is OK to drop 'j2' since there is other supporting index.
+drop index j2 on t2;
+drop tables t1, t2;
+#
+# Test for bug #46124  "Foreign keys: crash if alter table drop foreign
+# key".
+# 
+# In --foreign-key-all-engines=1 mode ALTER TABLE with DROP FOREIGN KEY
+# clause without foreign key name specified crashed server even if 
+# table didn't exist. After the fix such statement should produce
+# ER_PARSE_ERROR in all cases.
+drop table if exists t1, t2;
+alter table t1 drop foreign key;
+ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' at line 1
+create table t1 (pk int primary key);
+create table t2 (fk int references t1 (pk));
+alter table t2 drop foreign key;
+ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' at line 1
+drop tables t1, t2;
+#
+# Test for bug #46136 "Foreign keys: adding primary keys causes dangling
+# reference".
+#
+drop table if exists t1, t2;
+create table t1 (s1 int not null unique);
+insert into t1 values (1), (2), (3);
+create table t2 (s1 int constraint c references t1 (s1));
+insert into t2 values (NULL), (1), (2), (3);
+# Making child columns of a foreign key part of PRIMARY KEY should be
+# disallowed as this can replace NULL values in these columns with
+# type's default values and thus create orphans.
+alter table t2 add primary key (s1);
+ERROR HY000: Foreign key error: Constraint 'c': can't do ADD PRIMARY KEY for column 's1' since it participates in a relationship
+# Making parent columns part of PRIMARY KEY should be OK as they are
+# required to have NOT NULL attribute already.
+alter table t1 add primary key (s1);
+select * from t1;
+s1
+1
+2
+3
+select * from t2;
+s1
+NULL
+1
+2
+3
+drop table t2;
+# OTOH making non-nullable child columns part of PRIMARY KEY should
+# be fine.
+create table t2 (s1 int not null references t1 (s1));
+alter table t2 add primary key (s1);
+drop tables t1, t2;
+#
+# Test for bug #46138 "Foreign keys: unjustified error when dropping
+#                      primary key".
+#
+# Dropping of UNIQUE/PRIMARY keys should be allowed even if they
+# serve as supporting key for some foreign key. As replacement we
+# should generate an equivalent non-unique key. An appropriate
+# warning should be also emitted.
+drop table if exists t1, t2;
+create table t1 (pk varchar(5) primary key);
+create table t2 (fk varchar(5) constraint c references t1 (pk),
+primary key (fk));
+show create table t2;
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `fk` varchar(5) NOT NULL DEFAULT '' CONSTRAINT `c` REFERENCES `t1` (`pk`),
+  PRIMARY KEY (`fk`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+alter table t2 drop primary key;
+Warnings:
+Warning	1818	Foreign key warning: Constraint 'c': a non-unique supporting key was created as a replacement for unique key 'PRIMARY' being dropped
+show create table t2;
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `fk` varchar(5) NOT NULL DEFAULT '' CONSTRAINT `c` REFERENCES `t1` (`pk`),
+  KEY `c` (`fk`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+drop table t2;
+create table t2 (fk varchar(5) constraint c references t1 (pk),
+unique u (fk));
+show create table t2;
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `fk` varchar(5) DEFAULT NULL CONSTRAINT `c` REFERENCES `t1` (`pk`),
+  UNIQUE KEY `u` (`fk`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+alter table t2 drop key u;
+Warnings:
+Warning	1818	Foreign key warning: Constraint 'c': a non-unique supporting key was created as a replacement for unique key 'u' being dropped
+show create table t2;
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `fk` varchar(5) DEFAULT NULL CONSTRAINT `c` REFERENCES `t1` (`pk`),
+  KEY `c` (`fk`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+drop table t2;
+# Also check that we don't create replacement key and emit warning
+# if another suitable key exists or going to be created explicitly.
+create table t2 (fk varchar(5) constraint c references t1 (pk),
+unique u (fk), key k (fk));
+show create table t2;
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `fk` varchar(5) DEFAULT NULL CONSTRAINT `c` REFERENCES `t1` (`pk`),
+  UNIQUE KEY `u` (`fk`),
+  KEY `k` (`fk`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+alter table t2 drop key u;
+show create table t2;
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `fk` varchar(5) DEFAULT NULL CONSTRAINT `c` REFERENCES `t1` (`pk`),
+  KEY `k` (`fk`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+drop table t2;
+create table t2 (fk varchar(5) constraint c references t1 (pk),
+primary key (fk), unique u (fk));
+show create table t2;
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `fk` varchar(5) NOT NULL DEFAULT '' CONSTRAINT `c` REFERENCES `t1` (`pk`),
+  PRIMARY KEY (`fk`),
+  UNIQUE KEY `u` (`fk`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+alter table t2 drop key u;
+show create table t2;
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `fk` varchar(5) NOT NULL DEFAULT '' CONSTRAINT `c` REFERENCES `t1` (`pk`),
+  PRIMARY KEY (`fk`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+drop table t2;
+create table t2 (fk varchar(5) constraint c references t1 (pk),
+unique u (fk));
+show create table t2;
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `fk` varchar(5) DEFAULT NULL CONSTRAINT `c` REFERENCES `t1` (`pk`),
+  UNIQUE KEY `u` (`fk`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+alter table t2 drop key u, add key k (fk);
+show create table t2;
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `fk` varchar(5) DEFAULT NULL CONSTRAINT `c` REFERENCES `t1` (`pk`),
+  KEY `k` (`fk`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+drop tables t1, t2;

=== modified file 'mysql-test/r/foreign_key_all_engines_2.result'
--- a/mysql-test/r/foreign_key_all_engines_2.result	2009-05-27 03:08:49 +0000
+++ b/mysql-test/r/foreign_key_all_engines_2.result	2009-07-31 15:10:25 +0000
@@ -150,6 +150,332 @@ unlock tables;
 # Switching to connection 'default'
 drop table t3, t2, t1;
 #
+# Additional coverage for the 13th milestone of WL#148 "Foreign keys"
+# ("DDL checks and changes: ALTER, CREATE INDEX, DROP INDEX").
+#
+drop tables if exists t1, t2, t3, t4;
+#
+# Until several problems with Falcon are fixed tests below
+# has to use InnoDB engine.
+#
+set storage_engine= InnoDB;
+# The below test is here only until bug #45885 "Primary key added to
+# Falcon table allows non-unique values" is fixed.
+#
+# There is one subtle moment though when we create PRIMARY KEY or
+# UNIQUE constraint on a parent table we should ensure that this
+# action won't delete any rows. Thus IGNORE clause of ALTER TABLE
+# should be ignored in this case [sic!].
+create table t1 (a int not null unique, b int, c int);
+insert into t1 values (1, 1, 1), (2, 1, 2);
+create table t2 (fk int constraint c references t1 (a));
+insert into t2 values (1), (2);
+alter table t1 add primary key (b);
+ERROR 23000: Duplicate entry '1' for key 'PRIMARY'
+alter ignore table t1 add primary key (b);
+ERROR 23000: Duplicate entry '1' for key 'PRIMARY'
+alter table t1 add unique (b);
+ERROR 23000: Duplicate entry '1' for key 'b'
+alter ignore table t1 add unique (b);
+ERROR 23000: Duplicate entry '1' for key 'b'
+# Adding PRIMARY/UNIQUE without deleting rows should be fine.
+alter table t1 add unique u (c);
+# Also ALTER TABLE using IGNORE clause should generate a warning.
+alter table t1 drop key u;
+alter ignore table t1 add unique u (c);
+Warnings:
+Warning	1819	Foreign key warning: IGNORE clause in ALTER TABLE was ignored since table being altered is parent in a foreign key relationship
+drop tables t1, t2;
+# 
+# Let us check how ALTER TABLE with partitioning clauses works on
+# tables with foreign keys.
+# The only thing which should be prohibited is DROP PARTITION on
+# parent table as all other operations do not really change
+# contents of tables.
+#
+create table t1 (pk int primary key);
+insert into t1 values (1), (2), (3), (4), (5);
+create table t2 (fk int, constraint c foreign key (fk) references t1 (pk));
+insert into t2 values (1), (2), (3), (4), (5);
+alter table t1 partition by range (pk) (partition a values less than (3),
+partition b values less than (6));
+alter table t2 partition by list (fk) (partition a values in (1, 2, 3),
+partition b values in (4, 5));
+alter table t1 add partition (partition c values less than maxvalue);
+alter table t2 add partition (partition c values in (6, 7));
+#
+# Dropping partition in parent is illegal as this might
+# remove parent values for which child values exist.
+#
+alter table t1 drop partition b;
+ERROR HY000: Foreign key error: you cannot use 'ALTER TABLE ... DROP PARTITION' statement because 't1' table is in a relationship
+#
+# OTOH dropping partition in child table should be fine.
+#
+alter table t2 drop partition b;
+select * from t2;
+fk
+1
+2
+3
+#
+# Changing the "driver" engine of the partitioned
+# engine from transactional to non-transactional
+# or vice versa is not allowed.
+#
+alter table t1 engine=myisam;
+ERROR 42000: Foreign key error: Constraint 'c': Mixing transactional and non-transactional engines in one foreign key is not allowed
+#
+# REBUILD/REORGANIZE PARTITION work OK.
+#
+alter table t1 rebuild partition a;
+alter table t2 rebuild partition a;
+alter table t1 reorganize partition a into (partition aa values less than (2),
+partition ab values less than (3));
+alter table t2 reorganize partition a into (partition aa values in (1, 2),
+partition ab values in (3));
+alter table t1 remove partitioning;
+alter table t2 remove partitioning;
+select * from t1;
+pk
+1
+2
+3
+4
+5
+select * from t2;
+fk
+1
+2
+3
+drop tables t1, t2;
+create table t1 (pk int primary key) partition by hash (pk) partitions 4;
+insert into t1 values (1), (2), (3), (4), (5);
+create table t2 (fk int references t1 (pk)) partition by hash (fk) partitions 5;
+insert into t2 values (1), (2), (3), (4), (5);
+alter table t1 coalesce partition 2;
+alter table t2 coalesce partition 3;
+select * from t1;
+pk
+1
+2
+3
+4
+5
+select * from t2;
+fk
+1
+2
+3
+4
+5
+drop tables t1, t2;
+# 
+# Finally, let us test how ALTER TABLE ... ADD/DROP CONSTRAINT
+# works under LOCK TABLES mode.
+#
+# Let us begin with ALTER TABLE ... ADD CONSTRAINT.
+create table t1 (a int primary key);
+insert into t1 values (1);
+create table t2 (a int);
+insert into t2 values (1);
+create table t3 (a int);
+#
+# To add constraint you need to have write-lock on both child
+# and parent tables.
+#
+lock tables t3 read;
+alter table t2 add constraint c foreign key (a) references t1 (a);
+ERROR HY000: Table 't2' was not locked with LOCK TABLES
+unlock tables;
+lock tables t2 read;
+alter table t2 add constraint c foreign key (a) references t1 (a);
+ERROR HY000: Table 't1' was not locked with LOCK TABLES
+unlock tables;
+lock tables t2 read, t1 read;
+alter table t2 add constraint c foreign key (a) references t1 (a);
+ERROR HY000: Table 't2' was locked with a READ lock and can't be updated
+unlock tables;
+lock tables t2 write, t1 read;
+alter table t2 add constraint c foreign key (a) references t1 (a);
+ERROR HY000: Table 't1' was locked with a READ lock and can't be updated
+unlock tables;
+lock tables t2 write, t1 write;
+#
+# Add constraint and check that metadata for both tables
+# were properly updated.
+#
+alter table t2 add constraint c foreign key (a) references t1 (a);
+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'
+unlock tables;
+create table t4 (pk int primary key, fk int constraint c references t4 (pk));
+ERROR 42000: Foreign key error: Constraint 'c': Duplicate constraint name
+#
+# Now let's try ALTER TABLE ... DROP CONSTRAINT.
+# 
+# Again we need write-locks on both child and parent tables.
+#
+lock tables t3 read;
+alter table t2 drop constraint c;
+ERROR HY000: Table 't2' was not locked with LOCK TABLES
+unlock tables;
+lock tables t2 read;
+alter table t2 drop constraint c;
+ERROR HY000: Table 't1' was not locked with LOCK TABLES
+unlock tables;
+lock tables t2 read, t1 read;
+alter table t2 drop constraint c;
+ERROR HY000: Table 't2' was locked with a READ lock and can't be updated
+unlock tables;
+lock tables t2 write, t1 read;
+alter table t2 drop constraint c;
+ERROR HY000: Table 't1' was locked with a READ lock and can't be updated
+unlock tables;
+lock tables t2 write, t1 write;
+#
+# Now let's drop constraint and check that metadata for both
+# tables were properly updated.
+#
+alter table t2 drop constraint c;
+show create table t2;
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `a` int(11) DEFAULT NULL,
+  KEY `c` (`a`)
+) ENGINE=InnoDB DEFAULT CHARSET=latin1
+delete from t1 where a = 1;
+unlock tables;
+create table t4 (pk int primary key, fk int constraint c references t4 (pk));
+drop tables t4;
+#
+# Try to drop and add constraint within one statement.
+#
+insert into t1 values (1);
+alter table t2 add constraint c foreign key (a) references t1 (a);
+lock tables t1 write, t2 write;
+alter table t2 drop constraint c,
+add constraint c foreign key (a) references t1 (a);
+#
+# Check that metadata was properly updated for both child and parent.
+#
+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'
+# And now using different statements.
+alter table t2 drop constraint c;
+alter table t2 add constraint c foreign key (a) references t1 (a);
+#
+# Check that metadata was properly updated for both child and parent.
+#
+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'
+unlock tables;
+create table t4 (pk int primary key, fk int constraint c references t4 (pk));
+ERROR 42000: Foreign key error: Constraint 'c': Duplicate constraint name
+#
+# Let us also check what happens if ALTER fails for some reason.
+#
+lock tables t1 write, t2 write;
+alter table t2 drop constraint c, drop constraint d;
+ERROR 42000: Foreign key error: Constraint 'd': there is no such constraint on this 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'
+alter table t2 drop constraint c,
+add constraint d foreign key (a) references t1 (no_such_column);
+ERROR 42000: Foreign key error: Constraint 'd': 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'
+# 
+# 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;
+ERROR 42000: Foreign key error: Constraint 'c': there is no such constraint on this table
+alter table t2 add constraint d foreign key (a) references t1 (a),
+add constraint d foreign key (a) references t1 (a);
+ERROR 42000: Foreign key error: Constraint 'd': Duplicate constraint name
+unlock tables;
+drop tables t1, t2, t3;
+#
+# The below test is here only until bug #45885 "Primary key added to
+# Falcon table allows non-unique values" is fixed.
+#
+# CREATE INDEX does not support IGNORE clause so removal of rows from
+# a parent table which can happen when one adds UNIQUE index on it
+# will always result in an error and won't create orphans.
+#
+create table t1 (a int not null unique, b int, c int);
+insert into t1 values (1, 1, 1), (2, 1, 2);
+create table t2 (fk int constraint c references t1 (a));
+insert into t2 values (1), (2);
+create unique index b on t1 (b);
+ERROR 23000: Duplicate entry '1' for key 'b'
+#
+# Adding UNIQUE index without deleting rows should be fine.
+#
+create unique index c on t1 (c);
+drop tables t1, t2;
+set storage_engine= Falcon;
+#
 # Additional coverage for the 14th milestone of WL#148 "Foreign keys"
 # ("DDL checks and changes: DROP, TRUNCATE, RENAME").
 #
@@ -426,3 +752,41 @@ Qcache_hits	4
 # Clean-up
 drop tables t1, t2, t3;
 set global query_cache_size= default;
+#
+# Test for bug #46125 "Foreign keys: reorganizing partitions causes
+#                      dangling reference".
+#
+drop tables if exists t1, t2;
+create table t1 (pk int primary key)
+partition by list (pk) (partition p1 values in (1, 2));
+insert into t1 values (1), (2);
+create table t2 (fk int constraint c references t1 (pk));
+insert into t2 values (1), (2);
+#
+# Reorganizations of partitions which lead to dangling references should
+# be prohibited.
+#
+alter table t1 reorganize partition p1 into (partition p1 values in (0, 1));
+ERROR HY000: Foreign key error: you cannot use 'ALTER TABLE ... REORGANIZE PARTITION' statement because 't1' table is in a relationship
+select * from t1;
+pk
+1
+2
+select * from t2;
+fk
+1
+2
+#
+# OTOH reorganizations which preserve data should be fine.
+#
+alter table t1 reorganize partition p1 into
+(partition p2 values in (0, 1), partition p3 values in (2, 3));
+select * from t1;
+pk
+1
+2
+select * from t2;
+fk
+1
+2
+drop tables t1, t2;

=== modified file 'mysql-test/r/foreign_key_all_engines_3.result'
--- a/mysql-test/r/foreign_key_all_engines_3.result	2009-05-27 03:08:49 +0000
+++ b/mysql-test/r/foreign_key_all_engines_3.result	2009-07-31 15:10:25 +0000
@@ -1,5 +1,399 @@
 set storage_engine= Falcon;
 #
+# Additional coverage for the 13th milestone of WL#148 "Foreign keys"
+# ("DDL checks and changes: ALTER, CREATE INDEX, DROP INDEX words").
+# 
+# Check how ALTER TABLE handles errors which occur on various stages
+# of its execution. Note that her we focus on hard to trigger errors
+# as other errors should be tested elsewhere.
+drop tables if exists t1, t2, t3, t4, t5;
+set @old_debug= @@debug;
+#
+# First, let us check what happens when we fail to upgrade metadata
+# lock on table being altered and parents tables which .FRMs should
+# modified.
+#
+create table t1 (pk int primary key);
+create table t2 (pk int primary key);
+create table t3 (fk1 int constraint c1 references t1 (pk), fk2 int);
+insert into t1 values (1);
+insert into t2 values (1);
+insert into t3 values (1, 1);
+#
+# Simulate killing of DROP TABLES while performing metadata lock upgrade.
+#
+set debug= "d,upgrade_mdl_for_altered_and_parent_tables_simulate_kill";
+alter table t3 drop constraint c1,
+add constraint c2 foreign key (fk2) references t2 (pk);
+ERROR 70100: Query execution was interrupted
+set debug= @old_debug;
+#
+# Let us check that constraint c1 was not dropped,
+# c2 was not created and FRMs for all tables involved are intact.
+#
+show create table t3;
+Table	Create Table
+t3	CREATE TABLE `t3` (
+  `fk1` int(11) DEFAULT NULL CONSTRAINT `c1` REFERENCES `t1` (`pk`),
+  `fk2` int(11) DEFAULT NULL,
+  KEY `c1` (`fk1`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+delete from t1 where pk = 1;
+ERROR 23000: Foreign key error: constraint 'c1': cannot change because foreign key refers to value '1'
+delete from t2 where pk = 1;
+create table t4 (pk int primary key, fk int constraint c1 references t4 (pk));
+ERROR 42000: Foreign key error: Constraint 'c1': Duplicate constraint name
+create table t4 (pk int primary key, fk int constraint c2 references t4 (pk));
+drop table t1, t2, t3, t4;
+#
+# Let us check what happens when we can't install new .FRM for
+# the parent table.
+#
+create table t1 (pk int primary key);
+create table t2 (pk int primary key);
+create table t3 (pk int primary key);
+create table t4 (fk1 int constraint c1 references t2 (pk), fk2 int, fk3 int);
+insert into t1 values (1);
+insert into t2 values (1);
+insert into t3 values (1);
+insert into t4 values (1, 1, 1);
+set debug= "d,fk_install_new_frms_fail_for_t1";
+alter table t4 drop constraint c1,
+add constraint c2 foreign key (fk2) references t3 (pk),
+add constraint c3 foreign key (fk3) references t1 (pk);
+ERROR HY000: Unknown error
+set debug= @old_debug;
+#
+# Again let us check that .FRMs are not changed, and constraint
+# names were not touched.
+#
+show create table t4;
+Table	Create Table
+t4	CREATE TABLE `t4` (
+  `fk1` int(11) DEFAULT NULL CONSTRAINT `c1` REFERENCES `t2` (`pk`),
+  `fk2` int(11) DEFAULT NULL,
+  `fk3` int(11) DEFAULT NULL,
+  KEY `c1` (`fk1`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+delete from t1 where pk = 1;
+delete from t2 where pk = 1;
+ERROR 23000: Foreign key error: constraint 'c1': cannot change because foreign key refers to value '1'
+delete from t3 where pk = 1;
+create table t5 (pk int primary key, fk int constraint c1 references t5 (pk));
+ERROR 42000: Foreign key error: Constraint 'c1': Duplicate constraint name
+create table t5 (pk int primary key, fk int constraint c2 references t5 (pk));
+drop table t5;
+create table t5 (pk int primary key, fk int constraint c3 references t5 (pk));
+drop table t5;
+#
+# Now let us check what happens when we can't create/drop .CNS files
+# for constraints being added/dropped.
+#
+insert into t1 values (1);
+insert into t3 values (1);
+set debug= "d,fk_install_new_frms_fail_drop_cns";
+alter table t4 drop constraint c1,
+add constraint c2 foreign key (fk2) references t3 (pk),
+add constraint c3 foreign key (fk3) references t1 (pk);
+ERROR HY000: Unknown error
+set debug= @old_debug;
+#
+# Again let us check that .FRMs are not changed, and constraint
+# names were not touched.
+#
+show create table t4;
+Table	Create Table
+t4	CREATE TABLE `t4` (
+  `fk1` int(11) DEFAULT NULL CONSTRAINT `c1` REFERENCES `t2` (`pk`),
+  `fk2` int(11) DEFAULT NULL,
+  `fk3` int(11) DEFAULT NULL,
+  KEY `c1` (`fk1`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+delete from t1 where pk = 1;
+delete from t2 where pk = 1;
+ERROR 23000: Foreign key error: constraint 'c1': cannot change because foreign key refers to value '1'
+delete from t3 where pk = 1;
+create table t5 (pk int primary key, fk int constraint c1 references t5 (pk));
+ERROR 42000: Foreign key error: Constraint 'c1': Duplicate constraint name
+create table t5 (pk int primary key, fk int constraint c2 references t5 (pk));
+drop table t5;
+create table t5 (pk int primary key, fk int constraint c3 references t5 (pk));
+drop table t5;
+#
+# Now test for scenario when attempt to replace old version of table
+# with its new version fails.
+#
+insert into t1 values (1);
+insert into t3 values (1);
+set debug= "d,alter_table_rename_table_fail";
+alter table t4 drop constraint c1,
+add constraint c2 foreign key (fk2) references t3 (pk),
+add constraint c3 foreign key (fk3) references t1 (pk);
+ERROR HY000: Unknown error
+set debug= @old_debug;
+#
+# Again let us check that .FRMs are not changed, and constraint
+# names were not touched.
+#
+show create table t4;
+Table	Create Table
+t4	CREATE TABLE `t4` (
+  `fk1` int(11) DEFAULT NULL CONSTRAINT `c1` REFERENCES `t2` (`pk`),
+  `fk2` int(11) DEFAULT NULL,
+  `fk3` int(11) DEFAULT NULL,
+  KEY `c1` (`fk1`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+delete from t1 where pk = 1;
+delete from t2 where pk = 1;
+ERROR 23000: Foreign key error: constraint 'c1': cannot change because foreign key refers to value '1'
+delete from t3 where pk = 1;
+create table t5 (pk int primary key, fk int constraint c1 references t5 (pk));
+ERROR 42000: Foreign key error: Constraint 'c1': Duplicate constraint name
+create table t5 (pk int primary key, fk int constraint c2 references t5 (pk));
+drop table t5;
+create table t5 (pk int primary key, fk int constraint c3 references t5 (pk));
+drop tables t1, t2, t3, t4, t5;
+#
+# Now let us perform similar checks for "fast/online" version of ALTER.
+#
+# We can't check that operations are performed as "fast/online" ALTER
+# TABLE by using --enable_info and verifying that number of affected
+# rows is 0 as failed statements do not return this information.
+# Instead we rely on that foreign_key_all_engines.test checks this.
+# 
+# Again let us begin with scenarion in which we fail to upgrade metadata
+# lock on table being altered and parents tables which .FRMs should
+# modified.
+#
+create table t1 (pk int primary key);
+create table t2 (pk int primary key);
+create table t3 (fk1 int constraint c1 references t1 (pk), fk2 int, key (fk2));
+insert into t1 values (1);
+insert into t2 values (1);
+insert into t3 values (1, 1);
+#
+# Turn-off FOREIGN_KEY_CHECKS to enable "fast" ALTER TABLE for
+# addition of foreign key.
+#
+set foreign_key_checks= 0;
+set debug= "d,upgrade_mdl_for_altered_and_parent_tables_simulate_kill";
+alter table t3 drop constraint c1,
+add constraint c2 foreign key (fk2) references t2 (pk);
+ERROR 70100: Query execution was interrupted
+set debug= @old_debug;
+set foreign_key_checks= 1;
+#
+# Let us check that constraint c1 was not drop, c2 was not created and
+# FRMs for all tables involved are intact.
+#
+show create table t3;
+Table	Create Table
+t3	CREATE TABLE `t3` (
+  `fk1` int(11) DEFAULT NULL CONSTRAINT `c1` REFERENCES `t1` (`pk`),
+  `fk2` int(11) DEFAULT NULL,
+  KEY `fk2` (`fk2`),
+  KEY `c1` (`fk1`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+delete from t1 where pk = 1;
+ERROR 23000: Foreign key error: constraint 'c1': cannot change because foreign key refers to value '1'
+delete from t2 where pk = 1;
+create table t4 (pk int primary key, fk int constraint c1 references t4 (pk));
+ERROR 42000: Foreign key error: Constraint 'c1': Duplicate constraint name
+create table t4 (pk int primary key, fk int constraint c2 references t4 (pk));
+drop table t1, t2, t3, t4;
+#
+# Test for for failure to install new .FRM for parent table during
+# "fast" ALTER TABLE;
+#
+create table t1 (pk int primary key);
+create table t2 (pk int primary key);
+create table t3 (pk int primary key);
+create table t4 (fk1 int constraint c1 references t2 (pk), fk2 int, fk3 int,
+key (fk2), key (fk3));
+insert into t1 values (1);
+insert into t2 values (1);
+insert into t3 values (1);
+insert into t4 values (1, 1, 1);
+set foreign_key_checks= 0;
+set debug= "d,fk_install_new_frms_fail_for_t1";
+alter table t4 drop constraint c1,
+add constraint c2 foreign key (fk2) references t3 (pk),
+add constraint c3 foreign key (fk3) references t1 (pk);
+ERROR HY000: Unknown error
+set debug= @old_debug;
+set foreign_key_checks= 1;
+#
+# Again let us check that .FRMs are not changed, and constraint
+# names were not touched.
+#
+show create table t4;
+Table	Create Table
+t4	CREATE TABLE `t4` (
+  `fk1` int(11) DEFAULT NULL CONSTRAINT `c1` REFERENCES `t2` (`pk`),
+  `fk2` int(11) DEFAULT NULL,
+  `fk3` int(11) DEFAULT NULL,
+  KEY `fk2` (`fk2`),
+  KEY `fk3` (`fk3`),
+  KEY `c1` (`fk1`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+delete from t1 where pk = 1;
+delete from t2 where pk = 1;
+ERROR 23000: Foreign key error: constraint 'c1': cannot change because foreign key refers to value '1'
+delete from t3 where pk = 1;
+create table t5 (pk int primary key, fk int constraint c1 references t5 (pk));
+ERROR 42000: Foreign key error: Constraint 'c1': Duplicate constraint name
+create table t5 (pk int primary key, fk int constraint c2 references t5 (pk));
+drop table t5;
+create table t5 (pk int primary key, fk int constraint c3 references t5 (pk));
+drop table t5;
+#
+# Check what happens when we can't create/drop .CNS files
+# for constraints being added/dropped.
+#
+insert into t1 values (1);
+insert into t3 values (1);
+set foreign_key_checks= 0;
+set debug= "d,fk_install_new_frms_fail_drop_cns";
+alter table t4 drop constraint c1,
+add constraint c2 foreign key (fk2) references t3 (pk),
+add constraint c3 foreign key (fk3) references t1 (pk);
+ERROR HY000: Unknown error
+set debug= @old_debug;
+set foreign_key_checks= 1;
+#
+# Again let us check that .FRMs are not changed, and constraint
+# names were not touched.
+#
+show create table t4;
+Table	Create Table
+t4	CREATE TABLE `t4` (
+  `fk1` int(11) DEFAULT NULL CONSTRAINT `c1` REFERENCES `t2` (`pk`),
+  `fk2` int(11) DEFAULT NULL,
+  `fk3` int(11) DEFAULT NULL,
+  KEY `fk2` (`fk2`),
+  KEY `fk3` (`fk3`),
+  KEY `c1` (`fk1`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+delete from t1 where pk = 1;
+delete from t2 where pk = 1;
+ERROR 23000: Foreign key error: constraint 'c1': cannot change because foreign key refers to value '1'
+delete from t3 where pk = 1;
+create table t5 (pk int primary key, fk int constraint c1 references t5 (pk));
+ERROR 42000: Foreign key error: Constraint 'c1': Duplicate constraint name
+create table t5 (pk int primary key, fk int constraint c2 references t5 (pk));
+drop table t5;
+create table t5 (pk int primary key, fk int constraint c3 references t5 (pk));
+drop table t5;
+#
+# Now let us check what happens when attempt to replace old version of
+# table being altered with its new version fails (for "fast" ALTER).
+#
+insert into t1 values (1);
+insert into t3 values (1);
+set foreign_key_checks= 0;
+set debug= "d,fast_or_online_alter_table_rename_table_fail";
+alter table t4 drop constraint c1,
+add constraint c2 foreign key (fk2) references t3 (pk),
+add constraint c3 foreign key (fk3) references t1 (pk);
+ERROR HY000: Unknown error
+set debug= @old_debug;
+set foreign_key_checks= 1;
+#
+# Again let us check that .FRMs are not changed, and constraint
+# names were not touched.
+#
+show create table t4;
+Table	Create Table
+t4	CREATE TABLE `t4` (
+  `fk1` int(11) DEFAULT NULL CONSTRAINT `c1` REFERENCES `t2` (`pk`),
+  `fk2` int(11) DEFAULT NULL,
+  `fk3` int(11) DEFAULT NULL,
+  KEY `fk2` (`fk2`),
+  KEY `fk3` (`fk3`),
+  KEY `c1` (`fk1`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+delete from t1 where pk = 1;
+delete from t2 where pk = 1;
+ERROR 23000: Foreign key error: constraint 'c1': cannot change because foreign key refers to value '1'
+delete from t3 where pk = 1;
+create table t5 (pk int primary key, fk int constraint c1 references t5 (pk));
+ERROR 42000: Foreign key error: Constraint 'c1': Duplicate constraint name
+create table t5 (pk int primary key, fk int constraint c2 references t5 (pk));
+drop table t5;
+create table t5 (pk int primary key, fk int constraint c3 references t5 (pk));
+drop tables t1, t2, t3, t4, t5;
+#
+# Finally let us tests how ALTER TABLE failures are handled when it
+# is executed under LOCK TABLES.
+# 
+# Until bugs #44230 'Altering Falcon table under LOCK TABLES fails
+# with "Can't lock file" error' and #45035 'Altering table under LOCK
+# TABLES results in "Error 1213 Deadlock found..."' are fixed we have
+# to use InnoDB for this test.
+#
+set storage_engine= InnoDB;
+create table t1 (pk int primary key);
+create table t2 (pk int primary key);
+create table t3 (fk1 int constraint c1 references t1 (pk), fk2 int);
+insert into t1 values (1);
+insert into t2 values (1);
+insert into t3 values (1, 1);
+lock tables t1 write, t2 write, t3 write;
+#
+# Check what happens when we fail to upgrade metadata locks.
+#
+set debug= "d,upgrade_mdl_for_altered_and_parent_tables_simulate_kill";
+alter table t3 drop constraint c1,
+add constraint c2 foreign key (fk1) references t1 (pk);
+ERROR 70100: Query execution was interrupted
+set debug= @old_debug;
+#
+# Let us test that tables are still locked and were left intact.
+#
+show create table t3;
+Table	Create Table
+t3	CREATE TABLE `t3` (
+  `fk1` int(11) DEFAULT NULL CONSTRAINT `c1` REFERENCES `t1` (`pk`),
+  `fk2` int(11) DEFAULT NULL,
+  KEY `c1` (`fk1`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+delete from t1 where pk = 1;
+ERROR 23000: Foreign key error: constraint 'c1': cannot change because foreign key refers to value '1'
+delete from t2 where pk = 1;
+insert into t2 values (1);
+#
+# Now let us see how server handles failure when locks are already
+# updated.
+#
+set debug= "d,alter_table_rename_table_fail";
+alter table t3 drop constraint c1,
+add constraint c2 foreign key (fk2) references t2 (pk);
+ERROR HY000: Unknown error
+set debug= @old_debug;
+#
+# Table which we tried to alter and all parent tables involved
+# should be unlocked but left intact.
+#
+select * from t1;
+ERROR HY000: Table 't1' was not locked with LOCK TABLES
+select * from t2;
+ERROR HY000: Table 't2' was not locked with LOCK TABLES
+select * from t3;
+ERROR HY000: Table 't3' was not locked with LOCK TABLES
+unlock tables;
+show create table t3;
+Table	Create Table
+t3	CREATE TABLE `t3` (
+  `fk1` int(11) DEFAULT NULL CONSTRAINT `c1` REFERENCES `t1` (`pk`),
+  `fk2` int(11) DEFAULT NULL,
+  KEY `c1` (`fk1`)
+) ENGINE=<engine_type> DEFAULT CHARSET=latin1
+delete from t1 where pk = 1;
+ERROR 23000: Foreign key error: constraint 'c1': cannot change because foreign key refers to value '1'
+delete from t2 where pk = 1;
+drop tables t1, t2, t3;
+set storage_engine= Falcon;
+#
 # Additional coverage for the 14th milestone of WL#148 "Foreign keys"
 # ("DDL checks and changes: DROP, TRUNCATE, RENAME").
 #
@@ -7,7 +401,7 @@ set storage_engine= Falcon;
 # tables.
 drop tables if exists t1, t2, t3, t4;
 set @old_debug= @@debug;
-# First of all let us check that happens when we can't properly
+# First of all let us check what happens when we can't properly
 # update information in .FRM of parent table.
 # To begin with let us try to drop only child table.
 create table t1 (pk int primary key);

=== modified file 'mysql-test/r/innodb_mysql.result'
--- a/mysql-test/r/innodb_mysql.result	2009-05-18 04:18:37 +0000
+++ b/mysql-test/r/innodb_mysql.result	2009-07-31 15:10:25 +0000
@@ -2027,6 +2027,26 @@ DROP TABLE t2;
 DROP TABLE t3;
 End of 5.1 tests
 #
+# Test for bug #39932 "create table fails if column for FK is in different
+#                      case than in corr index".
+#
+drop tables if exists t1, t2;
+create table t1 (pk int primary key) engine=InnoDB;
+# Even although the below statement uses uppercased field names in
+# foreign key definition it still should be able to find explicitly
+# created supporting index. So it should succeed and should not
+# create any additional supporting indexes.
+create table t2 (fk int, key x (fk),
+constraint x foreign key (FK) references t1 (PK)) engine=InnoDB;
+show create table t2;
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `fk` int(11) DEFAULT NULL,
+  KEY `x` (`fk`),
+  CONSTRAINT `x` FOREIGN KEY (`fk`) REFERENCES `t1` (`pk`)
+) ENGINE=InnoDB DEFAULT CHARSET=latin1
+drop table t2, t1;
+#
 # BUG#42744: Crash when using a join buffer to join a table with a blob
 # column and an additional column used for duplicate elimination.
 #

=== modified file 'mysql-test/t/foreign_key.test'
--- a/mysql-test/t/foreign_key.test	2009-05-27 09:00:35 +0000
+++ b/mysql-test/t/foreign_key.test	2009-07-31 15:10:25 +0000
@@ -209,3 +209,21 @@ let $MYSQLD_DATADIR= `select @@datadir`;
 --echo # DROP DATABASE should succeed.
 drop database mysqltest;
 use test;
+
+--echo #
+--echo # Additional test for bug #46124  "Foreign keys: crash if alter table
+--echo # drop foreign key".
+--echo # 
+--echo # Check that even in --foreign-key-all-engines=0 mode we reject DROP
+--echo # FOREIGN KEY clause without foreign key name which crashed server in
+--echo # --foreign-key-all-engines=1 mode.
+--disable_warnings
+drop table if exists t1, t2;
+--enable_warnings
+--error ER_PARSE_ERROR
+alter table t1 drop foreign key;
+create table t1 (pk int primary key);
+create table t2 (fk int references t1 (pk));
+--error ER_PARSE_ERROR
+alter table t2 drop foreign key;
+drop tables t1, t2;

=== modified file 'mysql-test/t/foreign_key_all_engines.test'
--- a/mysql-test/t/foreign_key_all_engines.test	2009-06-01 11:37:20 +0000
+++ b/mysql-test/t/foreign_key_all_engines.test	2009-07-31 15:10:25 +0000
@@ -221,9 +221,9 @@ drop tables if exists t1, t2, t3, v1;
 drop view if exists v1;
 --enable_warnings
 --echo #
---echo # Tests for various checks that happen during foreign key creation
+--echo # Tests for various checks that happen during foreign key creation.
 --echo #
---echo # Checks for child and parent's tables types
+--echo # Checks for child and parent table types.
 create table t1 (pk int primary key);
 --error ER_FK_CHILD_TEMPORARY
 create temporary table t2 (fk int references t1 (pk));
@@ -236,11 +236,33 @@ create table t1 (pk int primary key);
 create view v1 as select * from t1;
 --error ER_FK_PARENT_VIEW
 create table t2 (fk int references v1 (pk));
+drop view v1;
 drop table t1;
+--echo #
+--echo # A slightly more complicated example when the view uses
+--echo # stored functions.
+--echo #
+create table t1 (pk int primary key);
+create table t2 (a int);
+create table t3 (a int);
+create trigger t2_ai after insert on t2 for each row insert into t3 (a)
+values (new.a);
+create procedure p1(p_a int) insert into t2 (a) values (p_a);
+delimiter |;
+create function f1(p_a int) returns int begin call p1(p_a); return p_a; end|
+delimiter ;|
+create view v1 as select f1(pk) as pk from t1;
+--error ER_FK_PARENT_VIEW
+create table t4 (fk int, constraint c foreign key (fk) references v1 (pk));
+drop table t1, t2, t3;
 drop view v1;
+drop procedure p1;
+drop function f1;
+--echo #
 --echo # It should be impossible to create a foreign key referencing
 --echo # I_S tables. So statement below should emit reasonable error
 --echo # like ER_FK_PARENT_VIEW or ER_DBACCESS_DENIED_ERROR.
+--echo #
 --error ER_DBACCESS_DENIED_ERROR
 create table t2 (fk varchar(64)
                  references information_schema.tables (table_name));
@@ -386,7 +408,29 @@ create table t3 like t2;
 --replace_result $engine_type <engine_type>
 show create table t3;
 drop table t3, t2, t1;
-
+--echo # 
+--echo # Coverage for use of prepared statemetns and foreign keys.
+--echo # We need to check that foreign keys with auto-generated names
+--echo # work okay during re-execution.
+--echo #
+create table t1 (pk int primary key);
+prepare stmt from "create table  t2 (fk int, fk1 int references t1 (pk),
+constraint c foreign key (fk) references t1 (pk))";
+execute stmt;
+show create table t2;
+--error ER_FK_CONSTRAINT_NAME_DUPLICATE
+execute stmt;
+drop table t2;
+execute stmt;
+show create table t2;
+flush table t1, t2;
+--error ER_FK_CONSTRAINT_NAME_DUPLICATE
+execute stmt;
+flush table t1, t2;
+drop table t2;
+execute stmt;
+drop table t1, t2;
+deallocate prepare stmt;
 
 --echo #
 --echo # Basic coverage for the 8th milestone of WL#148 "Foreign keys"
@@ -1092,10 +1136,21 @@ select * from t1;
 --error ER_FK_CHILD_NO_MATCH
 update t1 set pk= if(pk = 2, 2, 3), fk= if(fk = 3, 4, 2);
 drop table t1;
---echo # TODO: Implement coverage for UPDATE where matching PK value is
---echo # added by cascading action once milestone 13 "DDL checks and changes:
---echo # ALTER, CREATE INDEX, DROP INDEX words" is complete.
---echo # 
+
+--echo # TODO: Enable and extend coverage for UPDATE where matching PK value
+--echo # is added by cascading action once Bug #46474 "Foreign keys: some
+--echo # forms of cascading changes are disallowed" is fixed.
+--disable_parsing
+create table t1 (pkfk int primary key);
+insert into t1 values (1);
+create table t2 (pk int primary key, fk int references t1 (pkfk));
+insert into t2 values (1, 1);
+alter table t1 add constraint c foreign key (pkfk) references t2 (pk)
+                                on update cascade;
+update t2 set pk = 2, fk = 2;
+drop tables t1, t2;
+--enable_parsing
+
 --echo # TODO: Implement coverage for multi-UPDATE adding dangling FK value
 --echo # to the child and then adding matching PK value to the parent once
 --echo # milestone 12 "MULTI-DELETE, MULTI-UPDATE" is complete.
@@ -1371,191 +1426,1597 @@ drop table t1;
 
 
 --echo #
---echo # Coverage for the 14th milestone of WL#148 "Foreign keys"
---echo # ("DDL checks and changes: DROP, TRUNCATE, RENAME").
+--echo # Coverage for the 13th milestone of WL#148 "Foreign keys"
+--echo # ("DDL checks and changes: ALTER, CREATE INDEX, DROP INDEX").
 --echo #
 --disable_warnings
-drop tables if exists t1, t2, t3, t4, t1_2, t2_2, t3_2, t1_3, t2_3, t3_3;
+drop tables if exists t1, t2, t3, t4, t5, v1;
 drop database if exists mysqltest;
 --enable_warnings
 
+--echo #
+--echo # Let us test how ALTER TABLE ... ADD CONSTRAINT works.
 --echo # 
---echo # DROP TABLES.
---echo # 
---echo # Let us check that we disallow dropping of parent tables
---echo # without dropping child tables.
 create table t1 (pk int primary key);
-create table t2 (fk int constraint c references t1 (pk));
---error ER_FK_CHILD_TABLE_EXISTS
-drop table t1;
---echo # But dropping a child without the parent should be allowed.
-drop table t2;
---echo # Also dropping the child and the parent simultaneously should be fine.
-create table t2 (fk int references t1 (pk));
+create table t2 (fk int);
+insert into t1 values (1);
+insert into t2 values (1);
+--echo #
+--echo # Add a constraint and check that metadata was properly updated.
+--echo #
+alter table t2 add constraint c foreign key (fk) references t1 (pk);
+--replace_result $engine_type <engine_type>
+show create table t2;
+--error ER_FK_CHILD_VALUE_EXISTS
+delete from t1 where pk = 1;
+--error ER_FK_CONSTRAINT_NAME_DUPLICATE
+create table t3 (pk int primary key, fk int constraint c references t3 (pk));
+--echo #
+--echo # Now try to add constraint with the same name.
+--echo #
+--error ER_FK_CONSTRAINT_NAME_DUPLICATE
+alter table t2 add constraint c foreign key (fk) references t1 (pk);
 drop tables t1, t2;
---echo # Dropping a table with a self-reference also should be OK.
-create table t1 (pk int primary key, fk int references t1 (pk));
-drop table t1;
---echo # Now let us check that dropping a child without without a parent
---echo # leaves no traces in the parent description (this is a test for
---echo # Bug #42063 "Foreign keys: constraint survives after table 
---echo # is dropped").
+--echo #
+--echo # Now similar test for constraint with auto-generated name.
+--echo #
+--echo # Let us make the name repeatable first.
+--echo #
+set @@rand_seed1=10000000,@@rand_seed2=1000000;
 create table t1 (pk int primary key);
-create table t2 (fk int references t1 (pk));
+create table t2 (fk int);
 insert into t1 values (1);
-drop table t2;
---echo # This update should succeed and should not emit any errors
-update t1 set pk = 0;
+insert into t2 values (1);
+--echo #
+--echo # Add constraint and check that metadata was properly updated.
+--echo #
+alter table t2 add constraint foreign key (fk) references t1 (pk);
+--replace_result $engine_type <engine_type>
+show create table t2;
+--error ER_FK_CHILD_VALUE_EXISTS
+delete from t1 where pk = 1;
+--error ER_FK_CONSTRAINT_NAME_DUPLICATE
+create table t3 (pk int primary key, fk int constraint fk_t2_11f06 references t3 (pk));
+drop tables t1, t2;
+--echo #
+--echo # Let us try to add a self-referencing foreign key.
+--echo # 
+create table t1 (pk int primary key, fk int);
+insert into t1 values (1, 1), (2, 1);
+alter table t1 add constraint c foreign key (fk) references t1 (pk);
+--replace_result $engine_type <engine_type>
+show create table t1;
+--error ER_FK_CHILD_VALUE_EXISTS
+delete from t1 where pk = 1;
+--error ER_FK_CONSTRAINT_NAME_DUPLICATE
+create table t2 (pk int primary key, fk int constraint c references t2 (pk));
 drop table t1;
---echo # A bit more complex scenario in which we drop two tables
---echo # with foreign keys.
+--echo #
+--echo # Now let us try to add several constraints at the same time.
+--echo #
 create table t1 (pk int primary key);
 create table t2 (pk int primary key);
-create table t3 (fk int references t1 (pk));
-create table t4 (fk int references t2 (pk));
-drop tables t3, t4;
---echo # Now a scenario with more complex dependencies
-drop table t2;
-create table t2 (pk int primary key, fk int references t1 (pk));
-create table t4 (pk int primary key, fk int references t1 (pk));
-create table t3 (fk1 int references t1 (pk), fk2 int references t2 (pk),
-                 fk3 int references t4 (pk));
-drop tables t4, t3, t2;
---echo # An even more complex scenario involving 3 clusters of
---echo # differently connected tables.
-create table t2 (fk int references t1 (pk));
-create table t3 (fk int references t1 (pk));
-create table t1_2 (pk int primary key);
-create table t2_2 (pk int primary key);
-create table t3_2 (fk1 int references t1_2 (pk), fk2 int references t2_2 (pk));
-create table t1_3 (pk int primary key);
-create table t2_3 (pk int primary key, fk int references t1_3 (pk));
-create table t3_3 (fk int references t2_3 (pk));
---echo # Let us try to drop all child tables in some arbitrary order.
-drop tables t2, t2_3, t3, t3_2, t3_3;
---echo # Re-create children and try to drop all tables at once.
-create table t2 (fk int references t1 (pk));
-create table t3 (fk int references t1 (pk));
-create table t3_2 (fk1 int references t1_2 (pk), fk2 int references t2_2 (pk));
-create table t2_3 (pk int primary key, fk int references t1_3 (pk));
-create table t3_3 (fk int references t2_3 (pk));
-drop tables t1, t1_2, t1_3, t2, t2_2, t2_3, t3, t3_2, t3_3;
---echo # TODO: Add coverage for dropping tables with circular references once
---echo # milestone 13 "DDL checks and changes: ALTER, CREATE INDEX, DROP INDEX
---echo # words" is complete.
-
---echo # Let us also check scenarios in which there is a temporary
---echo # table shadowing one of the tables participating in the constraint.
+create table t3 (pk int primary key);
+create table t4 (fk1 int, fk2 int);
+insert into t1 values (1);
+insert into t2 values (1);
+insert into t3 values (1);
+insert into t4 values (1, 1);
+alter table t4 add constraint c1 foreign key (fk1) references t1 (pk),
+               add constraint c2 foreign key (fk2) references t2 (pk)
+                                 on delete restrict on update restrict,
+               add column fk3 int default 1,
+               add constraint c3 foreign key (fk3) references t3 (pk)
+                                 on update cascade;
+--replace_result $engine_type <engine_type>
+show create table t4;
+--error ER_FK_CHILD_VALUE_EXISTS
+delete from t1 where pk = 1;
+--error ER_FK_CHILD_VALUE_EXISTS
+delete from t2 where pk = 1;
+--error ER_FK_CHILD_VALUE_EXISTS
+delete from t3 where pk = 1;
+--error ER_FK_CONSTRAINT_NAME_DUPLICATE
+create table t5 (pk int primary key, fk int constraint c1 references t5 (pk));
+--error ER_FK_CONSTRAINT_NAME_DUPLICATE
+create table t5 (pk int primary key, fk int constraint c2 references t5 (pk));
+--error ER_FK_CONSTRAINT_NAME_DUPLICATE
+create table t5 (pk int primary key, fk int constraint c3 references t5 (pk));
+drop table t4;
+--echo #
+--echo # And now let us add them in sequence.
+--echo #
+create table t4 (fk1 int, fk2 int);
+insert into t4 values (1, 1);
+alter table t4 add constraint c1 foreign key (fk1) references t1 (pk);
+alter table t4 add constraint c2 foreign key (fk2) references t2 (pk)
+                                 on delete restrict on update restrict;
+alter table t4 add column fk3 int default 1,
+               add constraint c3 foreign key (fk3) references t3 (pk)
+                                 on update cascade;
+--replace_result $engine_type <engine_type>
+show create table t4;
+--error ER_FK_CHILD_VALUE_EXISTS
+delete from t1 where pk = 1;
+--error ER_FK_CHILD_VALUE_EXISTS
+delete from t2 where pk = 1;
+--error ER_FK_CHILD_VALUE_EXISTS
+delete from t3 where pk = 1;
+--error ER_FK_CONSTRAINT_NAME_DUPLICATE
+create table t5 (pk int primary key, fk int constraint c1 references t5 (pk));
+--error ER_FK_CONSTRAINT_NAME_DUPLICATE
+create table t5 (pk int primary key, fk int constraint c2 references t5 (pk));
+--error ER_FK_CONSTRAINT_NAME_DUPLICATE
+create table t5 (pk int primary key, fk int constraint c3 references t5 (pk));
+drop tables t1, t2, t3, t4;
+--echo #
+--echo # Let us also check that when adding constraints we correctly
+--echo # change metadata of the parent table.
+--echo #
 create table t1 (pk int primary key);
-create table t2 (fk int constraint c references t1 (pk));
---echo # Attempt to drop the parent without dropping the child should
---echo # lead to an error.
-create temporary table t2 (i int);
---error ER_FK_CHILD_TABLE_EXISTS
-drop tables t1, t2;
-drop temporary table t2;
---echo # When dropping the child we should not be confused by the
---echo # presence of a temporary table, that is shadowing the parent.
-create temporary table t1 (i int);
-drop table t2;
---echo # Check that parent .FRM was properly modified.
-drop temporary table t1;
---echo # The below statements should succeed.
+create table t2 (pk int primary key, fk int constraint c1 references t1 (pk));
+create table t3 (fk int);
 insert into t1 values (1);
-update t1 set pk= 0;
---echo # Finally a bit more complicated scenario in which we
---echo # are dropping the temporary table and modify .FRM of
---echo # the parent table which is shadowed by this temporary table.
-create table t2 (fk int references t1 (pk));
-create temporary table t1 (i int);
-drop tables t1, t2;
-drop table t1;
-
---echo # Until bug #44230 "Altering Falcon table under LOCK TABLES fails with
---echo # "Can't lock file" error" is fixed tests for DROP TABLE under LOCK
---echo # TABLES have to reside in foreign_key_all_engines_2.test.
-
---echo # Let us check that we are able to drop a table participating in a 
---echo # foreign key even if the other table in this relationship does
---echo # not exist (i.e. if the data-dictionary is inconsistent).
---echo # First let us drop a child with non-existing parent.
-create table t1 (pk int primary key) engine=myisam;
-create table t2 (fk int references t1 (pk)
-                        on delete restrict on update restrict) engine=myisam;
---echo # Now let us simulate manual dropping of parent table.
-flush table t1;
-let $MYSQLD_DATADIR= `select @@datadir`;
---remove_file $MYSQLD_DATADIR/test/t1.frm
---remove_file $MYSQLD_DATADIR/test/t1.MYI
---remove_file $MYSQLD_DATADIR/test/t1.MYD
---echo # Dropping of child should succeed.
-drop table t2;
---echo # Perform a similar test for parent table.
-create table t1 (pk int primary key) engine=myisam;
-create table t2 (fk int references t1 (pk)
-                        on delete restrict on update restrict) engine=myisam;
---echo # Simulate manual dropping of table t2.
-flush table t2;
---remove_file $MYSQLD_DATADIR/test/t2.frm
---remove_file $MYSQLD_DATADIR/test/t2.MYI
---remove_file $MYSQLD_DATADIR/test/t2.MYD
---echo # The below statement should succeed. We have to mention both tables
---echo # in it as otherwise error about attempt to drop parent table without
---echo # child table will be reported.
-drop tables if exists t1, t2;
-
---echo # Test that CREATE and DROP TABLE are able to modify .FRMs of parent
---echo # table even if this table was created under more relaxed sql_mode and
---echo # thus, for example, straightforward attempt to alter such table might
---echo # fail. We want to allow creation/dropping of foreign keys in such
---echo # circumstances since, after all, the fact that we modify .FRM of
---echo # the parent in the process is just an implementation detail.
-set @old_sql_mode:= @@sql_mode;
-set sql_mode='ALLOW_INVALID_DATES';
---echo # Create a parent-to-be with defaults which prohibit its re-creation
---echo # or normal altering in 'traditional' mode.
-create table t1 (pk int primary key,
-                 a datetime default '2009-03-30 00:00:00',
-                 b datetime default '2009-03-00 00:00:00',
-                 c datetime default '0000-00-00 00:00:00');
-set sql_mode= 'TRADITIONAL';
---echo # The below CREATE and DROP TABLE should succeed.
-create table t2 (fk int references t1 (pk));
-drop table t2;
-set sql_mode= @old_sql_mode;
-drop table t1;
-
-
+insert into t2 values (1, 1);
+insert into t3 values (1);
+alter table t3 add constraint c2 foreign key (fk) references t2 (pk);
+--replace_result $engine_type <engine_type>
+show create table t3;
+--replace_result $engine_type <engine_type>
+show create table t2;
+--error ER_FK_CHILD_VALUE_EXISTS
+delete from t2 where pk = 1;
+--error ER_FK_CHILD_VALUE_EXISTS
+delete from t1 where pk = 1;
+--error ER_FK_CONSTRAINT_NAME_DUPLICATE
+create table t4 (pk int primary key, fk int constraint c1 references t4 (pk));
+--error ER_FK_CONSTRAINT_NAME_DUPLICATE
+create table t4 (pk int primary key, fk int constraint c2 references t4 (pk));
+drop tables t1, t2, t3;
+--echo # 
+--echo # It's time for tables with a loop of constraints.
 --echo #
---echo # DROP DATABASE
+create table t1 (pk int primary key, fk int);
+create table t2 (pk int primary key, fk int constraint c1 references t1 (pk));
+insert into t1 values (1, 1);
+insert into t2 values (1, 1);
+alter table t1 add constraint c2 foreign key (fk) references t2 (pk);
+--replace_result $engine_type <engine_type>
+show create table t1;
+--replace_result $engine_type <engine_type>
+show create table t2;
+--error ER_FK_CHILD_VALUE_EXISTS
+delete from t2 where pk = 1;
+--error ER_FK_CHILD_VALUE_EXISTS
+delete from t1 where pk = 1;
+--error ER_FK_CONSTRAINT_NAME_DUPLICATE
+create table t3 (pk int primary key, fk int constraint c1 references t3 (pk));
+--error ER_FK_CONSTRAINT_NAME_DUPLICATE
+create table t3 (pk int primary key, fk int constraint c2 references t3 (pk));
+drop table t1, t2;
 --echo #
---echo # From the foreign keys point of view DROP DATABASE is simply a
---echo # special form of DROP TABLES.
+--echo # Test that when a constraint is added we also check that there
+--echo # is a matching parent value for values in each child record.
 --echo # 
---echo # It should be impossible to drop a database containing tables which
---echo # are referenced by tables from other databases.
-create database mysqltest;
-create table mysqltest.t1 (pk int primary key);
-create table test.t2 (fk int constraint c references mysqltest.t1 (pk));
---error ER_FK_CHILD_TABLE_EXISTS
-drop database mysqltest;
---echo # It should be possible to drop a database which tables are not
---echo # referenced from other databases.
-drop table test.t2;
-create table mysqltest.t2 (fk int references mysqltest.t1 (pk));
-drop database mysqltest;
---echo # Also it should be possible to drop a database which is not
---echo # referenced from other databases, but itself constains tables
---echo # referencing to other databases.
+--echo # First, the case with one constraint referencing to some other table.
+--echo #
 create table t1 (pk int primary key);
-create database mysqltest;
-create table mysqltest.t2 (fk int references test.t1 (pk));
-drop database mysqltest;
-drop table t1;
-
---echo # Test DROP DATABASE which has foreign keys and
---echo # damaged tables lying around.
-create database mysql_test;
+create table t2 (fk int);
+insert into t2 values (1);
+--error ER_FK_CHILD_NO_MATCH
+alter table t2 add constraint c foreign key (fk) references t1 (pk);
+--replace_result $engine_type <engine_type>
+show create table t2;
+--echo # 
+--echo # Now the case with two constraints only one of which fails the check.
+--echo #
+insert into t1 values (1);
+alter table t2 add column fk2 int;
+update t2 set fk2= 2;
+--error ER_FK_CHILD_NO_MATCH
+alter table t2 add constraint c1 foreign key (fk) references t1 (pk),
+               add constraint c2 foreign key (fk2) references t1 (pk);
+--replace_result $engine_type <engine_type>
+show create table t2;
+--echo #
+--echo # Test that this check honors FOREIGN_KEY_CHECKS=0.
+--echo #
+set foreign_key_checks= 0;
+alter table t2 add constraint c1 foreign key (fk2) references t1 (pk); 
+--replace_result $engine_type <engine_type>
+show create table t2;
+set foreign_key_checks= 1;
+--echo #
+--echo # Also let us test that we perform the check for newly added
+--echo # constraints only.
+--echo #
+alter table t2 add constraint c2 foreign key (fk) references t1 (pk);
+--replace_result $engine_type <engine_type>
+show create table t2;
+drop tables t1, t2;
+--echo #
+--echo # Now the case with a transactional table and a self-referencing key.
+--echo #
+create table t1 (pk int primary key, fk1 int, fk2 int);
+insert into t1 values (1, 2, 4), (2, 2, 2), (3, 2, 4);
+--error ER_FK_CHILD_NO_MATCH
+alter table t1 add constraint c foreign key (fk2) references t1 (pk);
+--echo #
+--echo # The below statement should succeed because EOS check will happen.
+--echo #
+alter table t1 add constraint c foreign key (fk1) references t1 (pk);
+--replace_result $engine_type <engine_type>
+show create table t1;
+drop table t1;
+--echo #
+--echo # A bit more complex case in which we add simultaneously two
+--echo # constraints which might interact with each other
+--echo # if implemented wrong.
+--echo #
+create table t1 (pk int primary key, a int not null, b int not null,
+                 c int, d int, e int not null, f int not null,
+                 unique (a, b), unique (e, f));
+insert into t1 values (1, 1, 2, 1, 1, 1, 2), (2, 1, 1, 1, 1, 2, 1);
+--error ER_FK_CHILD_NO_MATCH
+alter table t1 add constraint c1 foreign key (d, e) references t1 (a, b),
+               add constraint c2 foreign key (b, c) references t1 (e, f);
+drop table t1;
+--echo #
+--echo # Another case which emphasizes why it is important to use new
+--echo # version of the table being altered for the checks.
+--echo #
+create table t1 (fk int);
+insert into t1 values (1);
+alter table t1 add column pk int primary key default 1,
+               add constraint c foreign key (fk) references t1 (pk);
+drop table t1;
+--echo #
+--echo # Now test of a non-transactional table and a self-referencing key.
+--echo #
+create table t1 (pk int primary key, fk1 int, fk2 int, fk3 int) engine=myisam;
+insert into t1 values (1, 1, 2, 3), (2, 1, 2, 3);
+--echo #
+--echo # The below ALTER should succeed as we allow self-referencing
+--echo # rows even for non-transactional tables.
+--echo #
+alter table t1 add constraint c1 foreign key (fk1) references t1 (pk)
+                                 on delete restrict on update restrict;
+--echo #
+--echo # The below ALTER should fail as we decided that for the sake of
+--echo # consistency of behavior we won't enable EOS checks if table being
+--echo # altered is non-transactional (event though it is theoretically
+--echo # possible). This might change in future.
+--echo #
+--error ER_FK_CHILD_NO_MATCH
+alter table t1 add constraint c2 foreign key (fk2) references t1 (pk)
+                                 on delete restrict on update restrict;
+--echo #
+--echo # Finally the below ALTER should always fail as otherwise we
+--echo # would get an orphan foreign key value.
+--echo #
+--error ER_FK_CHILD_NO_MATCH
+alter table t1 add constraint c3 foreign key (fk3) references t1 (pk)
+                                 on delete restrict on update restrict;
+show create table t1;
+drop table t1;
+--echo #
+--echo # Let us also have a case in which ADD CONSTRAINT can be
+--echo # performed as a "fast" alter table operation, i.e. when
+--echo # supporting key already exists and FOREIGN_KEY_CHECKS=0.
+--echo #
+--echo # By using --enable_info and verifying that number of affected
+--echo # rows is 0 we check that this ALTER TABLE is really carried
+--echo # out as "fast/online" operation.
+--echo #
+create table t1 (pk int primary key);
+insert into t1 values (1);
+create table t2 (fk int, key(fk));
+insert into t2 values (1);
+set foreign_key_checks= 0;
+--enable_info
+alter table t2 add constraint c foreign key (fk) references t1 (pk);
+--disable_info
+set foreign_key_checks= 1;
+--replace_result $engine_type <engine_type>
+show create table t2;
+--error ER_FK_CHILD_VALUE_EXISTS
+delete from t1 where pk = 1;
+--error ER_FK_CONSTRAINT_NAME_DUPLICATE
+create table t3 (pk int primary key, fk int constraint c references t3 (pk));
+drop tables t1, t2;
+--echo #
+--echo # ADD FOREIGN KEY clause should be supported as well
+--echo # (One instance of an appropriate warning should be emitted).
+--echo #
+create table t1 (pk int primary key, fk int);
+insert into t1 values (1, 1), (2, 1);
+alter table t1 add foreign key c (fk) references t1 (pk);
+--replace_result $engine_type <engine_type>
+show create table t1;
+--error ER_FK_CHILD_VALUE_EXISTS
+delete from t1 where pk = 1;
+--error ER_FK_CONSTRAINT_NAME_DUPLICATE
+create table t2 (pk int primary key, fk int constraint c references t2 (pk));
+drop table t1;
+--echo #
+--echo # Now let us check that during ALTER TABLE ... ADD CONSTRAINT we
+--echo # perform all the checks which are performed when foreign key is
+--echo # created by CREATE TABLE. Basically this is test coverage for
+--echo # milestone 7 ("DDL checks and changes CREATE, CREATE TABLE LIKE")
+--echo # which was changed to use ALTER TABLE.
+--echo #
+--echo # Let us begin with various checks for constraint name legality.
+--echo #
+create table t1 (pk int primary key);
+create table t2 (fk int);
+--error ER_FK_CONSTRAINT_NAME_ILLEGAL
+alter table t2 add constraint `` foreign key (fk) references t1 (pk);
+--error ER_FK_CONSTRAINT_NAME_ILLEGAL
+alter table t2 add constraint ` ` foreign key (fk) references t1 (pk);
+--error ER_FK_CONSTRAINT_NAME_ILLEGAL
+alter table t2 add constraint `c ` foreign key (fk) references t1 (pk);
+--error ER_FK_CONSTRAINT_NAME_ILLEGAL
+alter table t2 add constraint CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
+                   foreign key (fk)  references t1 (pk);
+--error ER_FK_CONSTRAINT_NAME_ILLEGAL
+alter table t2 add constraint `PRIMARY` foreign key (fk) references t1 (pk);
+--echo #
+--echo # Check for duplicate constraint names within the same statement.
+--echo #
+--error ER_FK_CONSTRAINT_NAME_DUPLICATE
+alter table t2 add constraint c foreign key (fk) references t1 (pk),
+               add fk2 int constraint c references t1 (pk);
+--echo #
+--echo # Check for duplicate constraint names created by different
+--echo # statements.
+--echo #
+alter table t2 add constraint c foreign key (fk) references t1 (pk);
+--error ER_FK_CONSTRAINT_NAME_DUPLICATE
+alter table t2 add fk2 int constraint c references t1 (pk);
+create table t3 (fk int);
+--error ER_FK_CONSTRAINT_NAME_DUPLICATE
+alter table t2 add constraint c foreign key (fk) references t1 (pk);
+--echo #
+--echo # OTOH it should be possible to create constraint with the same name
+--echo # in other database.
+--echo #
+create database mysqltest;
+use mysqltest;
+create table t3 (fk int);
+alter table t3 add constraint c foreign key (fk) references test.t1(pk);
+use test;
+drop database mysqltest;
+drop tables t2, t3;
+--echo # 
+--echo # Checks for child and parent table types.
+--echo #
+create temporary table t2 (fk int);
+--error ER_FK_CHILD_TEMPORARY
+alter table t2 add constraint c foreign key (fk) references t1 (pk);
+drop temporary table t2;
+drop table t1;
+create temporary table t1 (pk int primary key);
+create table t2 (fk int);
+--error ER_FK_PARENT_TEMPORARY
+alter table t2 add constraint c foreign key (fk) references t1 (pk);
+drop temporary table t1;
+create table t1 (pk int primary key);
+create view v1 as select * from t1;
+--error ER_FK_PARENT_VIEW
+alter table t2 add constraint c foreign key (fk) references v1 (pk);
+drop view v1;
+drop table t1;
+--error ER_DBACCESS_DENIED_ERROR
+alter table t2 add fk2 varchar(64) constraint c
+                   references information_schema.tables (table_name);
+--error ER_FK_PARENT_MYSQL
+alter table t2 add fk2 char(16) constraint c references mysql.user (user);
+drop table t2;
+--echo #
+--echo # Check for columns of child and parent tables.
+--echo #
+create table t1 (pk1 int, pk2 int, primary key (pk1, pk2));
+create table t2 (fk int);
+--error ER_FK_CHILD_COLUMN_MISSING
+alter table t2 add constraint c foreign key (no_such_column) references t1 (pk);
+--error ER_FK_CHILD_COLUMN_DUPLICATED
+alter table t2 add constraint c foreign key (fk, fk) references t1 (pk1, pk2);
+--error ER_FK_PARENT_COLUMN_MISSING
+alter table t2 add constraint c foreign key (fk) references t1 (no_such_column);
+--error ER_FK_PARENT_COLUMN_DUPLICATED
+alter table t2 add fk1 int, add fk2 int, add fk3 int,
+               add constraint c foreign key (fk1, fk2, fk3)
+                                references t1 (pk1, pk2, pk2);
+drop tables t1, t2;
+--echo #
+--echo # Checks for types of columns of child and parent tables.
+--echo #
+create table t1 (pk enum('a', 'b') primary key);
+create table t2 (fk enum('b', 'a'));
+--error ER_FK_CHILD_DATA_TYPE
+alter table t2 add constraint c foreign key (fk) references t1 (pk);
+drop tables t1, t2;
+create table t1 (pk set('a', 'b') primary key);
+create table t2 (fk set('b', 'a'));
+--error ER_FK_CHILD_DATA_TYPE
+alter table t2 add constraint c foreign key (fk) references t1 (pk);
+drop tables t1, t2;
+create table t1 (pk timestamp primary key);
+create table t2 (fk timestamp);
+--error ER_FK_CHILD_DATA_TYPE
+alter table t2 add constraint c foreign key (fk) references t1 (pk);
+drop tables t2, t1;
+create table t1 (a int unique);
+create table t2 (fk int);
+--error ER_FK_PARENT_NULLABLE
+alter table t2 add constraint c foreign key (fk) references t1 (a);
+drop table t1;
+--echo #
+--echo # Parent column without explicit NOT NULL attribute but part
+--echo # of PK should be OK.
+--echo #
+create table t1 (pk int, primary key (pk));
+alter table t2 add constraint c foreign key (fk) references t1 (pk);
+drop tables t1, t2;
+create table t1 (a int unsigned not null unique,
+                 b decimal(10,5) not null unique,
+                 c char(10) not null unique,
+                 d datetime not null unique);
+create table t2 (fk_bu bigint unsigned, fk_i int,
+                 fk_du_10_5 decimal(10,5) unsigned,
+                 fk_d_9_5 decimal(9,5), fk_d_10_4 decimal(10,4),
+                 fk_c_10_utf8 char(10) character set utf8, fk_c_9 char(9));
+--error ER_FK_PARENT_DATA_TYPE
+alter table t2 add constraint c foreign key (fk_bu) references t1 (a);
+--error ER_FK_PARENT_DATA_TYPE
+alter table t2 add constraint c foreign key (fk_i) references t1 (a);
+--error ER_FK_PARENT_DATA_TYPE
+alter table t2 add constraint c foreign key (fk_du_10_5) references t1 (b);
+--error ER_FK_PARENT_DATA_TYPE
+alter table t2 add constraint c foreign key (fk_d_9_5) references t1 (b);
+--error ER_FK_PARENT_DATA_TYPE
+alter table t2 add constraint c foreign key (fk_d_10_4) references t1 (b);
+--error ER_FK_PARENT_DATA_TYPE
+alter table t2 add constraint c foreign key (fk_c_10_utf8) references t1 (c);
+--error ER_FK_PARENT_DATA_TYPE
+alter table t2 add constraint c foreign key (fk_c_9) references t1 (c);
+drop tables t1, t2;
+--echo #
+--echo # Check that one can't specify SET NULL clause if some of child
+--echo # columns are defined as NOT NULL.
+--echo #
+create table t1 (pk int primary key);
+create table t2 (fk int not null);
+--error ER_FK_SET_NULL_NOT_NULL
+alter table t2 add constraint c foreign key (fk) references t1 (pk) 
+                                on delete set null;
+--error ER_FK_SET_NULL_NOT_NULL
+alter table t2 add constraint c foreign key (fk) references t1 (pk) 
+                                on update set null;
+drop tables t1, t2;
+--echo #
+--echo # Check that we also detect implict NOT NULL specification.
+--echo #
+create table t1 (pk1 int, pk2 int, primary key (pk1, pk2));
+create table t2 (a int, b int, c int, primary key (a, b));
+--error ER_FK_SET_NULL_NOT_NULL
+alter table t2 add constraint c foreign key (b, c) references t1 (pk1, pk2)
+                                on delete set null;
+--error ER_FK_SET_NULL_NOT_NULL
+alter table t2 add constraint c foreign key (b, c) references t1 (pk1, pk2)
+                                on update set null;
+drop tables t1, t2;
+--echo #
+--echo # Check that parent columns are part of primary key or unique
+--echo # constraint.
+--echo #
+create table t1 (a int not null);
+create table t2 (fk int);
+--error ER_FK_PARENT_KEY_NO_PK_OR_UNIQUE
+alter table t2 add constraint c foreign key (fk) references t1 (a);
+drop tables t1, t2;
+create table t1 (a int not null, b int not null, key (a, b));
+create table t2 (a int, b int);
+--error ER_FK_PARENT_KEY_NO_PK_OR_UNIQUE
+alter table t2 add constraint c foreign key (a, b) references t1 (a, b);
+drop table t1;
+create table t1 (a int not null, b int not null, c int not null,
+                 unique (a, b, c));
+--error ER_FK_PARENT_KEY_NO_PK_OR_UNIQUE
+alter table t2 add constraint c foreign key (a, b) references t1 (a, b);
+drop tables t1, t2;
+--echo #
+--echo # Check that we prohibit foreign keys in which one table is
+--echo # transactional and another is not (see bug #41692 "Foreign keys:
+--echo # failure if mixed engines").
+--echo #
+create table t1 (pk int primary key) engine=myisam;
+create table t2 (fk int);
+--error ER_FK_NON_TRANSACTIONAL_AND_TRANSACTIONAL
+alter table t2 add constraint c foreign key (fk) references t1 (pk);
+drop tables t1, t2;
+create table t1 (pk int primary key);
+create table t2 (fk int) engine=myisam;
+--error ER_FK_NON_TRANSACTIONAL_AND_TRANSACTIONAL
+alter table t2 add constraint c foreign key (fk) references t1 (pk);
+drop tables t1, t2;
+--echo #
+--echo # Check for action types incompatible with non-transactional engines.
+--echo # Non-transactional tables are incompatible with NO ACTION/CASCADE/
+--echo # SET NULL/SET DEFAULT actions.
+--echo #
+create table t1 (pk int primary key) engine=myisam;
+create table t2 (fk int) engine=myisam;
+--error ER_FK_NON_TRANSACTIONAL_CASCADING_ACTION
+alter table t2 add constraint c foreign key (fk) references t1 (pk)
+                                on delete cascade;
+--error ER_FK_NON_TRANSACTIONAL_CASCADING_ACTION
+alter table t2 add constraint c foreign key (fk) references t1 (pk)
+                                on delete set null;
+--error ER_FK_NON_TRANSACTIONAL_CASCADING_ACTION
+alter table t2 add constraint c foreign key (fk) references t1 (pk)
+                                on delete set default;
+--error ER_FK_NON_TRANSACTIONAL_CASCADING_ACTION
+alter table t2 add constraint c foreign key (fk) references t1 (pk)
+                                on update cascade;
+--error ER_FK_NON_TRANSACTIONAL_CASCADING_ACTION
+alter table t2 add constraint c foreign key (fk) references t1 (pk)
+                                on update set null;
+--error ER_FK_NON_TRANSACTIONAL_CASCADING_ACTION
+alter table t2 add constraint c foreign key (fk) references t1 (pk)
+                                on update set default;
+--error ER_FK_NON_TRANSACTIONAL_NO_ACTION
+alter table t2 add constraint c foreign key (fk) references t1 (pk)
+                                on delete no action;
+--error ER_FK_NON_TRANSACTIONAL_NO_ACTION
+alter table t2 add constraint c foreign key (fk) references t1 (pk)
+                                on update no action;
+--echo # Creating foreign key with both RESTRICT actions should be OK
+alter table t2 add constraint c foreign key (fk) references t1 (pk)
+                                on delete restrict on update restrict;
+drop table t1, t2;
+
+--echo #
+--echo # Now let us test how ALTER TABLE ... DROP CONSTRAINT works.
+--echo #
+create table t1 (pk int primary key);
+create table t2 (fk int constraint c references t1 (pk));
+insert into t1 values (1);
+insert into t2 values (1);
+--echo #
+--echo # First, let us drop the constraint and check that metadata
+--echo # was properly updated.
+--echo #
+alter table t2 drop constraint c;
+--replace_result $engine_type <engine_type>
+show create table t2;
+delete from t1 where pk = 1;
+--echo #
+--echo # It should be possible to create a different
+--echo # constraint with name 'c' now.
+--echo #
+create table t3 (pk int primary key, fk int constraint c references t3 (pk));
+drop table t3;
+--echo #
+--echo # Now let us try to drop a constraint which does not exist.
+--echo #
+--error ER_FK_NO_SUCH_CONSTRAINT
+alter table t2 drop constraint c;
+--echo #
+--echo # Let us see how DROP CONSTRAINT works for a table with a
+--echo # couple of constraints.
+--echo #
+insert into t1 values (1);
+create table t3 (pk int primary key);
+insert into t3 values (1);
+alter table t2 add constraint c1 foreign key (fk) references t1 (pk),
+               add fk2 int constraint c2 references t3 (pk);
+insert into t2 values (1, 1);
+alter table t2 drop constraint c1;
+--replace_result $engine_type <engine_type>
+show create table t2;
+delete from t1 where pk = 1;
+create table t4 (pk int primary key, fk int constraint c1 references t4(pk));
+drop table t4;
+--error ER_FK_CHILD_VALUE_EXISTS
+delete from t3 where pk = 1;
+--error ER_FK_CONSTRAINT_NAME_DUPLICATE
+create table t4 (pk int primary key, fk int constraint c2 references t4 (pk));
+drop tables t1, t2, t3;
+--echo #
+--echo # Check that we properly update metadata when dropping a constraint
+--echo # with the same name as other constraint (e.g. when we have several
+--echo # databases).
+--echo #
+create table t1 (pk int primary key);
+insert into t1 values (1), (2);
+create table t2 (fk int constraint c references t1 (pk));
+insert into t2 values (2);
+create database mysqltest;
+use mysqltest;
+create table t2 (fk int constraint c references test.t1 (pk));
+insert into t2 values (1);
+use test;
+alter table t2 drop constraint c;
+--echo #
+--echo # Foreign key on test.t2 should be gone.
+--echo #
+--replace_result $engine_type <engine_type>
+show create table t2;
+delete from t1 where pk = 2;
+create table t3 (pk int primary key, fk int constraint c references t3 (pk));
+drop table t3;
+--echo #
+--echo # But foreign key on mysqltest.t2 should be there!
+--echo #
+--replace_result $engine_type <engine_type>
+show create table mysqltest.t2;
+--error ER_FK_CHILD_VALUE_EXISTS
+delete from t1 where pk = 1;
+--error ER_FK_CONSTRAINT_NAME_DUPLICATE
+create table mysqltest.t3 (pk int primary key,
+                           fk int constraint c references mysqltest.t3 (pk));
+drop tables t1, t2, mysqltest.t2;
+--echo # 
+--echo # Now similar test for table with self-reference.
+--echo #
+create table t1 (pk int primary key, fk int constraint c references t1 (pk));
+insert into t1 values (1, 1), (2, 2), (3, 2);
+use mysqltest;
+create table t1 (fk int constraint c references test.t1 (pk));
+insert into t1 values (1);
+use test;
+alter table t1 drop constraint c;
+--echo #
+--echo # Again test.c constraint should be completely gone.
+--echo #
+--replace_result $engine_type <engine_type>
+show create table t1;
+delete from t1 where pk = 2;
+create table t2 (pk int primary key, fk int constraint c references t2 (pk));
+drop table t2;
+--echo #
+--echo # Again mysqltest.c constraint should be still there.
+--echo #
+--replace_result $engine_type <engine_type>
+show create table mysqltest.t1;
+--error ER_FK_CHILD_VALUE_EXISTS
+delete from t1 where pk = 1;
+--error ER_FK_CONSTRAINT_NAME_DUPLICATE
+create table mysqltest.t2 (pk int primary key,
+                           fk int constraint c references mysqltest.t2 (pk));
+drop table t1, mysqltest.t1;
+drop database mysqltest;
+--echo # 
+--echo # Let us try to drop constraint which belongs to other table.
+--echo #
+create table t1 (pk int primary key);
+insert into t1 values (1), (2);
+create table t2 (fk int constraint c1 references t1 (pk));
+insert into t2 values (1);
+create table t3 (fk int constraint c2 references t1 (pk));
+insert into t3 values (2);
+--error ER_FK_NO_SUCH_CONSTRAINT
+alter table t2 drop constraint c2;
+--echo #
+--echo # Check that c2 is still there.
+--echo #
+--replace_result $engine_type <engine_type>
+show create table t3;
+--error ER_FK_CHILD_VALUE_EXISTS
+delete from t1 where pk = 2;
+--error ER_FK_CONSTRAINT_NAME_DUPLICATE
+create table t4 (pk int primary key, fk int constraint c2 references t4 (pk));
+--error ER_FK_NO_SUCH_CONSTRAINT
+alter table t1 drop constraint c1;
+--echo #
+--echo # Check that c1 is still there.
+--echo #
+--replace_result $engine_type <engine_type>
+show create table t2;
+--error ER_FK_CHILD_VALUE_EXISTS
+delete from t1 where pk = 1;
+--error ER_FK_CONSTRAINT_NAME_DUPLICATE
+create table t4 (pk int primary key, fk int constraint c1 references t4 (pk));
+drop tables t1, t2, t3;
+--echo #
+--echo # Now let us see what happens when we drop two constraints which
+--echo # reference the same table.
+--echo #
+create table t1 (pk int primary key);
+insert into t1 values (1), (2);
+create table t2 (fk1 int constraint c1 references t1 (pk),
+                 fk2 int constraint c2 references t1 (pk));
+insert into t2 values (1, 2);
+alter table t2 drop constraint c1, drop constraint c2;
+--echo #
+--echo # Check that both c1 and c2 are gone.
+--echo #
+--replace_result $engine_type <engine_type>
+show create table t2;
+delete from t1;
+create table t3 (pk int primary key, fk int constraint c1 references t3 (pk));
+drop table t3;
+create table t3 (pk int primary key, fk int constraint c2 references t3 (pk));
+drop tables t1, t2, t3;
+--echo #
+--echo # Now let us test dropping of two constraints referencing two different
+--echo # tables.
+--echo #
+create table t1 (pk int primary key);
+insert into t1 values (1);
+create table t2 (pk int primary key);
+insert into t2 values (1);
+create table t3 (fk1 int constraint c1 references t1 (pk),
+                 fk2 int constraint c2 references t2 (pk));
+insert into t3 values (1, 1);
+alter table t3 drop constraint c1, drop constraint c2;
+--echo #
+--echo # Check that both c1 and c2 are gone.
+--echo #
+--replace_result $engine_type <engine_type>
+show create table t3;
+delete from t1 where pk = 1;
+delete from t2 where pk = 1;
+create table t4 (pk int primary key, fk int constraint c1 references t4 (pk));
+drop table t4;
+create table t4 (pk int primary key, fk int constraint c2 references t4 (pk));
+drop tables t1, t2, t3, t4;
+--echo #
+--echo # Additional test for dropping a constraint that participates
+--echo # in a loop.
+--echo #
+create table t1 (pk int primary key, fk int);
+insert into t1 values (1, 1), (2, 2);
+create table t2 (pk int primary key, fk int constraint c1 references t1 (pk));
+insert into t2 values (1, 1), (2, 1);
+alter table t1 add constraint c2 foreign key (fk) references t2 (pk);
+--echo #
+--echo # Now let us drop c2 and check that it is completely gone.
+--echo #
+alter table t1 drop constraint c2;
+--replace_result $engine_type <engine_type>
+show create table t1;
+delete from t2 where pk = 2;
+create table t3 (pk int primary key, fk int constraint c2 references t3 (pk));
+drop table t3;
+--echo #
+--echo # And c1 should still be there.
+--echo #
+--replace_result $engine_type <engine_type>
+show create table t2;
+--error ER_FK_CHILD_VALUE_EXISTS
+delete from t1 where pk = 1;
+--error ER_FK_CONSTRAINT_NAME_DUPLICATE
+create table t3 (pk int primary key, fk int constraint c1 references t3 (pk));
+drop tables t1, t2;
+--echo #
+--echo # Let us also check that DROP CONSTRAINT is actually a "fast"
+--echo # ALTER TABLE operation, i.e. does not require a full table copy.
+--echo # 
+--echo # We do that by using --enable_info and verifying that number of
+--echo # affected rows is 0.
+--echo #
+create table t1 (pk int primary key);
+create table t2 (fk int constraint c references t1 (pk));
+insert into t1 values (1);
+insert into t2 values (1);
+--enable_info
+alter table t2 drop constraint c;
+--disable_info
+--replace_result $engine_type <engine_type>
+show create table t2;
+drop tables t1, t2;
+--echo # 
+--echo # Check that we accept and properly interpret DROP FOREIGN KEY clause.
+--echo # We should also emit one instance of appropriate warning in this case.
+--echo #
+create table t1 (pk int primary key, fk int constraint c references t1 (pk));
+insert into t1 values (1, 1), (2, 1);
+alter table t1 drop foreign key c;
+--replace_result $engine_type <engine_type>
+show create table t1;
+delete from t1 where pk = 1;
+create table t2 (pk int primary key, fk int constraint c references t2 (pk));
+drop tables t1, t2;
+
+--echo # 
+--echo # Now let us test how ADD CONSTRAINT and DROP CONSTRAINT clauses
+--echo # work together.
+--echo #
+--echo # Similarly to ADD/DROP COLUMN we first process all DROP clauses
+--echo # and then all ADD clauses. This means that order in which these
+--echo # clauses are used in ALTER TABLE statement doesn't really matter.
+--echo #
+create table t1 (pk int primary key);
+create table t2 (fk int);
+--error ER_FK_NO_SUCH_CONSTRAINT
+alter table t2 add constraint c foreign key (fk) references t1 (pk),
+               drop constraint c;
+alter table t2 add constraint c foreign key (fk) references t1 (pk);
+alter table t2 drop constraint c,
+               add constraint c foreign key (fk) references t1 (pk)
+                                on delete cascade;
+--replace_result $engine_type <engine_type>
+show create table t2;
+alter table t2 add constraint c foreign key (fk) references t1 (pk),
+               drop constraint c;
+--replace_result $engine_type <engine_type>
+show create table t2;
+drop tables t1, t2;
+
+--echo #
+--echo # Let us check how different clauses of ALTER TABLE interact with
+--echo # foreign keys.
+--echo #
+--echo # ALTER TABLE .., ADD COLUMN should work fine for both parent and
+--echo # child tables.
+--echo #
+create table t1 (pk int primary key);
+create table t2 (fk int constraint c references t1 (pk));
+alter table t2 add column a int;
+--replace_result $engine_type <engine_type>
+show create table t2;
+alter table t1 add column b int;
+--replace_result $engine_type <engine_type>
+show create table t1;
+--echo #
+--echo # ALTER TABLE ... ADD KEY should also work fine even if new key covers
+--echo # columns participating in a foreign key. 
+--echo #
+alter table t2 add key (fk, a);
+--replace_result $engine_type <engine_type>
+show create table t2;
+alter table t1 add key (pk, b);
+--replace_result $engine_type <engine_type>
+show create table t1;
+drop tables t1, t2;
+--echo #
+--echo # There is one subtle moment: when we create PRIMARY KEY or
+--echo # UNIQUE constraint on a parent table we should ensure that this
+--echo # action won't delete any rows. Thus IGNORE clause of ALTER TABLE
+--echo # should be ignored in this case [sic!].
+--echo #
+--echo # Until bug #45885 "Primary key added to Falcon table allows
+--echo # non-unique values" this part of the test uses InnoDB and resides
+--echo # in foreign_key_all_engines_2.test.
+--echo #
+--echo # But it should be OK to remove rows in cases when foreign key
+--echo # is added in the same ALTER TABLE statement.
+--echo #
+create table t1 (a int primary key, b int , c int);
+insert into t1 values (1, 1, 1), (2, 1, 1);
+alter ignore table t1 add constraint c foreign key (b) references t1 (a),
+                      add constraint u unique (c);
+--echo #
+--echo # Removal of rows in cases when foreign key is dropped is also OK.
+--echo #
+alter table t1 drop key u;
+insert into t1 values (2, 1, 1);
+alter ignore table t1 drop constraint c, add constraint u unique (b);
+drop tables t1;
+--echo #
+--echo # ALTER TABLE ... CHANGE/MODIFY COLUMN is disallowed if it affects
+--echo # column participating in an existing foreign key.
+--echo #
+create table t1 (a int primary key, b int);
+create table t2 (a int constraint c references t1 (a), b int);
+--error ER_FK_CANT_CHANGE_COLUMN
+alter table t2 change column a a bigint;
+--error ER_FK_CANT_CHANGE_COLUMN
+alter table t2 modify column a bigint;
+--error ER_FK_CANT_CHANGE_COLUMN
+alter table t1 change column a a bigint;
+--error ER_FK_CANT_CHANGE_COLUMN
+alter table t1 modify column a bigint;
+--echo #
+--echo # Changing column which does not participate in such foreign key
+--echo # should be fine.
+--echo #
+alter table t2 change column b b bigint;
+alter table t1 modify column b bigint;
+--echo #
+--echo # Also it should be OK to change column which participates
+--echo # in newly created foreign key.
+--echo #
+alter table t2 drop constraint c;
+alter table t1 modify a bigint;
+alter table t2 add constraint c foreign key (a) references t1 (a),
+               modify a bigint;
+drop tables t1, t2;
+create table t1 (pk int primary key, fk int);
+alter table t1 modify column pk bigint,
+               modify column fk bigint,
+               add constraint c foreign key (fk) references t1 (pk);
+drop table t1;
+--echo #
+--echo # And finally, let us have a test case which will show that even
+--echo # changing columns which do not participate in foreign key can be
+--echo # important from their point of view.
+--echo #
+create table t1 (a int primary key, b char(4) unique);
+insert into t1 values (1, 'aaaa'), (2, 'aaab');
+create table t2 (fk int constraint c references t1 (a));
+insert into t2 values (1), (2);
+--error ER_DUP_ENTRY
+alter ignore table t1 modify b char(3);
+drop tables t1, t2;
+--echo #
+--echo # ALTER TABLE ... DROP COLUMN affecting one of foreign key columns
+--echo # is disallowed too.
+--echo #
+create table t1 (a int not null, b int not null, primary key(a, b));
+create table t2 (a int, b int, constraint c foreign key (a, b)
+                                            references t1 (a, b));
+--error ER_FK_CANT_CHANGE_COLUMN
+alter table t1 drop column b;
+--error ER_FK_CANT_CHANGE_COLUMN
+alter table t2 drop column b;
+drop tables t1, t2;
+--echo #
+--echo # ALTER TABLE ... DROP PRIMARY KEY/KEY should be disallowed if it
+--echo # tries to drop parent key of a foreign key relationship. Also it
+--echo # it should not be allowed to drop supporting index (if there are
+--echo # no other suitable indexes).
+--echo #
+create table t1 (pk int primary key);
+create table t2 (fk int constraint c references t1 (pk), key c (fk));
+--error ER_FK_PARENT_SO_UNIQUE_INDEX_UNDROPPABLE
+alter table t1 drop primary key;
+--error ER_FK_CHILD_SO_CHILD_INDEX_UNDROPPABLE
+alter table t2 drop key c;
+--echo #
+--echo # PRIMARY/UNIQUE KEY serving as a parent key should be undroppable
+--echo # even if there is another UNIQUE key covering the same columns.
+--echo #
+alter table t1 add unique u (pk);
+--error ER_FK_PARENT_SO_UNIQUE_INDEX_UNDROPPABLE
+alter table t1 drop primary key;
+--echo #
+--echo # OTOH it should be possible to drop this other unique index,
+--echo # since it is not used as parent key.
+--echo #
+alter table t1 drop key u;
+--echo #
+--echo # Also it should be possible to drop supporting index if there
+--echo # is another suitable index.
+--echo #
+alter table t2 add key d (fk);
+alter table t2 drop key c;
+drop tables t1, t2;
+create table t1 (pk int not null unique);
+create table t2 (fk int constraint c references t1 (pk));
+--error ER_FK_PARENT_SO_UNIQUE_INDEX_UNDROPPABLE
+alter table t1 drop key pk;
+--echo #
+--echo # And again PRIMARY/UNIQUE KEY with same columns as the parent key
+--echo # but not used as parent key should be droppable and the parent key
+--echo # should be undroppable.
+--echo #
+alter table t1 add primary key (pk);
+--error ER_FK_PARENT_SO_UNIQUE_INDEX_UNDROPPABLE
+alter table t1 drop key pk;
+alter table t1 drop primary key;
+--echo #
+--echo # Dropping of unrelated keys should be allowed as well.
+--echo #
+alter table t1 add column a int, add unique (a);
+alter table t1 drop key a;
+alter table t2 add column a int, add key (a);
+alter table t2 drop key a;
+drop tables t1, t2;
+--echo #
+--echo # ALTER TABLE ... DISABLE KEYS should be disallowed on tables
+--echo # which participate in foreign keys as a child (since it will
+--echo # disable supporting index). We can allow this statement on
+--echo # parent tables as it won't disable unique indexes anyway.
+--echo # ALTER TABLE ... ENABLE KEYS won't do any harm so it should
+--echo # be allowed for both parent and child tables.
+--echo #
+--echo # We use MyISAM for this test as other engines are unlikely to
+--echo # support these clauses.
+--echo #
+create table t1 (pk int primary key, a int) engine=myisam;
+create table t2 (fk int constraint c references t1 (pk)
+                        on delete restrict on update restrict,
+                 a int) engine=myisam;
+--echo #
+--echo # Let us check that DISABLE KEYS in on the child table is
+--echo # disallowed for simple/fast/full variants of ALTER TABLE.
+--echo #
+--error ER_FK_STATEMENT_ILLEGAL
+alter table t2 disable keys;
+--error ER_FK_STATEMENT_ILLEGAL
+alter table t2 disable keys, alter column a set default 0;
+--error ER_FK_STATEMENT_ILLEGAL
+alter table t2 disable keys, add column b int;
+--echo #
+--echo # Same statements for parent table should succeed.
+--echo #
+alter table t1 disable keys;
+alter table t1 disable keys, alter column a set default 0;
+alter table t1 disable keys, add column b int;

+--echo #
+--echo # Although disabling keys with dropping foreign keys should be OK.
+--echo #
+alter table t2 drop constraint c, disable keys;
+--echo #
+--echo # Let us also check that we disallow creation of foreign keys
+--echo # on tables with disabled foreign keys.
+--echo #
+--error ER_FK_CHILD_NO_INDEX
+alter table t2 add constraint c foreign key (fk) references t1 (pk)
+                                on delete restrict on update restrict;
+--echo #
+--echo # Finally let us check that enabling indexes is fine on both child
+--echo # and parent tables and for all internal ALTER variants.
+--echo #
+alter table t2 enable keys;
+alter table t2 enable keys, alter column a set default 1;
+alter table t2 enable keys, add column c int;
+alter table t1 enable keys;
+alter table t1 enable keys, alter column a set default 1;
+alter table t1 enable keys, add column c int;
+drop tables t1, t2;
+--echo #
+--echo # ALTER TABLE ... DROP/SET DEFAULT is disallowed when it is going
+--echo # to affect one of child columns participating in a foreign key
+--echo # with ON DELETE/UPDATE SET DEFAULT clauses.
+--echo #
+create table t1 (pk int primary key);
+create table t2 (fk1 int default 1 constraint c1 references t1 (pk) on update set default,
+                 fk2 int constraint c2 references t1 (pk));
+--error ER_FK_CHILD_SET_DEFAULT
+alter table t2 alter column fk1 drop default;
+--error ER_FK_CHILD_SET_DEFAULT
+alter table t2 alter column fk1 set default 2;
+--echo #
+--echo # Changing defaults for parent columns of child columns of foreign
+--echo # keys without ON DELETE/UPDATE SET DEFAULT clauses should be OK.
+--echo #
+alter table t2 alter column fk2 drop default;
+alter table t2 alter column fk2 set default 2;
+alter table t1 alter column pk set default 2;
+alter table t1 alter column pk drop default;
+--echo #
+--echo # And of course dropping the DEFAULT and the foreign key
+--echo # simultaneously also works.
+--echo #
+alter table t2 drop constraint c1, alter column fk1 drop default;
+drop tables t1, t2;
+--echo #
+--echo # ALTER TABLE ... RENAME is disallowed in the presence of foreign keys
+--echo # (even if they are created by the same alter).
+--echo #
+create table t1 (pk int primary key);
+create table t2 (fk int constraint c references t1 (pk));
+--error ER_FK_STATEMENT_ILLEGAL
+alter table t2 rename to t3;
+--error ER_FK_STATEMENT_ILLEGAL
+alter table t1 rename to t3;
+--error ER_FK_STATEMENT_ILLEGAL
+alter table t2 rename to t3, add column a int;
+--error ER_FK_STATEMENT_ILLEGAL
+alter table t1 rename to t3, add column a int;
+--echo # But renaming and dropping foreign keys should be OK.
+alter table t2 drop constraint c, rename to t3;
+--error ER_FK_STATEMENT_ILLEGAL
+alter table t3 rename to t2,
+               add constraint c foreign key (fk) references t1 (pk);
+drop tables t1, t3;
+--echo #
+--echo # ALTER TABLE ... CONVERT TO CHARSET is also prohibited in the presence
+--echo # of foreign keys since it can affect columns partiticipating in them.
+--echo #
+create table t1 (pk varchar(10) character set latin1 primary key);
+create table t2 (fk varchar(10) character set latin1
+                                constraint c references t1 (pk));
+--error ER_FK_STATEMENT_ILLEGAL
+alter table t2 convert to character set utf8;
+--error ER_FK_STATEMENT_ILLEGAL
+alter table t1 convert to character set utf8;
+--echo #
+--echo # OTOH it should be possible to use this clause if all foreign keys
+--echo # are dropped within same statement.
+--echo #
+alter table t2 drop constraint c, convert to character set utf8;
+--echo #
+--echo # Or if foreign keys are added within same statement.
+--echo #
+alter table t2 add constraint c foreign key (fk) references t1 (pk),
+               convert to character set latin1;
+drop tables t1, t2;
+--echo #
+--echo # ALTER TABLE ... ORDER BY should work fine for both child
+--echo # and parent tables, as it does not change row contents.
+--echo #
+--echo # We use MyISAM for this test as we know that this engine
+--echo # does not support clustered indexes so using ORDER BY
+--echo # makes some sense for it.
+--echo #
+create table t1 (pk int primary key) engine=myisam;
+insert into t1 values (1), (3), (2);
+create table t2 (fk int constraint c references t1 (pk)
+                        on delete restrict on update restrict) engine=myisam;
+insert into t2 values (3), (2), (1);
+alter table t1 order by pk;
+alter table t2 order by fk;
+select * from t1;
+select * from t2;
+drop tables t1, t2;
+--echo #
+--echo # Now let see how ALTER TABLE that changes some table options
+--echo # works in the presence of foreign keys.
+--echo #
+--echo # When doing ALTER TABLE ... ENGINE= ... we should check that
+--echo # new engine is compatible with existing foreign keys.
+--echo # E.g. for non-self-referencing foreing key it should be disallowed
+--echo # to switch engine from non-transactional to transactional and vice
+--echo # versa as it will create a foreign key between transactional and
+--echo # non-transactional table which is disallowed. It should also be
+--echo # disallowed to change table engine to non-transactional if table
+--echo # participates in foreign keys which have something else than
+--echo # RESTRICT as cascading actions (again this is because we disallow
+--echo # of such foreign keys for such tables).
+--echo #
+create table t1 (pk int primary key);
+create table t2 (fk int constraint c references t1 (pk) on update restrict
+                                     on delete restrict);
+--error ER_FK_NON_TRANSACTIONAL_AND_TRANSACTIONAL
+alter table t1 engine= myisam;
+--error ER_FK_NON_TRANSACTIONAL_AND_TRANSACTIONAL
+alter table t2 engine= myisam;
+drop tables t1, t2;
+create table t1 (pk int primary key) engine=myisam;
+create table t2 (fk int constraint c references t1 (pk) on update restrict
+                 on delete restrict) engine=myisam;
+--replace_result $engine_type <engine_type>
+--error ER_FK_NON_TRANSACTIONAL_AND_TRANSACTIONAL
+--eval alter table t1 engine= $engine_type
+--replace_result $engine_type <engine_type>
+--error ER_FK_NON_TRANSACTIONAL_AND_TRANSACTIONAL
+--eval alter table t2 engine= $engine_type
+drop tables t1, t2;
+--echo #
+--echo # It is possible to change engine from transactional to non-transactional
+--echo # and vice versa for tables with only self-referencing foreign keys.
+--echo #
+create table t1 (pk int primary key,
+                 fk int constraint c references t1 (pk) on update restrict
+                                     on delete restrict);
+alter table t1 engine= myisam;
+--replace_result $engine_type <engine_type>
+--eval alter table t1 engine= $engine_type
+drop table t1;
+--echo #
+--echo # ... unless the foreign key has something else than RESTRICT
+--echo # as one of cascading actions (the default is NO ACTION).
+--echo #
+create table t1 (pk int primary key,
+                 fk int constraint c references t1 (pk));
+--error ER_FK_NON_TRANSACTIONAL_NO_ACTION
+alter table t1 engine= myisam;
+alter table t1 drop constraint c,
+               add constraint c foreign key (fk) references t1 (pk)
+               on update cascade on delete cascade;
+--error ER_FK_NON_TRANSACTIONAL_CASCADING_ACTION
+alter table t1 engine= myisam;
+drop table t1;
+--echo #
+--echo # Let us also try setting some harmless (from foreign key POV)
+--echo # option, e.g. COMMENT. ALTER TABLE setting such options should
+--echo # work normally for tables with foreign keys.
+--echo #
+create table t1 (pk int primary key);
+create table t2 (fk int constraint c references t1 (pk));
+alter table t2 comment='A comment';
+--replace_result $engine_type <engine_type>
+show create table t2;
+alter table t1 comment='Another comment';
+--replace_result $engine_type <engine_type>
+show create table t1;
+drop tables t1, t2;
+
+--echo # 
+--echo # For tests covering interaction of ALTER TABLE with partitioning clauses
+--echo # and table with foreign keys see foreign_key_all_engines_2.test.
+--echo #
+
+--echo #
+--echo # Let us have basic coverage for the order in which ALTER TABLE's
+--echo # clauses are processed. The idea is that all DROP clauses are
+--echo # processed before corresponding ADD clauses. We also process
+--echo # DROP CONSTRAINT/FOREIGN KEY clauses before COLUMN/other KEY
+--echo # clauses in order to be able to change properties of columns/keys
+--echo # participating in foreign keys even though doing this directly
+--echo # is prohibited.
+--echo #
+create table t1 (pk int primary key);
+insert into t1 values (1), (2);
+create table t2 (fk int constraint c references t1 (pk));
+insert into t2 values (NULL), (2);
+--echo #
+--echo # Let us show that dropping foreign key, changing column and
+--echo # then re-adding foreign key is safe because in the process
+--echo # orphan child rows introduced by column change are detected.
+--echo #
+--error ER_FK_CHILD_NO_MATCH
+alter table t2 drop constraint c,
+               modify column fk int not null,
+               add constraint c foreign key (fk) references t1 (pk);
+--echo #
+--echo # But if there are no orphans as result the same statement is OK.
+--echo #
+insert into t1 values (0);
+alter table t2 drop constraint c,
+               modify column fk int not null,
+               add constraint c foreign key (fk) references t1 (pk);
+--replace_result $engine_type <engine_type>
+show create table t2;
+drop tables t1, t2;
+--echo #
+--echo # Also when ADD CONSTRAINT is mixed with other ALTER TABLE's
+--echo # clauses checks that are performed during creation of foreign key
+--echo # should be based on table's final state and not some stale version
+--echo # (in other words it should look as if ADD CONSTRAINT was processed
+--echo # after all other ALTER TABLE's clauses).
+--echo #
+create table t1 (pk int primary key, fk int);
+--error ER_FK_PARENT_KEY_NO_PK_OR_UNIQUE
+alter table t1 add constraint c foreign key (fk) references t1 (pk),
+               drop primary key;
+alter table t1 drop primary key;
+alter table t1 add constraint c foreign key (fk) references t1 (pk),
+               add primary key (pk);
+drop table t1;
+
+--echo #
+--echo # Even though CREATE INDEX is just another form of ALTER TABLE
+--echo # let us have coverage which shows how it works in the presence
+--echo # of foreign keys.
+--echo #
+--echo # Adding index which covers columns participating in a foreign
+--echo # key is OK.
+create table t1 (pk int primary key, a int);
+create table t2 (fk int constraint c references t1 (pk), b int);
+create index b on t2 (fk, b);
+--replace_result $engine_type <engine_type>
+show create table t2;
+create index a on t1 (pk, a);
+--replace_result $engine_type <engine_type>
+show create table t1;
+drop tables t1, t2;
+--echo #
+--echo # CREATE INDEX does not support IGNORE clause so removal of rows from
+--echo # a parent table which can happen when one adds UNIQUE index on it
+--echo # will always result in an error and won't create orphans.
+--echo #
+--echo # Until bug #45885 "Primary key added to Falcon table allows
+--echo # non-unique values" this part of the test uses InnoDB and resides
+--echo # in foreign_key_all_engines_2.test.
+
+--echo #
+--echo # Let us also test how DROP INDEX (yet another variant of ALTER TABLE)
+--echo # interacts with foreign keys.
+--echo #
+--echo # It is disallowed to drop parent key for a foreign key relationship.
+--echo # Also it is prohibited to drop last suitable supporting index for it.
+--echo #
+create table t1 (pk int primary key);
+create table t2 (fk int constraint c references t1 (pk), key c (fk));
+--error ER_FK_PARENT_SO_UNIQUE_INDEX_UNDROPPABLE
+drop index `primary` on t1;
+--error ER_FK_CHILD_SO_CHILD_INDEX_UNDROPPABLE
+drop index c on t2;
+--echo #
+--echo # It should be OK to drop supporting index if there is another
+--echo # suitable one.
+--echo #
+create index d on t2 (fk);
+drop index c on t2;
+drop tables t1, t2;
+create table t1 (pk int not null unique);
+create table t2 (fk int constraint c references t1 (pk));
+--error ER_FK_PARENT_SO_UNIQUE_INDEX_UNDROPPABLE
+drop index pk on t1;
+--echo #
+--echo # Unlike for supporting indexes the original parent key is undroppable
+--echo # even if there is another suitable key. 
+--echo # It is OK to drop this another key.
+--echo #
+create unique index u on t1 (pk); 
+--error ER_FK_PARENT_SO_UNIQUE_INDEX_UNDROPPABLE
+drop index pk on t1;
+drop index u on t1;
+--echo #
+--echo # But it is OK to drop an unrelated key.
+--echo #
+alter table t1 add column a int, add unique (a);
+drop index a on t1;
+alter table t2 add column a int, add key (a);
+drop index a on t2;
+drop tables t1, t2;
+--echo # 
+--echo # Coverage for use of prepared statemetns and ALTER on table
+--echo # with foreign keys.
+--echo # We need to check that foreign keys with auto-generated names
+--echo # work okay during re-execution.
+--echo #
+create table t1 (pk int primary key);
+create table  t2 (fk int, fk1 int);
+prepare stmt from "alter table t2 add constraint c foreign key (fk)
+references t1 (pk), add constraint foreign key (fk1) references t1 (pk)";
+execute stmt;
+show create table t2;
+--error ER_FK_CONSTRAINT_NAME_DUPLICATE
+execute stmt;
+drop table t2;
+create table  t2 (fk int, fk1 int);
+execute stmt;
+show create table t2;
+flush table t1, t2;
+--error ER_FK_CONSTRAINT_NAME_DUPLICATE
+execute stmt;
+flush table t1, t2;
+drop table t2;
+create table  t2 (fk int, fk1 int);
+execute stmt;
+drop table t1, t2;
+deallocate prepare stmt;
+--echo #
+--echo # Test the situation when we try to add a foreign key on 
+--echo # a view that uses stored functions.
+--echo #
+create table t1 (pk int primary key);

+create table t2 (a int);
+create table t3 (a int);
+create table t4 (fk int);
+create trigger t2_ai after insert on t2 for each row insert into t3 (a)
+values (new.a);
+create procedure p1(p_a int) insert into t2 (a) values (p_a);
+delimiter |;
+create function f1(p_a int) returns int begin call p1(p_a); return p_a; end|
+delimiter ;|
+create view v1 as select f1(pk) as pk from t1;
+--error ER_FK_PARENT_VIEW
+alter table t4 add constraint c foreign key (fk) references v1 (pk);
+lock table t4 write, v1 write;
+--error ER_FK_PARENT_VIEW
+alter table t4 add constraint c foreign key (fk) references v1 (pk);
+drop table t1, t2, t3, t4;
+unlock tables;
+drop view v1;
+drop procedure p1;
+drop function f1;
+
+--echo #
+--echo # Coverage for the 14th milestone of WL#148 "Foreign keys"
+--echo # ("DDL checks and changes: DROP, TRUNCATE, RENAME").
+--echo #
+--disable_warnings
+drop tables if exists t1, t2, t3, t4, t1_2, t2_2, t3_2, t1_3, t2_3, t3_3;
+drop database if exists mysqltest;
+--enable_warnings
+
+--echo # 
+--echo # DROP TABLES.
+--echo # 
+--echo # Let us check that we disallow dropping of parent tables
+--echo # without dropping child tables.
+create table t1 (pk int primary key);
+create table t2 (fk int constraint c references t1 (pk));
+--error ER_FK_CHILD_TABLE_EXISTS
+drop table t1;
+--echo # But dropping a child without the parent should be allowed.
+drop table t2;
+--echo # Also dropping the child and the parent simultaneously should be fine.
+create table t2 (fk int references t1 (pk));
+drop tables t1, t2;
+--echo # Dropping a table with a self-reference also should be OK.
+create table t1 (pk int primary key, fk int references t1 (pk));
+drop table t1;
+--echo # Now let us check that dropping a child without without a parent
+--echo # leaves no traces in the parent description (this is a test for
+--echo # Bug #42063 "Foreign keys: constraint survives after table 
+--echo # is dropped").
+create table t1 (pk int primary key);
+create table t2 (fk int references t1 (pk));
+insert into t1 values (1);
+drop table t2;
+--echo # This update should succeed and should not emit any errors
+update t1 set pk = 0;
+drop table t1;
+--echo # A bit more complex scenario in which we drop two tables
+--echo # with foreign keys.
+create table t1 (pk int primary key);
+create table t2 (pk int primary key);
+create table t3 (fk int references t1 (pk));
+create table t4 (fk int references t2 (pk));
+drop tables t3, t4;
+--echo # Now a scenario with more complex dependencies
+drop table t2;
+create table t2 (pk int primary key, fk int references t1 (pk));
+create table t4 (pk int primary key, fk int references t1 (pk));
+create table t3 (fk1 int references t1 (pk), fk2 int references t2 (pk),
+                 fk3 int references t4 (pk));
+drop tables t4, t3, t2;
+--echo # An even more complex scenario involving 3 clusters of
+--echo # differently connected tables.
+create table t2 (fk int references t1 (pk));
+create table t3 (fk int references t1 (pk));
+create table t1_2 (pk int primary key);
+create table t2_2 (pk int primary key);
+create table t3_2 (fk1 int references t1_2 (pk), fk2 int references t2_2 (pk));
+create table t1_3 (pk int primary key);
+create table t2_3 (pk int primary key, fk int references t1_3 (pk));
+create table t3_3 (fk int references t2_3 (pk));
+--echo # Let us try to drop all child tables in some arbitrary order.
+drop tables t2, t2_3, t3, t3_2, t3_3;
+--echo # Re-create children and try to drop all tables at once.
+create table t2 (fk int references t1 (pk));
+create table t3 (fk int references t1 (pk));
+create table t3_2 (fk1 int references t1_2 (pk), fk2 int references t2_2 (pk));
+create table t2_3 (pk int primary key, fk int references t1_3 (pk));
+create table t3_3 (fk int references t2_3 (pk));
+drop tables t1, t1_2, t1_3, t2, t2_2, t2_3, t3, t3_2, t3_3;
+--echo # Now let us test dropping of tables with circular references.
+--echo # Let us check the most trivial scenario first. 
+create table t1 (pk int primary key, fk int);
+create table t2 (pk int primary key, fk int constraint a references t1 (pk));
+alter table t1 add constraint b foreign key (fk) references t2 (pk);
+--echo # It should not possible to drop only one of tables.
+--error ER_FK_CHILD_TABLE_EXISTS
+drop table t1;
+--error ER_FK_CHILD_TABLE_EXISTS
+drop table t2;
+--echo # But dropping both of them at once should be fine.
+drop tables t1, t2;
+--echo # Now let us check more complicated scenario with circular references.
+create table t1 (pk int primary key);
+create table t2 (pk int primary key, fk int);
+create table t3 (pk int primary key,
+                 fk1 int constraint a references t1 (pk),
+                 fk2 int constraint b references t2 (pk));
+create table t4 (pk int primary key, fk int constraint c references t3 (pk));
+alter table t2 add constraint d foreign key (fk) references t4 (pk);
+--echo # Again minimal subset of tables to be dropped should fully
+--echo # include reference cycle. Let us check that we can't drop
+--echo # some of the smaller subsets.
+--error ER_FK_CHILD_TABLE_EXISTS
+drop table t2, t4;
+--error ER_FK_CHILD_TABLE_EXISTS
+drop table t2, t3;
+--error ER_FK_CHILD_TABLE_EXISTS
+drop table t4, t3;
+--error ER_FK_CHILD_TABLE_EXISTS
+drop table t1, t2, t3;
+--echo # Subset in the below statement fully includes cycle and
+--echo # therefore it should be possible to drop it.
+drop tables t2, t3, t4;
+drop table t1;
+
+--echo # Let us also check scenarios in which there is a temporary
+--echo # table shadowing one of the tables participating in the constraint.
+--echo #
+create table t1 (pk int primary key);
+create table t2 (fk int constraint c references t1 (pk));
+--echo # Attempt to drop the parent without dropping the child should
+--echo # lead to an error.
+create temporary table t2 (i int);
+--error ER_FK_CHILD_TABLE_EXISTS
+drop tables t1, t2;
+drop temporary table t2;
+--echo #
+--echo # When dropping the child we should not be confused by the
+--echo # presence of a temporary table, that is shadowing the parent.
+--echo #
+create temporary table t1 (i int);
+drop table t2;
+--echo #
+--echo # Check that parent .FRM was properly modified.
+--echo #
+drop temporary table t1;
+--echo #
+--echo # The below statements should succeed.
+--echo #
+insert into t1 values (1);
+update t1 set pk= 0;
+--echo #
+--echo # Finally a bit more complicated scenario in which we
+--echo # are dropping the temporary table and modify .FRM of
+--echo # the parent table which is shadowed by this temporary table.
+--echo #
+create table t2 (fk int references t1 (pk));
+create temporary table t1 (i int);
+drop tables t1, t2;
+drop table t1;
+
+--echo # Until bug #44230 "Altering Falcon table under LOCK TABLES fails with
+--echo # "Can't lock file" error" is fixed tests for DROP TABLE under LOCK
+--echo # TABLES have to reside in foreign_key_all_engines_2.test.
+
+--echo # Let us check that we are able to drop a table participating in a 
+--echo # foreign key even if the other table in this relationship does
+--echo # not exist (i.e. if the data-dictionary is inconsistent).
+--echo # First let us drop a child with non-existing parent.
+create table t1 (pk int primary key) engine=myisam;
+create table t2 (fk int references t1 (pk)
+                        on delete restrict on update restrict) engine=myisam;
+--echo # Now let us simulate manual dropping of parent table.
+flush table t1;
+let $MYSQLD_DATADIR= `select @@datadir`;
+--remove_file $MYSQLD_DATADIR/test/t1.frm
+--remove_file $MYSQLD_DATADIR/test/t1.MYI
+--remove_file $MYSQLD_DATADIR/test/t1.MYD
+--echo # Dropping of child should succeed.
+drop table t2;
+--echo # Perform a similar test for parent table.
+create table t1 (pk int primary key) engine=myisam;
+create table t2 (fk int references t1 (pk)
+                        on delete restrict on update restrict) engine=myisam;
+--echo # Simulate manual dropping of table t2.
+flush table t2;
+--remove_file $MYSQLD_DATADIR/test/t2.frm
+--remove_file $MYSQLD_DATADIR/test/t2.MYI
+--remove_file $MYSQLD_DATADIR/test/t2.MYD
+--echo # The below statement should succeed. We have to mention both tables
+--echo # in it as otherwise error about attempt to drop parent table without
+--echo # child table will be reported.
+drop tables if exists t1, t2;
+
+--echo # Test that CREATE and DROP TABLE are able to modify .FRMs of parent
+--echo # table even if this table was created under more relaxed sql_mode and
+--echo # thus, for example, straightforward attempt to alter such table might
+--echo # fail. We want to allow creation/dropping of foreign keys in such
+--echo # circumstances since, after all, the fact that we modify .FRM of
+--echo # the parent in the process is just an implementation detail.
+--echo #
+set @old_sql_mode:= @@sql_mode;
+set sql_mode='ALLOW_INVALID_DATES';
+--echo # Create a parent-to-be with defaults which prohibit its re-creation
+--echo # or normal altering in 'traditional' mode.
+create table t1 (pk int primary key,
+                 a datetime default '2009-03-30 00:00:00',
+                 b datetime default '2009-03-00 00:00:00',
+                 c datetime default '0000-00-00 00:00:00');
+set sql_mode= 'TRADITIONAL';
+--echo # The below CREATE and DROP TABLE should succeed.
+create table t2 (fk int references t1 (pk));
+drop table t2;
+set sql_mode= @old_sql_mode;
+drop table t1;
+
+
+--echo #
+--echo # DROP DATABASE
+--echo #
+--echo # From the foreign keys point of view DROP DATABASE is simply a
+--echo # special form of DROP TABLES.
+--echo # 
+--echo # It should be impossible to drop a database containing tables which
+--echo # are referenced by tables from other databases.
+create database mysqltest;
+create table mysqltest.t1 (pk int primary key);
+create table test.t2 (fk int constraint c references mysqltest.t1 (pk));
+--error ER_FK_CHILD_TABLE_EXISTS
+drop database mysqltest;
+--echo # It should be possible to drop a database which tables are not
+--echo # referenced from other databases.
+drop table test.t2;
+create table mysqltest.t2 (fk int references mysqltest.t1 (pk));
+drop database mysqltest;
+--echo # Also it should be possible to drop a database which is not
+--echo # referenced from other databases, but itself constains tables
+--echo # referencing to other databases.
+create table t1 (pk int primary key);
+create database mysqltest;
+create table mysqltest.t2 (fk int references test.t1 (pk));
+drop database mysqltest;
+drop table t1;
+
+--echo # Test DROP DATABASE which has foreign keys and
+--echo # damaged tables lying around.
+create database mysql_test;
 create table mysql_test.t1(f1 int primary key);
 create table mysql_test.t2 (a int references mysql_test.t1 (f1));
 insert into mysql_test.t2 (a) values (null);
@@ -1845,6 +3306,30 @@ drop table t1;
 
 
 --echo #
+--echo # Additional test for bug #39932 "create table fails if column for FK
+--echo # is in different case than in corr index".
+--echo #
+--echo # See --foreign-key-all-engines=0 version of it in innodb_mysql.test.
+--echo #
+--disable_warnings
+drop tables if exists t1, t2;
+--enable_warnings
+create table t1 (pk int primary key);
+--echo #
+--echo # Even though the below statement uses uppercased field names in
+--echo # foreign key definition it still should be able to find explicitly
+--echo # created supporting index and primary key on parent column. So it
+--echo # should succeed and should not create any additional supporting
+--echo # indexes.
+--echo #
+create table t2 (fk int, key x (fk),
+                 constraint x foreign key (FK) references t1 (PK));
+--replace_result $engine_type <engine_type>
+show create table t2;
+drop tables t1, t2;
+
+
+--echo #
 --echo # Test for bug #41687 "Foreign keys: crash if varbinary or varchar or
 --echo #                      datetime or decimal".
 --echo #
@@ -2385,3 +3870,137 @@ update t1 set pk= 2;
 --error ER_DUP_ENTRY
 delete from t1 where pk = 1;
 drop tables t1, t2, t3, t4;
+
+
+--echo #
+--echo # Test for bug #45985 "Foreign keys: crash if drop duplicate index"
+--echo #
+--echo # Server crashed when user tried to drop multi-column index which
+--echo # might have been used by a foreign key (the fact that index was
+--echo # duplicate was not significant).
+--disable_warnings
+drop table if exists t1, t2;
+--enable_warnings
+create table t1 (s1 int not null, s2 int not null,
+                 unique i1 (s1, s2));
+create table t2 (s1 int, s2 int,
+                 key j1 (s1, s2),
+                 constraint c foreign key (s1, s2) references t1 (s1, s2));
+--error ER_FK_PARENT_SO_UNIQUE_INDEX_UNDROPPABLE
+drop index i1 on t1;
+--error ER_FK_CHILD_SO_CHILD_INDEX_UNDROPPABLE
+drop index j1 on t2;
+--echo # Also test situation which were originally described in the bug report.
+create unique index i2 on t1 (s1, s2);
+create index j2 on t2 (s1, s2);
+--echo # It is OK to drop 'i2' since it is different unique key which is used
+--echo # as a parent key.
+drop index i2 on t1;
+--echo # And it is OK to drop 'j2' since there is other supporting index.
+drop index j2 on t2;
+drop tables t1, t2;
+
+
+--echo #
+--echo # Test for bug #46124  "Foreign keys: crash if alter table drop foreign
+--echo # key".
+--echo # 
+--echo # In --foreign-key-all-engines=1 mode ALTER TABLE with DROP FOREIGN KEY
+--echo # clause without foreign key name specified crashed server even if 
+--echo # table didn't exist. After the fix such statement should produce
+--echo # ER_PARSE_ERROR in all cases.
+--disable_warnings
+drop table if exists t1, t2;
+--enable_warnings
+--error ER_PARSE_ERROR
+alter table t1 drop foreign key;
+create table t1 (pk int primary key);
+create table t2 (fk int references t1 (pk));
+--error ER_PARSE_ERROR
+alter table t2 drop foreign key;
+drop tables t1, t2;
+
+
+--echo #
+--echo # Test for bug #46136 "Foreign keys: adding primary keys causes dangling
+--echo # reference".
+--echo #
+--disable_warnings
+drop table if exists t1, t2;
+--enable_warnings
+create table t1 (s1 int not null unique);
+insert into t1 values (1), (2), (3);
+create table t2 (s1 int constraint c references t1 (s1));
+insert into t2 values (NULL), (1), (2), (3);
+--echo # Making child columns of a foreign key part of PRIMARY KEY should be
+--echo # disallowed as this can replace NULL values in these columns with
+--echo # type's default values and thus create orphans.
+--error ER_FK_CANT_CHANGE_COLUMN
+alter table t2 add primary key (s1);
+--echo # Making parent columns part of PRIMARY KEY should be OK as they are
+--echo # required to have NOT NULL attribute already.
+alter table t1 add primary key (s1);
+select * from t1;
+select * from t2;
+drop table t2;
+--echo # OTOH making non-nullable child columns part of PRIMARY KEY should
+--echo # be fine.
+create table t2 (s1 int not null references t1 (s1));
+alter table t2 add primary key (s1);
+drop tables t1, t2;
+
+
+--echo #
+--echo # Test for bug #46138 "Foreign keys: unjustified error when dropping
+--echo #                      primary key".
+--echo #
+--echo # Dropping of UNIQUE/PRIMARY keys should be allowed even if they
+--echo # serve as supporting key for some foreign key. As replacement we
+--echo # should generate an equivalent non-unique key. An appropriate
+--echo # warning should be also emitted.
+--disable_warnings
+drop table if exists t1, t2;
+--enable_warnings
+create table t1 (pk varchar(5) primary key);
+create table t2 (fk varchar(5) constraint c references t1 (pk),
+                 primary key (fk));
+--replace_result $engine_type <engine_type>
+show create table t2;
+alter table t2 drop primary key;
+--replace_result $engine_type <engine_type>
+show create table t2;
+drop table t2;
+create table t2 (fk varchar(5) constraint c references t1 (pk),
+                 unique u (fk));
+--replace_result $engine_type <engine_type>
+show create table t2;
+alter table t2 drop key u;
+--replace_result $engine_type <engine_type>
+show create table t2;
+drop table t2;
+--echo # Also check that we don't create replacement key and emit warning
+--echo # if another suitable key exists or going to be created explicitly.
+create table t2 (fk varchar(5) constraint c references t1 (pk),
+                 unique u (fk), key k (fk));
+--replace_result $engine_type <engine_type>
+show create table t2;
+alter table t2 drop key u;
+--replace_result $engine_type <engine_type>
+show create table t2;
+drop table t2;
+create table t2 (fk varchar(5) constraint c references t1 (pk),
+                 primary key (fk), unique u (fk));
+--replace_result $engine_type <engine_type>
+show create table t2;
+alter table t2 drop key u;
+--replace_result $engine_type <engine_type>
+show create table t2;
+drop table t2;
+create table t2 (fk varchar(5) constraint c references t1 (pk),
+                 unique u (fk));
+--replace_result $engine_type <engine_type>
+show create table t2;
+alter table t2 drop key u, add key k (fk);
+--replace_result $engine_type <engine_type>
+show create table t2;
+drop tables t1, t2;

=== modified file 'mysql-test/t/foreign_key_all_engines_2.test'
--- a/mysql-test/t/foreign_key_all_engines_2.test	2009-05-27 03:08:49 +0000
+++ b/mysql-test/t/foreign_key_all_engines_2.test	2009-07-31 15:10:25 +0000
@@ -319,6 +319,280 @@ drop table t3, t2, t1;
 
 
 --echo #
+--echo # Additional coverage for the 13th milestone of WL#148 "Foreign keys"
+--echo # ("DDL checks and changes: ALTER, CREATE INDEX, DROP INDEX").
+--echo #
+--disable_warnings
+drop tables if exists t1, t2, t3, t4;
+--enable_warnings
+--echo #
+--echo # Until several problems with Falcon are fixed tests below
+--echo # has to use InnoDB engine.
+--echo #
+set storage_engine= InnoDB;
+let $engine_type= InnoDB;
+
+--echo # The below test is here only until bug #45885 "Primary key added to
+--echo # Falcon table allows non-unique values" is fixed.
+--echo #
+--echo # There is one subtle moment though when we create PRIMARY KEY or
+--echo # UNIQUE constraint on a parent table we should ensure that this
+--echo # action won't delete any rows. Thus IGNORE clause of ALTER TABLE
+--echo # should be ignored in this case [sic!].
+create table t1 (a int not null unique, b int, c int);
+insert into t1 values (1, 1, 1), (2, 1, 2);
+create table t2 (fk int constraint c references t1 (a));
+insert into t2 values (1), (2);
+--error ER_DUP_ENTRY
+alter table t1 add primary key (b);
+--error ER_DUP_ENTRY
+alter ignore table t1 add primary key (b);
+--error ER_DUP_ENTRY
+alter table t1 add unique (b);
+--error ER_DUP_ENTRY
+alter ignore table t1 add unique (b);
+--echo # Adding PRIMARY/UNIQUE without deleting rows should be fine.
+alter table t1 add unique u (c);
+--echo # Also ALTER TABLE using IGNORE clause should generate a warning.
+alter table t1 drop key u;
+alter ignore table t1 add unique u (c);
+drop tables t1, t2;
+
+--echo # 
+--echo # Let us check how ALTER TABLE with partitioning clauses works on
+--echo # tables with foreign keys.
+--echo # The only thing which should be prohibited is DROP PARTITION on
+--echo # parent table as all other operations do not really change
+--echo # contents of tables.
+--echo #
+create table t1 (pk int primary key);
+insert into t1 values (1), (2), (3), (4), (5);
+create table t2 (fk int, constraint c foreign key (fk) references t1 (pk));
+insert into t2 values (1), (2), (3), (4), (5);
+alter table t1 partition by range (pk) (partition a values less than (3),
+                                        partition b values less than (6));
+alter table t2 partition by list (fk) (partition a values in (1, 2, 3),
+                                       partition b values in (4, 5));
+alter table t1 add partition (partition c values less than maxvalue);
+alter table t2 add partition (partition c values in (6, 7));
+--echo #
+--echo # Dropping partition in parent is illegal as this might
+--echo # remove parent values for which child values exist.
+--echo #
+--error ER_FK_STATEMENT_ILLEGAL
+alter table t1 drop partition b;
+--echo #
+--echo # OTOH dropping partition in child table should be fine.
+--echo #
+alter table t2 drop partition b;
+select * from t2;
+--echo #
+--echo # Changing the "driver" engine of the partitioned
+--echo # engine from transactional to non-transactional
+--echo # or vice versa is not allowed.
+--echo #
+--error ER_FK_NON_TRANSACTIONAL_AND_TRANSACTIONAL
+alter table t1 engine=myisam;
+--echo #
+--echo # REBUILD/REORGANIZE PARTITION work OK.
+--echo #
+alter table t1 rebuild partition a;
+alter table t2 rebuild partition a;
+alter table t1 reorganize partition a into (partition aa values less than (2),
+                                            partition ab values less than (3));
+alter table t2 reorganize partition a into (partition aa values in (1, 2),
+                                            partition ab values in (3));
+alter table t1 remove partitioning;
+alter table t2 remove partitioning;
+select * from t1;
+select * from t2;
+drop tables t1, t2;
+create table t1 (pk int primary key) partition by hash (pk) partitions 4;
+insert into t1 values (1), (2), (3), (4), (5);
+create table t2 (fk int references t1 (pk)) partition by hash (fk) partitions 5;
+insert into t2 values (1), (2), (3), (4), (5);
+alter table t1 coalesce partition 2;
+alter table t2 coalesce partition 3;
+select * from t1;
+select * from t2;
+drop tables t1, t2;
+
+--echo # 
+--echo # Finally, let us test how ALTER TABLE ... ADD/DROP CONSTRAINT
+--echo # works under LOCK TABLES mode.
+--echo #
+--echo # Let us begin with ALTER TABLE ... ADD CONSTRAINT.
+create table t1 (a int primary key);
+insert into t1 values (1);
+create table t2 (a int);
+insert into t2 values (1);
+create table t3 (a int);
+--echo #
+--echo # To add constraint you need to have write-lock on both child
+--echo # and parent tables.
+--echo #
+lock tables t3 read;
+--error ER_TABLE_NOT_LOCKED
+alter table t2 add constraint c foreign key (a) references t1 (a);
+unlock tables;
+lock tables t2 read;
+--error ER_TABLE_NOT_LOCKED
+alter table t2 add constraint c foreign key (a) references t1 (a);
+unlock tables;
+lock tables t2 read, t1 read;
+--error ER_TABLE_NOT_LOCKED_FOR_WRITE
+alter table t2 add constraint c foreign key (a) references t1 (a);
+unlock tables;
+lock tables t2 write, t1 read;
+--error ER_TABLE_NOT_LOCKED_FOR_WRITE
+alter table t2 add constraint c foreign key (a) references t1 (a);
+unlock tables;
+lock tables t2 write, t1 write;
+--echo #
+--echo # Add constraint and check that metadata for both tables
+--echo # were properly updated.
+--echo #
+alter table t2 add constraint c foreign key (a) references t1 (a);
+--replace_result $engine_type <engine_type>
+show create table t2;
+--error ER_FK_CHILD_VALUE_EXISTS
+delete from t1 where a = 1;
+unlock tables;
+--error ER_FK_CONSTRAINT_NAME_DUPLICATE
+create table t4 (pk int primary key, fk int constraint c references t4 (pk));
+--echo #
+--echo # Now let's try ALTER TABLE ... DROP CONSTRAINT.
+--echo # 
+--echo # Again we need write-locks on both child and parent tables.
+--echo #
+lock tables t3 read;
+--error ER_TABLE_NOT_LOCKED
+alter table t2 drop constraint c;
+unlock tables;
+lock tables t2 read;
+--error ER_TABLE_NOT_LOCKED
+alter table t2 drop constraint c;
+unlock tables;
+lock tables t2 read, t1 read;
+--error ER_TABLE_NOT_LOCKED_FOR_WRITE
+alter table t2 drop constraint c;
+unlock tables;
+lock tables t2 write, t1 read;
+--error ER_TABLE_NOT_LOCKED_FOR_WRITE
+alter table t2 drop constraint c;
+unlock tables;
+lock tables t2 write, t1 write;
+--echo #
+--echo # Now let's drop constraint and check that metadata for both
+--echo # tables were properly updated.
+--echo #
+alter table t2 drop constraint c;
+show create table t2;
+delete from t1 where a = 1;
+unlock tables;
+create table t4 (pk int primary key, fk int constraint c references t4 (pk));
+drop tables t4;
+--echo #
+--echo # Try to drop and add constraint within one statement.
+--echo #
+insert into t1 values (1);
+alter table t2 add constraint c foreign key (a) references t1 (a);
+lock tables t1 write, t2 write;
+alter table t2 drop constraint c,
+               add constraint c foreign key (a) references t1 (a);
+--echo #
+--echo # Check that metadata was properly updated for both child and parent.
+--echo #
+--replace_result $engine_type <engine_type>
+show create table t2;
+--error ER_FK_CHILD_VALUE_EXISTS
+delete from t1 where a = 1;
+--echo # And now using different statements.
+alter table t2 drop constraint c;
+alter table t2 add constraint c foreign key (a) references t1 (a);
+--echo #
+--echo # Check that metadata was properly updated for both child and parent.
+--echo #
+--replace_result $engine_type <engine_type>
+show create table t2;
+--error ER_FK_CHILD_VALUE_EXISTS
+delete from t1 where a = 1;
+unlock tables;
+--error ER_FK_CONSTRAINT_NAME_DUPLICATE
+create table t4 (pk int primary key, fk int constraint c references t4 (pk));
+--echo #
+--echo # Let us also check what happens if ALTER fails for some reason.
+--echo #
+lock tables t1 write, t2 write;
+--error ER_FK_NO_SUCH_CONSTRAINT
+alter table t2 drop constraint c, drop constraint d;
+--echo #
+--echo # Check that metadata was not changed.
+--echo #
+--replace_result $engine_type <engine_type>
+show create table t2;
+--error ER_FK_CHILD_VALUE_EXISTS
+delete from t1 where a = 1;
+--error ER_FK_PARENT_COLUMN_MISSING
+alter table t2 drop constraint c,
+               add constraint d foreign key (a) references t1 (no_such_column);
+--echo #
+--echo # Check that metadata was not changed.
+--echo #
+--replace_result $engine_type <engine_type>
+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.
+--echo #
+--error ER_FK_PARENT_COLUMN_MISSING
+alter table t2 drop constraint c,
+               add constraint c foreign key (a) references t1 (no_such_column);
+--echo #
+--echo # Check that metadata was not changed.
+--echo #
+--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.
+--echo #
+--error ER_FK_NO_SUCH_CONSTRAINT
+alter table t2 drop constraint c, drop constraint c;
+--error ER_FK_CONSTRAINT_NAME_DUPLICATE
+alter table t2 add constraint d foreign key (a) references t1 (a),
+               add constraint d foreign key (a) references t1 (a);
+unlock tables;
+drop tables t1, t2, t3;
+
+--echo #
+--echo # The below test is here only until bug #45885 "Primary key added to
+--echo # Falcon table allows non-unique values" is fixed.
+--echo #
+--echo # CREATE INDEX does not support IGNORE clause so removal of rows from
+--echo # a parent table which can happen when one adds UNIQUE index on it
+--echo # will always result in an error and won't create orphans.
+--echo #
+create table t1 (a int not null unique, b int, c int);
+insert into t1 values (1, 1, 1), (2, 1, 2);
+create table t2 (fk int constraint c references t1 (a));
+insert into t2 values (1), (2);
+--error ER_DUP_ENTRY
+create unique index b on t1 (b);
+--echo #
+--echo # Adding UNIQUE index without deleting rows should be fine.
+--echo #
+create unique index c on t1 (c);
+drop tables t1, t2;
+
+set storage_engine= Falcon;
+let $engine_type= Falcon;
+
+
+--echo #
 --echo # Additional coverage for the 14th milestone of WL#148 "Foreign keys"
 --echo # ("DDL checks and changes: DROP, TRUNCATE, RENAME").
 --echo #
@@ -570,3 +844,33 @@ show status like "Qcache_hits";
 --echo # Clean-up
 drop tables t1, t2, t3;
 set global query_cache_size= default;
+
+
+--echo #
+--echo # Test for bug #46125 "Foreign keys: reorganizing partitions causes
+--echo #                      dangling reference".
+--echo #
+--disable_warnings
+drop tables if exists t1, t2;
+--enable_warnings
+create table t1 (pk int primary key)
+  partition by list (pk) (partition p1 values in (1, 2));
+insert into t1 values (1), (2);
+create table t2 (fk int constraint c references t1 (pk));
+insert into t2 values (1), (2);
+--echo #
+--echo # Reorganizations of partitions which lead to dangling references should
+--echo # be prohibited.
+--echo #
+--error ER_FK_STATEMENT_ILLEGAL
+alter table t1 reorganize partition p1 into (partition p1 values in (0, 1));
+select * from t1;
+select * from t2;
+--echo #
+--echo # OTOH reorganizations which preserve data should be fine.
+--echo #
+alter table t1 reorganize partition p1 into
+  (partition p2 values in (0, 1), partition p3 values in (2, 3));
+select * from t1;
+select * from t2;
+drop tables t1, t2;

=== modified file 'mysql-test/t/foreign_key_all_engines_3.test'
--- a/mysql-test/t/foreign_key_all_engines_3.test	2009-05-27 03:08:49 +0000
+++ b/mysql-test/t/foreign_key_all_engines_3.test	2009-07-31 15:10:25 +0000
@@ -1,6 +1,5 @@
-# Additional test-coverage for the 14th and other milestones of
-# WL#148 "Foreign keys". This file is for tests which should be
-# run only for debug build of server.
+# Additional test-coverage for various  milestones of WL#148 "Foreign keys".
+# This file is for tests which should be run only for debug build of server.
 #
 # We use Falcon as default engine in order to avoid errors caused
 # by lack of support of NO ACTION/CASCADE/SET NULL/SET DEFAULT for
@@ -14,6 +13,351 @@ let $engine_type= Falcon;
 
 
 --echo #
+--echo # Additional coverage for the 13th milestone of WL#148 "Foreign keys"
+--echo # ("DDL checks and changes: ALTER, CREATE INDEX, DROP INDEX words").
+--echo # 
+--echo # Check how ALTER TABLE handles errors which occur on various stages
+--echo # of its execution. Note that her we focus on hard to trigger errors
+--echo # as other errors should be tested elsewhere.
+--disable_warnings
+drop tables if exists t1, t2, t3, t4, t5; 
+--enable_warnings
+set @old_debug= @@debug;
+
+--echo #
+--echo # First, let us check what happens when we fail to upgrade metadata
+--echo # lock on table being altered and parents tables which .FRMs should
+--echo # modified.
+--echo #
+create table t1 (pk int primary key);
+create table t2 (pk int primary key);
+create table t3 (fk1 int constraint c1 references t1 (pk), fk2 int);
+insert into t1 values (1);
+insert into t2 values (1);
+insert into t3 values (1, 1);
+--echo #
+--echo # Simulate killing of DROP TABLES while performing metadata lock upgrade.
+--echo #
+set debug= "d,upgrade_mdl_for_altered_and_parent_tables_simulate_kill";
+--error ER_QUERY_INTERRUPTED
+alter table t3 drop constraint c1,
+               add constraint c2 foreign key (fk2) references t2 (pk);
+set debug= @old_debug;
+--echo #
+--echo # Let us check that constraint c1 was not dropped,
+--echo # c2 was not created and FRMs for all tables involved are intact.
+--echo #
+--replace_result $engine_type <engine_type>
+show create table t3;
+--error ER_FK_CHILD_VALUE_EXISTS
+delete from t1 where pk = 1;
+delete from t2 where pk = 1;
+--error ER_FK_CONSTRAINT_NAME_DUPLICATE
+create table t4 (pk int primary key, fk int constraint c1 references t4 (pk));
+create table t4 (pk int primary key, fk int constraint c2 references t4 (pk));
+drop table t1, t2, t3, t4;
+--echo #
+--echo # Let us check what happens when we can't install new .FRM for
+--echo # the parent table.
+--echo #
+create table t1 (pk int primary key);
+create table t2 (pk int primary key);
+create table t3 (pk int primary key);
+create table t4 (fk1 int constraint c1 references t2 (pk), fk2 int, fk3 int);
+insert into t1 values (1);
+insert into t2 values (1);
+insert into t3 values (1);
+insert into t4 values (1, 1, 1);
+set debug= "d,fk_install_new_frms_fail_for_t1";
+--error ER_UNKNOWN_ERROR
+alter table t4 drop constraint c1,
+               add constraint c2 foreign key (fk2) references t3 (pk),
+               add constraint c3 foreign key (fk3) references t1 (pk);
+set debug= @old_debug;
+--echo #
+--echo # Again let us check that .FRMs are not changed, and constraint
+--echo # names were not touched.
+--echo #
+--replace_result $engine_type <engine_type>
+show create table t4;
+delete from t1 where pk = 1;
+--error ER_FK_CHILD_VALUE_EXISTS
+delete from t2 where pk = 1;
+delete from t3 where pk = 1;
+--error ER_FK_CONSTRAINT_NAME_DUPLICATE
+create table t5 (pk int primary key, fk int constraint c1 references t5 (pk));
+create table t5 (pk int primary key, fk int constraint c2 references t5 (pk));
+drop table t5;
+create table t5 (pk int primary key, fk int constraint c3 references t5 (pk));
+drop table t5;
+
+--echo #
+--echo # Now let us check what happens when we can't create/drop .CNS files
+--echo # for constraints being added/dropped.
+--echo #
+insert into t1 values (1);
+insert into t3 values (1);
+set debug= "d,fk_install_new_frms_fail_drop_cns";
+--error ER_UNKNOWN_ERROR
+alter table t4 drop constraint c1,
+               add constraint c2 foreign key (fk2) references t3 (pk),
+               add constraint c3 foreign key (fk3) references t1 (pk);
+set debug= @old_debug;
+--echo #
+--echo # Again let us check that .FRMs are not changed, and constraint
+--echo # names were not touched.
+--echo #
+--replace_result $engine_type <engine_type>
+show create table t4;
+delete from t1 where pk = 1;
+--error ER_FK_CHILD_VALUE_EXISTS
+delete from t2 where pk = 1;
+delete from t3 where pk = 1;
+--error ER_FK_CONSTRAINT_NAME_DUPLICATE
+create table t5 (pk int primary key, fk int constraint c1 references t5 (pk));
+create table t5 (pk int primary key, fk int constraint c2 references t5 (pk));
+drop table t5;
+create table t5 (pk int primary key, fk int constraint c3 references t5 (pk));
+drop table t5;
+
+--echo #
+--echo # Now test for scenario when attempt to replace old version of table
+--echo # with its new version fails.
+--echo #
+insert into t1 values (1);
+insert into t3 values (1);
+set debug= "d,alter_table_rename_table_fail";
+--error ER_UNKNOWN_ERROR
+alter table t4 drop constraint c1,
+               add constraint c2 foreign key (fk2) references t3 (pk),
+               add constraint c3 foreign key (fk3) references t1 (pk);
+set debug= @old_debug;
+--echo #
+--echo # Again let us check that .FRMs are not changed, and constraint
+--echo # names were not touched.
+--echo #
+--replace_result $engine_type <engine_type>
+show create table t4;
+delete from t1 where pk = 1;
+--error ER_FK_CHILD_VALUE_EXISTS
+delete from t2 where pk = 1;
+delete from t3 where pk = 1;
+--error ER_FK_CONSTRAINT_NAME_DUPLICATE
+create table t5 (pk int primary key, fk int constraint c1 references t5 (pk));
+create table t5 (pk int primary key, fk int constraint c2 references t5 (pk));
+drop table t5;
+create table t5 (pk int primary key, fk int constraint c3 references t5 (pk));
+drop tables t1, t2, t3, t4, t5;
+
+--echo #
+--echo # Now let us perform similar checks for "fast/online" version of ALTER.
+--echo #
+--echo # We can't check that operations are performed as "fast/online" ALTER
+--echo # TABLE by using --enable_info and verifying that number of affected
+--echo # rows is 0 as failed statements do not return this information.
+--echo # Instead we rely on that foreign_key_all_engines.test checks this.
+--echo # 
+--echo # Again let us begin with scenarion in which we fail to upgrade metadata
+--echo # lock on table being altered and parents tables which .FRMs should
+--echo # modified.
+--echo #
+create table t1 (pk int primary key);
+create table t2 (pk int primary key);
+create table t3 (fk1 int constraint c1 references t1 (pk), fk2 int, key (fk2));
+insert into t1 values (1);
+insert into t2 values (1);
+insert into t3 values (1, 1);
+--echo #
+--echo # Turn-off FOREIGN_KEY_CHECKS to enable "fast" ALTER TABLE for
+--echo # addition of foreign key.
+--echo #
+set foreign_key_checks= 0;
+set debug= "d,upgrade_mdl_for_altered_and_parent_tables_simulate_kill";
+--error ER_QUERY_INTERRUPTED
+alter table t3 drop constraint c1,
+               add constraint c2 foreign key (fk2) references t2 (pk);
+set debug= @old_debug;
+set foreign_key_checks= 1;
+--echo #
+--echo # Let us check that constraint c1 was not drop, c2 was not created and
+--echo # FRMs for all tables involved are intact.
+--echo #
+--replace_result $engine_type <engine_type>
+show create table t3;
+--error ER_FK_CHILD_VALUE_EXISTS
+delete from t1 where pk = 1;
+delete from t2 where pk = 1;
+--error ER_FK_CONSTRAINT_NAME_DUPLICATE
+create table t4 (pk int primary key, fk int constraint c1 references t4 (pk));
+create table t4 (pk int primary key, fk int constraint c2 references t4 (pk));
+drop table t1, t2, t3, t4;
+--echo #
+--echo # Test for for failure to install new .FRM for parent table during
+--echo # "fast" ALTER TABLE;
+--echo #
+create table t1 (pk int primary key);
+create table t2 (pk int primary key);
+create table t3 (pk int primary key);
+create table t4 (fk1 int constraint c1 references t2 (pk), fk2 int, fk3 int,
+                 key (fk2), key (fk3));
+insert into t1 values (1);
+insert into t2 values (1);
+insert into t3 values (1);
+insert into t4 values (1, 1, 1);
+set foreign_key_checks= 0;
+set debug= "d,fk_install_new_frms_fail_for_t1";
+--error ER_UNKNOWN_ERROR
+alter table t4 drop constraint c1,
+               add constraint c2 foreign key (fk2) references t3 (pk),
+               add constraint c3 foreign key (fk3) references t1 (pk);
+set debug= @old_debug;
+set foreign_key_checks= 1;
+--echo #
+--echo # Again let us check that .FRMs are not changed, and constraint
+--echo # names were not touched.
+--echo #
+--replace_result $engine_type <engine_type>
+show create table t4;
+delete from t1 where pk = 1;
+--error ER_FK_CHILD_VALUE_EXISTS
+delete from t2 where pk = 1;
+delete from t3 where pk = 1;
+--error ER_FK_CONSTRAINT_NAME_DUPLICATE
+create table t5 (pk int primary key, fk int constraint c1 references t5 (pk));
+create table t5 (pk int primary key, fk int constraint c2 references t5 (pk));
+drop table t5;
+create table t5 (pk int primary key, fk int constraint c3 references t5 (pk));
+drop table t5;
+--echo #
+--echo # Check what happens when we can't create/drop .CNS files
+--echo # for constraints being added/dropped.
+--echo #
+insert into t1 values (1);
+insert into t3 values (1);
+set foreign_key_checks= 0;
+set debug= "d,fk_install_new_frms_fail_drop_cns";
+--error ER_UNKNOWN_ERROR
+alter table t4 drop constraint c1,
+               add constraint c2 foreign key (fk2) references t3 (pk),
+               add constraint c3 foreign key (fk3) references t1 (pk);
+set debug= @old_debug;
+set foreign_key_checks= 1;
+--echo #
+--echo # Again let us check that .FRMs are not changed, and constraint
+--echo # names were not touched.
+--echo #
+--replace_result $engine_type <engine_type>
+show create table t4;
+delete from t1 where pk = 1;
+--error ER_FK_CHILD_VALUE_EXISTS
+delete from t2 where pk = 1;
+delete from t3 where pk = 1;
+--error ER_FK_CONSTRAINT_NAME_DUPLICATE
+create table t5 (pk int primary key, fk int constraint c1 references t5 (pk));
+create table t5 (pk int primary key, fk int constraint c2 references t5 (pk));
+drop table t5;
+create table t5 (pk int primary key, fk int constraint c3 references t5 (pk));
+drop table t5;
+
+--echo #
+--echo # Now let us check what happens when attempt to replace old version of
+--echo # table being altered with its new version fails (for "fast" ALTER).
+--echo #
+insert into t1 values (1);
+insert into t3 values (1);
+set foreign_key_checks= 0;
+set debug= "d,fast_or_online_alter_table_rename_table_fail";
+--error ER_UNKNOWN_ERROR
+alter table t4 drop constraint c1,
+               add constraint c2 foreign key (fk2) references t3 (pk),
+               add constraint c3 foreign key (fk3) references t1 (pk);
+set debug= @old_debug;
+set foreign_key_checks= 1;
+--echo #
+--echo # Again let us check that .FRMs are not changed, and constraint
+--echo # names were not touched.
+--echo #
+--replace_result $engine_type <engine_type>
+show create table t4;
+delete from t1 where pk = 1;
+--error ER_FK_CHILD_VALUE_EXISTS
+delete from t2 where pk = 1;
+delete from t3 where pk = 1;
+--error ER_FK_CONSTRAINT_NAME_DUPLICATE
+create table t5 (pk int primary key, fk int constraint c1 references t5 (pk));
+create table t5 (pk int primary key, fk int constraint c2 references t5 (pk));
+drop table t5;
+create table t5 (pk int primary key, fk int constraint c3 references t5 (pk));
+drop tables t1, t2, t3, t4, t5;
+
+--echo #
+--echo # Finally let us tests how ALTER TABLE failures are handled when it
+--echo # is executed under LOCK TABLES.
+--echo # 
+--echo # Until bugs #44230 'Altering Falcon table under LOCK TABLES fails
+--echo # with "Can't lock file" error' and #45035 'Altering table under LOCK
+--echo # TABLES results in "Error 1213 Deadlock found..."' are fixed we have
+--echo # to use InnoDB for this test.
+--echo #
+set storage_engine= InnoDB;
+let $engine_type= InnoDB;
+
+create table t1 (pk int primary key);
+create table t2 (pk int primary key);
+create table t3 (fk1 int constraint c1 references t1 (pk), fk2 int);
+insert into t1 values (1);
+insert into t2 values (1);
+insert into t3 values (1, 1);
+lock tables t1 write, t2 write, t3 write;
+--echo #
+--echo # Check what happens when we fail to upgrade metadata locks.
+--echo #
+set debug= "d,upgrade_mdl_for_altered_and_parent_tables_simulate_kill";
+--error ER_QUERY_INTERRUPTED
+alter table t3 drop constraint c1,
+               add constraint c2 foreign key (fk1) references t1 (pk);
+set debug= @old_debug;
+--echo #
+--echo # Let us test that tables are still locked and were left intact.
+--echo #
+--replace_result $engine_type <engine_type>
+show create table t3;
+--error ER_FK_CHILD_VALUE_EXISTS
+delete from t1 where pk = 1;
+delete from t2 where pk = 1;
+insert into t2 values (1);
+--echo #
+--echo # Now let us see how server handles failure when locks are already
+--echo # updated.
+--echo #
+set debug= "d,alter_table_rename_table_fail";
+--error ER_UNKNOWN_ERROR
+alter table t3 drop constraint c1,
+               add constraint c2 foreign key (fk2) references t2 (pk);
+set debug= @old_debug;
+--echo #
+--echo # Table which we tried to alter and all parent tables involved
+--echo # should be unlocked but left intact.
+--echo #
+--error ER_TABLE_NOT_LOCKED
+select * from t1;
+--error ER_TABLE_NOT_LOCKED
+select * from t2;
+--error ER_TABLE_NOT_LOCKED
+select * from t3;
+unlock tables;
+--replace_result $engine_type <engine_type>
+show create table t3;
+--error ER_FK_CHILD_VALUE_EXISTS
+delete from t1 where pk = 1;
+delete from t2 where pk = 1;
+drop tables t1, t2, t3;
+
+set storage_engine= Falcon;
+let $engine_type= Falcon;
+
+
+--echo #
 --echo # Additional coverage for the 14th milestone of WL#148 "Foreign keys"
 --echo # ("DDL checks and changes: DROP, TRUNCATE, RENAME").
 --echo #
@@ -24,7 +368,7 @@ drop tables if exists t1, t2, t3, t4; 
 --enable_warnings
 set @old_debug= @@debug;
 
---echo # First of all let us check that happens when we can't properly
+--echo # First of all let us check what happens when we can't properly
 --echo # update information in .FRM of parent table.
 
 --echo # To begin with let us try to drop only child table.

=== modified file 'mysql-test/t/innodb_mysql.test'
--- a/mysql-test/t/innodb_mysql.test	2009-03-30 11:17:49 +0000
+++ b/mysql-test/t/innodb_mysql.test	2009-07-31 15:10:25 +0000
@@ -334,6 +334,25 @@ DROP TABLE t3;
 
 --echo End of 5.1 tests
 
+
+--echo #
+--echo # Test for bug #39932 "create table fails if column for FK is in different
+--echo #                      case than in corr index".
+--echo #
+--disable_warnings
+drop tables if exists t1, t2;
+--enable_warnings
+create table t1 (pk int primary key) engine=InnoDB;
+--echo # Even although the below statement uses uppercased field names in
+--echo # foreign key definition it still should be able to find explicitly
+--echo # created supporting index. So it should succeed and should not
+--echo # create any additional supporting indexes.
+create table t2 (fk int, key x (fk),
+                 constraint x foreign key (FK) references t1 (PK)) engine=InnoDB;
+show create table t2;
+drop table t2, t1;
+
+
 --echo #
 --echo # BUG#42744: Crash when using a join buffer to join a table with a blob
 --echo # column and an additional column used for duplicate elimination.

=== modified file 'sql/fk.cc'
--- a/sql/fk.cc	2009-05-23 13:41:04 +0000
+++ b/sql/fk.cc	2009-07-31 15:10:25 +0000
@@ -101,12 +101,15 @@ public:
   ~Foreign_key_child_rcontext();
 
   bool prepare();
+  bool prepare(TABLE *parent_table);
   bool check_parent_exists();
   bool do_eos_check();
 
 private:
   void can_eos_check_help(bool *can_eos_check_in_child_help,
                           bool *can_eos_check_in_parent_help);
+  bool prepare_check();
+
 private:
   THD *m_thd;
   TABLE_LIST m_parent_table_list;
@@ -520,6 +523,198 @@ Fk_constraint_list::prepare_check_parent
 
 
 /**
+  For ALTER TABLE operation on the table, prepare runtime contexts for foreign
+  keys which are added by this statement and which may require checking.
+
+  This is invoked when we need to copy data from the old table to the
+  new table in ALTER that performs a deep table copy.
+
+  @param thd             Thread context.
+  @param table           TABLE object representing new version of table being
+                         altered. This version has a correct
+                         definition (all constraints are already created),
+                         but is not populated with any data.
+  @param table_tmp_name  Temporary name for new version of table being altered
+                         (used for creation of additional TABLE object for
+                          table being altered if some self-referencing foreign
+                          keys need to be checked).
+  @param fk_list         List of foreign keys in new version of table, with new
+                         foreign keys marked.
+
+  @retval FALSE  Success.
+  @retval TRUE   Error, out of resources.
+*/
+
+bool
+Fk_constraint_list::prepare_alter_check_parent(THD *thd, TABLE *table,
+                                               const char *table_tmp_name,
+                                               List<Foreign_key_child> &fk_list)
+{
+  List_iterator<Foreign_key_child_share> fk_s_it(table->s->fkeys.fkeys_child);
+  Foreign_key_child_share *fk_s;
+  List_iterator<Foreign_key_child> fk_def_it(fk_list);
+  Foreign_key_child *fk_def;
+
+  /*
+    The list is emptied in the constructor. Prepare must be called only
+    once.
+  */
+  DBUG_ASSERT(m_fkeys_child.is_empty());
+
+  /* No foreign key checks should be performed when FOREIGN_KEY_CHECKS is 0. */
+  if (thd->options & OPTION_NO_FOREIGN_KEY_CHECKS)
+    return FALSE;
+
+  DBUG_ASSERT(m_table == 0 || m_table == table);
+  m_table= table;
+
+  /* For each new constraint prepare a runtime context for checks. */
+  while ((fk_def= fk_def_it++))
+  {
+    TABLE *parent_table;
+    Foreign_key_child_rcontext *rctx;
+
+    if (fk_def->exists)
+      continue;                                 /* skip existing constraints */
+
+    fk_s_it.rewind();
+
+    /* Find the corresponding share object. */
+    while ((fk_s= fk_s_it++))
+      if (! my_strcasecmp(system_charset_info, fk_s->name.str,
+                          fk_def->name.str))
+        break;
+
+    DBUG_ASSERT(fk_s || !opt_fk_all_engines);
+
+    if (! fk_s)                                 /* running in old mode. */
+      continue;
+
+    if (fk_s->is_self_reference)
+    {
+      if (opt_create_extra_table(thd, table, table_tmp_name))
+        return TRUE;
+      parent_table= m_extra_table;
+    }
+    else
+      parent_table= fk_def->parent_table->table;
+
+    /* Create a context for a newly added constraint. */
+    rctx= new (thd->mem_root)
+           Foreign_key_child_rcontext(thd, table, fk_s, this);
+
+    if (rctx == NULL || rctx->prepare(parent_table) ||
+        m_fkeys_child.push_back(rctx, thd->mem_root))
+    {
+      delete rctx;
+      return TRUE;
+    }
+  }
+  return FALSE;
+}
+
+
+/**
+  Create an additional instance of the table that is being altered.
+
+  To perform checks for self-referencing foreign key constraints on
+  table being altered we have to create an additional TABLE object
+  for its new version. To do this we use an approach similar to
+  one used in open_temporary_table(), with the exception that here
+  we already have properly loaded TABLE_SHARE object for table.
+
+  Create m_extra_table only if it's not yet done: it might have been
+  created for another self-referencing constraint.
+
+  @todo At some point in future we will probably use a cursor created
+        through cursor API instead of a full-blown TABLE instance.
+
+  @return TRUE if out of memory, otherwise FALSE.
+*/
+
+bool Fk_constraint_list::opt_create_extra_table(THD *thd, TABLE *table,
+                                                const char *table_tmp_name)
+{
+  if (! m_extra_table)
+  {
+    if (! (m_extra_table= (TABLE*) my_malloc(sizeof(*m_extra_table),
+                                             MYF(MY_WME))))
+      return TRUE;
+
+    if (open_table_from_share(thd, table->s, table_tmp_name,
+                              (uint) (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE |
+                                      HA_GET_INDEX),
+                              (READ_KEYINFO | COMPUTE_TYPES |
+                               EXTRA_RECORD),
+                              ha_open_options, m_extra_table, OTM_OPEN))
+    {
+      my_free((char*) m_extra_table, MYF(0));
+      m_extra_table= 0;
+      return TRUE;
+    }
+
+    m_extra_table->reginfo.lock_type= TL_READ_NO_INSERT;
+    m_extra_table->pos_in_table_list= 0;
+  }
+  return FALSE;
+}
+
+
+/**
+   Set external lock for extra TABLE instance which was created for performing
+   checks for self-referencing foreign keys during ALTER TABLE.
+
+   @param thd  Thread context.
+
+   @note Some engines use handler::ha_external_lock() called by this method as
+         a mark of statement boundary and to set up some structures needed for
+         statement execution.
+
+   @retval FALSE  Success.
+   @retval TRUE   Error.
+*/
+
+bool Fk_constraint_list::lock_extra_table(THD *thd)
+{
+  int error;
+
+  if (m_extra_table &&
+      (error= m_extra_table->file->ha_external_lock(thd, F_RDLCK)))
+  {
+    m_extra_table->file->print_error(error, MYF(0));
+    return TRUE;
+  }
+  return FALSE;
+}
+
+
+/**
+   "Unlock" extra TABLE instance which was created for performing checks for
+   self-referencing foreign keys during ALTER TABLE.
+
+   @param thd  Thread context.
+
+   @sa Fk_constraint_list::lock_extra_table()
+
+   @retval FALSE  Success.
+   @retval TRUE   Error.
+*/
+
+bool Fk_constraint_list::unlock_extra_table(THD *thd)
+{
+  int error;
+
+  if (m_extra_table &&
+      (error= m_extra_table->file->ha_external_lock(thd, F_UNLCK)))
+  {
+    m_extra_table->file->print_error(error, MYF(0));
+    return TRUE;
+  }
+  return FALSE;
+}
+
+
+/**
   For UPDATE or DELETE operation on the table prepare runtime contexts
   for all foreign keys for which this table is a parent and which may
   require checks or cascading actions.
@@ -610,6 +805,12 @@ Fk_constraint_list::~Fk_constraint_list(
 
   while ((parent_ctx= cascade_it++))
     delete parent_ctx;
+
+  if (m_extra_table)
+  {
+    close_temporary(m_extra_table, FALSE, FALSE);
+    my_free((char*) m_extra_table, MYF(0));
+  }
 }
 
 
@@ -1170,6 +1371,40 @@ bool Foreign_key_child_rcontext::prepare
 
   m_parent_table= m_parent_table_list.table;
 
+  return prepare_check();
+}
+
+
+/**
+  Prepare a foreign key runtime context for checks performed when a
+  statement modifies a child table.
+
+  @param parent_table  TABLE instance for the parent table to be used
+                       for checks.
+
+  @retval FALSE  Success.
+  @retval TRUE   Error (data-dictionary inconsistency, OOM, etc).
+*/
+
+bool Foreign_key_child_rcontext::prepare(TABLE *parent_table)
+{
+  m_parent_table= parent_table;
+  return prepare_check();
+}
+
+
+/**
+  Prepare a foreign key runtime context for checks performed when
+  a statement modifies a child table, given that the context already has
+  information about TABLE instance of the parent table to be used for
+  checks.
+
+  @retval FALSE  Success.
+  @retval TRUE   Error (data-dictionary inconsistency, OOM, etc).
+*/
+
+bool Foreign_key_child_rcontext::prepare_check()
+{
   /************* Step 2: find a supporting key. ***********/
 
   /* Find a PRIMARY/UNIQUE index which will be used for lookups in parent. */

=== modified file 'sql/fk.h'
--- a/sql/fk.h	2009-03-27 19:19:55 +0000
+++ b/sql/fk.h	2009-07-31 15:10:25 +0000
@@ -38,7 +38,7 @@ public:
   Fk_constraint_list(trg_event_type action_type_arg,
                      Fk_constraint_list *parent_fk_list)
     : m_action_type(action_type_arg),
-      m_table(NULL),
+      m_table(NULL), m_extra_table(NULL),
       m_parent_fk_list(parent_fk_list)
   { }
 
@@ -49,6 +49,10 @@ public:
   bool prepare_check_parent(THD *thd, TABLE *table,
                             MY_BITMAP *changed_column_map);
 
+  bool prepare_alter_check_parent(THD *thd, TABLE *table,
+                                  const char *table_tmp_name,
+                                  List<Foreign_key_child> &fk_list);
+
   bool prepare_check_child(THD *thd, TABLE *table,
                            bool old_row_is_record1,
                            MY_BITMAP *changed_column_map);
@@ -107,6 +111,9 @@ public:
 
   bool has_triggers();
 
+  bool lock_extra_table(THD *thd);
+  bool unlock_extra_table(THD *thd);
+
 private:
   /** Implementation. */
 
@@ -120,6 +127,8 @@ private:
 
   void do_invalidate_query_cache();
 
+  bool opt_create_extra_table(THD *thd, TABLE *table,
+                              const char *table_tmp_name);
 private:
   /**
     Type of performed operation: INSERT, UPDATE or DELETE. Defines
@@ -131,6 +140,13 @@ private:
   */
   TABLE *m_table;
   /**
+    Additional TABLE instance which is created for performing checks for
+    self-referencing foreign keys during ALTER TABLE. Unlike instance
+    from 'm_table' member it is created within a method of this class and
+    it is responsibility of this class to destroy it at its end-of-life.
+  */
+  TABLE *m_extra_table;
+  /**
     List of foreign key constraints in which the subject table
     participates as a child.
   */

=== modified file 'sql/fk_dd.cc'
--- a/sql/fk_dd.cc	2009-06-01 11:37:20 +0000
+++ b/sql/fk_dd.cc	2009-07-31 15:10:25 +0000
@@ -70,18 +70,32 @@ uint Foreign_key_parent::get_frm_descrip
     of parent table we save database and name of child.
     If child and parent tables belong to the same database we don't store
     name of database in .FRM (actually we store empty string instead).
+    If child and parent are actually the same table we don't store
+    their name in .FRM (again, we store an empty string instead).
   */
   if (!is_within_one_db)
     length+= get_table_for_frm()->db_length;
   length+= FK_FRM_NAMES_SEP_SIZE;
-  length+= get_table_for_frm()->table_name_length + FK_FRM_NAMES_SEP_SIZE;
+  if (!is_self_reference)
+    length+= get_table_for_frm()->table_name_length;
+  length+= FK_FRM_NAMES_SEP_SIZE;
 
   while ((col= col_it1++))
     length+= col->field_name.length + FK_FRM_NAMES_SEP_SIZE;
   while ((col= col_it2++))
     length+= col->field_name.length + FK_FRM_NAMES_SEP_SIZE;
 
-  length+= get_unique_constraint_name_frm_length();
+  /*
+    We store the name of the unique constraint that is used as
+    a parent key in .FRMs of both child and parent tables. The
+    child table needs this name in order to provide a proper
+    value for UNIQUE_CONSTRAINT_NAME column of
+    I_S.TABLE_CONSTRAINTS view. The parent table needs this
+    name in order for ALTER TABLE to be able detect cases when
+    one attempts to drop a parent key on columns that are used in
+    a foreign key relationship.
+  */
+  length+= parent_constraint_name->length + FK_FRM_NAMES_SEP_SIZE;
 
   return length;
 }
@@ -100,18 +114,6 @@ uint Foreign_key_parent::get_match_opt_f
 
 
 /**
-  Get number of bytes to be reserved in parent .FRM for storing
-  name of unique constraint participating in this foreign key
-  (we don't store this information .FRM of parent so it is 0).
-*/
-
-uint Foreign_key_parent::get_unique_constraint_name_frm_length()
-{
-  return 0;
-}
-
-
-/**
   Get number of bytes to be reserved in child .FRM for storing match
   option of the foreign key.
 */
@@ -123,21 +125,6 @@ uint Foreign_key_child::get_match_opt_fr
 
 
 /**
-  Get number of bytes to be reserved in child .FRM for storing
-  name of unique constraint participating in this foreign key.
-
-  We store unique constraint name of the parent in the child
-  .FRM in order to properly reflect it in UNIQUE_CONSTRAINT_NAME
-  of I_S.TABLE_CONSTRAINTS view.
-*/
-
-uint Foreign_key_child::get_unique_constraint_name_frm_length()
-{
-  return parent_constraint_name->length + FK_FRM_NAMES_SEP_SIZE;
-}
-
-
-/**
   Calculate total length of .FRM section describing foreign keys.
 
   @param fkey_list        List of foreign keys for which this table is child.
@@ -167,6 +154,7 @@ uint fk_get_frm_section_length(List<Fore
 
 static const char FK_FRM_FLAG_TABLE_CONSTRAINT= 1;
 static const char FK_FRM_FLAG_WITHIN_ONE_DATABASE= 2;
+static const char FK_FRM_FLAG_SELF_REFERENCE= 4;
 
 
 /**
@@ -196,9 +184,12 @@ void Foreign_key_parent::save_to_frm(cha
 
   /*
     If child and parent belong to the same database we don't store its name.
+    If child and parent are actually the same table we don't store the
+    table name either.
   */
   *cur_pos= get_additional_flags() |
-            (is_within_one_db ? FK_FRM_FLAG_WITHIN_ONE_DATABASE : 0);
+            (is_within_one_db ? FK_FRM_FLAG_WITHIN_ONE_DATABASE : 0) |
+            (is_self_reference ? FK_FRM_FLAG_SELF_REFERENCE : 0);
   cur_pos+= FK_FRM_FLAGS_SIZE;
 
   /* Ensure that we are not saving non-fully-initialized object into .FRM. */
@@ -234,7 +225,8 @@ void Foreign_key_parent::save_to_frm(cha
     cur_pos= strmov(cur_pos, get_table_for_frm()->db);
   *cur_pos= NAMES_SEP_CHAR;
   cur_pos+= FK_FRM_NAMES_SEP_SIZE;
-  cur_pos= strmov(cur_pos, get_table_for_frm()->table_name);
+  if (!is_self_reference)
+    cur_pos= strmov(cur_pos, get_table_for_frm()->table_name);
   *cur_pos= NAMES_SEP_CHAR;
   cur_pos+= FK_FRM_NAMES_SEP_SIZE;
   while ((col= col_it1++))
@@ -249,7 +241,18 @@ void Foreign_key_parent::save_to_frm(cha
     *cur_pos= NAMES_SEP_CHAR;
     cur_pos+= FK_FRM_NAMES_SEP_SIZE;
   }
-  save_unique_constraint_name(&cur_pos);
+
+  /*
+    Save name of the unique constraint from the parent table participating
+    in the foreign key in .FRMs of both child and parent. This is necessary
+    to properly fill UNIQUE_CONSTRAINT_NAME field of I_S.TABLE_CONSTRAINTS
+    view and in order to prevent users from dropping this unique constraint
+    via ALTER TABLE.
+  */
+  cur_pos= strmov(cur_pos, parent_constraint_name->str);
+  *cur_pos= NAMES_SEP_CHAR;
+  cur_pos+= FK_FRM_NAMES_SEP_SIZE;
+
   /* Names & columns part ends. Write its length. */
   int2store(names_len_pos, cur_pos - (names_len_pos + FK_FRM_NAMES_LENGTH_SIZE));
 
@@ -297,17 +300,6 @@ TABLE_LIST* Foreign_key_parent::get_tabl
 
 
 /**
-  Save name of the unique constraint participating in the foreign key the
-  .FRM of parent table (does nothing as we don't store this information
-  in this .FRM).
-*/
-
-void Foreign_key_parent::save_unique_constraint_name(char **buff_p)
-{
-}
-
-
-/**
   Get flags for foreign key description in .FRM which are specific for
   description in child table.
 */
@@ -343,20 +335,6 @@ TABLE_LIST *Foreign_key_child::get_table
 
 
 /**
-  Save name of the unique constraint of the parent table participating
-  in the foreign key in the child .FRM. Necessary to properly fill
-  UNIQUE_CONSTRAINT_NAME field of I_S.TABLE_CONSTRAINTS view.
-*/
-
-void Foreign_key_child::save_unique_constraint_name(char **buff_p)
-{
-  *buff_p= strmov(*buff_p, parent_constraint_name->str);
-  **buff_p= NAMES_SEP_CHAR;
-  *buff_p+= FK_FRM_NAMES_SEP_SIZE;
-}
-
-
-/**
   Save descriptions of foreign keys in the .FRM file.
 
   @param thd              Thread context.
@@ -455,6 +433,7 @@ bool Foreign_key_share::restore_from_frm
   cur_pos+= FK_FRM_FK_DESCR_LENGTH_SIZE;
 
   is_within_one_db= (*cur_pos) & FK_FRM_FLAG_WITHIN_ONE_DATABASE;
+  is_self_reference= (*cur_pos) & FK_FRM_FLAG_SELF_REFERENCE;
   restore_additional_flags(*cur_pos);
   cur_pos+= FK_FRM_FLAGS_SIZE;
   update_opt= (enum_fk_option)*cur_pos;
@@ -475,14 +454,11 @@ bool Foreign_key_share::restore_from_frm
   names_cols_len= uint2korr(cur_pos);
   cur_pos+= FK_FRM_NAMES_LENGTH_SIZE;
   names_cols_buff= strmake_root(&share->mem_root, cur_pos, names_cols_len);
-  names_num= 3 +                                      // constraint, schema and
-                                                      // table names
-             column_count * 2 +                       // names of FK columns in
-                                                      // the child and of FK
-                                                      // columns in the parent
-             (has_unique_constraint_name() ? 1 : 0) + // optional name of unique
-                                                      // constraint for this FK
-             1;                                       // trailing NULL pointer
+  names_num= 3 +                // Constraint, schema and table names.
+             column_count * 2 + // Names of FK columns in the child and
+                                // of FK columns in the parent.
+             1 +                // Name of unique constraint for this FK.
+             1;                 // Trailing NULL pointer.
   if (!(multi_alloc_root(&share->mem_root,
                          &names_cols_arr, sizeof(char*) * names_num,
                          &names_cols_lengths, sizeof(unsigned int) * names_num,
@@ -522,13 +498,21 @@ bool Foreign_key_share::restore_from_frm
     db.str= (char *)names_cols_arr[1];
     db.length= names_cols_lengths[1];
   }
-  table_name.str= (char *)names_cols_arr[2];
-  table_name.length= names_cols_lengths[2];
+
+  if (is_self_reference)
+  {
+    table_name= share->table_name;
+  }
+  else
+  {
+    table_name.str= (char *)names_cols_arr[2];
+    table_name.length= names_cols_lengths[2];
+  }
 
   set_table_from_frm(db, table_name);
 
-  restore_unique_constraint_name(names_cols_arr[3 + 2 * column_count],
-                                 names_cols_lengths[3 + 2 * column_count]);
+  parent_constraint_name.str= (char *)names_cols_arr[3 + 2 * column_count];
+  parent_constraint_name.length= names_cols_lengths[3 + 2 * column_count];
 
   /* Skip part of description which comes from later versions of server. */
   *buff_p= fkey_descr_start + FK_FRM_FK_DESCR_LENGTH_SIZE + fkey_descr_length;
@@ -574,32 +558,6 @@ void Foreign_key_child_share::set_table_
 
 
 /**
-  Indicates if description of foreign key in .FRM contains
-  name of unique constraint in parent table participating
-  in the foreign key.
-*/
-
-bool Foreign_key_child_share::has_unique_constraint_name()
-{
-  return TRUE;
-}
-
-
-/**
-  Restore name of unique constraint in the parent table which
-  participates in the foreign key from the child .FRM.
-*/
-
-void
-Foreign_key_child_share::
-restore_unique_constraint_name(const char *name, uint name_len)
-{
-  parent_constraint_name.str= (char*)name;
-  parent_constraint_name.length= name_len;
-}
-
-
-/**
   Restore information about foreign key which was saved in .FRM as flags
   specific for foreign key description in parent table.
 */
@@ -635,31 +593,6 @@ void Foreign_key_parent_share::set_table
 
 
 /**
-  Indicates if description of foreign key in .FRM contains
-  name of unique constraint in parent table participating
-  in the foreign key.
-*/
-
-bool Foreign_key_parent_share::has_unique_constraint_name()
-{
-  return FALSE;
-}
-
-
-/**
-  Restore name of unique constraint in the parent table which participates
-  in the foreign key from the parent .FRM (Does nothing as we don't store
-  this information in .FRM of parent).
-*/
-
-void
-Foreign_key_parent_share::
-restore_unique_constraint_name(const char *name, uint name_len)
-{
-}
-
-
-/**
   Restore foreign keys for the table their description in the .FRM.
 
   @param thd      [in]     Thread context.
@@ -983,25 +916,9 @@ bool fk_generate_constraint_names(THD *t
 }
 
 
-Foreign_key_name::Foreign_key_name(LEX_STRING db_arg, LEX_STRING name_arg)
-  :m_db(db_arg),
-  m_name(name_arg)
+Foreign_key_name::Foreign_key_name(const char *db_arg, const char *name_arg)
 {
-  m_mdl_request.init(TYPE_ENUM_FOREIGN_KEY, m_db.str, m_name.str);
-}
-
-/**
-  Allocate and initalize metadata lock request for tshi foreign key name.
-
-  @param mem_root  Memory root for allocating lock request.
-
-  @retval non-NULL - Metadata lock request object for this foreign key name.
-  @revtal NULL     - Error (OOM).
-*/
-
-MDL_request *Foreign_key_name::get_mdl_request()
-{
-  return &m_mdl_request;
+  m_mdl_request.init(TYPE_ENUM_FOREIGN_KEY, db_arg, name_arg);
 }
 
 
@@ -1019,12 +936,8 @@ MDL_request *Foreign_key_name::get_mdl_r
 
 Foreign_key_name *
 Foreign_key_name::create(MEM_ROOT *mem_root,
-                         LEX_STRING db_arg, LEX_STRING name_arg)
+                         const char *db_arg, const char *name_arg)
 {
-  if (! (db_arg.str= strmake_root(mem_root, db_arg.str, db_arg.length)) ||
-      ! (name_arg.str= strmake_root(mem_root, name_arg.str, name_arg.length)))
-    return NULL;
-
   return new (mem_root) Foreign_key_name(db_arg, name_arg);
 }
 
@@ -1035,21 +948,26 @@ Foreign_key_name::create(MEM_ROOT *mem_r
 
 Foreign_key_name *Foreign_key_name::clone(MEM_ROOT *mem_root)
 {
-  Foreign_key_name *result;
-  LEX_STRING db, name;
+  return new (mem_root) Foreign_key_name(this);
+}
 
-  db= m_db;
-  name= m_name;
 
-  if (! (db.str= strmake_root(mem_root, db.str, db.length)) ||
-      ! (name.str= strmake_root(mem_root, name.str, name.length)) ||
-      ! (result= new (mem_root) Foreign_key_name(db, name)))
-    return NULL;
+/**
+  Find out if there is a shared upgradable metadata lock in the context
+  for this foreign key name and if yes get ticket for this lock.
 
-  result->m_mdl_request.set_type(m_mdl_request.type);
-  result->m_mdl_request.ticket= m_mdl_request.ticket;
+  @param mdl_context  MDL context which should be checked for presence
+                      of metadata lock.
 
-  return result;
+  @retval TRUE  - metadata lock was found, m_mdl_request::ticket points
+                  to corresponsing ticket.
+  @retval FALSE - metadata lock of specified type was not found.
+*/
+
+bool Foreign_key_name::find_ticket(MDL_context *mdl_context)
+{
+  m_mdl_request.set_type(MDL_SHARED_UPGRADABLE);
+  return (m_mdl_request.ticket= mdl_context->find_ticket(&m_mdl_request));
 }
 
 
@@ -1081,13 +999,21 @@ fk_find_name_in_list(List<Foreign_key_na
 
 
 /**
-  For each foreign key mentioned in CREATE/ALTER table specification, add
-  an entry to the list of foreign key names to acquire exclusive metadata
+  For each foreign key to-create mentioned in CREATE/ALTER table
+  specification, add an entry to the list of foreign key names to acquire
+  exclusive metadata locks on.
+
+  For each foreign key to be dropped according to ALTER table specification,
+  add an entry to the list of foreign key names to acquire exclusive metadata
   locks on.
 
-  @param thd        Thread context.
-  @param lex        LEX for the statement
-  @param fkey_list  List of foreign keys
+  @param thd         Thread context.
+  @param lex         LEX for the statement
+  @param fkey_list   List of foreign keys to be added.
+  @param alter_table Table which foreign keys are to be dropped.
+                     Can be NULL if drop_list is empty.
+  @param drop_list   List of Alter_drop clauses for the ALTER statement some
+                     of which specify names of constraints to be dropped.
 
   @note Keep in mind that elements added by this function are allocated
         on thread's current memory root.
@@ -1098,25 +1024,43 @@ fk_find_name_in_list(List<Foreign_key_na
   @retval TRUE  Error (OOM).
 */
 
-bool fk_add_constraint_names_to_lock(THD *thd, LEX *lex,
-                                     List<Foreign_key_child> &fkey_list)
-{
-  List_iterator<Foreign_key_child> fkey_it(fkey_list);
-  Foreign_key_child *fkey;
+bool
+fk_add_constraint_names_to_lock(THD *thd, LEX *lex,
+                                List<Foreign_key_child> &create_list,
+                                TABLE_LIST *alter_table,
+                                List<Alter_drop> &drop_list)
+{
+  List_iterator<Foreign_key_child> create_it(create_list);
+  List_iterator<Alter_drop> drop_it(drop_list);
+  Foreign_key_child *fkey_create;
+  Alter_drop *fkey_drop;
   Foreign_key_name *fk_name;
 
-  while ((fkey= fkey_it++))
+  while ((fkey_create= create_it++))
   {
-    LEX_STRING db_name = { fkey->child_table->db,
-                           fkey->child_table->db_length };
-
     if (! (fk_name= Foreign_key_name::create(thd->mem_root,
-                                             db_name, fkey->name)) ||
+                                             fkey_create->child_table->db,
+                                             fkey_create->name.str)) ||
         lex->foreign_keys_to_lock.push_back(fk_name, thd->mem_root))
-      return TRUE;
+      goto err;
+  }
+
+  while ((fkey_drop= drop_it++))
+  {
+    DBUG_ASSERT(alter_table);
+    if (fkey_drop->type == Alter_drop::FOREIGN_KEY)
+    {
+      if (! (fk_name= Foreign_key_name::create(thd->mem_root,
+                                               alter_table->db,
+                                               fkey_drop->name)) ||
+          lex->foreign_keys_to_lock.push_back(fk_name, thd->mem_root))
+        goto err;
+    }
   }
-  lex->has_own_foreign_keys_to_lock= TRUE;
   return FALSE;
+err:
+  lex->foreign_keys_to_lock.empty();
+  return TRUE;
 }
 
 
@@ -1194,11 +1138,11 @@ bool Foreign_key_name::create_constraint
   char fn_buff[FN_REFLEN];
 
   DBUG_ASSERT(current_thd->mdl_context.
-              is_exclusive_lock_owner(TYPE_ENUM_FOREIGN_KEY, m_db.str,
-                                      m_name.str));
+              is_exclusive_lock_owner(TYPE_ENUM_FOREIGN_KEY, db_name(),
+                                      table_name()));
 
-  file.length= build_table_filename(fn_buff, FN_REFLEN-1, m_db.str,
-                                    m_name.str, CNS_EXT, 0);
+  file.length= build_table_filename(fn_buff, FN_REFLEN-1, db_name(),
+                                    table_name(), CNS_EXT, 0);
   file.str= fn_buff;
 
   return sql_create_definition_file(NULL, &file,
@@ -1255,11 +1199,11 @@ bool Foreign_key_name::drop_constraint_n
   char fn_buff[FN_REFLEN];
 
   DBUG_ASSERT(current_thd->mdl_context.
-              is_exclusive_lock_owner(TYPE_ENUM_FOREIGN_KEY, m_db.str,
-                                      m_name.str));
+              is_exclusive_lock_owner(TYPE_ENUM_FOREIGN_KEY, db_name(),
+                                      table_name()));
 
-  build_table_filename(fn_buff, FN_REFLEN - 1, m_db.str,
-                       m_name.str, CNS_EXT, 0);
+  build_table_filename(fn_buff, FN_REFLEN - 1, db_name(),
+                       table_name(), CNS_EXT, 0);
   return my_delete(fn_buff, MYF(MY_WME));
 }
 
@@ -1297,6 +1241,67 @@ err:
 
 
 /**
+  Check if names of foreign keys which we are going to add or drop
+  were specified using deprecated FOREIGN KEY clause and emit an
+  appropriate warning or even error.
+
+  @param thd        Thread context
+  @param fkey_list  List of foreign keys to add.
+  @param drop_list  List of Alter_drop objects some of which specify
+                    name of foreign key to be dropped.
+
+  @retval FALSE  Success.
+  @retval TRUE   Error, name in FOREIGN KEY clause contradicts name
+                 specified in CONSTRAINT clause.
+*/
+
+bool fk_check_fkey_clause(THD *thd, List<Foreign_key_child> &fkey_list,
+                          List<Alter_drop> &drop_list)
+{
+  List_iterator<Foreign_key_child> add_it(fkey_list);
+  List_iterator<Alter_drop> drop_it(drop_list);
+  Foreign_key_child *fkey;
+  Alter_drop *drop;
+
+  while ((fkey= add_it++))
+  {
+    if (fkey->fkey_clause_name.str)
+    {
+      if (fkey->fkey_clause_name.str != fkey->name.str &&
+          my_strcasecmp(system_charset_info, fkey->fkey_clause_name.str,
+                        fkey->name.str))
+      {
+        /*
+          Some name was specified in FOREIGN KEY clause, but was not used as
+          a foreign key name (i.e. some name was also specified in CONSTRAINT
+          clause) and doesn't match the name specified in CONSTRAINT clause.
+        */
+        my_error(ER_FK_FOREIGN_KEY_ID_CONFLICT, MYF(0), fkey->name.str);
+        return TRUE;
+      }
+      else
+        push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
+                            ER_FK_FOREIGN_KEY_ID, ER(ER_FK_FOREIGN_KEY_ID),
+                            fkey->name.str);
+    }
+  }
+
+  while ((drop= drop_it++))
+  {
+    if (drop->type == Alter_drop::FOREIGN_KEY &&
+        drop->is_deprecated_foreign_key_clause)
+    {
+      push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
+                          ER_FK_FOREIGN_KEY_ID, ER(ER_FK_FOREIGN_KEY_ID),
+                          drop->name);
+    }
+  }
+
+  return FALSE;
+}
+
+
+/**
   Find definition of the foreign key column in the list of table's
   column definitions.
 
@@ -1596,26 +1601,6 @@ bool fk_check_constraint_added(THD *thd,
     DBUG_RETURN(TRUE);
   }
 
-  if (fkey->fkey_clause_name.str)
-  {
-    if (fkey->fkey_clause_name.str != fkey->name.str &&
-        my_strcasecmp(system_charset_info, fkey->fkey_clause_name.str,
-                      fkey->name.str))
-    {
-      /*
-        Some name was specified in FOREIGN KEY clause, but was not used as
-        a foreign key name (i.e. some name was also specified in CONSTRAINT
-        clause) and doesn't match the name specified in CONSTRAINT clause.
-      */
-      my_error(ER_FK_FOREIGN_KEY_ID_CONFLICT, MYF(0), fkey->name.str);
-      DBUG_RETURN(TRUE);
-    }
-    else
-      push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
-                          ER_FK_FOREIGN_KEY_ID, ER(ER_FK_FOREIGN_KEY_ID),
-                          fkey->name.str);
-  }
-
   /*
     All the primary-key constraints are named 'PRIMARY' so we
     should disallow creation of foreign keys with such name.
@@ -1625,13 +1610,14 @@ bool fk_check_constraint_added(THD *thd,
     my_error(ER_FK_CONSTRAINT_NAME_ILLEGAL, MYF(0), fkey->name.str);
     DBUG_RETURN(TRUE);
   }
-  if (fk_check_if_constraint_name_exists(fkey->child_table->db, fkey->name.str))
+  if (! fkey->replaces_existing &&
+      fk_check_if_constraint_name_exists(fkey->child_table->db, fkey->name.str))
   {
     my_error(ER_FK_CONSTRAINT_NAME_DUPLICATE, MYF(0), fkey->name.str);
     DBUG_RETURN(TRUE);
   }
   /*
-    Also check that there are no duplicate names among constraints
+    Check that there are no duplicate names among constraints
     which we are going to create.
   */
   List_iterator<Foreign_key_child> fkey_it2(alter_info->foreign_key_list);
@@ -1816,6 +1802,114 @@ bool fk_check_constraint_added(THD *thd,
 
 
 /**
+  Check if new storage engine for table is compatible with the foreign key
+  and emit an appropriate error otherwise.
+
+  @param thd             Thread context.
+  @param fkey            Foreign key to be checked.
+  @param old_table       Table list element for the table which was used for
+                         opening its old version.
+  @param new_file        Handler object for new version of table.
+
+  @retval FALSE  Foreign key is compatible with new engine.
+  @retval TRUE   Foreign keys is incompatible with new engine,
+                 an error was emitted.
+*/
+
+bool
+Foreign_key_parent::
+check_if_can_change_engine(THD *thd,
+                           TABLE_LIST *old_table, handler *new_file)
+{
+
+  if (!(thd->options & OPTION_NO_FOREIGN_KEY_CHECKS))
+  {

+    handler *old_file= old_table->table->file;
+
+    if (! is_self_reference &&
+        (new_file->ha_table_flags() & HA_NO_TRANSACTIONS) !=
+        (old_file->ha_table_flags() & HA_NO_TRANSACTIONS))
+    {
+      my_error(ER_FK_NON_TRANSACTIONAL_AND_TRANSACTIONAL, MYF(0), name.str);
+      return TRUE;
+    }
+    /*
+      We can't guarantee integrity if non-transactional update/delete,
+      and (CASCADE | SET NULL | SET DEFAULT). (For example, imagine
+      what will happen if we encounter some problem in the middle of
+      cascading update). So disallow it.
+     */
+    if (has_cascading_action())
+    {
+      if (new_file->ha_table_flags() & HA_NO_TRANSACTIONS)
+      {
+        my_error(ER_FK_NON_TRANSACTIONAL_CASCADING_ACTION, MYF(0),
+                 name.str, old_table->table_name);
+        return TRUE;
+      }
+    }
+    /*
+      We can't fully support NO ACTION (i.e. end of statement checks)
+      for non-transactional tables. Therefore we disallow it. (in the
+      best case it will give false negatives during INSERT and behave
+      like RESTRICT for UPDATE/DELETE). Alternative is emit warning in
+      this case.
+    */
+    if ((update_opt == FK_OPTION_NO_ACTION ||
+         delete_opt == FK_OPTION_NO_ACTION))
+    {
+      if (new_file->ha_table_flags() & HA_NO_TRANSACTIONS)
+      {
+        my_error(ER_FK_NON_TRANSACTIONAL_NO_ACTION, MYF(0), name.str,
+                 old_table->table_name);
+        return TRUE;
+      }
+    }
+  }
+  return FALSE;
+}
+
+
+/**
+  Check if storage engine of table can be changed without making its existing
+  foreign keys illegal and emit an appropriate error if they are incompatible
+  with new storage engine.
+
+  @param thd         Thread context.
+  @param alter_info  Alter_info object describing new version of the table.
+  @param old_table   Table list element for the table which was used for
+                     opening its old version.
+  @param new_file    Handler object for new version of table.
+
+  @retval FALSE  Exisiting foreign keys are compatible with new engine.
+  @retval TRUE   Existing foreign keys are incompatible with new engine,
+                 an error was emitted.
+*/
+
+bool
+fk_check_if_can_change_engine(THD *thd, Alter_info *alter_info,
+                              TABLE_LIST *old_table, handler *new_file)
+{
+  List_iterator<Foreign_key_child> c_it(alter_info->foreign_key_list);
+  List_iterator<Foreign_key_parent> p_it(alter_info->parent_foreign_key_list);
+  Foreign_key_child *fk_c;
+  Foreign_key_parent *fk_p;
+
+  while ((fk_c= c_it++))
+    if (fk_c->exists &&
+        fk_c->check_if_can_change_engine(thd, old_table, new_file))
+      return TRUE;
+
+  while ((fk_p= p_it++))
+    if (fk_p->exists &&
+        fk_p->check_if_can_change_engine(thd, old_table, new_file))
+      return TRUE;
+
+  return FALSE;
+}
+
+
+/**
   Find unique constraint which participates in the foreign key being
   created. Also add supporting index for this foreign key.
 
@@ -1847,6 +1941,7 @@ bool fk_find_unique_and_add_supporting_k
   Key *key;
   List_iterator<Key_part_spec> parent_col_it(fkey->parent_columns);
   Key_part_spec *col;
+  handler *parent_file= fkey->parent_table->parent_info->file;
 
   while ((key= key_it++))
   {
@@ -1892,6 +1987,18 @@ bool fk_find_unique_and_add_supporting_k
   }
 
   /*
+    Check that unique indexes on parent table are not disabled.
+    Although it is not possible to disable unique index by using
+    SQL-statement this is still can be done using 'myisamchk'
+    utility.
+  */
+  if (parent_file && parent_file->indexes_are_disabled())
+  {
+    my_error(ER_FK_PARENT_KEY_NO_PK_OR_UNIQUE, MYF(0), fkey->name.str);
+    DBUG_RETURN(TRUE);
+  }
+
+  /*
     Create appropriate supporting index on child table (i.e. index which
     columns exactly match columns of FK). If index which can serve as
     supporting already exists or being created explicitly, this "generated"
@@ -1964,6 +2071,140 @@ int fk_check_if_key_is_generated_and_red
 
 
 /**
+  Check that list of keys contains a supporting key for the foreign key
+  and emit an appropriate error if not.
+
+  @param fkey      Foreign key description.
+  @param key_list  List of keys to be checked.
+
+  @retval FALSE  Supporting key is present in the list.
+  @retval TRUE   Otherwise, error was emitted.
+*/
+
+bool fk_check_if_supporting_key_exists(Foreign_key_child *fkey,
+                                       List<Key> &key_list)
+{
+  List_iterator<Key> key_it(key_list);
+  Key *key;
+
+  while ((key= key_it++))
+    if (fkey->is_supporting_key(key))
+      break;
+
+  if (! key)
+  {
+    /*
+      New version of table being altered does not have supporting index
+      for this foreign key, Report an appropriate error.
+    */
+    my_error(ER_FK_CHILD_SO_CHILD_INDEX_UNDROPPABLE, MYF(0), fkey->name.str);
+    return TRUE;
+  }
+  return FALSE;
+}
+
+
+/**
+  Check if the provided key can serve as supporting for the foreign key.
+
+  @param key   Key object describing key to be checked.
+               Describes a new key (to be created), as taken from CREATE or
+               ALTER specification.
+
+  @retval FALSE  This key can't serve as supporting for this foreign key.
+  @retval TRUE   This key can serve as supporting for this foreign key.
+*/
+
+bool
+Foreign_key_child::is_supporting_key(Key *key)
+{
+  List_iterator<Key_part_spec> fk_col_it(child_columns);
+  List_iterator<Key_part_spec> key_col_it(key->columns);
+  Key_part_spec *fk_col, *key_col;
+
+  if (child_columns.elements != key->columns.elements)
+    return FALSE;
+
+  while ((fk_col= fk_col_it++))
+  {
+    key_col_it.rewind();
+    while ((key_col= key_col_it++))
+    {
+      if (*fk_col == *key_col)
+        break;
+    }
+    if (! key_col)
+    {
+      /*
+        Found column of the foreign key which is not part of this key.
+        This key can't serve as supporting index.
+      */
+      return FALSE;
+    }
+  }
+  /*
+    For all columns of the foreign key this key has a corresponding
+    column. This is a suitable supporting key!
+  */
+  return TRUE;
+}
+
+
+/**
+  Check if the provided key can serve as supporting for the foreign key.
+
+  Used in ALTER to check if some existing key can serve as a
+  supporting one.
+
+  @param key   KEY object describing an already existing
+               key to be checked.
+
+  @retval FALSE  This key can't serve as supporting for this foreign key.
+  @retval TRUE   This key can serve as supporting for this foreign key.
+*/
+
+bool
+Foreign_key_child::is_supporting_key(KEY *key)
+{
+  List_iterator<Key_part_spec> col_it(child_columns);
+  Key_part_spec *col;
+
+  /*
+    Supporting key can't have different number of columns or
+    contain partial segments.
+  */
+  if (key->key_parts != child_columns.elements ||
+      (key->flags & HA_KEY_HAS_PART_KEY_SEG))
+    return FALSE;
+
+  while ((col= col_it++))
+  {
+    KEY_PART_INFO *key_part, *key_part_end;
+
+    for (key_part= key->key_part, key_part_end= key_part + key->key_parts;
+         key_part != key_part_end; key_part++)
+      if (key_part->field &&
+          ! my_strcasecmp(system_charset_info, col->field_name.str,
+                          key_part->field->field_name))
+        break;
+    if (key_part == key_part_end)
+    {
+      /*
+        Found column of the foreign key which is not part of this key.
+        This key can't serve as supporting key.
+      */
+      return FALSE;
+    }
+  }
+  /*
+    All foreign key's columns were found in this key. It can serve
+    as a supporting key.
+  */
+  return TRUE;
+}
+
+
+/**
   Convert list of columns names to a list of Key_part_spec objects
   which can be used for creation of foreign key using this columns.
 
@@ -2037,7 +2278,9 @@ Foreign_key_parent_share::make_foreign_k
 
   return new (mem_root) Foreign_key_parent(name, child_tab, child_cols,
                                            parent_cols, delete_opt, update_opt,
-                                           is_within_one_db);
+                                           is_within_one_db, is_self_reference,
+                                           TRUE /* existing key */,
+                                           &parent_constraint_name);
 }
 
 
@@ -2084,7 +2327,9 @@ Foreign_key_child_share::make_foreign_ke
                                           is_table_constraint,
                                           child_tab, child_cols, parent_tab,
                                           parent_cols, delete_opt, update_opt,
-                                          match_opt, is_within_one_db, TRUE,
+                                          match_opt, is_within_one_db,
+                                          is_self_reference,
+                                          TRUE /* existing key */,
                                           &parent_constraint_name);
 }
 
@@ -2274,18 +2519,18 @@ enum fk_role {FK_ROLE_CHILD_INSERT= 0, F
   @note By doing this we ensure that each foreign key/role pair is
         handled by prelocking algorithm only once for each statement.
 
-  @param thd   Thread context.
-  @param lex   LEX of this statement
-  @param fk    Foreign key
-  @param role  Role of the foreign key to be added
+  @param thd             Thread context.
+  @param prelocking_ctx  Prelocking context of the statement.
+  @param fk              Foreign key.
+  @param role            Role of the foreign key to be added.
 
   @retval TRUE  - constraint/role pair was added.
   @retval FALSE - constraint/role pair was not added since
                   it is already present in the set.
 */
 
-static bool fk_add_used_fk(THD *thd, LEX *lex, const char *db,
-                           const char *name, fk_role role)
+static bool fk_add_used_fk(THD *thd, Query_tables_list *prelocking_ctx,
+                           const char *db, const char *name, fk_role role)
 {
   LEX_STRING key;
   char key_buff[1+1+2*NAME_LEN+1+1];
@@ -2294,7 +2539,7 @@ static bool fk_add_used_fk(THD *thd, LEX
   key.str= key_buff;
   key.length= strxnmov(key_buff+2, sizeof(key_buff)-3, db, ".",
                        name, NULL) - key_buff;
-  return sp_add_used_routine(lex, thd->stmt_arena, &key, 0);
+  return sp_add_used_routine(prelocking_ctx, thd->stmt_arena, &key, 0);
 }
 
 
@@ -2303,7 +2548,7 @@ static bool fk_add_used_fk(THD *thd, LEX
   table list of statement.
 
   @param thd             Thread context
-  @param lex             LEX for the statement
+  @param prelocking_ctx  Prelocking context of the statement.
   @param tab             TABLE_LIST for the table from the foreign key
                          description
   @param lock_type       Type of lock
@@ -2318,7 +2563,8 @@ static bool fk_add_used_fk(THD *thd, LEX
   @retval TRUE  Failure (OOM).
 */
 
-static bool fk_add_table_to_table_list(THD *thd, LEX *lex,
+static bool fk_add_table_to_table_list(THD *thd,
+                                       Query_tables_list *prelocking_ctx,
                                        const LEX_STRING &db,
                                        const LEX_STRING &table_name,
                                        thr_lock_type lock_type,
@@ -2352,7 +2598,7 @@ static bool fk_add_table_to_table_list(T
   if (arena)
     thd->restore_active_arena(arena, &backup);
 
-  lex->add_to_query_tables(table);
+  prelocking_ctx->add_to_query_tables(table);
 
   return FALSE;
 }
@@ -2363,9 +2609,26 @@ static bool fk_add_table_to_table_list(T
   in operation performed on the table and if needed add appropriate
   tables to the table list.
 
-  @param thd    Thread context
-  @param lex    LEX of the statement
-  @param table  Element of table list to be processed
+  This is used by prelocking of DML statements such as
+  INSERT/UPDATE/DELETE.
+
+  For DDL statements, such as CREATE/ALTER/DROP, we use @sa
+  add_parent_tables() method.
+
+  For LOCK TABLES, we don't implicitly add parents to the list of
+  tables to lock, but do implicitly lock foreign key names, so we
+  use @sa add_fk_names() method.
+
+  We don't need to lock foreign key names in DML statements,
+  since all statements that change foreign keys always lock
+  both child and parent tables (i.e. addition and deletion of
+  constraints is always part of CREATE, ALTER or DROP TABLE),
+  and either child or parent or both is always locked by DML.
+  We don't support DROP CONSTRAINT statement.
+
+  @param thd             Thread context.
+  @param prelocking_ctx  Prelocking context of the statement.
+  @param table           Element of table list to be processed.
 
   @retval FALSE - Success
   @retval TRUE  - Failure (OOM)
@@ -2373,7 +2636,8 @@ static bool fk_add_table_to_table_list(T
 
 bool
 Foreign_key_share_list::
-add_tables_for_fks(THD *thd, LEX *lex, TABLE_LIST *table)
+add_tables_for_fks(THD *thd, Query_tables_list *prelocking_ctx,
+                   TABLE_LIST *table)
 {
   /*
     When FOREIGN_KEY_CHECKS is 0 we are not going to do any foreign key checks
@@ -2420,10 +2684,10 @@ add_tables_for_fks(THD *thd, LEX *lex, T
       if ((is_insert ||
            !table->cols_to_be_updated ||
            bitmap_is_overlapping(&fk->column_set, cols_to_be_updated_set)) &&
-          fk_add_used_fk(thd, lex, table->db, fk->name.str,
+          fk_add_used_fk(thd, prelocking_ctx, table->db, fk->name.str,
                          FK_ROLE_CHILD_INSERT))
       {
-        if (fk_add_table_to_table_list(thd, lex,
+        if (fk_add_table_to_table_list(thd, prelocking_ctx,
                                        fk->parent_table_db,
                                        fk->parent_table_name,
                                        TL_READ_NO_INSERT, 0,
@@ -2441,13 +2705,13 @@ add_tables_for_fks(THD *thd, LEX *lex, T
       if (is_update &&
           (!table->cols_to_be_updated ||
            bitmap_is_overlapping(&fk->column_set, cols_to_be_updated_set)) &&
-          fk_add_used_fk(thd, lex, fk->child_table_db.str, fk->name.str,
-                         FK_ROLE_PARENT_UPDATE))
+          fk_add_used_fk(thd, prelocking_ctx, fk->child_table_db.str,
+                         fk->name.str, FK_ROLE_PARENT_UPDATE))
       {
         if (fk->update_opt != FK_OPTION_NO_ACTION &&
             fk->update_opt != FK_OPTION_RESTRICT)
         {
-          if (fk_add_table_to_table_list(thd, lex,
+          if (fk_add_table_to_table_list(thd, prelocking_ctx,
                 fk->child_table_db, fk->child_table_name, TL_WRITE,
                 static_cast<uint8>(1 << static_cast<int>(TRG_EVENT_UPDATE)),
                 table->belong_to_view, fk->child_columns, fk->column_count))
@@ -2455,7 +2719,8 @@ add_tables_for_fks(THD *thd, LEX *lex, T
         }
         else
         {
-          if (fk_add_table_to_table_list(thd, lex, fk->child_table_db,
+          if (fk_add_table_to_table_list(thd, prelocking_ctx,
+                                         fk->child_table_db,
                                          fk->child_table_name,
                                          TL_READ_NO_INSERT, 0,
                                          table->belong_to_view, NULL, 0))
@@ -2463,13 +2728,14 @@ add_tables_for_fks(THD *thd, LEX *lex, T
         }
       }
       if (is_delete &&
-          fk_add_used_fk(thd, lex, fk->child_table_db.str, fk->name.str,
-                         FK_ROLE_PARENT_DELETE))
+          fk_add_used_fk(thd, prelocking_ctx, fk->child_table_db.str,
+                         fk->name.str, FK_ROLE_PARENT_DELETE))
       {
         if (fk->delete_opt == FK_OPTION_NO_ACTION ||
             fk->delete_opt == FK_OPTION_RESTRICT)
         {
-          if (fk_add_table_to_table_list(thd, lex, fk->child_table_db,
+          if (fk_add_table_to_table_list(thd, prelocking_ctx,
+                                         fk->child_table_db,
                                          fk->child_table_name,
                                          TL_READ_NO_INSERT, 0,
                                          table->belong_to_view, NULL, 0))
@@ -2477,7 +2743,7 @@ add_tables_for_fks(THD *thd, LEX *lex, T
         }
         else if (fk->delete_opt == FK_OPTION_CASCADE)
         {
-          if (fk_add_table_to_table_list(thd, lex,
+          if (fk_add_table_to_table_list(thd, prelocking_ctx,
                 fk->child_table_db, fk->child_table_name, TL_WRITE,
                 static_cast<uint8>(1 << static_cast<int>(TRG_EVENT_DELETE)),
                 table->belong_to_view, NULL, 0))
@@ -2485,7 +2751,7 @@ add_tables_for_fks(THD *thd, LEX *lex, T
         }
         else
         {
-          if (fk_add_table_to_table_list(thd, lex,
+          if (fk_add_table_to_table_list(thd, prelocking_ctx,
                 fk->child_table_db, fk->child_table_name, TL_WRITE,
                 static_cast<uint8>(1 << static_cast<int>(TRG_EVENT_UPDATE)),
                 table->belong_to_view, fk->child_columns, fk->column_count))
@@ -2499,12 +2765,147 @@ add_tables_for_fks(THD *thd, LEX *lex, T
 
 
 /**
+  Add foreign key constraint name to the set of "stored routines" used by
+  the statement.
+
+  @note By doing this we ensure that each foreign key name is handled
+        by prelocking algorithm only once for each statement.
+
+  @param thd             Thread context.
+  @param prelocking_ctx  Prelocking context of the statement.
+  @param db              Name of foreign key database.
+  @param name            Name of foreign key.
+
+  @retval TRUE  - Foreign key name was added.
+  @retval FALSE - Foreign key name was not added since it is already
+                  present in the set.
+*/
+
+static bool fk_add_used_fk_name(THD *thd, Query_tables_list *prelocking_ctx,
+                                const char *db, const char *name)
+{
+  LEX_STRING key;
+  char key_buff[1+2*NAME_LEN+1+1];
+  key_buff[0]= TYPE_ENUM_FOREIGN_KEY_NAME;
+  key.str= key_buff;
+  key.length= strmov(strmov(key_buff + 1, db) + 1, name) - key_buff;
+  return sp_add_used_routine(prelocking_ctx, thd->stmt_arena, &key, 0);
+}
+
+
+/**
+  Add names of foreign keys in which this table participates as child to
+  to set of "stored routines" used by the statement in order to ensure
+  that prelocking algorithm will acquire shared upgradable metadata locks
+  on those names.
+  This is used by LOCK TABLE ... WRITE, so that later on
+  we can do DDL on the subject table that affects constraints (e.g.
+  ALTER TABLE ... DROP CONSTRAINT).
+
+  We don't automatically add parent tables -- all tables must be locked
+  explicitly. But since there is no way to explicitly lock a constraint
+  name, and some constraint names are auto-generated, it was considered
+  user-friendly to prelock constraints.
+
+  @param thd             Thread context.
+  @param prelocking_ctx  Prelocking context of the statement.
+  @param db              Table's database name.
+
+  @retval FALSE  Success.
+  @retval TRUE   Failure (OOM).
+*/
+
+bool
+Foreign_key_share_list::
+add_fk_names(THD *thd, Query_tables_list *prelocking_ctx, const char *db)
+{
+  List_iterator<Foreign_key_child_share> fk_it(fkeys_child);
+  Foreign_key_child_share *fk;
+
+  while ((fk= fk_it++))
+    (void) fk_add_used_fk_name(thd, prelocking_ctx, db, fk->name.str);
+
+  return FALSE;
+}
+
+
+/**
+  Add parent tables for foreign keys to be dropped to the statement's
+  table list for prelocking with TL_WRITE_ALLOW_READ as lock type.
+
+  This is used when prelocking tables by ALTER TABLE statement,
+  if this ALTER statement has DROP [CONSTRAINT] FOREIGN KEY.
+  clause.
+
+  For ADD CONSTRAINT, parent tables are added in the parser,
+  since the grammar requires the table name to be specified explicitly,
+  and we process these tables anyway.
+
+  We need to lock parents since for each constraint we must
+  modify parent's .FRM.
+
+  Interaction with re-execution: like for DML, ALTER creates
+  the prelocking list only once, therefore all added tables
+  are allocated on statement memory root.
+
+  @param thd             Thread context.
+  @param prelocking_ctx  Prelocking context of the statement.
+  @param drop_list       List of objects to be dropped by this ALTER TABLE.
+
+  @retval FALSE  Success.
+  @retval TRUE   Failure (OOM).
+*/
+
+bool
+Foreign_key_share_list::
+add_parent_tables(THD *thd, Query_tables_list *prelocking_ctx,
+                  List<Alter_drop> &drop_list)
+{
+  List_iterator<Foreign_key_child_share> fk_it(fkeys_child);
+  Foreign_key_child_share *fk_s;
+  List_iterator<Alter_drop> drop_it(drop_list);
+  Alter_drop *drop;
+
+  while ((drop= drop_it++))
+  {
+    /* Ignore DROP clauses other DROP CONSTRAINT/FOREIGN KEY. */
+    if (drop->type != Alter_drop::FOREIGN_KEY)
+      continue;
+
+    /* Find constraint to be dropped in table description. */
+    fk_it.rewind();
+    while ((fk_s= fk_it++))
+      if (! strcmp(drop->name, fk_s->name.str))
+        break;
+
+    /*
+      Add parent table for constraint to be dropped to table list if:
+      - constraint belongs to the table being altered
+      - constraint is not self-referencing so its parent table is not
+        one being altered.
+    */
+    if (fk_s && ! fk_s->is_self_reference)
+    {
+      if (fk_add_table_to_table_list(thd, prelocking_ctx,
+                                     fk_s->parent_table_db,
+                                     fk_s->parent_table_name,
+                                     TL_WRITE_ALLOW_READ,
+                                     0, 0, NULL, 0))
+        return TRUE;
+    }
+  }
+  return FALSE;
+}
+
+
+/**
   Create a foreign key specification for the new (altered)
   table based on foreign key information in the share.
 
-  Until milestone 8 of WL#148 "DDL checks and changes: ALTER word"
-  is implemented we simply copy foreign keys from old definition
-  of table without adding or dropping anything.
+  This function simply converts existing foreign keys,
+  as defined in the table share, to foreign key specifications
+  used in ALTER, without adding or dropping anything.
+  All added keys are marked as "existing".
 */
 
 bool
@@ -2660,64 +3061,6 @@ accept_visitor(THD *thd, Execution_tree_
 }
 
 
-/**
-  Acquire shared upgradable metadata locks on names of all foreign keys
-  for which this tables serves as child, add those names and metadata
-  locks to THD::foreign_keys_to_lock list.
-
-  @param      thd                Thread context.
-  @param      share              Share in which foreign keys we are interested.
-  @param[out] back_off_and_retry TRUE  - if attempt to acquire metadata lock
-                                         encountered conflicting lock, so
-                                         back-off and retry-process should
-                                         be performed.
-                                 FALSE - Otherwise.
-
-  @retval FALSE  Success.
-  @retval TRUE   Failure (conflicting lock, OOM).
-*/
-
-bool fk_take_mdl_on_fk_names(THD *thd, TABLE_SHARE *share,
-                            bool *back_off_and_retry)
-{
-  List_iterator<Foreign_key_child_share> fk_it(share->fkeys.fkeys_child);
-  Foreign_key_child_share *fk;
-  Foreign_key_name *fk_name;
-  MDL_request *mdl_request;
-
-  /*
-    This code should not be called for statements that require obtaining
-    metadata locks on foreign key names directly and not through prelocking
-    algorithm as in this case we won't be able to properly handle back-off.
-  */
-  DBUG_ASSERT(! thd->lex->has_own_foreign_keys_to_lock);
-
-  /* Ensure that this out parameter has proper value if we fail due to OOM. */
-  *back_off_and_retry= FALSE;
-
-  while ((fk= fk_it++))
-  {
-    if (! (fk_name= Foreign_key_name::create(thd->mem_root, share->db,
-                                             fk->name)))
-      return TRUE;
-
-    mdl_request= fk_name->get_mdl_request();
-    mdl_request->set_type(MDL_SHARED_UPGRADABLE);
-    thd->mdl_context.add_request(mdl_request);
-
-    thd->lex->foreign_keys_to_lock.push_back(fk_name, thd->mem_root);
-
-    if (thd->mdl_context.acquire_shared_lock(mdl_request, back_off_and_retry))
-    {
-      if (! *back_off_and_retry)
-        thd->mdl_context.remove_request(mdl_request);
-      return TRUE;
-    }
-  }
-
-  return FALSE;
-}
-
 
 /**
   Check if table which is going to be affected by the statement has certain
@@ -2819,10 +3162,7 @@ bool Truncate_illegal_if_parent_checker:
 
   while ((fk_s= fk_it++))
   {
-    if (share->db.length != fk_s->child_table_db.length ||
-        strcmp(share->db.str, fk_s->child_table_db.str) ||
-        share->table_name.length != fk_s->child_table_name.length ||
-        strcmp(share->table_name.str, fk_s->child_table_name.str))
+    if (! fk_s->is_self_reference)
     {
       my_error(ER_FK_TRUNCATE_ILLEGAL, MYF(0), fk_s->name.str,
                share->table_name.str);

=== modified file 'sql/fk_dd.h'
--- a/sql/fk_dd.h	2009-05-27 03:08:49 +0000
+++ b/sql/fk_dd.h	2009-07-31 15:10:25 +0000
@@ -45,15 +45,17 @@ bool fk_generate_constraint_names(THD *t
 class Foreign_key_name: public Sql_alloc
 {
 public:
-  Foreign_key_name(LEX_STRING db_arg, LEX_STRING name_arg);
+  Foreign_key_name(const char *db_arg, const char *name_arg);
 
   static Foreign_key_name* create(MEM_ROOT *mem_root,
-                                  LEX_STRING db_arg, LEX_STRING name_arg);
-
-  Foreign_key_name* clone(MEM_ROOT *mem_root);
+                                  const char *db_arg, const char *name_arg);
+  static Foreign_key_name* create(MEM_ROOT *mem_root,
+                                  LEX_STRING db_arg, LEX_STRING name_arg)
+  { return create(mem_root, db_arg.str, name_arg.str); }
 
+  Foreign_key_name *clone(MEM_ROOT *mem_root);
 
-  MDL_request *get_mdl_request();
+  MDL_request *get_mdl_request() { return &m_mdl_request; }
 
   bool create_constraint_name_file();
 
@@ -68,18 +70,25 @@ public:
       OTOH we don't perform normalization for name part so we have to perform
       case-insensitive comparison for it.
     */
-    return (! strcmp(m_db.str, db)) &&
-           (! my_strcasecmp(system_charset_info, m_name.str, name));
+    return (! strcmp(m_mdl_request.key.db_name(), db)) &&
+           (! my_strcasecmp(system_charset_info,
+                            m_mdl_request.key.table_name(), name));
   }
 
   bool is_equal(const Foreign_key_name *fk_name) const
   {
-    return is_equal(fk_name->m_db.str, fk_name->m_name.str);
+    return is_equal(fk_name->m_mdl_request.key.db_name(),
+                    fk_name->m_mdl_request.key.table_name());
   }
+  const char *db_name() const { return m_mdl_request.key.db_name(); }
+  const char *table_name() const { return m_mdl_request.key.table_name(); }
 
+  bool find_ticket(MDL_context *mdl_context);
+private:
+  Foreign_key_name(const Foreign_key_name *rhs)
+    :m_mdl_request(&rhs->m_mdl_request)
+  {}
 private:
-  LEX_STRING m_db;
-  LEX_STRING m_name;
   MDL_request m_mdl_request;
 };
 
@@ -89,23 +98,27 @@ fk_find_name_in_list(List<Foreign_key_na
                      const char *db, const char *name);
 
 bool fk_add_constraint_names_to_lock(THD *thd, LEX *lex,
-                                     List<Foreign_key_child> &fkey_list);
-
-bool fk_take_mdl_on_fk_names(THD *thd, TABLE_SHARE *share,
-                             bool *back_off_and_retry);
+                                     List<Foreign_key_child> &create_list,
+                                     TABLE_LIST *alter_table,
+                                     List<Alter_drop> &drop_list);
 
 bool fk_check_if_constraint_name_exists(const char *db, const char *name);
 bool fk_create_constraint_names(List<Foreign_key_name> &fk_list);
 bool fk_drop_constraint_names(List<Foreign_key_name> &fk_list);
 
+bool fk_check_fkey_clause(THD *thd, List<Foreign_key_child> &fkey_list,
+                          List<Alter_drop> &drop_list);
 bool fk_check_constraint_added(THD *thd, Foreign_key_child *fkey,
                                HA_CREATE_INFO *create_info,
                                Alter_info *alter_info,
                                handler *file);
+bool fk_check_if_can_change_engine(THD *thd, Alter_info *alter_info,
+                                   TABLE_LIST *old_table, handler *new_file);
 bool fk_find_unique_and_add_supporting_key(THD *thd, Foreign_key_child *fkey,
                                            Alter_info *alter_info);
 int fk_check_if_key_is_generated_and_redundant(Key *a, Key *b);
-
+bool fk_check_if_supporting_key_exists(Foreign_key_child *fkey,
+                                       List<Key> &key_list);
 
 /**
    Base class for classes representing foreign keys in table's share.
@@ -128,6 +141,11 @@ public:
   uint column_count;
   bool is_within_one_db;
   /**
+    Indicates if this foreign key is a self-reference, i.e. has
+    the same table as both child and parent.
+  */
+  bool is_self_reference;
+  /**
      Set of columns in the table participating in foreign key.
      Note that it is always set of columns in the table for
      which this Foreign_key_share object represents a constraint.
@@ -144,6 +162,11 @@ public:
     The size of the array is in column_count. The list is NULL-terminated.
   */
   Field **column_list;
+  /**
+    Name of the unique constraint in the parent table
+    participating in this foreign key as a parent key.
+  */
+  LEX_STRING parent_constraint_name;
 

 
   /** A trivial constructor for safety in case of OOM */
@@ -161,9 +184,6 @@ protected:
   virtual void restore_additional_flags(char flags) = 0;
   virtual void restore_match_opt(const char **buff_p) = 0;
   virtual void set_table_from_frm(LEX_STRING &db, LEX_STRING &table_name) = 0;
-  virtual bool has_unique_constraint_name() = 0;
-  virtual void restore_unique_constraint_name(const char *name,
-                                              uint name_len) = 0;
   virtual LEX_STRING *get_table_columns() = 0;
 };
 
@@ -180,7 +200,6 @@ public:
   LEX_STRING parent_table_name;
   enum_fk_match_opt match_opt;
   bool is_table_constraint;
-  LEX_STRING parent_constraint_name;
 
   void get_create_statement_clause(THD *thd, String *str);
 
@@ -190,9 +209,6 @@ protected:
   virtual void restore_additional_flags(char flags);
   virtual void restore_match_opt(const char **buff_p);
   virtual void set_table_from_frm(LEX_STRING &db, LEX_STRING &table_name);
-  virtual bool has_unique_constraint_name();
-  virtual void restore_unique_constraint_name(const char *name,
-                                              uint name_len);
   virtual LEX_STRING *get_table_columns();
 };
 
@@ -214,9 +230,6 @@ protected:
   virtual void restore_additional_flags(char flags);
   virtual void restore_match_opt(const char **buff_p);
   virtual void set_table_from_frm(LEX_STRING &db, LEX_STRING &table_name);
-  virtual bool has_unique_constraint_name();
-  virtual void restore_unique_constraint_name(const char *name,
-                                              uint name_len);
   virtual LEX_STRING *get_table_columns();
 };
 

=== modified file 'sql/mdl.h'
--- a/sql/mdl.h	2009-05-27 03:08:49 +0000
+++ b/sql/mdl.h	2009-07-31 15:10:25 +0000
@@ -204,6 +204,13 @@ public:
     /* Do nothing, in particular, don't try to copy the key. */
     return *this;
   }
+  MDL_request(const MDL_request *rhs)
+    :type(rhs->type),
+    ticket(rhs->ticket),
+    key(&rhs->key)
+  {}
+  MDL_request()
+  {}
 };
 
 
@@ -312,6 +319,8 @@ public:
   bool try_acquire_exclusive_lock(MDL_request *mdl_request);
   bool acquire_global_shared_lock();
 
+  MDL_ticket *find_ticket(MDL_request *mdl_req);
+
   bool wait_for_locks();
 
   void release_all_locks();
@@ -351,7 +360,6 @@ private:
   THD *m_thd;
 private:
   void release_ticket(MDL_ticket *ticket);
-  MDL_ticket *find_ticket(MDL_request *mdl_req);
 };
 
 

=== modified file 'sql/mysql_priv.h'
--- a/sql/mysql_priv.h	2009-05-27 09:00:35 +0000
+++ b/sql/mysql_priv.h	2009-07-31 15:10:25 +0000
@@ -1131,6 +1131,7 @@ void create_select_for_variable(const ch
 void mysql_init_multi_delete(LEX *lex);
 bool multi_delete_set_locks_and_link_aux_tables(LEX *lex);
 void create_table_set_open_action_and_adjust_tables(LEX *lex);
+void alter_table_adjust_tables(LEX *lex);
 void init_max_user_conn(void);
 void init_update_queries(void);
 void free_max_user_conn(void);
@@ -1593,10 +1594,26 @@ int setup_ftfuncs(SELECT_LEX* select);
 int init_ftfuncs(THD *thd, SELECT_LEX* select, bool no_order);
 void wait_for_condition(THD *thd, pthread_mutex_t *mutex,
                         pthread_cond_t *cond);
-int open_tables(THD *thd, TABLE_LIST **tables, uint *counter, uint flags);
+int open_tables(THD *thd, TABLE_LIST **tables, uint *counter, uint flags,
+                Prelocking_strategy *prelocking_strategy);
+inline int open_tables(THD *thd, TABLE_LIST **tables, uint *counter, uint flags)
+{
+  DML_prelocking_strategy prelocking_strategy;
+
+  return open_tables(thd, tables, counter, flags, &prelocking_strategy);
+}
 /* open_and_lock_tables with optional derived handling */
-int open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, bool derived,
-                                 uint flags);
+int open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables,
+                                 bool derived, uint flags,
+                                 Prelocking_strategy *prelocking_strategy);
+inline int open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables,
+                                        bool derived, uint flags)
+{
+  DML_prelocking_strategy prelocking_strategy;
+
+  return open_and_lock_tables_derived(thd, tables, derived, flags,
+                                      &prelocking_strategy);
+}
 /* simple open_and_lock_tables without derived handling */
 inline int simple_open_n_lock_tables(THD *thd, TABLE_LIST *tables)
 {
@@ -2196,11 +2213,6 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, 
 #define MYSQL_OPEN_GET_NEW_TABLE                0x0080
 /** Don't look up the table in the list of temporary tables. */
 #define MYSQL_OPEN_SKIP_TEMPORARY               0x0100
-/**
-  Take upgradable shared metadata locks on names of foreign
-  keys for which table being opened is a child.
-*/
-#define MYSQL_OPEN_TAKE_FK_NAMES_MDL            0x0200
 
 /** Please refer to the internals manual. */
 #define MYSQL_OPEN_REOPEN  (MYSQL_LOCK_IGNORE_FLUSH |\

=== modified file 'sql/share/errmsg.txt'
--- a/sql/share/errmsg.txt	2009-06-01 11:37:20 +0000
+++ b/sql/share/errmsg.txt	2009-07-31 15:10:25 +0000
@@ -6577,4 +6577,17 @@ ER_FK_STATEMENT_ILLEGAL HY000
   eng "Foreign key error: you cannot use '%s' statement because '%s' table is in a relationship"
 ER_FK_TRUNCATE_ILLEGAL 42000
   eng "Foreign key error: constraint '%-.192s': you cannot use 'TRUNCATE TABLE' statement because table '%s' has a non-self-referencing foreign key that refers to it"
-
+ER_FK_CHILD_SET_DEFAULT 42000
+  eng "Foreign key error: constraint '%-.192s': SET/DROP DEFAULT illegal while foreign key exists with SET DEFAULT"
+ER_FK_CHILD_SO_CHILD_INDEX_UNDROPPABLE 42000
+  eng "Foreign key error: constraint '%-.192s': you cannot drop an index that the child key depends on"
+ER_FK_PARENT_SO_UNIQUE_INDEX_UNDROPPABLE 42000
+  eng "Foreign key error: constraint '%-.192s': references table, so you cannot drop unique index it depends on"
+ER_FK_NO_SUCH_CONSTRAINT 42000
+  eng "Foreign key error: Constraint '%-.192s': there is no such constraint on this table"
+ER_FK_CANT_CHANGE_COLUMN
+  eng "Foreign key error: Constraint '%-.192s': can't do %s for column '%s' since it participates in a relationship"
+ER_FK_REPLACEMENT_KEY
+  eng "Foreign key warning: Constraint '%-.192s': a non-unique supporting key was created as a replacement for unique key '%s' being dropped"
+ER_FK_ALTER_IGNORE_IGNORED
+  eng "Foreign key warning: IGNORE clause in ALTER TABLE was ignored since table being altered is parent in a foreign key relationship"

=== modified file 'sql/sp.cc'
--- a/sql/sp.cc	2009-05-23 13:31:58 +0000
+++ b/sql/sp.cc	2009-07-31 15:10:25 +0000
@@ -17,7 +17,6 @@
 #include "sp.h"
 #include "sp_head.h"
 #include "sp_cache.h"
-#include "sql_trigger.h"
 
 #include <my_user.h>
 
@@ -1411,51 +1410,16 @@ extern "C" uchar* sp_sroutine_key(const 
 
 
 /**
-  Check if
-   - current statement (the one in thd->lex) needs table prelocking
-   - first routine in thd->lex->sroutines_list needs to execute its body in
-     prelocked mode.
-
-  @param thd                  Current thread, thd->lex is the statement to be
-                              checked.
-  @param[out] need_prelocking    TRUE  - prelocked mode should be activated
-                                 before executing the statement; 
-                                 FALSE - Don't activate prelocking
-  @param[out] first_no_prelocking  TRUE  - Tables used by first routine in
-                                   thd->lex->sroutines_list should be
-                                   prelocked. FALSE - Otherwise.
-
-  @note
-    This function assumes that for any "CALL proc(...)" statement routines_list 
-    will have 'proc' as first element (it may have several, consider e.g.
-    "proc(sp_func(...)))". This property is currently guaranted by the parser.
-*/
-
-void sp_get_prelocking_info(THD *thd, bool *need_prelocking, 
-                            bool *first_no_prelocking)
-{
-  Sroutine_hash_entry *routine;
-  routine= (Sroutine_hash_entry*)thd->lex->sroutines_list.first;
-
-  DBUG_ASSERT(routine);
-  bool first_is_procedure= (routine->key.str[0] == TYPE_ENUM_PROCEDURE);
-
-  *first_no_prelocking= first_is_procedure;

-  *need_prelocking= !first_is_procedure || test(routine->next);
-}
-
-
-/**
   Auxilary function that adds new element to the set of stored routines
   used by statement.
 
   In case when statement uses stored routines but does not need
   prelocking (i.e. it does not use any tables) we will access the
-  elements of LEX::sroutines set on prepared statement re-execution.
-  Because of this we have to allocate memory for both hash element
-  and copy of its key in persistent arena.
+  elements of Query_tables_list::sroutines set on prepared statement
+  re-execution. Because of this we have to allocate memory for both
+  hash element and copy of its key in persistent arena.
 
-  @param lex             LEX representing statement
+  @param prelocking_ctx  Prelocking context of the statement
   @param arena           Arena in which memory for new element will be
                          allocated
   @param key             Key for the hash representing set
@@ -1463,7 +1427,7 @@ void sp_get_prelocking_info(THD *thd, bo
                          (0 if routine is not used by view)
 
   @note
-    Will also add element to end of 'LEX::sroutines_list' list.
+    Will also add element to end of 'Query_tables_list::sroutines_list' list.
 
   @todo
     When we will got rid of these accesses on re-executions we will be
@@ -1478,15 +1442,15 @@ void sp_get_prelocking_info(THD *thd, bo
     the set).
 */
 
-bool sp_add_used_routine(LEX *lex, Query_arena *arena,
-                         const LEX_STRING *key,
-                         TABLE_LIST *belong_to_view)
+bool sp_add_used_routine(Query_tables_list *prelocking_ctx, Query_arena *arena,
+                         const LEX_STRING *key, TABLE_LIST *belong_to_view)
 {
-  my_hash_init_opt(&lex->sroutines, system_charset_info,
+  my_hash_init_opt(&prelocking_ctx->sroutines, system_charset_info,
                    Query_tables_list::START_SROUTINES_HASH_SIZE,
                    0, 0, sp_sroutine_key, 0, 0);
 
-  if (!my_hash_search(&lex->sroutines, (uchar *)key->str, key->length))
+  if (!my_hash_search(&prelocking_ctx->sroutines, (uchar *)key->str,
+                      key->length))
   {
     Sroutine_hash_entry *rn=
       (Sroutine_hash_entry *)arena->alloc(sizeof(Sroutine_hash_entry) +
@@ -1496,8 +1460,8 @@ bool sp_add_used_routine(LEX *lex, Query
     rn->key.length= key->length;
     rn->key.str= (char *)rn + sizeof(Sroutine_hash_entry);
     memcpy(rn->key.str, key->str, key->length + 1);
-    my_hash_insert(&lex->sroutines, (uchar *)rn);
-    lex->sroutines_list.link_in_list((uchar *)rn, (uchar **)&rn->next);
+    my_hash_insert(&prelocking_ctx->sroutines, (uchar *)rn);
+    prelocking_ctx->sroutines_list.link_in_list((uchar *)rn, (uchar **)&rn->next);
     rn->belong_to_view= belong_to_view;
     return TRUE;
   }
@@ -1512,24 +1476,25 @@ bool sp_add_used_routine(LEX *lex, Query
   To be friendly towards prepared statements one should pass
   persistent arena as second argument.
 
-  @param lex       LEX representing statement
-  @param arena     arena in which memory for new element of the set
-                   will be allocated
-  @param rt        routine name
-  @param rt_type   routine type (one of TYPE_ENUM_PROCEDURE/...)
+  @param prelocking_ctx  Prelocking context of the statement
+  @param arena           Arena in which memory for new element of the set
+                         will be allocated
+  @param rt              Routine name
+  @param rt_type         Routine type (one of TYPE_ENUM_PROCEDURE/...)
 
   @note
-    Will also add element to end of 'LEX::sroutines_list' list (and will
-    take into account that this is explicitly used routine).
+    Will also add element to end of 'Query_tables_list::sroutines_list' list
+    (and will take into account that this is an explicitly used routine).
 */
 
-void sp_add_used_routine(LEX *lex, Query_arena *arena,
+void sp_add_used_routine(Query_tables_list *prelocking_ctx, Query_arena *arena,
                          sp_name *rt, char rt_type)
 {
   rt->set_routine_type(rt_type);
-  (void)sp_add_used_routine(lex, arena, &rt->m_sroutines_key, 0);
-  lex->sroutines_list_own_last= lex->sroutines_list.next;
-  lex->sroutines_list_own_elements= lex->sroutines_list.elements;
+  (void)sp_add_used_routine(prelocking_ctx, arena, &rt->m_sroutines_key, 0);
+  prelocking_ctx->sroutines_list_own_last= prelocking_ctx->sroutines_list.next;
+  prelocking_ctx->sroutines_list_own_elements=
+                    prelocking_ctx->sroutines_list.elements;
 }
 
 
@@ -1537,13 +1502,14 @@ void sp_add_used_routine(LEX *lex, Query
   Remove routines which are only indirectly used by statement from
   the set of routines used by this statement.
 
-  @param lex  LEX representing statement
+  @param prelocking_ctx  Prelocking context of the statement
 */
 
-void sp_remove_not_own_routines(LEX *lex)
+void sp_remove_not_own_routines(Query_tables_list *prelocking_ctx)
 {
   Sroutine_hash_entry *not_own_rt, *next_rt;
-  for (not_own_rt= *(Sroutine_hash_entry **)lex->sroutines_list_own_last;
+  for (not_own_rt= 
+         *(Sroutine_hash_entry **)prelocking_ctx->sroutines_list_own_last;
        not_own_rt; not_own_rt= next_rt)
   {
     /*
@@ -1551,12 +1517,13 @@ void sp_remove_not_own_routines(LEX *lex
       but we want to be more future-proof.
     */
     next_rt= not_own_rt->next;
-    my_hash_delete(&lex->sroutines, (uchar *)not_own_rt);
+    my_hash_delete(&prelocking_ctx->sroutines, (uchar *)not_own_rt);
   }
 
-  *(Sroutine_hash_entry **)lex->sroutines_list_own_last= NULL;
-  lex->sroutines_list.next= lex->sroutines_list_own_last;
-  lex->sroutines_list.elements= lex->sroutines_list_own_elements;
+  *(Sroutine_hash_entry **)prelocking_ctx->sroutines_list_own_last= NULL;
+  prelocking_ctx->sroutines_list.next= prelocking_ctx->sroutines_list_own_last;
+  prelocking_ctx->sroutines_list.elements= 
+                    prelocking_ctx->sroutines_list_own_elements;
 }
 
 
@@ -1592,23 +1559,24 @@ void sp_update_sp_used_routines(HASH *ds
   routines used by statement.
 
   @param thd             Thread context
-  @param lex             LEX representing statement
+  @param prelocking_ctx  Prelocking context of the statement
   @param src             Hash representing set from which routines will
                          be added
   @param belong_to_view  Uppermost view which uses these routines, 0 if none
 
-  @note
-    It will also add elements to end of 'LEX::sroutines_list' list.
+  @note It will also add elements to end of
+        'Query_tables_list::sroutines_list' list.
 */
 
-static void
-sp_update_stmt_used_routines(THD *thd, LEX *lex, HASH *src,
-                             TABLE_LIST *belong_to_view)
+void
+sp_update_stmt_used_routines(THD *thd, Query_tables_list *prelocking_ctx,
+                             HASH *src, TABLE_LIST *belong_to_view)
 {
   for (uint i=0 ; i < src->records ; i++)
   {
     Sroutine_hash_entry *rt= (Sroutine_hash_entry *)my_hash_element(src, i);
-    (void)sp_add_used_routine(lex, thd->stmt_arena, &rt->key, belong_to_view);
+    (void)sp_add_used_routine(prelocking_ctx, thd->stmt_arena, &rt->key,
+                              belong_to_view);
   }
 }
 
@@ -1618,252 +1586,101 @@ sp_update_stmt_used_routines(THD *thd, L
   routines used by statement.
 
   @param thd             Thread context
-  @param lex             LEX representing statement
+  @param prelocking_ctx  Prelocking context of the statement
   @param src             List representing set from which routines will
                          be added
   @param belong_to_view  Uppermost view which uses these routines, 0 if none
 
-  @note
-    It will also add elements to end of 'LEX::sroutines_list' list.
+  @note It will also add elements to end of
+        'Query_tables_list::sroutines_list' list.
 */
 
-static void sp_update_stmt_used_routines(THD *thd, LEX *lex, SQL_LIST *src,
-                                         TABLE_LIST *belong_to_view)
+void sp_update_stmt_used_routines(THD *thd, Query_tables_list *prelocking_ctx,
+                                  SQL_LIST *src, TABLE_LIST *belong_to_view)
 {
   for (Sroutine_hash_entry *rt= (Sroutine_hash_entry *)src->first;
        rt; rt= rt->next)
-    (void)sp_add_used_routine(lex, thd->stmt_arena, &rt->key, belong_to_view);
+    (void)sp_add_used_routine(prelocking_ctx, thd->stmt_arena, &rt->key,
+                              belong_to_view);
 }
 
 
 /**
-  Cache sub-set of routines used by statement, add tables used by these
-  routines to statement table list. Do the same for all routines used
-  by these routines.
-
-  @param thd               thread context
-  @param lex               LEX representing statement
-  @param start             first routine from the list of routines to be cached
-                           (this list defines mentioned sub-set).
-  @param first_no_prelock  If true, don't add tables or cache routines used by
-                           the body of the first routine (i.e. *start)
-                           will be executed in non-prelocked mode.
-  @param tabs_changed      Set to TRUE some tables were added, FALSE otherwise
+  Ensure that routine is present in cache by loading it from the mysql.proc
+  table if needed. Emit an appropriate error if there was a problem during
+  loading.
 
-  @note
-    If some function is missing this won't be reported here.
-    Instead this fact will be discovered during query execution.
+  @param[in]  thd   Thread context.
+  @param[in]  type  Type of object (TYPE_ENUM_FUNCTION or TYPE_ENUM_PROCEDURE).
+  @param[in]  name  Name of routine.
+  @param[out] sp    Pointer to sp_head object for routine, NULL if routine was
+                    not found,
 
-  @retval
-    0       success
-  @retval
-    non-0   failure
+  @retval 0      Either routine is found and was succesfully loaded into cache
+                 or it does not exist.
+  @retval non-0  Error while loading routine from mysql,proc table.
 */
 
-static int
-sp_cache_routines_and_add_tables_aux(THD *thd, LEX *lex,
-                                     Sroutine_hash_entry *start, 
-                                     bool first_no_prelock)
+int sp_cache_routine(THD *thd, int type, sp_name *name, sp_head **sp)
 {
   int ret= 0;
-  bool first= TRUE;
-  DBUG_ENTER("sp_cache_routines_and_add_tables_aux");
 
-  for (Sroutine_hash_entry *rt= start; rt; rt= rt->next)
-  {
-    sp_name name(thd, rt->key.str, rt->key.length);
-    int type= rt->key.str[0];
-    sp_head *sp;
+  DBUG_ENTER("sp_cache_routine");
 
-    /*
-      Triggers can't happen here: their bodies are always processed
-      in sp_cache_routines_and_add_tables_for_triggers().
-    */
-    DBUG_ASSERT(type == TYPE_ENUM_FUNCTION || type == TYPE_ENUM_PROCEDURE);
+  DBUG_ASSERT(type == TYPE_ENUM_FUNCTION || type == TYPE_ENUM_PROCEDURE);
 
-    if (!(sp= sp_cache_lookup((type == TYPE_ENUM_FUNCTION ?
+  if (!(*sp= sp_cache_lookup((type == TYPE_ENUM_FUNCTION ?
                               &thd->sp_func_cache : &thd->sp_proc_cache),
-                              &name)))
+                             name)))
+  {
+    switch ((ret= db_find_routine(thd, type, name, sp)))
     {
-      switch ((ret= db_find_routine(thd, type, &name, &sp)))
-      {
-      case SP_OK:
-        {
-          if (type == TYPE_ENUM_FUNCTION)
-            sp_cache_insert(&thd->sp_func_cache, sp);
-          else
-            sp_cache_insert(&thd->sp_proc_cache, sp);
-        }
-        break;
-      case SP_KEY_NOT_FOUND:
-        ret= SP_OK;
+    case SP_OK:
+      if (type == TYPE_ENUM_FUNCTION)
+        sp_cache_insert(&thd->sp_func_cache, *sp);
+      else
+        sp_cache_insert(&thd->sp_proc_cache, *sp);
+      break;
+    case SP_KEY_NOT_FOUND:
+      ret= SP_OK;
+      break;
+    default:
+      /* Query might have been killed, don't set error. */
+      if (thd->killed)
         break;
-      default:
-        /* Query might have been killed, don't set error. */
-        if (thd->killed)
-          break;
+      /*
+        Any error when loading an existing routine is either some problem
+        with the mysql.proc table, or a parse error because the contents
+        has been tampered with (in which case we clear that error).
+      */
+      if (ret == SP_PARSE_ERROR)
+        thd->clear_error();
+      /*
+        If we cleared the parse error, or when db_find_routine() flagged
+        an error with it's return value without calling my_error(), we
+        set the generic "mysql.proc table corrupt" error here.
+      */
+      if (! thd->is_error())
+      {
         /*
-          Any error when loading an existing routine is either some problem
-          with the mysql.proc table, or a parse error because the contents
-          has been tampered with (in which case we clear that error).
+          SP allows full NAME_LEN chars thus he have to allocate enough
+          size in bytes. Otherwise there is stack overrun could happen
+          if multibyte sequence is `name`. `db` is still safe because the
+          rest of the server checks agains NAME_LEN bytes and not chars.
+          Hence, the overrun happens only if the name is in length > 32 and
+          uses multibyte (cyrillic, greek, etc.)
         */
-        if (ret == SP_PARSE_ERROR)
-          thd->clear_error();
-        /*
-          If we cleared the parse error, or when db_find_routine() flagged
-          an error with it's return value without calling my_error(), we
-          set the generic "mysql.proc table corrupt" error here.
-         */
-        if (! thd->is_error())
-        {
-          /*
-            SP allows full NAME_LEN chars thus he have to allocate enough
-            size in bytes. Otherwise there is stack overrun could happen
-            if multibyte sequence is `name`. `db` is still safe because the
-            rest of the server checks agains NAME_LEN bytes and not chars.
-            Hence, the overrun happens only if the name is in length > 32 and
-            uses multibyte (cyrillic, greek, etc.)
-          */
-          char n[NAME_LEN*2+2];
-
-          /* m_qname.str is not always \0 terminated */
-          memcpy(n, name.m_qname.str, name.m_qname.length);
-          n[name.m_qname.length]= '\0';
-          my_error(ER_SP_PROC_TABLE_CORRUPT, MYF(0), n, ret);
-        }
-        break;
-      }
-    }
-    if (sp)
-    {
-      if (!(first && first_no_prelock))
-      {
-        sp_update_stmt_used_routines(thd, lex, &sp->m_sroutines,
-                                     rt->belong_to_view);
-        (void)sp->add_used_tables_to_table_list(thd, &lex->query_tables_last,
-                                                rt->belong_to_view);
-      }
-      sp->propagate_attributes(lex);
-    }
-    first= FALSE;
-  }
-  DBUG_RETURN(ret);
-}
+        char n[NAME_LEN*2+2];
 
-
-/**
-  Cache all routines from the set of used by statement, add tables used
-  by those routines to statement table list. Do the same for all routines
-  used by those routines.
-
-  @param thd               thread context
-  @param lex               LEX representing statement
-  @param first_no_prelock  If true, don't add tables or cache routines used by
-                           the body of the first routine (i.e. *start)
-
-  @retval
-    0       success
-  @retval
-    non-0   failure
-*/
-
-int
-sp_cache_routines_and_add_tables(THD *thd, LEX *lex, bool first_no_prelock)
-{
-  return sp_cache_routines_and_add_tables_aux(thd, lex,
-           (Sroutine_hash_entry *)lex->sroutines_list.first,
-           first_no_prelock);
-}
-
-
-/**
-  Add all routines used by view to the set of routines used by
-  statement.
-
-  Add tables used by those routines to statement table list. Do the same
-  for all routines used by these routines.
-
-  @param thd   Thread context
-  @param lex   LEX representing statement
-  @param view  Table list element representing view
-
-  @retval
-    0       success
-  @retval
-    non-0   failure
-*/
-
-int
-sp_cache_routines_and_add_tables_for_view(THD *thd, LEX *lex, TABLE_LIST *view)
-{
-  Sroutine_hash_entry **last_cached_routine_ptr=
-                          (Sroutine_hash_entry **)lex->sroutines_list.next;
-  sp_update_stmt_used_routines(thd, lex, &view->view->sroutines_list,
-                               view->top_table());
-  return sp_cache_routines_and_add_tables_aux(thd, lex,
-                                              *last_cached_routine_ptr, FALSE);
-}
-
-
-/**
-  Add triggers for table to the set of routines used by statement.
-  Add tables used by them to statement table list. Do the same for
-  all implicitly used routines.
-
-  @param thd    thread context
-  @param lex    LEX respresenting statement
-  @param table  Table list element for table with trigger
-
-  @retval
-    0       success
-  @retval
-    non-0   failure
-*/
-
-int
-sp_cache_routines_and_add_tables_for_triggers(THD *thd, LEX *lex,
-                                              TABLE_LIST *table)
-{
-  if (static_cast<int>(table->lock_type) >=
-      static_cast<int>(TL_WRITE_ALLOW_WRITE))
-  {
-    for (int i= 0; i < (int)TRG_EVENT_MAX; i++)
-    {
-      if (table->trg_event_map &
-          static_cast<uint8>(1 << static_cast<int>(i)))
-      {
-        for (int j= 0; j < (int)TRG_ACTION_MAX; j++)
-        {
-          /* We can have only one trigger per action type currently */
-          sp_head *trigger= table->table->triggers->bodies[i][j];
-          if (trigger &&
-              sp_add_used_routine(lex, thd->stmt_arena,
-                                  &trigger->m_sroutines_key,
-                                  table->belong_to_view))
-          {
-            int ret;
-            /* Sic: excludes the trigger key from processing */
-            Sroutine_hash_entry **last_cached_routine_ptr=
-              (Sroutine_hash_entry **)lex->sroutines_list.next;
-
-            trigger->add_used_tables_to_table_list(thd, &lex->query_tables_last,
-                                                   table->belong_to_view);
-            trigger->propagate_attributes(lex);
-            sp_update_stmt_used_routines(thd, lex,
-                                         &trigger->m_sroutines,
-                                         table->belong_to_view);
-
-            ret= sp_cache_routines_and_add_tables_aux(thd, lex,
-                                                      *last_cached_routine_ptr,
-                                                      FALSE);
-            if (ret)
-              return ret;
-          }
-        }
+        /* m_qname.str is not always \0 terminated */
+        memcpy(n, name->m_qname.str, name->m_qname.length);
+        n[name->m_qname.length]= '\0';
+        my_error(ER_SP_PROC_TABLE_CORRUPT, MYF(0), n, ret);
       }
+      break;
     }
   }
-  return 0;
+  DBUG_RETURN(ret);
 }
 
 

=== modified file 'sql/sp.h'
--- a/sql/sp.h	2009-01-21 22:27:26 +0000
+++ b/sql/sp.h	2009-07-31 15:10:25 +0000
@@ -68,6 +68,9 @@ sp_find_routine(THD *thd, int type, sp_n
                 sp_cache **cp, bool cache_only);
 
 int
+sp_cache_routine(THD *thd, int type, sp_name *name, sp_head **sp);
+
+int
 sp_exist_routines(THD *thd, TABLE_LIST *procs, bool any, bool no_error);
 
 int
@@ -112,24 +115,18 @@ struct Sroutine_hash_entry
 
 
 /*
-  Procedures for pre-caching of stored routines and building table list
-  for prelocking.
+  Procedures for handling sets of stored routines used by statement or routine.
 */
-void sp_get_prelocking_info(THD *thd, bool *need_prelocking, 
-                            bool *first_no_prelocking);
-void sp_add_used_routine(LEX *lex, Query_arena *arena,
+void sp_add_used_routine(Query_tables_list *prelocking_ctx, Query_arena *arena,
                          sp_name *rt, char rt_type);
-bool sp_add_used_routine(LEX *lex, Query_arena *arena,
-                         const LEX_STRING *key,
-                         TABLE_LIST *belong_to_view);
-void sp_remove_not_own_routines(LEX *lex);
+bool sp_add_used_routine(Query_tables_list *prelocking_ctx, Query_arena *arena,
+                         const LEX_STRING *key, TABLE_LIST *belong_to_view);
+void sp_remove_not_own_routines(Query_tables_list *prelocking_ctx);
 void sp_update_sp_used_routines(HASH *dst, HASH *src);
-int sp_cache_routines_and_add_tables(THD *thd, LEX *lex,
-                                     bool first_no_prelock);
-int sp_cache_routines_and_add_tables_for_view(THD *thd, LEX *lex,
-                                              TABLE_LIST *view);
-int sp_cache_routines_and_add_tables_for_triggers(THD *thd, LEX *lex,
-                                                  TABLE_LIST *table);
+void sp_update_stmt_used_routines(THD *thd, Query_tables_list *prelocking_ctx,
+                                  HASH *src, TABLE_LIST *belong_to_view);
+void sp_update_stmt_used_routines(THD *thd, Query_tables_list *prelocking_ctx,
+                                  SQL_LIST *src, TABLE_LIST *belong_to_view);
 
 extern "C" uchar* sp_sroutine_key(const uchar *ptr, size_t *plen,
                                   my_bool first);

=== modified file 'sql/sp_head.h'
--- a/sql/sp_head.h	2009-05-18 04:18:37 +0000
+++ b/sql/sp_head.h	2009-07-31 15:10:25 +0000
@@ -34,6 +34,7 @@
 #define TYPE_ENUM_PROCEDURE 2
 #define TYPE_ENUM_TRIGGER   3
 #define TYPE_ENUM_FOREIGN_KEY 4
+#define TYPE_ENUM_FOREIGN_KEY_NAME 5
 
 Item_result
 sp_map_result_type(enum enum_field_types type);
@@ -456,10 +457,10 @@ public:
 
   /*
     This method is intended for attributes of a routine which need
-    to propagate upwards to the LEX of the caller (when a property of a
-    sp_head needs to "taint" the caller).
+    to propagate upwards to the Query_tables_list of the caller (when
+    a property of a sp_head needs to "taint" the calling statement).
   */
-  void propagate_attributes(LEX *lex)
+  void propagate_attributes(Query_tables_list *prelocking_ctx)
   {
     /*
       If this routine needs row-based binary logging, the entire top statement
@@ -468,7 +469,7 @@ public:
       the substatements not).
     */
     if (m_flags & BINLOG_ROW_BASED_IF_MIXED)
-      lex->set_stmt_unsafe();
+      prelocking_ctx->set_stmt_unsafe();
   }
 
   sp_pcontext *get_parse_context() { return m_pcont; }

=== modified file 'sql/sql_base.cc'
--- a/sql/sql_base.cc	2009-05-27 03:08:49 +0000
+++ b/sql/sql_base.cc	2009-07-31 15:10:25 +0000
@@ -2971,7 +2971,6 @@ Locked_tables_list::init_locked_tables(T
 {
   DBUG_ASSERT(thd->locked_tables_mode == LTM_NONE);
   DBUG_ASSERT(m_locked_tables == NULL);
-  DBUG_ASSERT(m_locked_fk_names.is_empty());
 
   for (TABLE *table= thd->open_tables; table; table= table->next)
   {
@@ -3032,19 +3031,6 @@ Locked_tables_list::init_locked_tables(T
     table->pos_in_locked_tables= dst_table_list;
   }
 
-  List_iterator<Foreign_key_name> fk_names_it(thd->lex->foreign_keys_to_lock);
-  Foreign_key_name *fk_name_src, *fk_name_dst;
-
-  while ((fk_name_src= fk_names_it++))
-  {
-    if (! (fk_name_dst= fk_name_src->clone(&m_locked_tables_root)) ||
-        m_locked_fk_names.push_back(fk_name_dst, &m_locked_tables_root))
-    {
-      unlock_locked_tables(0);
-      return TRUE;
-    }
-  }
-
   /*
     We don't need old requests to hang around in the context any more:
     their memory will be gone at the end of this statement.
@@ -3099,7 +3085,6 @@ Locked_tables_list::unlock_locked_tables
   free_root(&m_locked_tables_root, MYF(0));
   m_locked_tables= NULL;
   m_locked_tables_last= &m_locked_tables;
-  m_locked_fk_names.empty();
 }
 
 
@@ -3132,7 +3117,8 @@ void Locked_tables_list::unlink_from_lis
     If mode is not LTM_LOCK_TABLES, we needn't do anything. Moreover,
     outside this mode pos_in_locked_tables value is not trustworthy.
   */
-  if (thd->locked_tables_mode != LTM_LOCK_TABLES)
+  if (thd->locked_tables_mode != LTM_LOCK_TABLES &&
+      thd->locked_tables_mode != LTM_PRELOCKED_UNDER_LOCK_TABLES)
     return;
 
   /*
@@ -3251,40 +3237,6 @@ Locked_tables_list::reopen_tables(THD *t
 }
 
 
-/**
-  Find an entry representing foreign key name/metadata lock for prelocked
-  foreign key name.
-
-  @param db   Foreign key database.
-  @param name Foreign key name.
-
-  @returns Pointer to Foreign_key_name object representing prelocked
-           foreign key name.
-*/
-
-Foreign_key_name *
-Locked_tables_list::find_fk_name(const char *db, const char *name)
-{
-  return fk_find_name_in_list(m_locked_fk_names, db, name);
-}
-
-
-/**
-  Remove all entries representing foreign key name/metadata lock from
-  the list of prelocked foreign key names.
-*/
-
-void Locked_tables_list::unlink_fk_name(const Foreign_key_name *fk_name_arg)
-{
-  List_iterator<Foreign_key_name> fk_names_it(m_locked_fk_names);

-  Foreign_key_name *fk_name;
-
-  while ((fk_name= fk_names_it++))
-    if (fk_name->is_equal(fk_name_arg))
-      fk_names_it.remove();
-}
-
-
 /*
   Function to assign a new table map id to a table share.
 
@@ -3691,34 +3643,150 @@ thr_lock_type read_lock_type_for_table(T
 
 
 /*
+  Perform steps of prelocking algorithm for elements of the
+  prelocking set other than tables. E.g. cache routines and, if
+  prelocking strategy prescribes so, extend the prelocking set with
+  tables and routines used by them.
+
+  @param[in]  thd                  Thread context.
+  @param[in]  prelocking_ctx       Prelocking context.
+  @param[in]  start                First element in the list representing
+                                   subset of the prelocking set to be
+                                   processed.
+  @param[in]  prelocking_strategy  Strategy which specifies how the
+                                   prelocking set should be extended when
+                                   one of its elements is processed.
+  @param[out] need_prelocking      Set to TRUE  if it was detected that this
+                                   statement will require prelocked mode for
+                                   its execution, not touched otherwise.
+  @param[out] back_off_and_retry   TRUE  - if attempt to acquire metadata lock
+                                           which were performed by this call
+                                           encountered conflicting lock, so
+                                           back-off and retry-process should
+                                           be performed.
+                                   FALSE - Otherwise.
+
+  @retval FALSE  Success.
+  @retval TRUE   Failure (Conflicting metadata lock, OOM, other errors).
+*/
+
+static bool
+open_routines(THD *thd, Query_tables_list *prelocking_ctx,
+              Sroutine_hash_entry *start,
+              Prelocking_strategy *prelocking_strategy,
+              bool *need_prelocking, bool *back_off_and_retry)
+{
+  DBUG_ENTER("open_routines");
+
+  /*
+    Ensure that out parameter has proper value if we fail due to some error.
+  */
+  *back_off_and_retry= FALSE;
+
+  for (Sroutine_hash_entry *rt= start; rt; rt= rt->next)
+  {
+    int type= rt->key.str[0];
+
+    switch (type)
+    {
+    case TYPE_ENUM_FUNCTION:
+    case TYPE_ENUM_PROCEDURE:
+      {
+        sp_name name(thd, rt->key.str, rt->key.length);
+        sp_head *sp;
+
+        if (sp_cache_routine(thd, type, &name, &sp))
+          DBUG_RETURN(TRUE);
+
+        if (sp)
+        {
+          prelocking_strategy->handle_routine(thd, prelocking_ctx, rt, sp,
+                                              need_prelocking);
+        }
+      }
+      break;
+    case TYPE_ENUM_TRIGGER:
+    case TYPE_ENUM_FOREIGN_KEY:
+      /*
+        Triggers and foreign keys are added to the prelocking set
+        only to avoid processing them twice. We don't have to do
+        anything about them here as they were already processed by
+        Prelocking_strategy method.
+      */
+      break;
+    case TYPE_ENUM_FOREIGN_KEY_NAME:
+      {
+        /*
+          Elements of this type are only present in the list if
+          the employed prelocking strategy is for LOCK TABLES.
+          Currently we only prelock foreign key names in case of
+          LOCK TABLE ... WRITE and this statement needs foreign
+          key names to be locked with MDL_SHARED_UPGRADABLE lock.
+          So here we simply assume that if there are foreign key
+          names to lock, all these names should be locked with
+          MDL_SHARED_UPGRADABLE lock.
+          @sa Foreign_key_share_list::add_fk_names()
+        */
+        LEX_STRING db = { rt->key.str + 1, strlen(rt->key.str + 1) };
+        LEX_STRING name = { db.str + db.length + 1,
+                            rt->key.length - db.length - 2 };
+        MDL_request *mdl_request;
+
+        if (! (mdl_request= MDL_request::create(TYPE_ENUM_FOREIGN_KEY,
+                                                db.str, name.str,
+                                                thd->mem_root)))
+          DBUG_RETURN(TRUE);
+        mdl_request->set_type(MDL_SHARED_UPGRADABLE);
+        thd->mdl_context.add_request(mdl_request);
+        if (thd->mdl_context.acquire_shared_lock(mdl_request,
+                                                 back_off_and_retry))
+        {
+          if (! *back_off_and_retry)
+            thd->mdl_context.remove_request(mdl_request);
+          DBUG_RETURN(TRUE);
+        }
+      }
+      break;
+    default:
+      /* Impossible type value. */
+      DBUG_ASSERT(0);
+    }
+  }
+  DBUG_RETURN(FALSE);
+}
+
+
+/**
   Open all tables in list
 
-  SYNOPSIS
-    open_tables()
-    thd - thread handler
-    start - list of tables in/out
-    counter - number of opened tables will be return using this parameter
-    flags   - bitmap of flags to modify how the tables will be open:
-              MYSQL_LOCK_IGNORE_FLUSH - open table even if someone has
-              done a flush or namelock on it.
+  @param[in]     thd      Thread context.
+  @param[in,out] start    List of tables to be open (it can be adjusted for
+                          statement that uses tables only implicitly, e.g.
+                          for "SELECT f1()").
+  @param[out]    counter  Number of tables which were open.
+  @param[in]     flags    Bitmap of flags to modify how the tables will be
+                          open, see open_table() description for details.
+  @param[in]     prelocking_strategy  Strategy which specifies how prelocking
+                                      algorithm should work for this statement.
 
-  NOTE
-    Unless we are already in prelocked mode, this function will also precache
-    all SP/SFs explicitly or implicitly (via views and triggers) used by the
-    query and add tables needed for their execution to table list. If resulting
-    tables list will be non empty it will mark query as requiring precaching.
+  @note
+    Unless we are already in prelocked mode and prelocking strategy prescribes
+    so this function will also precache all SP/SFs explicitly or implicitly
+    (via views and triggers) used by the query and add tables needed for their
+    execution to table list. Statement that uses SFs, invokes triggers or
+    requires foreign key checks will be marked as requiring prelocking.
     Prelocked mode will be enabled for such query during lock_tables() call.
 
     If query for which we are opening tables is already marked as requiring
     prelocking it won't do such precaching and will simply reuse table list
     which is already built.
 
-  RETURN
-    0  - OK
-    -1 - error
+  @retval  0   OK
+  @retval  -1  Error.
 */
 
-int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags)
+int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags,
+                Prelocking_strategy *prelocking_strategy)
 {
   TABLE_LIST *tables= NULL;
   enum_open_table_action action;
@@ -3765,13 +3833,21 @@ int open_tables(THD *thd, TABLE_LIST **s
       !thd->lex->requires_prelocking() &&
       thd->lex->uses_stored_routines())
   {
-    bool first_no_prelocking, need_prelocking;
+    bool need_prelocking= FALSE;
+    bool not_used;
     TABLE_LIST **save_query_tables_last= thd->lex->query_tables_last;
 
     DBUG_ASSERT(thd->lex->query_tables == *start);
-    sp_get_prelocking_info(thd, &need_prelocking, &first_no_prelocking);
 
-    if (sp_cache_routines_and_add_tables(thd, thd->lex, first_no_prelocking))
+    /*
+      TODO: Once we start acquiring metadata locks on functions we will
+            have to replace 'not_used' with 'back_off_and_retry' and
+            start handling its value.
+    */
+
+    if (open_routines(thd, thd->lex,
+                      (Sroutine_hash_entry *)thd->lex->sroutines_list.first,
+                      prelocking_strategy, &need_prelocking, &not_used))
     {
       /*
         Serious error during reading stored routines from mysql.proc table.
@@ -3804,31 +3880,38 @@ int open_tables(THD *thd, TABLE_LIST **s
     List_iterator<Foreign_key_name> fk_name_it(thd->lex->foreign_keys_to_lock);
     Foreign_key_name *fk_name;
 
-    /*
-      We should not have any foreign key names in the 'foreign_keys_to_lock'
-      unless their locking was explicitly requested by statement (i.e. they
-      were not added by prelocking algorithm).
-    */
-    DBUG_ASSERT(thd->lex->has_own_foreign_keys_to_lock);
-
     while ((fk_name= fk_name_it++))
     {
       MDL_request *mdl_request= fk_name->get_mdl_request();
       mdl_request->set_type(MDL_EXCLUSIVE);
       thd->mdl_context.add_request(mdl_request);
     }
-    /*
-      Check that we can rely that MDL_context::acquire_exclusive_locks() will
-      be called later for metadata lock requests which we just have added.
-    */
-    DBUG_ASSERT ((*start)->lock_strategy != TABLE_LIST::SHARED_MDL);
 
-    /*
-      Some code paths for open_type != OT_BASE_ONLY are missing handling
-      of metadata locks on foreign key names. It's OK since they are not
-      used ATM.
-    */
-    DBUG_ASSERT ((*start)->open_type == OT_BASE_ONLY);
+    if ((*start)->lock_strategy == TABLE_LIST::SHARED_MDL)
+    {
+      /*
+        The first table is to be open using default metadata locking strategy.
+        So we need to acquire exclusive metadata locks on foreign key names now.
+      */
+      if (thd->mdl_context.acquire_exclusive_locks())
+      {
+        thd->mdl_context.remove_all_requests();
+        result= -1;
+        goto err;
+      }
+    }
+    else
+    {
+      /*
+        We can rely that MDL_context::acquire_exclusive_locks() will be
+        called later for metadata lock requests which we just have added.
+
+        Some code paths for open_type != OT_BASE_ONLY are missing handling
+        of metadata locks on foreign key names. It's OK since they are not
+        used ATM.
+      */
+      DBUG_ASSERT ((*start)->open_type == OT_BASE_ONLY);
+    }
   }
 
   /*
@@ -4007,72 +4090,84 @@ int open_tables(THD *thd, TABLE_LIST **s
 
     /*
       If we are not already in prelocked mode and extended table list is not
-      yet built and we have trigger for table being opened then we should
-      cache all routines used by its triggers and add their tables to
-      prelocking list.
-      If we lock table for reading we won't update it so there is no need to
-      process its triggers since they never will be activated.
-
-      The same applies to foreign keys. If we are going to update table
-      participating in a foreign key relationship we need to add tables which
-      are going to be involved in foreign key checks/actions to the table list.
+      yet built we might have to build the prelocking set for this statement.
+
+      Since currently no prelocking strategy prescribes doing anything for
+      tables which are only read, we do below checks only if table is going
+      to be changed.
     */
     if (thd->locked_tables_mode <= LTM_LOCK_TABLES &&
         !thd->lex->requires_prelocking() &&
-        tables->trg_event_map &&
         tables->lock_type >= TL_WRITE_ALLOW_WRITE)
     {
-      if (tables->table->triggers)
+      bool need_prelocking= FALSE;
+      bool not_used, back_off_and_retry;
+      TABLE_LIST **save_query_tables_last= thd->lex->query_tables_last;
+      Sroutine_hash_entry **sroutines_next= 
+        (Sroutine_hash_entry **)thd->lex->sroutines_list.next;
+
+      /*
+        Extend statement's table list and the prelocking set with
+        tables and routines according to the current prelocking
+        strategy.
+
+        For example, for DML statements we need to add tables and routines
+        used by triggers which are going to be invoked for this element of
+        table list and also add tables required for handling of foreign keys.
+
+        For LOCK TABLES statement in addition to the above in order to be
+        able to execute DDL which drops foreign key names under LOCK TABLES
+        we have to obtain shared upgradable metadata locks on names of
+        foreign keys for which this table serves as child.
+
+        For ALTER TABLE statement we have to add to the table list parent
+        tables of foreign keys that are being dropped.
+      */
+      error= prelocking_strategy->handle_table(thd, thd->lex, tables,
+                                               &need_prelocking);
+
+      if (need_prelocking && ! query_tables_last_own)
+        query_tables_last_own= save_query_tables_last;
+
+      if (error)
       {
-        if (!query_tables_last_own)
-          query_tables_last_own= thd->lex->query_tables_last;
-        if (sp_cache_routines_and_add_tables_for_triggers(thd, thd->lex,
-                                                          tables))
-        {
-          /*
-            Serious error during reading stored routines from mysql.proc table.
-            Something's wrong with the table or its contents, and an error has
-            been emitted; we must abort.
-          */
-          result= -1;
-          goto err;
-        }
+        result= -1;
+        goto err;
       }
-      if (!tables->table->s->fkeys.is_empty())
-      {
-        DBUG_ASSERT(opt_fk_all_engines);
 
-        if (!query_tables_last_own)
-          query_tables_last_own= thd->lex->query_tables_last;
-        if (tables->table->s->fkeys.add_tables_for_fks(thd, thd->lex, tables))
-        {
-          result= -1;
-          goto err;
-        }
-      }
-    }
+      /*
+        Process elements of the prelocking set which were added
+        by the above invocation of Prelocking_strategy method.
 
-    if ((flags & MYSQL_OPEN_TAKE_FK_NAMES_MDL) &&
-        tables->lock_type >= TL_WRITE_ALLOW_WRITE &&
-        ! tables->table->s->fkeys.fkeys_child.is_empty())
-    {
-      bool back_off_and_retry;
-      if (fk_take_mdl_on_fk_names(thd, tables->table->s, &back_off_and_retry))
+        For example, if new element is a routine, cache it and then, if
+        prelocking strategy prescribes so, add tables it uses to the table
+        list and routines it might invoke to the prelocking set.
+
+        Or, for example, if the new element is a foreign key name, which means
+        that we probably are executing LOCK TABLES statement, acquire shared
+        upgradable metadata lock on this name (so later DDL statements which
+        will be run under LOCK TABLES can drop or replace this foreign key).
+      */
+      if (open_routines(thd, thd->lex, *sroutines_next, prelocking_strategy,
+                        &not_used, &back_off_and_retry))
       {
         if (back_off_and_retry)
         {
           if (query_tables_last_own)
             thd->lex->mark_as_requiring_prelocking(query_tables_last_own);
           close_tables_for_reopen(thd, start, TRUE);
-          recover_from_failed_open_table_attempt(thd, tables,
-                                                 OT_BACK_OFF_AND_RETRY);
-          goto restart;
-        }
-        else
-        {
-          result= -1;
-          goto err;
+          if (! recover_from_failed_open_table_attempt(thd, tables,
+                                                       OT_BACK_OFF_AND_RETRY))
+          {
+            /*
+              If we haven't been killed while waiting for conflicting locks to
+              go away we can try restarting prelocking process.
+            */
+            goto restart;
+          }
         }
+        result= -1;
+        goto err;
       }
     }
 
@@ -4116,13 +4211,34 @@ process_view_routines:
     */
     if (tables->view &&
         thd->locked_tables_mode <= LTM_LOCK_TABLES &&
-        !thd->lex->requires_prelocking() &&
-        tables->view->uses_stored_routines())
+        !thd->lex->requires_prelocking())
     {
-      /* We have at least one table in TL here. */
-      if (!query_tables_last_own)
-        query_tables_last_own= thd->lex->query_tables_last;
-      if (sp_cache_routines_and_add_tables_for_view(thd, thd->lex, tables))
+      bool need_prelocking= FALSE;
+      bool not_used, not_used2;
+      TABLE_LIST **save_query_tables_last= thd->lex->query_tables_last;
+      Sroutine_hash_entry **sroutines_next= 
+        (Sroutine_hash_entry **)thd->lex->sroutines_list.next;
+
+      error= prelocking_strategy->handle_view(thd, thd->lex, tables,
+                                              &need_prelocking);
+
+      if (need_prelocking && ! query_tables_last_own)
+        query_tables_last_own= save_query_tables_last;
+
+      if (error)
+      {
+        result= -1;
+        goto err;
+      }
+
+      /*
+        TODO: Once we start acquiring metadata locks on functions we will
+              have to replace 'not_used2' with 'back_off_and_retry' and
+              start handling its value.
+      */
+
+      if (open_routines(thd, thd->lex, *sroutines_next, prelocking_strategy,
+                        &not_used, &not_used2))
       {
         /*
           Serious error during reading stored routines from mysql.proc table.
@@ -4174,6 +4290,262 @@ process_view_routines:
 }
 
 
+/**
+  Defines how prelocking algorithm for DML statements should handle routines:
+  - For CALL statements we do unrolling (i.e. open and lock tables for each
+    sub-statement individually). So for such statements prelocking is enabled
+    only if stored functions are used in parameter list and only for period
+    during which we calculate values of parameters. Thus in this strategy we
+    ignore procedure which is directly called by such statement and extend
+    the prelocking set only with tables/functions used by SF called from the
+    parameter list.
+  - For any other statement any routine which is directly or indirectly called
+    by statement is going to be executed in prelocked mode. So in this case we
+    simply add all tables and routines used by it to the prelocking set.
+
+  @param[in]  thd              Thread context.
+  @param[in]  prelocking_ctx   Prelocking context of the statement.
+  @param[in]  rt               Prelocking set element describing routine.
+  @param[in]  sp               Routine body.
+  @param[out] need_prelocking  Set to TRUE if method detects that prelocking
+                               required, not changed otherwise.
+
+  @retval FALSE  Success.
+  @retval TRUE   Failure (OOM).
+*/
+
+bool DML_prelocking_strategy::
+handle_routine(THD *thd, Query_tables_list *prelocking_ctx,
+               Sroutine_hash_entry *rt, sp_head *sp, bool *need_prelocking)
+{
+  /*
+    We assume that for any "CALL proc(...)" statement sroutines_list will
+    have 'proc' as first element (it may have several, consider e.g.
+    "proc(sp_func(...)))". This property is currently guaranted by the
+    parser.
+  */
+
+  if (rt != (Sroutine_hash_entry*)prelocking_ctx->sroutines_list.first ||
+      rt->key.str[0] != TYPE_ENUM_PROCEDURE)
+  {
+    *need_prelocking= TRUE;
+    sp_update_stmt_used_routines(thd, prelocking_ctx, &sp->m_sroutines,
+                                 rt->belong_to_view);
+    (void)sp->add_used_tables_to_table_list(thd,
+                                            &prelocking_ctx->query_tables_last,
+                                            rt->belong_to_view);
+  }
+  sp->propagate_attributes(prelocking_ctx);
+  return FALSE;
+}
+
+
+/**
+  Defines how prelocking algorithm for DML statements should handle table list
+  elements:
+  - If table has triggers we should add all tables and routines
+    used by them to the prelocking set.
+  - If we are going to change the table participating in a foreign
+    key relationship we need to add tables which are going to be
+    involved in foreign key checks/ actions to the prelocking list.
+
+  We do not need to acquire metadata locks on trigger names
+  or foreign key names in DML statements, since all DDL statements
+  that change trigger/foreign key metadata always lock their
+  subject tables as well.
+
+  @param[in]  thd              Thread context.
+  @param[in]  prelocking_ctx   Prelocking context of the statement.
+  @param[in]  table_list       Table list element for table.
+  @param[in]  sp               Routine body.
+  @param[out] need_prelocking  Set to TRUE if method detects that prelocking
+                               required, not changed otherwise.
+
+  @retval FALSE  Success.
+  @retval TRUE   Failure (OOM).
+*/
+
+bool DML_prelocking_strategy::
+handle_table(THD *thd, Query_tables_list *prelocking_ctx,
+             TABLE_LIST *table_list, bool *need_prelocking)
+{
+  /* We rely on a caller to check that table is going to be changed. */
+  DBUG_ASSERT(table_list->lock_type >= TL_WRITE_ALLOW_WRITE);
+
+  if (table_list->trg_event_map)
+  {
+    if (table_list->table->triggers)
+    {
+      *need_prelocking= TRUE;
+
+      if (table_list->table->triggers->
+          add_tables_and_routines_for_triggers(thd, prelocking_ctx, table_list))
+        return TRUE;
+    }
+    if (! table_list->table->s->fkeys.is_empty())
+    {
+      DBUG_ASSERT(opt_fk_all_engines);
+
+      *need_prelocking= TRUE;
+
+      if (table_list->table->s->fkeys.add_tables_for_fks(thd, prelocking_ctx,
+                                                         table_list))
+        return TRUE;
+    }
+  }
+
+  return FALSE;
+}
+
+
+/**
+  Defines how prelocking algorithm for DML statements should handle view -
+  all view routines should be added to the prelocking set.
+
+  @param[in]  thd              Thread context.
+  @param[in]  prelocking_ctx   Prelocking context of the statement.
+  @param[in]  table_list       Table list element for view.
+  @param[in]  sp               Routine body.
+  @param[out] need_prelocking  Set to TRUE if method detects that prelocking
+                               required, not changed otherwise.
+
+  @retval FALSE  Success.
+  @retval TRUE   Failure (OOM).
+*/
+
+bool DML_prelocking_strategy::
+handle_view(THD *thd, Query_tables_list *prelocking_ctx,
+            TABLE_LIST *table_list, bool *need_prelocking)
+{
+  if (table_list->view->uses_stored_routines())
+  {
+    *need_prelocking= TRUE;
+
+    sp_update_stmt_used_routines(thd, prelocking_ctx,
+                                 &table_list->view->sroutines_list,
+                                 table_list->top_table());
+  }
+  return FALSE;
+}
+
+
+/**
+  Defines how prelocking algorithm for LOCK TABLES statement should handle
+  table list elements.
+
+  In addition to standard handling as for DML statement
+  we should ensure that for write-locked tables with foreign keys
+  we obtain shared upgradable metadata locks on foreign key names.
+  This is required in order to allow DDL statements which are executed
+  under LOCK TABLES to drop or replace foreign keys on these tables.
+
+  @param[in]  thd              Thread context.
+  @param[in]  prelocking_ctx   Prelocking context of the statement.
+  @param[in]  table_list       Table list element for table.
+  @param[in]  sp               Routine body.
+  @param[out] need_prelocking  Set to TRUE if method detects that prelocking
+                               required, not changed otherwise.
+
+  @retval FALSE  Success.
+  @retval TRUE   Failure (OOM).
+*/
+
+bool Lock_tables_prelocking_strategy::
+handle_table(THD *thd, Query_tables_list *prelocking_ctx,
+             TABLE_LIST *table_list, bool *need_prelocking)
+{
+  if (DML_prelocking_strategy::handle_table(thd, prelocking_ctx, table_list,
+                                            need_prelocking))
+    return TRUE;
+
+  /* We rely on a caller to check that table is going to be changed. */
+  DBUG_ASSERT(table_list->lock_type >= TL_WRITE_ALLOW_WRITE);
+
+  if (! table_list->table->s->fkeys.fkeys_child.is_empty())
+  {
+    DBUG_ASSERT(opt_fk_all_engines);
+
+    *need_prelocking= TRUE;
+
+    if (table_list->table->s->fkeys.add_fk_names(thd, prelocking_ctx,
+                                                 table_list->table->s->db.str))
+      return TRUE;
+  }
+
+  return FALSE;
+}
+
+
+/**
+  Defines how prelocking algorithm for ALTER TABLE statement should handle
+  routines - do nothing as this statement is not supposed to call routines.
+
+  We still can end up in this method when someone tries
+  to define a foreign key referencing a view, and not just
+  a simple view, but one that uses stored routines.
+*/
+
+bool Alter_table_prelocking_strategy::
+handle_routine(THD *thd, Query_tables_list *prelocking_ctx,
+               Sroutine_hash_entry *rt, sp_head *sp, bool *need_prelocking)
+{
+  return FALSE;
+}
+
+
+/**
+  Defines how prelocking algorithm for ALTER TABLE statement should handle
+  table list elements - for table being altered we should add all parent
+  tables for foreign keys which are going to be dropped to the prelocking
+  list.
+
+  @param[in]  thd              Thread context.
+  @param[in]  prelocking_ctx   Prelocking context of the statement.
+  @param[in]  table_list       Table list element for table.
+  @param[in]  sp               Routine body.
+  @param[out] need_prelocking  Set to TRUE if method detects that prelocking
+                               required, not changed otherwise.
+
+
+  @retval FALSE  Success.
+  @retval TRUE   Failure (OOM).
+*/
+
+bool Alter_table_prelocking_strategy::
+handle_table(THD *thd, Query_tables_list *prelocking_ctx,
+             TABLE_LIST *table_list, bool *need_prelocking)
+{
+  if (thd->lex->query_tables == table_list &&
+      ! table_list->table->s->fkeys.fkeys_child.is_empty())
+  {
+    DBUG_ASSERT(opt_fk_all_engines);
+
+    *need_prelocking= TRUE;
+
+    if (table_list->table->s->fkeys.add_parent_tables(thd, prelocking_ctx,
+                                                      m_alter_info->drop_list))
+      return TRUE;
+  }
+
+  return FALSE;
+}
+
+
+/**
+  Defines how prelocking algorithm for ALTER TABLE statement
+  should handle view - do nothing. We don't need to add view
+  routines to the prelocking set in this case as view is not going
+  to be materialized.
+*/
+
+bool Alter_table_prelocking_strategy::
+handle_view(THD *thd, Query_tables_list *prelocking_ctx,
+            TABLE_LIST *table_list, bool *need_prelocking)
+{
+  return FALSE;
+}
+
+
 /*
   Check that lock is ok for tables; Call start stmt if ok
 
@@ -4375,34 +4747,35 @@ retry:
 }
 
 
-/*
+/**
   Open all tables in list, locks them and optionally process derived tables.
 
-  SYNOPSIS
-    open_and_lock_tables_derived()
-    thd		- thread handler
-    tables	- list of tables for open&locking
-    flags       - set of options to be used to open and lock tables (see
-                  open_tables() and mysql_lock_tables() for details).
-    derived     - if to handle derived tables
-
-  RETURN
-    FALSE - ok
-    TRUE  - error
+  @param thd		      Thread context.
+  @param tables	              List of tables for open and locking.
+  @param derived              If to handle derived tables.
+  @param flags                Bitmap of options to be used to open and lock
+                              tables (see open_tables() and mysql_lock_tables()
+                              for details).
+  @param prelocking_strategy  Strategy which specifies how prelocking algorithm
+                              should work for this statement.
 
-  NOTE
+  @note
     The lock will automaticaly be freed by close_thread_tables()
 
-  NOTE
-    There are two convenience functions:
+  @note
+    There are several convenience functions, e.g. :
     - simple_open_n_lock_tables(thd, tables)  without derived handling
     - open_and_lock_tables(thd, tables)       with derived handling
     Both inline functions call open_and_lock_tables_derived() with
     the third argument set appropriately.
+
+  @retval FALSE  OK.
+  @retval TRUE   Error
 */
 
-int open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, bool derived,
-                                 uint flags)
+int open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables,
+                                 bool derived, uint flags,
+                                 Prelocking_strategy *prelocking_strategy)
 {
   uint counter;
   bool need_reopen;
@@ -4411,7 +4784,7 @@ int open_and_lock_tables_derived(THD *th
 
   for ( ; ; ) 
   {
-    if (open_tables(thd, &tables, &counter, flags))
+    if (open_tables(thd, &tables, &counter, flags, prelocking_strategy))
       DBUG_RETURN(-1);
     DBUG_EXECUTE_IF("sleep_open_and_lock_after_open", {
       const char *old_proc_info= thd->proc_info;
@@ -4846,8 +5219,9 @@ int lock_tables(THD *thd, TABLE_LIST *ta
 void close_tables_for_reopen(THD *thd, TABLE_LIST **tables, bool is_back_off)
 {
   /*
-    If table list consists only from tables from prelocking set, table list
-    for new attempt should be empty, so we have to update list's root pointer.
+    If table list consists only from tables from the prelocking
+    set, table list for new attempt should be empty, so we have to
+    update list's root pointer.
   */
   if (thd->lex->first_not_own_table() == *tables)
     *tables= 0;
@@ -4855,13 +5229,7 @@ void close_tables_for_reopen(THD *thd, T
   sp_remove_not_own_routines(thd->lex);
   for (TABLE_LIST *tmp= *tables; tmp; tmp= tmp->next_global)
     tmp->table= 0;
-  /*
-    When metadata locks on foreign key names are added to 'foreign_keys_to_lock'
-    list by prelocking algorithm (as it happens for LOCK TABLES) we need to
-    remove them from the list during back-off.
-  */
-  if (! thd->lex->has_own_foreign_keys_to_lock)
-    thd->lex->foreign_keys_to_lock.empty();
+
   close_thread_tables(thd, is_back_off);
   if (!thd->locked_tables_mode)
     thd->mdl_context.release_all_locks();

=== modified file 'sql/sql_class.cc'
--- a/sql/sql_class.cc	2009-05-27 03:08:49 +0000
+++ b/sql/sql_class.cc	2009-07-31 15:10:25 +0000
@@ -112,7 +112,8 @@ bool Key_part_spec::operator==(const Key
 {
   return length == other.length &&
          field_name.length == other.field_name.length &&
-         !strcmp(field_name.str, other.field_name.str);
+         !my_strcasecmp(system_charset_info, field_name.str,
+                        other.field_name.str);
 }
 
 /**
@@ -148,7 +149,10 @@ Foreign_key_parent::Foreign_key_parent(c
   parent_columns(rhs.parent_columns, mem_root),
   delete_opt(rhs.delete_opt),
   update_opt(rhs.update_opt),
-  is_within_one_db(rhs.is_within_one_db)
+  is_within_one_db(rhs.is_within_one_db),
+  is_self_reference(rhs.is_self_reference),
+  exists(rhs.exists),
+  parent_constraint_name(rhs.parent_constraint_name)
 {
   list_copy_and_replace_each_value(child_columns, mem_root);
   list_copy_and_replace_each_value(parent_columns, mem_root);
@@ -171,8 +175,7 @@ Foreign_key_child::Foreign_key_child(con
   fkey_clause_name(rhs.fkey_clause_name),
   is_table_constraint(rhs.is_table_constraint),
   is_name_generated(rhs.is_name_generated),
-  exists(rhs.exists),
-  parent_constraint_name(rhs.parent_constraint_name)
+  replaces_existing(rhs.replaces_existing)
 {
 }
 

=== modified file 'sql/sql_class.h'
--- a/sql/sql_class.h	2009-05-27 03:08:49 +0000
+++ b/sql/sql_class.h	2009-07-31 15:10:25 +0000
@@ -40,6 +40,7 @@ class sp_cache;
 class Lex_input_stream;
 class Parser_state;
 class Rows_log_event;
+class Sroutine_hash_entry;
 
 enum enum_enable_or_disable { LEAVE_AS_IS, ENABLE, DISABLE };
 enum enum_ha_read_modes { RFIRST, RNEXT, RPREV, RLAST, RKEY, RNEXT_SAME };
@@ -228,8 +229,14 @@ public:
   enum drop_type {KEY, COLUMN, FOREIGN_KEY };
   const char *name;
   enum drop_type type;
-  Alter_drop(enum drop_type par_type,const char *par_name)
-    :name(par_name), type(par_type) {}
+  /**
+    Indicates that this Alter_drop instance represents deprecated
+    DROP FOREIGN KEY clause.
+  */
+  bool is_deprecated_foreign_key_clause;
+  Alter_drop(enum drop_type par_type,const char *par_name, bool par_fkey_clause)
+    :name(par_name), type(par_type),
+    is_deprecated_foreign_key_clause(par_fkey_clause) {}
   /**
     Used to make a clone of this object for ALTER/CREATE TABLE
     @sa comment for Key_part_spec::clone
@@ -298,10 +305,10 @@ enum enum_fk_option { FK_OPTION_UNDEF, F
 
 
 /**
-   Class that represents foreign key in the structures describing its
-   parent table in CREATE TABLE/ALTER TABLE statements. Contains all
-   information about foreign key which should be saved in .FRM of its
-   parent table.
+  Class that represents a foreign key in the structures describing its
+  parent table in CREATE TABLE/ALTER TABLE statements. Contains all
+  information about foreign key which should be saved in .FRM of its
+  parent table.
 */
 
 class Foreign_key_parent: public Sql_alloc {
@@ -312,10 +319,26 @@ public:
   List<Key_part_spec> parent_columns;
   enum_fk_option delete_opt, update_opt;
   /**
-     Indicates whether child and parent table of foreign key are in
-     the same database.
+    Indicates whether child and parent table of foreign key are in
+    the same database.
   */
   bool is_within_one_db;
+  /**
+    Indicates whether foreign key has the same table as both child and
+    parent.
+  */
+  bool is_self_reference;
+  /**
+    Indicates that this object represents an already existing constraint
+    so we don't need to perform some checks and actions for it when
+    saving its description to .FRM file.
+  */
+  bool exists;
+  /**
+    A pointer to the string with name of the unique constraint
+    in the parent table participating in this foreign key.
+  */
+  const LEX_STRING *parent_constraint_name;
 
   Foreign_key_parent(const LEX_STRING &name_arg,
                      TABLE_LIST *child_tab_arg,
@@ -323,11 +346,16 @@ public:
                      const List<Key_part_spec> &parent_cols_arg,
                      enum_fk_option delete_opt_arg,
                      enum_fk_option update_opt_arg,
-                     bool within_one_db_arg)
+                     bool within_one_db_arg,
+                     bool is_self_reference_arg,
+                     bool exists_arg,
+                     const LEX_STRING *par_cons_name_arg)
     : name(name_arg), child_columns(child_cols_arg),
       child_table(child_tab_arg), parent_columns(parent_cols_arg),
       delete_opt(delete_opt_arg), update_opt(update_opt_arg),
-      is_within_one_db(within_one_db_arg)
+      is_within_one_db(within_one_db_arg),
+      is_self_reference(is_self_reference_arg),
+      exists(exists_arg), parent_constraint_name(par_cons_name_arg)
   {}
 
   Foreign_key_parent(const Foreign_key_parent &rhs, MEM_ROOT *mem_root);
@@ -342,13 +370,22 @@ public:
 
   uint get_frm_description_length();
   void save_to_frm(char **buff_p);
+
+  bool has_cascading_action()
+  {
+    return ((update_opt != FK_OPTION_NO_ACTION &&
+             update_opt != FK_OPTION_RESTRICT) ||
+            (delete_opt != FK_OPTION_NO_ACTION &&
+             delete_opt != FK_OPTION_RESTRICT));
+  }
+
+  bool check_if_can_change_engine(THD *thd, TABLE_LIST *old_table, handler
+                                  *new_file);
 protected:
   virtual uint get_match_opt_frm_length();
   virtual char get_additional_flags();
   virtual void save_match_opt(char **buff_p);
   virtual TABLE_LIST* get_table_for_frm();
-  virtual uint get_unique_constraint_name_frm_length();
-  virtual void save_unique_constraint_name(char **buff_p);
 };
 
 
@@ -400,7 +437,8 @@ public:
    in .FRM file of its child table).
 */
 
-class Foreign_key_child: public Foreign_key_parent {
+class Foreign_key_child: public Foreign_key_parent
+{
 public:
   TABLE_LIST *parent_table;
   enum_fk_match_opt match_opt;
@@ -421,16 +459,17 @@ public:
   */
   bool is_name_generated;
   /**
-     Indicates that this object represents already existing constraint
-     so we don't need to perform some of checks and actions for it when
-     saving its description to .FRM file.
-  */
-  bool exists;
-  /**
-     Pointer to string with name of unique constraint in the parent
-     table participating in this foreign key.
+     Indicates whether ALTER TABLE statement going to replace an existing
+     foreign key with this foreign key (i.e. this ALTER TABLE drops old
+     foreign key and creates a new one with the same name).
+
+     Code that checks if name of the new foreign key conflicts with name of
+     an existing constraint does not produce errors in such case, even though
+     at the moment when this check is performed there still exists a .CNS
+     file for the name of foreign key being created. This .CNS file corresponds
+     to the old foreign key.
   */
-  const LEX_STRING *parent_constraint_name;
+  bool replaces_existing;
 
   Foreign_key_child(const LEX_STRING &name_arg,
                     const LEX_STRING &fkey_clause_name_arg,
@@ -441,15 +480,19 @@ public:
                     enum_fk_option delete_opt_arg,
                     enum_fk_option update_opt_arg,
                     enum_fk_match_opt match_opt_arg,
-                    bool within_one_db_arg, bool exists_arg,
+                    bool within_one_db_arg,
+                    bool is_self_reference_arg,
+                    bool exists_arg,
                     const LEX_STRING *par_cons_name_arg)
     : Foreign_key_parent(name_arg, child_tab_arg, child_cols_arg,
                          parent_cols_arg, delete_opt_arg,
-                         update_opt_arg, within_one_db_arg),
+                         update_opt_arg, within_one_db_arg,
+                         is_self_reference_arg, exists_arg,
+                         par_cons_name_arg),
       parent_table(parent_tab_arg), match_opt(match_opt_arg),
       fkey_clause_name(fkey_clause_name_arg),
       is_table_constraint(table_con_arg), is_name_generated(FALSE),
-      exists(exists_arg), parent_constraint_name(par_cons_name_arg)
+      replaces_existing(FALSE)
   {}
   Foreign_key_child(const Foreign_key_child &rhs, MEM_ROOT *mem_root);
   Foreign_key_child *clone(MEM_ROOT *mem_root) const
@@ -471,25 +514,21 @@ public:
 
     if (!strcmp(child_table->db, parent_table->db))
       is_within_one_db= TRUE;
+
+    if (is_within_one_db &&
+        !strcmp(child_table->table_name, parent_table->table_name))
+      is_self_reference= TRUE;
   }
 
   bool generate_name_if_needed(THD *thd, const char *table_name);
 
-  bool has_cascading_action()
-  {
-    return ((update_opt != FK_OPTION_NO_ACTION &&
-             update_opt != FK_OPTION_RESTRICT) ||
-            (delete_opt != FK_OPTION_NO_ACTION &&
-             delete_opt != FK_OPTION_RESTRICT));
-  }
-
+  bool is_supporting_key(KEY *key);
+  bool is_supporting_key(Key *key);
 protected:
   virtual uint get_match_opt_frm_length();
   virtual char get_additional_flags();
   virtual void save_match_opt(char **buff_p);
   virtual TABLE_LIST* get_table_for_frm();
-  virtual uint get_unique_constraint_name_frm_length();
-  virtual void save_unique_constraint_name(char **buff_p);
 };
 
 
@@ -1642,6 +1681,93 @@ public:
 
 
 /**
+  An abstract class for a strategy specifying how the prelocking
+  algorithm should extend the prelocking set while processing
+  already existing elements in the set.
+*/
+
+class Prelocking_strategy
+{
+public:
+  virtual ~Prelocking_strategy() { }
+
+  virtual bool handle_routine(THD *thd, Query_tables_list *prelocking_ctx,
+                              Sroutine_hash_entry *rt, sp_head *sp,
+                              bool *need_prelocking) = 0;
+  virtual bool handle_table(THD *thd, Query_tables_list *prelocking_ctx,
+                            TABLE_LIST *table_list, bool *need_prelocking) = 0;
+  virtual bool handle_view(THD *thd, Query_tables_list *prelocking_ctx,
+                           TABLE_LIST *table_list, bool *need_prelocking)= 0;
+};
+
+
+/**
+  A Strategy for prelocking algorithm suitable for DML statements.
+
+  Ensures that all tables used by all statement's SF/SP/triggers and
+  required for foreign key checks are prelocked and SF/SPs used are
+  cached.
+*/
+
+class DML_prelocking_strategy : public Prelocking_strategy
+{
+public:
+  virtual bool handle_routine(THD *thd, Query_tables_list *prelocking_ctx,
+                              Sroutine_hash_entry *rt, sp_head *sp,
+                              bool *need_prelocking);
+  virtual bool handle_table(THD *thd, Query_tables_list *prelocking_ctx,
+                            TABLE_LIST *table_list, bool *need_prelocking);
+  virtual bool handle_view(THD *thd, Query_tables_list *prelocking_ctx,
+                           TABLE_LIST *table_list, bool *need_prelocking);
+};
+
+
+/**
+  A strategy for prelocking algorithm to be used for LOCK TABLES
+  statement.
+
+  Extends the strategy for DML statements by ensuring that also
+  upgradable shared metadata locks on foreign key names are
+  acquired for foreign keys on tables which can be later affected
+  by DDL (while still in LOCK TABLES mode).
+*/
+
+class Lock_tables_prelocking_strategy : public DML_prelocking_strategy
+{
+  virtual bool handle_table(THD *thd, Query_tables_list *prelocking_ctx,
+                            TABLE_LIST *table_list, bool *need_prelocking);
+};
+
+
+/**
+  Strategy for prelocking algorithm to be used for ALTER TABLE statements.
+
+  Ensures that all parent tables for foreign keys to be dropped by this
+  ALTER TABLE are prelocked.
+*/
+
+class Alter_table_prelocking_strategy : public Prelocking_strategy
+{
+public:
+
+  Alter_table_prelocking_strategy(Alter_info *alter_info)
+    : m_alter_info(alter_info)
+  {}
+
+  virtual bool handle_routine(THD *thd, Query_tables_list *prelocking_ctx,
+                              Sroutine_hash_entry *rt, sp_head *sp,
+                              bool *need_prelocking);
+  virtual bool handle_table(THD *thd, Query_tables_list *prelocking_ctx,
+                            TABLE_LIST *table_list, bool *need_prelocking);
+  virtual bool handle_view(THD *thd, Query_tables_list *prelocking_ctx,
+                           TABLE_LIST *table_list, bool *need_prelocking);
+
+private:
+  Alter_info *m_alter_info;
+};
+
+
+/**
   Tables that were locked with LOCK TABLES statement.
 
   Encapsulates a list of TABLE_LIST instances for tables
@@ -1672,7 +1798,6 @@ private:
   MEM_ROOT m_locked_tables_root;
   TABLE_LIST *m_locked_tables;
   TABLE_LIST **m_locked_tables_last;
-  List<Foreign_key_name> m_locked_fk_names;
 public:
   Locked_tables_list()
     :m_locked_tables(NULL),
@@ -1692,8 +1817,6 @@ public:
                         bool remove_from_locked_tables);
   void unlink_all_closed_tables();
   bool reopen_tables(THD *thd);
-  Foreign_key_name *find_fk_name(const char *db, const char *name);
-  void unlink_fk_name(const Foreign_key_name *fk_name_arg);
 };
 
 

=== modified file 'sql/sql_lex.cc'
--- a/sql/sql_lex.cc	2009-05-27 03:08:49 +0000
+++ b/sql/sql_lex.cc	2009-07-31 15:10:25 +0000
@@ -2234,7 +2234,6 @@ void Query_tables_list::reset_query_tabl
   sroutines_list_own_last= sroutines_list.next;
   sroutines_list_own_elements= 0;
   foreign_keys_to_lock.empty();
-  has_own_foreign_keys_to_lock= FALSE;
   binlog_stmt_flags= 0;
 }
 

=== modified file 'sql/sql_lex.h'
--- a/sql/sql_lex.h	2009-05-27 03:08:49 +0000
+++ b/sql/sql_lex.h	2009-07-31 15:10:25 +0000
@@ -1027,15 +1027,6 @@ public:
     locks are upgraded to exclusive metadata locks.
   */
   List<Foreign_key_name> foreign_keys_to_lock;
-  /**
-    Indicates that foreign key names in 'foreign_keys_to_lock' list
-    were added by DDL statement itself and not by prelocking algorithm
-    (as it happens for LOCK TABLES) and therefore there is no need to
-    remove these locks from the list when open_tables() performs back-off
-    after encountering a conflicting metadata lock.
-  */
-  bool has_own_foreign_keys_to_lock;
-
   /*
     These constructor and destructor serve for creation/destruction
     of Query_tables_list instances which are used as backup storage.
@@ -1084,6 +1075,17 @@ public:
     }
   }
 
+  /** Return a pointer to the last element in query table list. */
+  TABLE_LIST *last_table()
+  {
+    /* Don't use offsetof() macro in order to avoid warnings. */
+    return query_tables ?
+           (TABLE_LIST*) ((char*) query_tables_last -
+                          ((char*) &(query_tables->next_global) -
+                           (char*) query_tables)) :
+           0;
+  }
+
   /**
      Has the parser/scanner detected that this statement is unsafe?
    */

=== modified file 'sql/sql_parse.cc'
--- a/sql/sql_parse.cc	2009-05-27 03:08:49 +0000
+++ b/sql/sql_parse.cc	2009-07-31 15:10:25 +0000
@@ -2660,7 +2660,9 @@ mysql_execute_command(THD *thd)
         alter_info.foreign_key_list.empty();
 
       if (fk_generate_constraint_names(thd, create_table->table_name,
-                                       alter_info.foreign_key_list))
+                                       alter_info.foreign_key_list) ||
+          fk_check_fkey_clause(thd, alter_info.foreign_key_list,
+                               alter_info.drop_list))
       {
         res= 1;
         goto end_with_restore_list;
@@ -2673,7 +2675,9 @@ mysql_execute_command(THD *thd)
       */
       if (!(create_info.options & HA_LEX_CREATE_TMP_TABLE) &&
           fk_add_constraint_names_to_lock(thd, lex,
-                                          alter_info.foreign_key_list))
+                                          alter_info.foreign_key_list,
+                                          0,
+                                          alter_info.drop_list))
       {
         res= 1;
         goto end_with_restore_list;
@@ -2796,16 +2800,7 @@ mysql_execute_command(THD *thd)
       if (!res)
 	my_ok(thd);
     }
-
 end_with_restore_list:
-    /*
-      During next execution of this statement foreign keys without
-      explicitly specified names will get new generated names.
-      This means that current list of foreign key names to lock
-      will become invalid and should be thrown away.
-    */
-    lex->foreign_keys_to_lock.empty();
-    lex->has_own_foreign_keys_to_lock= FALSE;
     break;
   }
   case SQLCOM_CREATE_INDEX:
@@ -3706,10 +3701,14 @@ end_with_restore_list:
     thd->options|= OPTION_TABLE_LOCK;
     thd->in_lock_tables=1;
 
-    res= (open_and_lock_tables_derived(thd, all_tables, FALSE,
-                                       MYSQL_OPEN_TAKE_UPGRADABLE_MDL |
-                                       MYSQL_OPEN_TAKE_FK_NAMES_MDL) ||
-          thd->locked_tables_list.init_locked_tables(thd));
+    {
+      Lock_tables_prelocking_strategy lock_tables_prelocking_strategy;
+
+      res= (open_and_lock_tables_derived(thd, all_tables, FALSE,
+                                         MYSQL_OPEN_TAKE_UPGRADABLE_MDL,
+                                         &lock_tables_prelocking_strategy) ||
+            thd->locked_tables_list.init_locked_tables(thd));
+    }
 
     thd->in_lock_tables= 0;
     /*
@@ -7634,7 +7633,7 @@ void create_table_set_open_action_and_ad
   if (!opt_fk_all_engines && lex->create_last_non_select_table != create_table)
   {
     /*
-      To be backwards compatible in --foreign-all-engines=0 mode we have to
+      To be backwards compatible in --foreign-key-all-engines=0 mode we have to
       ignore all parent tables on SQL-layer. So remove them from statement's
       table list.
     */
@@ -7650,6 +7649,29 @@ void create_table_set_open_action_and_ad
 
 
 /**
+  Adjust table list of ALTER TABLE statement to take into account value of
+  --foreign-key-all-engines option.
+
+  @param lex  LEX for the statement.
+
+  @note To preserve backwards compatibility in --foreign-key-all-engines=0
+        mode we have to ignore all parent tables on SQL-layer. Therefore
+        we have to remove them from statement's table list.
+*/
+
+void alter_table_adjust_tables(LEX *lex)
+{
+  TABLE_LIST *alter_table= lex->query_tables;
+
+  if (! opt_fk_all_engines)
+  {
+    alter_table->next_local= alter_table->next_global= 0;
+    lex->query_tables_last= &alter_table->next_global;
+  }
+}
+
+
+/**
   CREATE TABLE query pre-check.
 
   @param thd			Thread handler

=== modified file 'sql/sql_partition.cc'
--- a/sql/sql_partition.cc	2009-05-18 04:18:37 +0000
+++ b/sql/sql_partition.cc	2009-07-31 15:10:25 +0000
@@ -4214,6 +4214,18 @@ uint prep_alter_part_table(THD *thd, TAB
     }
     *fast_alter_partition=
       ((flags & (HA_FAST_CHANGE_PARTITION | HA_PARTITION_ONE_PHASE)) != 0);
+
+    /*
+      Prohibit one-phase partitioning changes for tables which participate
+      in a foreign key constraint as a parent. For this kind of changes we
+      can't detect that it has deleted any rows from table being altered
+      therefore applying them to parent table can create orphans in child
+      table.
+    */
+    if ((flags & HA_PARTITION_ONE_PHASE) &&
+        ! table->s->fkeys.fkeys_parent.is_empty())
+      *fast_alter_partition= 0;
+
     DBUG_PRINT("info", ("*fast_alter_partition: %d  flags: 0x%x",
                         *fast_alter_partition, flags));
     if (((alter_info->flags & ALTER_ADD_PARTITION) ||
@@ -5049,6 +5061,35 @@ static bool mysql_change_partitions(ALTE
     file->print_error(error, MYF(error != ER_OUTOFMEMORY ? 0 : ME_FATALERROR));
     DBUG_RETURN(TRUE);
   }
+
+  DBUG_RETURN(FALSE);
+}
+
+
+/**
+  Check if partitioning operation on the table which serves as parent in
+  foreing key relationship might have created orphans in child table and
+  emit an appropriate error.
+
+  @param lpt  Struct describing partitioning operation.
+
+  @retval FALSE  Success, orphans were not introduced.
+  @retval TRUE   Failure, orphans might have been introduced,
+                 error was emitted.
+*/
+
+static bool mysql_check_if_fk_orphans(ALTER_PARTITION_PARAM_TYPE *lpt)
+{
+  DBUG_ENTER("mysql_check_if_fk_orphans");
+
+  if (lpt->deleted && ! lpt->table->s->fkeys.fkeys_parent.is_empty())
+  {
+    my_error(ER_FK_STATEMENT_ILLEGAL, MYF(0),
+             "ALTER TABLE ... REORGANIZE PARTITION",
+             lpt->table_list->table_name);
+    DBUG_RETURN(TRUE);
+  }
+
   DBUG_RETURN(FALSE);
 }
 
@@ -6054,7 +6095,17 @@ uint fast_alter_partition_table(THD *thd
       Thus the need to downgrade the lock disappears.
       1) Write the new frm, pack it and then delete it
       2) Perform the change within the handler
+
+      One-phase partition changes should not be carried out for tables
+      which partiticipate in foreign key constraints as parents.
+      Unlike for multi-phase partition changes for one-phase change it
+      is impossible guarantee that such operation won't create orphans
+      in child table since it is impossible to determine if this
+      operation has removed any rows from table being altered and
+      rollback its results.
     */
+    DBUG_ASSERT(lpt->table->s->fkeys.fkeys_parent.is_empty());
+
     if (mysql_write_frm(lpt, WFRM_WRITE_SHADOW | WFRM_PACK_FRM) ||
         mysql_change_partitions(lpt))
     {
@@ -6278,6 +6329,7 @@ uint fast_alter_partition_table(THD *thd
         mysql_write_frm(lpt, WFRM_WRITE_SHADOW) ||
         ERROR_INJECT_CRASH("crash_change_partition_2") ||
         mysql_change_partitions(lpt) ||
+        mysql_check_if_fk_orphans(lpt) ||
         ERROR_INJECT_CRASH("crash_change_partition_3") ||
         write_log_final_change_partition(lpt) ||
         ERROR_INJECT_CRASH("crash_change_partition_4") ||

=== modified file 'sql/sql_prepare.cc'
--- a/sql/sql_prepare.cc	2009-05-18 04:18:37 +0000
+++ b/sql/sql_prepare.cc	2009-07-31 15:10:25 +0000
@@ -2435,6 +2435,15 @@ void reinit_stmt_before_use(THD *thd, LE
   }
   lex->allow_sum_func= 0;
   lex->in_sum_func= NULL;
+  /*
+    Used by ALTER TABLE and CREATE TABLE.
+
+    During next execution of this statement foreign keys without
+    explicitly specified names will get new generated names.
+    This means that current list of foreign key names to lock
+    will become invalid and should be thrown away.
+  */
+  lex->foreign_keys_to_lock.empty();
   DBUG_VOID_RETURN;
 }
 

=== modified file 'sql/sql_table.cc'
--- a/sql/sql_table.cc	2009-05-28 15:14:22 +0000
+++ b/sql/sql_table.cc	2009-07-31 15:10:25 +0000
@@ -42,7 +42,8 @@ static int copy_data_between_tables(TABL
                                     uint order_num, ORDER *order,
                                     ha_rows *copied,ha_rows *deleted,
                                     enum enum_enable_or_disable keys_onoff,
-                                    bool error_if_not_empty);
+                                    bool error_if_not_empty,
+                                    Fk_constraint_list *fk_list);
 
 static bool prepare_blob_field(THD *thd, Create_field *sql_field);
 static bool check_engine(THD *, const char *, HA_CREATE_INFO *);
@@ -1617,8 +1618,8 @@ public:
 class Foreign_key_ddl_rcontext
 {
 public:
-  Foreign_key_ddl_rcontext(THD *thd, bool drop)
-    : m_thd(thd), m_drop(drop)
+  Foreign_key_ddl_rcontext(THD *thd)
+    : m_thd(thd) 
   {
   }
   bool prepare_drop_table(TABLE_LIST *drop_table,
@@ -1628,6 +1629,12 @@ public:
                             TABLE_LIST *fkey_tables,
                             List<Foreign_key_child> &foreign_key_list,
                             Parent_info *create_table_parent_info);
+  bool add_fks_to_parent_descriptions(List<Foreign_key_child> &fk_list);
+  bool prepare_alter_table(TABLE_LIST *alter_table,
+                           TABLE_LIST *parent_tables,
+                           List<Alter_drop> &drop_list,
+                           List<Foreign_key_child> &foreign_key_list,
+                           Parent_info *alter_table_parent_info);
   bool install_new_frms();
   void restore_old_frms();
   void remove_old_frms();
@@ -1637,11 +1644,6 @@ private:
 
   THD *m_thd;
   /**
-    TRUE - if operation for which this context is created drops table,
-    FALSE - otherwise.
-  */
-  bool m_drop;
-  /**
     List of tables which are related to table being created/dropped through
     foreign key constraints and which .FRMs should be changed accordingly.
     For example, when we are dropping some table this list should include
@@ -1650,10 +1652,10 @@ private:
     table serves as a parent.
   */
   List<TABLE_LIST> m_related_tables;
-  /**
-    List of names for constraints to be created or dropped.
-  */
-  List<Foreign_key_name> m_fk_name_list;
+  /** List of constraint names to be marked as used. */
+  List<Foreign_key_name> m_create_fk_name_list;
+  /** List of constraint names to be marked as unused. */
+  List<Foreign_key_name> m_drop_fk_name_list;
 
   /**
     We construct temporary name for new version of .FRM of each related
@@ -1703,8 +1705,6 @@ Foreign_key_ddl_rcontext::prepare_drop_t
   TABLE_LIST *tab;
   Foreign_key_name *name;
 
-  DBUG_ASSERT(m_drop);
-
   /*
     Don't drop foreign keys from .FRMs of temporary tables which are
     left from aborted ALTER TABLE.
@@ -1746,8 +1746,7 @@ Foreign_key_ddl_rcontext::prepare_drop_t
   /* Go through list of foreign keys for which table being dropped is a child. */
   while ((fk_s= fkeys_it++))
   {
-    if (strcmp(drop_table->db, fk_s->parent_table_db.str) ||
-        strcmp(drop_table->table_name, fk_s->parent_table_name.str))
+    if (! fk_s->is_self_reference)
     {
       /*
         If it is not a self-referencing foreign key we should add its
@@ -1795,7 +1794,7 @@ Foreign_key_ddl_rcontext::prepare_drop_t
     LEX_STRING db_name= { drop_table->db, drop_table->db_length };
     if (! (name= Foreign_key_name::create(m_thd->mem_root,
                                           db_name, fk_s->name)) ||
-        m_fk_name_list.push_back(name, m_thd->mem_root))
+        m_drop_fk_name_list.push_back(name, m_thd->mem_root))
     {
       pthread_mutex_lock(&LOCK_open);
       release_table_share(share);
@@ -1816,8 +1815,7 @@ Foreign_key_ddl_rcontext::prepare_drop_t
   */
   while ((fk_s_p= fkeys_p_it++))
   {
-    if (strcmp(drop_table->db, fk_s_p->child_table_db.str) ||
-        strcmp(drop_table->table_name, fk_s_p->child_table_name.str))
+    if (! fk_s_p->is_self_reference)
     {
       tab_it.rewind();
       while ((tab= tab_it++))
@@ -1853,7 +1851,7 @@ Foreign_key_ddl_rcontext::prepare_drop_t
       if (! (name= Foreign_key_name::create(m_thd->mem_root,
                                             fk_s_p->child_table_db,
                                             fk_s_p->name)) ||
-          m_fk_name_list.push_back(name, m_thd->mem_root))
+          m_drop_fk_name_list.push_back(name, m_thd->mem_root))
       {
         pthread_mutex_lock(&LOCK_open);
         release_table_share(share);
@@ -2015,6 +2013,11 @@ lock_dropped_and_parent_tables(THD *thd,
   while ((fk_name= fk_names_it++))
   {
     mdl_request= fk_name->get_mdl_request();
+    /*
+      FIXME: need to always set request type
+      even though it's initialized in constructor, because
+      remove_all_requests() resets the request type.
+    */
     mdl_request->set_type(MDL_EXCLUSIVE);
     thd->mdl_context.add_request(mdl_request);
   }
@@ -2383,15 +2386,17 @@ drop_table_check_locks(THD *thd, TABLE_L
         be affected by a DDL statement when executing LOCK TABLES statement we
         always are able to find prelocked foreign key name/metadata lock pair
         for foreign key which to be dropped.
-
-        @sa fk_take_mdl_on_fk_names().
       */
-      fk_name= thd->locked_tables_list.find_fk_name(table->db,
-                                                    fk_c_s->name.str);
-      DBUG_ASSERT(fk_name);
+      LEX_STRING db_name= { table->db, table->db_length };
 
-      if (fk_names_locked->push_back(fk_name, thd->mem_root))
+      if (! (fk_name= Foreign_key_name::create(thd->mem_root, db_name,
+                                               fk_c_s->name)) ||
+          fk_names_locked->push_back(fk_name, thd->mem_root))
         return TRUE;
+
+      fk_name->find_ticket(&thd->mdl_context);
+
+      DBUG_ASSERT(fk_name->get_mdl_request()->ticket);
     }
   }
   return FALSE;
@@ -2722,7 +2727,7 @@ int mysql_rm_table_part2(THD *thd, TABLE
     char *db=table->db;
     handlerton *table_type;
     enum legacy_db_type frm_db_type;
-    Foreign_key_ddl_rcontext fk_ctx(thd, TRUE);
+    Foreign_key_ddl_rcontext fk_ctx(thd);
 
     DBUG_PRINT("table", ("table_l: '%s'.'%s'  table: %p  s: %p",
                          table->db, table->table_name, table->table,
@@ -3105,7 +3110,6 @@ err_abort:
         Also under LOCK TABLES we have to release metadata locks
         on names of foreign keys which were dropped.
       */
-      thd->locked_tables_list.unlink_fk_name(fk_name);
       thd->mdl_context.release_all_locks_for_name(fk_name->get_mdl_request()->ticket);
     }
   }
@@ -3917,6 +3921,36 @@ mysql_prepare_create_table(THD *thd,
       if (fk_check_constraint_added(thd, fkey, create_info, alter_info, file) ||
           fk_find_unique_and_add_supporting_key(thd, fkey, alter_info))
         DBUG_RETURN(TRUE);
+
+      /*
+        For all newly created self-referencing foreign keys on this table
+        add Foreign_key_parent describing them to the table's Alter_info
+        structure.
+        Note that these objects can be constructed only after call
+        to fk_find_unique_and_add_supporting_key() function since they use
+        information produced by it.
+        Self-referencing foreign keys are present in the table description
+        twice: first as foreign key on this child table, and second as
+        foreign key which references to this parent table.
+        Non-self-referencing foreign keys are handled separately in a
+        method of Foreign_key_ddl_rcontext class.
+     */
+      if (! fkey->exists && fkey->is_self_reference)
+      {
+        Foreign_key_parent *fkey_p;
+
+        /*
+          Since it is a self-referencing foreign key its Parent_info should
+          describe table being created.
+        */
+        DBUG_ASSERT(fkey->parent_table->parent_info->alter_info == alter_info);
+
+        if (! (fkey_p= new (thd->mem_root) Foreign_key_parent(*fkey,
+                                                              thd->mem_root)) ||
+            alter_info->parent_foreign_key_list.push_back(fkey_p,
+                                                          thd->mem_root))
+          DBUG_RETURN(TRUE);
+      }
     }
     else
     {
@@ -5060,8 +5094,8 @@ warn:
    @retval TRUE  Failure
 */
 
-static bool check_parent_tables(THD *thd, TABLE_LIST *fkey_tables,
-                                List<Foreign_key_child> &fkey_list)
+static bool fk_check_parent_tables(THD *thd, TABLE_LIST *fkey_tables,
+                                   List<Foreign_key_child> &fkey_list)
 {
   List_iterator<Foreign_key_child> fkey_it(fkey_list);
   Foreign_key_child *fkey;
@@ -5071,9 +5105,13 @@ static bool check_parent_tables(THD *thd
   {
     if (tab->schema_table || tab->view)
     {
-      fkey= fkey_it++;
-      while (fkey->parent_table != tab)
-        fkey= fkey_it++;
+      /* Find the corresponding key for which this table was opened. */
+      while ((fkey= fkey_it++))
+      {
+        if (fkey->parent_table == tab)
+          break;
+      }
+      DBUG_ASSERT(fkey);
       my_error(ER_FK_PARENT_VIEW, MYF(0), fkey->name.str);
       return TRUE;
     }
@@ -5169,35 +5207,14 @@ prepare_create_table(TABLE_LIST *create_
   List_iterator<Foreign_key_child> fkey_it(foreign_key_list);
   TABLE_LIST *tab;
   Foreign_key_child *fkey;
-  Foreign_key_parent *fkey_p;
   Foreign_key_name *fk_name;
 
-  DBUG_ASSERT(! m_drop);
-
   create_table->parent_info= create_table_parent_info;
 
   for (tab= fkey_tables; tab; tab= tab->next_local)
     if (prepare_parent_table_description(m_thd, tab))
       return TRUE;
 
-  /*
-    Add objects that represent foreign keys being created to descriptions
-    of parent tables.
-    Foreign keys for which table being created serves as both child and
-    parent present in table description twice. First as foreign key on
-    this child table and second as foreign key which references to this
-    parent table.
-  */
-  fkey_it.rewind();
-  while ((fkey= fkey_it++))
-  {
-    if (!(fkey_p= new (m_thd->mem_root) Foreign_key_parent(*fkey,
-                                                           m_thd->mem_root)) ||
-        fkey->parent_table->parent_info->alter_info->
-              parent_foreign_key_list.push_back(fkey_p, m_thd->mem_root))
-      return TRUE;
-  }
-
   for (tab= fkey_tables; tab; tab= tab->next_local)
     if (m_related_tables.push_back(tab, m_thd->mem_root))
       return TRUE;
@@ -5206,7 +5223,6 @@ prepare_create_table(TABLE_LIST *create_
     Add foreign key database and name pair to the list of constraint names
     to be marked as used.
   */
-  fkey_it.rewind();
   while ((fkey= fkey_it++))
   {
     LEX_STRING child_table_db= { fkey->child_table->db,
@@ -5214,7 +5230,337 @@ prepare_create_table(TABLE_LIST *create_
     if (! (fk_name= Foreign_key_name::create(m_thd->mem_root,
                                              child_table_db,
                                              fkey->name)) ||
-        m_fk_name_list.push_back(fk_name, m_thd->mem_root))
+        m_create_fk_name_list.push_back(fk_name, m_thd->mem_root))
+      return TRUE;
+  }
+
+  prepare_filenames();
+
+  return FALSE;
+}
+
+
+/**
+  Add objects that represent foreign keys being created to descriptions of
+  parent tables.
+
+  @param fk_list  List of descriptions of foreign keys to be created.
+
+  @retval FALSE  Success.
+  @retval TRUE   Error (OOM).
+*/
+
+bool Foreign_key_ddl_rcontext::
+add_fks_to_parent_descriptions(List<Foreign_key_child> &fk_list)
+{
+  List_iterator<Foreign_key_child> fk_it(fk_list);
+  Foreign_key_child *fk;
+  Foreign_key_parent *fk_p;
+  /*
+    For all newly created foreign keys create Foreign_key_parent
+    objects and add them to descriptions of parent tables.
+    Note that self-referencing foreign keys have to be handled in
+    mysql_prepare_create_table() since this method is called too
+    late to do this.
+  */
+  while ((fk= fk_it++))
+  {
+    if (! fk->exists && ! fk->is_self_reference)
+    {
+      if (! (fk_p= new (m_thd->mem_root) Foreign_key_parent(*fk,
+                                                            m_thd->mem_root)) ||
+          fk->parent_table->parent_info->alter_info->
+              parent_foreign_key_list.push_back(fk_p, m_thd->mem_root))
+        return TRUE;
+    }
+  }
+  return FALSE;
+}
+
+
+/**
+  Prepare for ALTER TABLE operation by constructing unique list of all
+  parent tables for foreign keys to be added or dropped.
+
+  @param[in]  alter_table             Table list element for table to be
+                                      altered.
+  @param[in]  fkey_tables             List of parent tables for foreign keys
+                                      to be created.
+  @param[in]  drop_list               List of Alter_drop objects some of which
+                                      contain names of foreign keys to be
+                                      dropped.
+  @param[out] parent_tables           On return contains unique list of all
+                                      parent tables for foreign keys to be
+                                      created or dropped.
+  @retval FALSE  Success.
+  @retval TRUE   Failure.
+*/
+
+bool
+alter_table_prepare_parent_list(TABLE_LIST *alter_table,
+                                TABLE_LIST *fkey_tables,
+                                List<Alter_drop> &drop_list,
+                                TABLE_LIST **parent_tables)
+{
+  List_iterator<Alter_drop> drop_it(drop_list);
+  Alter_drop *drop;
+  List_iterator<Foreign_key_child_share> fk_s_it(alter_table->table->s->
+                                                 fkeys.fkeys_child);
+  Foreign_key_child_share *fk_s;
+  TABLE_LIST *table;
+
+  /*
+    Initialize list of parent tables for all foreign keys with the list
+    of parent tables for foreign keys to be created, which is generated
+    in parser as an unique list.
+  */
+  *parent_tables= fkey_tables;
+
+  while ((drop= drop_it++))
+  {
+    if (drop->type == Alter_drop::FOREIGN_KEY)
+    {
+      fk_s_it.rewind();
+      while ((fk_s= fk_s_it++))
+        if (! strcmp(drop->name, fk_s->name.str))
+          break;
+
+      /*
+        If foreign key to be dropped belongs to table being altered and is not
+        self-referencing remove this foreign key from the description of parent
+        table.
+
+        Self-referencing foreign keys are handled in mysql_prepare_alter_table()
+        as in their case at this point we don't have description of parent table
+        i.e. of table being altered.
+      */
+      if (fk_s && ! fk_s->is_self_reference)
+      {
+        if (! (table= find_table_in_local_list(*parent_tables,
+                                               fk_s->parent_table_db.str,
+                                               fk_s->parent_table_name.str)))
+        {
+          /*
+            We know that foreign key belongs to the table being altered,
+            therefore its parent table should be among pre-opened and
+            pre-locked tables.
+          */
+          table= find_table_in_global_list(alter_table,
+                                           fk_s->parent_table_db.str,
+                                           fk_s->parent_table_name.str);
+
+          table->next_local= *parent_tables;
+          *parent_tables= table;
+        }
+      }
+    }
+  }
+
+  return FALSE;
+}
+
+
+/**
+  Check that we have proper locks on parent tables for which foreign keys
+  are to be dropped.
+
+  @param[in] parent_tables           List of all parent tables for foreign
+                                     keys to be created or dropped, head of
+                                     this list should contain parent tables
+                                     for which foreign keys are only dropped
+                                     and not created.
+  @param[in] fk_added_parent_tables  First element of 'parent_table' list
+                                     which represents parent table for which
+                                     foreign keys are added, all remaining
+                                     elements of the list also represent
+                                     tables for which foreign keys are
+                                     added.
+
+  @note In LOCK TABLES mode for tables which represent parent tables for
+        which foreign keys are only dropped extra check that table is
+        open and has proper lock type is necessary.
+        These tables come from statement's prelocking list and in normal
+        situation checks for such tables are performed by sub-statements
+        which really use prelocked table. In case of ALTER TABLE we have
+        to perform them explicitly. (We can fail to open parent table e.g.
+        if it was removed manually and not through DROP TABLE statement).
+
+  @retval FALSE Success, all tables are open and has proper locks.
+  @retval TRUE  Error.
+*/
+
+bool alter_table_check_parent_locks(TABLE_LIST *parent_tables,
+                                    TABLE_LIST *fk_added_parent_tables)
+{
+  TABLE_LIST *table;
+
+  for (table= parent_tables;
+       table != fk_added_parent_tables;
+       table= table->next_local)
+  {
+    if (! table->table)
+    {
+      my_error(ER_NO_SUCH_TABLE, MYF(0), table->db, table->table_name);
+      return TRUE;
+    }
+    if (table->table->reginfo.lock_type < TL_WRITE_ALLOW_READ)
+    {
+      my_error(ER_TABLE_NOT_LOCKED_FOR_WRITE, MYF(0), table->table_name);
+      return TRUE;
+    }
+  }
+
+  return FALSE;
+}
+
+
+/**
+  Prepare context for ALTER TABLE operation by preparing Parent_info
+  objects for related tables with removed descriptions of foreign keys
+  to be dropped and with added descriptions of foreign keys to be created.
+
+  @param[in]  alter_table             Table list element for table to be
+                                      altered.
+  @param[in]  parent_tables           Unique list of parent tables for foreign
+                                      keys to be created or dropped.
+  @param[in]  drop_list               List of Alter_drop objects some of which
+                                      contain names of foreign keys to be
+                                      dropped.
+  @param[in]  foreign_key_list        List of foreign keys to be created.
+  @param[in]  alter_table_parent_info Parent_info for table being altered.
+
+  @note For self-referencing foreign keys final steps of preparing Parent_info
+        for table being altered happen in mysql_prepare_alter_table(), since
+        at the point where this method is called we don't have description of
+        table being altered yet.
+
+  @retval FALSE  Success.
+  @retval TRUE   Failure (e.g. OOM or absence of one of the parent tables).
+*/
+
+bool Foreign_key_ddl_rcontext::
+prepare_alter_table(TABLE_LIST *alter_table, TABLE_LIST *parent_tables,
+                    List<Alter_drop> &drop_list,
+                    List<Foreign_key_child> &foreign_key_list,
+                    Parent_info *alter_table_parent_info)
+{
+  List_iterator<Alter_drop> drop_it(drop_list);
+  Alter_drop *drop;
+  List_iterator<Foreign_key_child_share> fk_s_it(alter_table->table->s->
+                                                 fkeys.fkeys_child);
+  Foreign_key_child_share *fk_s;
+  TABLE_LIST *table;
+  List_iterator<Foreign_key_child> add_it(foreign_key_list);
+  Foreign_key_child *fkey;
+  List_iterator<TABLE_LIST> related_it(m_related_tables);
+  LEX_STRING alter_table_db= { alter_table->db, alter_table->db_length };
+  Foreign_key_name *fk_name;
+
+  alter_table->parent_info= alter_table_parent_info;
+
+  /*
+    Add parent tables for foreign keys to be added and dropped to
+    the list of related tables and prepare their descriptions.
+  */
+  for (table= parent_tables; table; table= table->next_local)
+  {
+    if (m_related_tables.push_back(table, m_thd->mem_root))
+      return TRUE;
+    if (prepare_parent_table_description(m_thd, table))
+      return TRUE;
+  }
+
+  /*
+    For each foreign key to be dropped find element of related tables
+    list for its parent table and adjust description of this table.
+  */
+  while ((drop= drop_it++))
+  {
+    if (drop->type == Alter_drop::FOREIGN_KEY)
+    {
+      fk_s_it.rewind();
+      while ((fk_s= fk_s_it++))
+        if (! strcmp(drop->name, fk_s->name.str))
+          break;
+
+      /*
+        If foreign key to be dropped belongs to table being altered and is not
+        self-referencing remove this foreign key from the description of parent
+        table.
+
+        Self-referencing foreign keys are handled in mysql_prepare_alter_table()
+        as in their case at this point we don't have description of parent table
+        i.e. of table being altered.
+      */
+      if (fk_s && ! fk_s->is_self_reference)
+      {
+        /*
+          Find parent tablein the list of related tables. If it is not there
+          add it to this list and prepare Parent_info object describing it.
+        */
+        related_it.rewind();
+        while ((table= related_it++))
+          if (! strcmp(fk_s->parent_table_db.str, table->db) &&
+              ! strcmp(fk_s->parent_table_name.str, table->table_name))
+            break;
+
+        /*
+          This foreign key belongs to the table being altered so we can assume
+          that list of related tables contains parent table for it.
+        */
+        DBUG_ASSERT(table);
+
+        /*
+          Remove foreign key being dropped from the description of parent table.
+        */
+        List_iterator<Foreign_key_parent> fk_p_it(table->parent_info->
+                                                  alter_info->
+                                                  parent_foreign_key_list);
+        Foreign_key_parent *fk_p;
+
+        while ((fk_p= fk_p_it++))
+          if (! strcmp(alter_table->db, fk_p->child_table->db) &&
+              ! strcmp(drop->name, fk_p->name.str))
+            fk_p_it.remove();
+      }
+
+      /*
+        Add foreign key name to list of .CNS files to be removed, also check
+        if there going to be new foreign key with the same name and mark it
+        accordingly.
+      */
+      if (fk_s)
+      {
+        LEX_STRING name= { (char *)drop->name, fk_s->name.length };
+
+        if (! (fk_name= Foreign_key_name::create(m_thd->mem_root,
+                                                 alter_table_db, name)) ||
+            m_drop_fk_name_list.push_back(fk_name, m_thd->mem_root))
+          return TRUE;
+
+        add_it.rewind();
+        while ((fkey= add_it++))
+        {
+          if (! strcmp(drop->name, fkey->name.str))
+          {
+            fkey->replaces_existing= TRUE;
+            break;
+          }
+        }
+      }
+    }
+  }
+
+  /*
+    Add names of foreign keys  to the list of .CNS files to be created.
+  */
+  add_it.rewind();
+  while ((fkey= add_it++))
+  {
+    if (! (fk_name= Foreign_key_name::create(m_thd->mem_root,
+                                             alter_table_db,
+                                             fkey->name)) ||
+        m_create_fk_name_list.push_back(fk_name, m_thd->mem_root))
       return TRUE;
   }
 
@@ -5404,28 +5750,24 @@ bool Foreign_key_ddl_rcontext::install_n
     i++;
   }
 
-  if (m_drop)
-  {
-    DBUG_EXECUTE_IF("fk_install_new_frms_fail_drop_cns",
-                    {
-                      my_error(ER_UNKNOWN_ERROR, MYF(0));
-                      err_tab= 0;
-                      goto error_revert_renames;
-                    });
+  DBUG_EXECUTE_IF("fk_install_new_frms_fail_drop_cns",
+                  {
+                    my_error(ER_UNKNOWN_ERROR, MYF(0));
+                    err_tab= 0;
+                    goto error_revert_renames;
+                  });
 
-    if (fk_drop_constraint_names(m_fk_name_list))
-    {
-      err_tab= 0;
-      goto error_revert_renames;
-    }
+  if (fk_drop_constraint_names(m_drop_fk_name_list))
+  {
+    err_tab= 0;
+    goto error_revert_renames;
   }
-  else
+
+  if (fk_create_constraint_names(m_create_fk_name_list))
   {
-    if (fk_create_constraint_names(m_fk_name_list))
-    {
-      err_tab= 0;
-      goto error_revert_renames;
-    }
+    (void) fk_create_constraint_names(m_drop_fk_name_list);
+    err_tab= 0;
+    goto error_revert_renames;
   }
 
   m_thd->variables.sql_mode= save_sql_mode;
@@ -5483,10 +5825,8 @@ void Foreign_key_ddl_rcontext::restore_o
     i++;
   }
 
-  if (m_drop)
-    (void) fk_create_constraint_names(m_fk_name_list);
-  else
-    (void) fk_drop_constraint_names(m_fk_name_list);
+  (void) fk_drop_constraint_names(m_create_fk_name_list);
+  (void) fk_create_constraint_names(m_drop_fk_name_list);
 
   tab_it.rewind();
   i= 0;
@@ -5538,7 +5878,7 @@ bool mysql_create_table(THD *thd, TABLE_
                                         0,
 #endif
                                         0);
-  Foreign_key_ddl_rcontext fk_ctx(thd, FALSE);
+  Foreign_key_ddl_rcontext fk_ctx(thd);
   bool result;
   Ha_global_schema_lock_guard global_schema_lock_guard(thd);
   DBUG_ENTER("mysql_create_table");
@@ -5584,15 +5924,18 @@ bool mysql_create_table(THD *thd, TABLE_
           - fk-all-engines=0 .FRMs
   */
 
-  if (opt_fk_all_engines &&
-      !(create_info->options & HA_LEX_CREATE_TMP_TABLE) &&
-      (check_parent_tables(thd, fkey_tables, alter_info->foreign_key_list) ||
-       fk_ctx.prepare_create_table(create_table, fkey_tables,
-                                   alter_info->foreign_key_list,
-                                   &create_table_parent_info)))
+  if (opt_fk_all_engines)
   {
-    result= TRUE;
-    goto close_and_unlock;
+    if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE) &&
+        (fk_check_parent_tables(thd, fkey_tables,
+                                alter_info->foreign_key_list) ||
+         fk_ctx.prepare_create_table(create_table, fkey_tables,
+                                     alter_info->foreign_key_list,
+                                     &create_table_parent_info)))
+    {
+      result= TRUE;
+      goto close_and_unlock;
+    }
   }
 
   result= mysql_create_table_no_lock(thd, create_table->db,
@@ -5616,7 +5959,8 @@ bool mysql_create_table(THD *thd, TABLE_
       }
     }
 
-    if (fk_ctx.install_new_frms())
+    if (fk_ctx.add_fks_to_parent_descriptions(alter_info->foreign_key_list) ||
+        fk_ctx.install_new_frms())
     {
       (void) quick_rm_table(create_info->db_type, create_table->db,
                             create_table->table_name, 0);
@@ -6656,7 +7000,6 @@ bool mysql_create_like_table(THD* thd, T
 {
   HA_CREATE_INFO local_create_info;
   Alter_info local_alter_info;
-  bool has_mdl_lock= FALSE;
   bool res= TRUE;
   uint not_used;
   Ha_global_schema_lock_guard global_schema_lock_guard(thd);
@@ -6969,6 +7312,7 @@ compare_tables(THD *thd,
                TABLE *table,
                Alter_info *alter_info,
                HA_CREATE_INFO *create_info,
+               Parent_info *alter_table_parent_info,
                uint order_num,
                HA_ALTER_FLAGS *alter_flags,
                HA_ALTER_INFO *ha_alter_info,
@@ -7008,6 +7352,8 @@ compare_tables(THD *thd,
 
   DBUG_ENTER("compare_tables");
 
+  alter_table_parent_info->alter_info= &tmp_alter_info;
+
   /* Create the prepared information. */
   if (mysql_prepare_create_table(thd, create_info,
                                  &tmp_alter_info,
@@ -7018,6 +7364,9 @@ compare_tables(THD *thd,
                                  &ha_alter_info->key_count,
                                  /* select_field_count */ 0))
     DBUG_RETURN(TRUE);
+
+  alter_table_parent_info->alter_info= alter_info;
+
   /* Allocate result buffers. */
   if (! (ha_alter_info->index_drop_buffer=
           (uint*) thd->alloc(sizeof(uint) * table->s->keys)) ||
@@ -7065,6 +7414,15 @@ compare_tables(THD *thd,
     disable fast alter table for all tables created by mysql versions
     prior to 5.0 branch.
     See BUG#6236.
+
+    In --foreign-key-all-engines = 1 mode all information about foreign keys
+    is stored in .FRM, so adding or dropping foreign key does not change
+    table structure and can be carried out as fast operation (or online if
+    supporting index should be created and engine supports such operation).
+    OTOH, when executing ADD CONSTRAINT and FOREIGN_KEY_CHECKS mode is on, we
+    want to check that for all values of new foreign key there are matching
+    parent key values, so we force such ALTER TABLE to be perfomed using
+    approach with full table copying. But this happens elsewhere.
   */
   if (table->s->fields != alter_info->create_list.elements ||
       table->s->db_type() != create_info->db_type ||
@@ -7075,7 +7433,8 @@ compare_tables(THD *thd,
       create_info->used_fields & HA_CREATE_USED_ROW_FORMAT ||
       create_info->used_fields & HA_CREATE_USED_PAGE_CHECKSUM ||
       create_info->used_fields & HA_CREATE_USED_TRANSACTIONAL ||
-      (alter_info->flags & (ALTER_RECREATE | ALTER_FOREIGN_KEY)) ||
+      alter_info->flags & ALTER_RECREATE ||
+      (! opt_fk_all_engines && (alter_info->flags & ALTER_FOREIGN_KEY)) ||
       order_num ||
       !table->s->mysql_version ||
       (table->s->frm_version < FRM_VER_TRUE_VARCHAR && varchar))
@@ -7097,13 +7456,11 @@ compare_tables(THD *thd,
       *alter_flags|= HA_SET_DEFAULT_CHARACTER_SET;
     if (alter_info->flags & ALTER_RECREATE)
       *alter_flags|= HA_RECREATE;
-    /* TODO check for ADD/DROP FOREIGN KEY */
-    if (alter_info->flags & ALTER_FOREIGN_KEY)
-      *alter_flags|=  HA_ALTER_FOREIGN_KEY;
     if (!table->s->mysql_version ||
         (table->s->frm_version < FRM_VER_TRUE_VARCHAR && varchar))
       *alter_flags|=  HA_ALTER_COLUMN_TYPE;
   }
+
   /*
     Use transformed info to evaluate possibility of fast ALTER TABLE
     but use the preserved field to persist modifications.
@@ -7336,6 +7693,10 @@ compare_tables(THD *thd,
 
   /*
     Step through all keys of the new table and find matching old keys.
+
+    Note that code which automatically generates supporting indexes for
+    new foreign keys relies on this code to detect new index and adjust
+    alter_flags bitmap accordingly.
   */
   for (new_key= ha_alter_info->key_info_buffer;
        new_key < new_key_end;
@@ -7613,54 +7974,185 @@ TABLE *create_altered_table(THD *thd,
 }
 
 
-/*
-  Perform a fast or on-line alter table
-
-  @param  thd            Thread handle
-  @param  table_list     The original table
-  @param  altered_table  A temporary table showing how we will change table
-  @param  create_info    Information from the parsing phase about new
-                         table properties.
-  @param  alter_info     Storage place for data used during different phases
-  @param  ha_alter_flags Bitmask that shows what will be changed
-  @param  keys_onoff     Specifies if keys are to be enabled/disabled
+/**
+  Upgrade meta-data locks on table to be altered and parent tables for
+  which we have to update .FRMs.
 
-  If mysql_alter_table() does not need to copy the table, it is
-  either a fast alter table, where the storage engine does not
-  need to know about the change, only the frm will change,
-  or the storage engine supports performing the alter table
-  operation directly, on-line, without mysql having to copy
-  the table.
+  @param thd            Thread context.
+  @param altered_table  Table to be altered.
+  @param parent_tables  List of parent tables for which foreign keys
+                        are created or dropped.
 
-  @retval  0  success
-  @retval  1  error, that happened before we took an
-              exclusive metadata lock
-  @retval  2  error, that happened after we took an
-              exclusive metadata lock
+  @retval FALSE Success.
+  @retval TRUE  Failure (E.g. statement was killed).
 */
 
-int
-mysql_fast_or_online_alter_table(THD *thd,
-                                 TABLE_LIST *table_list,
-                                 TABLE *altered_table,
-                                 HA_CREATE_INFO *create_info,
-                                 HA_ALTER_INFO *alter_info,
-                                 HA_ALTER_FLAGS *ha_alter_flags,
-                                 enum enum_enable_or_disable keys_onoff)
+static bool
+upgrade_mdl_for_altered_and_parent_tables(THD *thd,
+                                          TABLE_LIST *altered_table,
+                                          TABLE_LIST *parent_tables)
 {
-  TABLE *table= table_list->table;
-  bool is_online_alter= table->file->ha_table_flags() & HA_ONLINE_ALTER;
-  int error;
+  TABLE_LIST *table, *err_table;
 
-  DBUG_ENTER("mysql_fast_or_online_alter_table");
+  if (wait_while_table_is_used(thd, altered_table->table,
+                               HA_EXTRA_PREPARE_FOR_RENAME))
+    return TRUE;
 
-  if (is_online_alter)
+  if (opt_fk_all_engines)
   {
-    /* Tell the storage engine to prepare for the online ALTER. */
-    if (table->file->alter_table_phase1(thd, altered_table,
-                                        create_info, alter_info,
-                                        ha_alter_flags))
-      DBUG_RETURN(1);
+    for (table= parent_tables; table; table= table->next_local)
+    {
+      table->mdl_request.ticket= table->table->mdl_ticket;
+      if (wait_while_table_is_used(thd, table->table, HA_EXTRA_FORCE_REOPEN))
+      {
+        err_table= table;
+        goto err_parents;
+      }
+    }
+
+    DBUG_EXECUTE_IF("upgrade_mdl_for_altered_and_parent_tables_simulate_kill",
+                    {
+                      thd->killed= THD::KILL_QUERY;
+                      err_table= 0;
+                      goto err_parents;
+                    });
+
+    return FALSE;
+
+err_parents:
+    for (table= parent_tables; table != err_table; table= table->next_local)
+    {
+      mysql_lock_downgrade_write(thd, table->table,
+                                 table->table->reginfo.lock_type);
+      table->mdl_request.ticket->downgrade_exclusive_lock();
+    }
+
+    mysql_lock_downgrade_write(thd, altered_table->table,
+                               altered_table->table->reginfo.lock_type);
+    altered_table->mdl_request.ticket->downgrade_exclusive_lock();
+
+    return TRUE;
+  }
+  else
+  {
+    return FALSE;
+  }
+}
+
+
+/**
+  Downgrade meta-data locks on table which were altered and parent tables
+  for which we have updated .FRMs.
+
+  @param thd            Thread context.
+  @param altered_table  Table which was altered.
+  @param parent_tables  List of parent tables for which foreign keys were
+                        created or dropped.
+
+  @retval FALSE Success.
+  @retval TRUE  Failure (E.g. statement was killed).
+*/
+
+static void
+downgrade_mdl_for_altered_and_parent_tables(TABLE_LIST *altered_table,
+                                            TABLE_LIST *parent_tables)
+{
+  TABLE_LIST *table;
+
+  if (opt_fk_all_engines)
+  {
+    for (table= parent_tables; table; table= table->next_local)
+      table->mdl_request.ticket->downgrade_exclusive_lock();
+  }
+
+  altered_table->mdl_request.ticket->downgrade_exclusive_lock();
+}
+
+
+/**
+  Release meta-data locks on table for which we failed to finalize ALTER TABLE
+  and parent tables for which we were supposed to update .FRMs.
+
+  @param thd            Thread context.
+  @param altered_table  Table which was supposed to be altered.
+  @param parent_tables  List of parent tables for which foreign keys were
+                        supposed to be added or dropped.
+
+  @retval FALSE Success.
+  @retval TRUE  Failure (E.g. statement was killed).
+*/
+
+static void
+release_mdl_for_altered_and_parent_tables(THD *thd, TABLE_LIST *altered_table,
+                                          TABLE_LIST *parent_tables)
+{
+  TABLE_LIST *table;
+
+  if (opt_fk_all_engines)
+  {
+    for (table= parent_tables; table; table= table->next_local)
+      thd->mdl_context.release_all_locks_for_name(table->mdl_request.ticket);
+  }
+
+  thd->mdl_context.release_all_locks_for_name(altered_table->
+                                              mdl_request.ticket);
+}
+
+
+/*
+  Perform a fast or on-line alter table
+
+  @param  thd            Thread handle
+  @param  table_list     The original table
+  @param  altered_table  A temporary table showing how we will change table
+  @param  create_info    Information from the parsing phase about new
+                         table properties.
+  @param  alter_info     Storage place for data used during different phases
+  @param  ha_alter_flags Bitmask that shows what will be changed
+  @param  keys_onoff     Specifies if keys are to be enabled/disabled
+  @param  parent_tables  List of foreign key's parent tables which .FRMs
+                         are to be affected by this statement.
+  @param  fk_ctx         Context with information about parent tables which
+                         descriptions are to be affected by this statement.
+
+  If mysql_alter_table() does not need to copy the table, it is
+  either a fast alter table, where the storage engine does not
+  need to know about the change, only the frm will change,
+  or the storage engine supports performing the alter table
+  operation directly, on-line, without mysql having to copy
+  the table.
+
+  @retval  0  success
+  @retval  1  error, that happened before we took an
+              exclusive metadata lock
+  @retval  2  error, that happened after we took an
+              exclusive metadata lock
+*/
+
+int
+mysql_fast_or_online_alter_table(THD *thd,
+                                 TABLE_LIST *table_list,
+                                 TABLE *altered_table,
+                                 HA_CREATE_INFO *create_info,
+                                 HA_ALTER_INFO *alter_info,
+                                 HA_ALTER_FLAGS *ha_alter_flags,
+                                 enum enum_enable_or_disable keys_onoff,
+                                 TABLE_LIST *parent_tables,
+                                 Foreign_key_ddl_rcontext *fk_ctx)
+{
+  TABLE *table= table_list->table;
+  bool is_online_alter= table->file->ha_table_flags() & HA_ONLINE_ALTER;
+  int error= 0;
+
+  DBUG_ENTER("mysql_fast_or_online_alter_table");
+
+  if (is_online_alter)
+  {
+    /* Tell the storage engine to prepare for the online ALTER. */
+    if (table->file->alter_table_phase1(thd, altered_table,
+                                        create_info, alter_info,
+                                        ha_alter_flags))
+      DBUG_RETURN(1);
 
     /*
       Tell the storage engine to perform the online ALTER.
@@ -7672,19 +8164,18 @@ mysql_fast_or_online_alter_table(THD *th
                                         create_info, alter_info,
                                         ha_alter_flags))
       DBUG_RETURN(1);
-
-    /*
-      Make the metadata lock available to open_table() called to
-      reopen the table down the road.
-    */
-    table_list->mdl_request.ticket= table->mdl_ticket;
   }
 
   /*
     Upgrade the shared metadata lock to exclusive and close all
-    instances of the table in the TDC except those used in this thread.
+    instances of the table in the TDC except those used in this
+    thread.
+
+    Also upgrade metadata locks on all parent tables for which
+    we are going to change .FRMs.
   */
-  if (wait_while_table_is_used(thd, table, HA_EXTRA_PREPARE_FOR_RENAME))
+  if (upgrade_mdl_for_altered_and_parent_tables(thd, table_list,
+                                                parent_tables))
     DBUG_RETURN(1);
 
   alter_table_manage_keys(table, table->file->indexes_are_disabled(),
@@ -7694,25 +8185,41 @@ mysql_fast_or_online_alter_table(THD *th
   close_all_tables_for_name(thd, table->s, FALSE);
   table_list->table= 0;                         /* The table was closed */
 
+  if (fk_ctx->install_new_frms())
+    DBUG_RETURN(2);
+
   /*
     The final .frm file is already created as a temporary file.
     Let's rename it to the original table name.
   */
   pthread_mutex_lock(&LOCK_open);
-  error= mysql_rename_table(NULL,
-                            altered_table->s->db.str,
-                            altered_table->s->table_name.str,
-                            table_list->db, table_list->table_name,
-                            FN_FROM_IS_TMP);
+
+  DBUG_EXECUTE_IF("fast_or_online_alter_table_rename_table_fail",
+                  { my_error(ER_UNKNOWN_ERROR, MYF(0)); error= 1; });
+
+  if (! error)
+  {
+    error= mysql_rename_table(NULL,
+                              altered_table->s->db.str,
+                              altered_table->s->table_name.str,
+                              table_list->db, table_list->table_name,
+                              FN_FROM_IS_TMP);
+  }
   pthread_mutex_unlock(&LOCK_open);
 
+  if (! error)
+    fk_ctx->remove_old_frms();
+  else
+    fk_ctx->restore_old_frms();
+
   /*
     The ALTER TABLE is always in its own transaction.
     Commit must not be called while LOCK_open is locked. It could call
     wait_if_global_read_lock(), which could create a deadlock if called
     with LOCK_open.
   */
-  error= trans_commit_stmt(thd);
+  if (trans_commit_stmt(thd))
+    error= 1;
   if (trans_commit_implicit(thd))
     error= 1;
 
@@ -7779,6 +8286,172 @@ blob_length_by_type(enum_field_types typ
 
 
 /**
+  Check that column which is going to be changed or deleted by ALTER TABLE
+  participates in existing foreign key as one of its child columns and if
+  it is true emit error that performing such operation on the column is
+  illegal.
+
+  @param col_name        Name of column.
+  @param fk_list         List of foreign keys in which table being altered
+                         participates as child.
+  @param operation       Operation name to be used in error message.
+
+  @retval FALSE  Column does not participate in any existing foreign key.
+  @retval TRUE   Column participates in one of foreign keys, error was
+                 emitted.
+*/
+
+static bool
+fk_column_participates_in_fk_as_child(const char *col_name,
+                                      List<Foreign_key_child> &fk_list,
+                                      const char *operation)
+{
+  List_iterator<Foreign_key_child> fk_c_it(fk_list);
+  Foreign_key_child *fk_c;
+
+  while ((fk_c= fk_c_it++))
+  {
+    if (fk_c->exists)
+    {
+      List_iterator<Key_part_spec> col_it(fk_c->child_columns);
+      Key_part_spec *col;
+
+      while ((col= col_it++))
+        if (! my_strcasecmp(system_charset_info, col_name, col->field_name.str))
+        {
+          my_error(ER_FK_CANT_CHANGE_COLUMN, MYF(0), fk_c->name.str,
+                   operation, col_name);
+          return TRUE;
+        }
+    }
+  }
+
+  return FALSE;
+}
+
+
+/**
+  Check that column which is going to be changed or deleted by ALTER TABLE
+  participates in existing foreign key (as one of child or parent columns)
+  and if it is true emit error that performing such operation on the column
+  is illegal.
+
+  @param col_name        Name of column.
+  @param fk_list         List of foreign keys in which table being altered
+                         participates as child.
+  @param parent_fk_list  List of foreign keys in which table being altered
+                         participates as parent.
+  @param operation       Operation name to be used in error message.
+
+  @retval FALSE  Column does not participate in any existing foreign key
+                 as a child.
+  @retval TRUE   Column participates in one of foreign keys as a child,
+                 error was emitted.
+*/
+
+static bool
+fk_column_participates_in_fk(const char *col_name,
+                             List<Foreign_key_child> &fk_list,
+                             List<Foreign_key_parent> &parent_fk_list,
+                             const char *operation)
+{
+  List_iterator<Foreign_key_parent> fk_p_it(parent_fk_list);
+  Foreign_key_parent *fk_p;
+
+  if (fk_column_participates_in_fk_as_child(col_name, fk_list, operation))
+    return TRUE;
+
+  while ((fk_p= fk_p_it++))
+  {
+    if (fk_p->exists)
+    {
+      List_iterator<Key_part_spec> col_it(fk_p->child_columns);
+      Key_part_spec *col;
+
+      while ((col= col_it++))
+        if (! my_strcasecmp(system_charset_info, col_name, col->field_name.str))
+        {
+          my_error(ER_FK_CANT_CHANGE_COLUMN, MYF(0), fk_p->name.str,
+                   operation, col_name);
+          return TRUE;
+        }
+    }
+  }
+
+  return FALSE;
+}
+
+
+/**
+  Check that column for which we are going to drop or set default value
+  is a child column in existing foreign key with SET DEFAULT referential
+  action and if it is true emit error that doing so is illegal.
+
+  @param col_name        Name of column.
+  @param fk_list         List of foreign keys in which table being altered
+                         participates as child.
+
+  @retval FALSE  Column does not participate in such foreign key.
+  @retval TRUE   Column is a child column in such foreign key, an
+                 appropriate error was emitted.
+*/
+
+static bool
+fk_column_participates_in_fk_with_set_default(const char *col_name,
+                                           List<Foreign_key_child> &fk_list)
+{
+  List_iterator<Foreign_key_child> fk_c_it(fk_list);
+  Foreign_key_child *fk_c;
+
+  while ((fk_c= fk_c_it++))
+  {
+    if (fk_c->exists &&
+        (fk_c->delete_opt == FK_OPTION_SET_DEFAULT ||
+         fk_c->update_opt == FK_OPTION_SET_DEFAULT))
+    {
+      List_iterator<Key_part_spec> col_it(fk_c->child_columns);
+      Key_part_spec *col;
+
+      while ((col= col_it++))
+        if (! my_strcasecmp(system_charset_info, col_name, col->field_name.str))
+        {
+          my_error(ER_FK_CHILD_SET_DEFAULT, MYF(0), fk_c->name.str);
+          return TRUE;
+        }
+    }
+  }
+
+  return FALSE;
+}
+
+
+/**
+  Check if table being altered participates in some foreign key as parent
+  which will be preserved in its new version.
+
+  @param alter_info Alter_info object describing new version of table.
+
+  @retval FALSE  Table was not referenced by foreign keys or all such
+                 foreign keys are dropped by this ALTER TABLE.
+  @retval TRUE   Table is parent in some foreign key which will be
+                 preserved in its new version.
+*/
+
+static bool fk_has_previosly_existing_referencing_fks(Alter_info *alter_info)
+{
+  List_iterator<Foreign_key_parent> fk_it(alter_info->parent_foreign_key_list);
+  Foreign_key_parent *fk;
+
+  while ((fk= fk_it++))
+    if (fk->exists)
+      return TRUE;
+
+  return FALSE;
+}
+
+
+
+/**
   Prepare column and key definitions for CREATE TABLE in ALTER TABLE.
 
   This function transforms parse output of ALTER TABLE - lists of
@@ -7840,12 +8513,14 @@ mysql_prepare_alter_table(THD *thd, TABL
   List_iterator<Key> key_it(alter_info->key_list);
   List_iterator<Create_field> find_it(new_create_list);
   List_iterator<Create_field> field_it(new_create_list);
+  List_iterator<Foreign_key_child> fk_c_it(new_foreign_key_list);
   List<Key_part_spec> key_parts;
   uint db_create_options= (table->s->db_create_options
                            & ~(HA_OPTION_PACK_RECORD));
   uint used_fields= create_info->used_fields;
   KEY *key_info=table->key_info;
   Alter_drop *drop;
+  Foreign_key_child *fk_c;
   bool rc= TRUE;
   Create_field *def;
   Field **f_ptr,*field;
@@ -7874,9 +8549,174 @@ mysql_prepare_alter_table(THD *thd, TABL
 
   restore_record(table, s->default_values);     // Empty record for DEFAULT
 
+  /*
+    We have to process foreign keys even before handling columns in order to
+    be able to use result of this processing when changing/dropping columns.
+  */
+  if (opt_fk_all_engines)
+  {
+    List_iterator<Foreign_key_child> fk_add_c_it(alter_info->foreign_key_list);
+    List_iterator<Foreign_key_parent> fk_add_p_it(alter_info->
+                                                  parent_foreign_key_list);
+    Foreign_key_parent *fk_p;
+
+    if (! table->s->fkeys.is_empty() &&
+        table->s->fkeys.make_foreign_key_list(thd,
+                                              table_list,
+                                              &new_foreign_key_list,
+                                              &new_parent_foreign_key_list))
+      goto err;
+
+    drop_it.rewind();
+    while ((drop= drop_it++))
+    {
+      if (drop->type == Alter_drop::FOREIGN_KEY)
+      {
+        fk_c_it.rewind();
+        while ((fk_c= fk_c_it++))
+          if (! strcmp(drop->name, fk_c->name.str))
+            break;
+
+        if (fk_c)
+        {
+          fk_c_it.remove();
+
+          if (fk_c->is_self_reference)
+          {
+            List_iterator<Foreign_key_parent> fk_p_it(new_parent_foreign_key_list);
+
+            while ((fk_p= fk_p_it++))
+              if (! strcmp(table_list->db, fk_p->child_table->db) &&
+                  ! strcmp(drop->name, fk_c->name.str))
+                break;
+
+            if (fk_p)
+              fk_p_it.remove();
+          }
+        }
+        else
+        {
+          my_error(ER_FK_NO_SUCH_CONSTRAINT, MYF(0), drop->name);
+          goto err;
+        }
+
+        drop_it.remove();
+      }
+    }
+
+    if (! new_parent_foreign_key_list.is_empty() &&
+        (alter_info->flags & ALTER_DROP_PARTITION))
+    {
+      /*
+        Disallow dropping of partitions if this table is parent in some
+        foreign key which is not added by this ALTER TABLE statement (i.e.
+        previously existing foreign key). Dropping partition in such case
+        might remove some parent key values creating orphans in child table.
+      */
+      my_error(ER_FK_STATEMENT_ILLEGAL, MYF(0),
+               "ALTER TABLE ... DROP PARTITION",
+               table_list->table_name);
+      goto err;
+    }
+
+    if ((! new_foreign_key_list.is_empty() ||
+         ! new_parent_foreign_key_list.is_empty()) &&
+        (alter_info->flags & ALTER_CONVERT))
+    {
+      /*
+        Disallow ALTER TABLE ... CONVERT TO CHARSET if table participates in
+        a foreign key which has existed before this ALTER TABLE, since this
+        clause is equivalent to modifying charset for all non-BLOB char/varchar
+        columns in the table.
+      */
+      my_error(ER_FK_STATEMENT_ILLEGAL, MYF(0),
+               "ALTER TABLE ... CONVERT TO CHARSET",
+               table_list->table_name);
+      goto err;
+    }
+
     /*
-    First collect all fields from table which isn't in drop_list
+      Add descriptions of foreign keys added by this ALTER TABLE statement.
     */
+    while ((fk_c= fk_add_c_it++))
+      new_foreign_key_list.push_back(fk_c, thd->mem_root);
+    while ((fk_p= fk_add_p_it++))
+      new_parent_foreign_key_list.push_back(fk_p, thd->mem_root);
+
+    if (! new_foreign_key_list.is_empty() ||
+        ! new_parent_foreign_key_list.is_empty())
+    {
+      if ((alter_info->flags & ALTER_RENAME))
+      {
+        /*
+          We disallow usage of RENAME clause if new version of table contains
+          any foreign keys (added by this ALTER or earlier). This is because
+          updating descriptions of existing foreign keys is hard and creation
+          of new foreign keys taking into account new table name is also
+          non-trivial.
+        */
+        my_error(ER_FK_STATEMENT_ILLEGAL, MYF(0),
+                 "ALTER TABLE ... RENAME TABLE",
+                 table_list->table_name);
+        goto err;
+      }
+    }
+
+    if (! new_foreign_key_list.is_empty())
+    {
+      if (alter_info->keys_onoff == DISABLE)
+      {
+        /*
+          Disallow ALTER TABLE ... DISABLE KEYS on tables which participate
+          in a foreign key as child, as this will disable supporting index
+          for foreign key.
+          We allow ALTER TABLE ... DISABLE KEYS on parent tables since this
+          operation won't disable unique indexes anyway.
+        */
+        my_error(ER_FK_STATEMENT_ILLEGAL, MYF(0),
+                 "ALTER TABLE ... DISABLE KEYS",
+                 table_list->table_name);
+        goto err;
+      }
+      else if (alter_info->keys_onoff != ENABLE &&
+               table->file->indexes_are_disabled())
+      {
+        /*
+          Also we disallow creation of foreign keys on tables with disabled
+          indexes. This check is done here as in mysql_prepare_create_table()
+          handler::indexes_are_disabled() always reports that all keys are
+          enabled (they are disabled during later steps of ALTER execution).
+        */
+        my_error(ER_FK_CHILD_NO_INDEX, MYF(0),
+                 new_foreign_key_list.head()->name.str);
+        goto err;
+      }
+    }
+  }
+  else
+  {
+    /*
+      For pre-WL#148 case we simply pass all foreign keys added by ALTER TABLE
+      as foreign keys for mysql_create_table_no_lock() in order to properly
+      create supporting indexes.
+    */
+    new_foreign_key_list.swap(alter_info->foreign_key_list);
+    new_parent_foreign_key_list.swap(alter_info->parent_foreign_key_list);
+    /*
+      Also to be compatible with pre-WL#148 server in old mode server have to
+      ignore DROP FOREIGN KEY clauses on SQL-layer (they are processed by
+      engines). To achieve this we remove all Alter_drop instances which
+      correspond to such clauses from the 'drop_list'.
+    */
+    drop_it.rewind();
+    while ((drop= drop_it++))
+      if (drop->type == Alter_drop::FOREIGN_KEY)
+        drop_it.remove();
+  }
+
+  /*
+    Now let us collect all fields from table which isn't in drop_list.
+  */
   for (f_ptr=table->field ; (field= *f_ptr) ; f_ptr++)
     {
     Alter_drop *drop;
@@ -7888,17 +8728,25 @@ mysql_prepare_alter_table(THD *thd, TABL
     {
       if (drop->type == Alter_drop::COLUMN &&
 	  !my_strcasecmp(system_charset_info,field->field_name, drop->name))
-    {
-	/* Reset auto_increment value if it was dropped */
+      {
+        /*
+          Check that the column which we are going to drop does not participate
+          in foreign key (unless this foreign key is created by this ALTER).
+        */
+        if (fk_column_participates_in_fk(drop->name, new_foreign_key_list,
+                                         new_parent_foreign_key_list, "DROP"))
+          goto err;
+
+        /* Reset auto_increment value if it was dropped */
 	if (MTYP_TYPENR(field->unireg_check) == Field::NEXT_NUMBER &&
 	    !(used_fields & HA_CREATE_USED_AUTO))
-      {
+        {
 	  create_info->auto_increment_value=0;
 	  create_info->used_fields|=HA_CREATE_USED_AUTO;
-      }
+        }
 	break;
+      }
     }
-  }
     if (drop)
       {
       drop_it.remove();
@@ -7914,6 +8762,15 @@ mysql_prepare_alter_table(THD *thd, TABL
     }
     if (def)
     {						// Field is changed
+      /*
+        Check that the column which is going to be changed does not participate
+        in foreign key (unless this foreign key is created by this ALTER).
+      */
+      if (fk_column_participates_in_fk(def->change, new_foreign_key_list,
+                                       new_parent_foreign_key_list,
+                                       "CHANGE/MODIFY"))
+        goto err;
+
       def->field=field;
       if (!def->after)
 	{
@@ -7937,7 +8794,10 @@ mysql_prepare_alter_table(THD *thd, TABL
 	  break;
         }
       if (alter)
-	{
+      {
+        if (fk_column_participates_in_fk_with_set_default(alter->name,
+                                                          new_foreign_key_list))
+          goto err;
 	if (def->sql_type == MYSQL_TYPE_BLOB)
 	{
 	  my_error(ER_BLOB_CANT_HAVE_DEFAULT, MYF(0), def->change);
@@ -8028,25 +8888,6 @@ mysql_prepare_alter_table(THD *thd, TABL
     goto err;
     }
 
-  if (opt_fk_all_engines && !table->s->fkeys.is_empty())
-  {
-    if (table->s->fkeys.make_foreign_key_list(thd,
-                                              table_list,
-                                              &new_foreign_key_list,
-                                              &new_parent_foreign_key_list))
-      goto err;
-  }
-  else
-  {
-    /*
-      For pre-WL#148 case we simply pass all foreign keys added by ALTER TABLE
-      as foreign keys for mysql_create_table_no_lock() in order to properly
-      create supporting indexes.
-    */
-    new_foreign_key_list.swap(alter_info->foreign_key_list);
-    new_parent_foreign_key_list.swap(alter_info->parent_foreign_key_list);
-  }
-
     /*
     Collect all keys which isn't in drop list. Add only those
     for which some fields exists.
@@ -8063,7 +8904,115 @@ mysql_prepare_alter_table(THD *thd, TABL
 	break;
       }
     if (drop)
+    {
+      if (key_info->flags & HA_NOSAME)
+      {
+        /*
+          If we are about to drop PRIMARY/UNIQUE key check that it is not
+          used as a parent key by some previously existing foreign key and
+          if yes emit an appropriate error message.
+        */
+        List_iterator<Foreign_key_parent> fk_p_it(new_parent_foreign_key_list);
+        Foreign_key_parent *fk_p;
+
+        while ((fk_p=  fk_p_it++))
+        {
+          if (fk_p->exists &&
+              ! my_strcasecmp(system_charset_info,
+                              fk_p->parent_constraint_name->str, key_name))
+          {
+            my_error(ER_FK_PARENT_SO_UNIQUE_INDEX_UNDROPPABLE, MYF(0),
+                     fk_p->name.str);
+            goto err;
+          }
+        }
+
+        /*
+          Also check if this PRIMARY/UNIQUE key serves as a supporting key
+          in some foreign key. In this case it makes sense to allow dropping
+          of this unique key (in order to allow user to get rid of unique
+          constraint which it implements) and automatically create a
+          non-unique supporting key as a replacement.
+        */
+        List_iterator<Foreign_key_child> fk_c_it(new_foreign_key_list);
+        Foreign_key_child *fk_c;
+
+        while ((fk_c=  fk_c_it++))
         {
+          if (fk_c->exists && fk_c->is_supporting_key(key_info))
+          {
+            /*
+              This key can serve as supporting key. Check that there will
+              be no other suitable keys and create a non-unique replacement
+              if yes. Also emit an appropriate warning in this case.
+
+              Note that if not only this warning we could have created
+              supporting key unconditionally and relied on code removing
+              redundant generated keys to drop this key in cases when there
+              is another suitable alternative.
+            */
+            KEY *not_added_key, *not_added_key_end;
+            List_iterator<Key> added_key_it(new_key_list);
+            Key *key;
+
+            /*
+              First of all check that there are no other suitable keys among
+              those which already exist but were not yet added to the new
+              definition of table which is being constructed by this call
+              to mysql_prepare_alter_table().
+            */
+            for (not_added_key= key_info + 1,
+                 not_added_key_end= table->key_info + table->s->keys;
+                 not_added_key != not_added_key_end; not_added_key++)
+              if (fk_c->is_supporting_key(not_added_key))
+                break;
+            if (not_added_key != not_added_key_end)
+              continue;
+
+            /*
+              Then check keys which were already added to this new definition.
+            */
+            while ((key= added_key_it++))
+              if (fk_c->is_supporting_key(key))
+                break;
+            if (key)
+              continue;
+
+            /*
+              Finally check if suitable key will be added within this ALTER
+              statement.
+            */
+            key_it.rewind();
+            while ((key= key_it++))
+              if (fk_c->is_supporting_key(key))
+                break;
+            if (key)
+              continue;
+
+            /*
+              No other suitable keys were found. Add new key and emit warning
+              saying what is happening.
+            */
+            if (! (key= new (thd->mem_root) Key(Key::MULTIPLE, fk_c->name,
+                                                &default_key_create_info,
+                                                TRUE, fk_c->child_columns)) ||
+                new_key_list.push_back(key, thd->mem_root))
+              goto err;
+
+            push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
+                                ER_FK_REPLACEMENT_KEY,
+                                ER(ER_FK_REPLACEMENT_KEY),
+                                fk_c->name.str, key_name);
+
+            /*
+              There is no need for checking other foreign keys as new key will
+              serve their needs as well.
+            */
+            break;
+          }
+        }
+      }
+
       drop_it.remove();
       continue;
     }
@@ -8173,9 +9122,39 @@ mysql_prepare_alter_table(THD *thd, TABL
   }
   {
     Key *key;
+    key_it.rewind();
     while ((key=key_it++))			// Add new keys
     {
-      new_key_list.push_back(key);
+      if (key->type == Key::PRIMARY)
+      {
+        List_iterator<Key_part_spec> col_it(key->columns);
+        Key_part_spec *col;
+
+        /*
+          Since ADD PRIMARY KEY implicitly makes all participating columns
+          NOT NULL it may change values in these columns (NULL values are
+          replaced to default values for type). Because of this making
+          NULL-able columns which participate in a foreign key as children
+          part of PRIMARY KEY can create dangling references and should be
+          prohibited.
+        */
+        while ((col= col_it++))
+        {
+          /* Find column definition in order to understand if it is nullable. */
+          field_it.rewind();
+          while ((def= field_it++))
+	    if (! my_strcasecmp(system_charset_info,
+				col->field_name.str, def->field_name))
+              break;
+
+          if (def && !(def->flags & NOT_NULL_FLAG) &&
+              fk_column_participates_in_fk_as_child(col->field_name.str,
+                                                    new_foreign_key_list,
+                                                    "ADD PRIMARY KEY"))
+            goto err;
+        }
+      }
+
       if (key->name.str &&
           !my_strcasecmp(system_charset_info,
                          key->name.str, primary_key_name.str))
@@ -8183,24 +9162,51 @@ mysql_prepare_alter_table(THD *thd, TABL
 	my_error(ER_WRONG_NAME_FOR_INDEX, MYF(0), key->name.str);
         goto err;
       }
+
+      new_key_list.push_back(key);
     }
   }
 
-
   /*
-    Until WL#148 is implemented foreign keys is handled in
-    engines so we have to ignore requests to drop FK here.
+    Check that for each previously existing foreign key in which
+    this table is a child the new version of the table still has
+    a supporting index (i.e. that we have not dropped last
+    supporting index for any such foreign key). Don't try to find
+    supporting indexes for new keys: we will auto-generate them in
+    mysql_prepare_create_table() if they are missing. We don't,
+    however, auto-generate a new supporting index for a foreign
+    key if this index is dropped (that would be a Sisyphean
+    task), instead, we don't let users drop it. But there is an
+    exception even to that: we *do* let users drop a supporting
+    index and auto-generate a new one in case when this supporting
+    index was a UNIQUE/PRIMARY index, so in fact was supporting
+    the UNIQUE constraint as well (please see checks several lines
+    above). In this case, it gets replaced by an auto-generated
+    non-unique index in mysql_prepare_create_table().
   */
-  drop_it.rewind();
-  while ((drop= drop_it++))
+  fk_c_it.rewind();
+  while ((fk_c=  fk_c_it++))
   {
-    if (drop->type != Alter_drop::FOREIGN_KEY)
+    if (fk_c->exists)
     {
-      my_error(ER_CANT_DROP_FIELD_OR_KEY, MYF(0), drop->name);
-      goto err;
+      /*
+        new_key_list contains the final list of keys.
+        This allows the user to replace a key in a single
+        ALTER specification, e.g. change a non-unique index to
+        unique.
+      */
+      if (fk_check_if_supporting_key_exists(fk_c, new_key_list))
+        goto err;
     }
   }
 
+  if (alter_info->drop_list.elements)
+  {
+    my_error(ER_CANT_DROP_FIELD_OR_KEY, MYF(0),
+             alter_info->drop_list.head()->name);
+    goto err;
+  }
+
   if (alter_info->alter_list.elements)
   {
     my_error(ER_CANT_DROP_FIELD_OR_KEY, MYF(0),
@@ -8241,6 +9247,299 @@ 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).
+*/
+
+static 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.
+*/
+
+static 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.
+*/
+
+static void
+alter_table_adjust_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++))
+    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();
+}
+
+
+/**
+  Release metadata locks on foreign key names after failure to finalize
+  ALTER TABLE under LOCK TABLES.
+
+  @param thd               Thread context.
+  @param fk_names_drop     List of names of foreign keys which were supposed
+                           to be dropped locks for which should be released.
+  @param fk_names_add      List of names of foreign keys which were supposed
+                           to be added locks for which should be released.
+  @param fk_names_replace  List of names of foreign keys which were supposed
+                           to replace old foreign keys locks for which should
+                           be downgraded.
+*/
+
+static 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;
+
+  while ((fk_name= fk_drop_it++))
+    thd->mdl_context.release_lock(fk_name->get_mdl_request()->ticket);
+
+  while ((fk_name= fk_add_it++))
+    thd->mdl_context.release_lock(fk_name->get_mdl_request()->ticket);
+
+  while ((fk_name= fk_replace_it++))
+    thd->mdl_context.release_lock(fk_name->get_mdl_request()->ticket);
+}
+
+
 /*
   Alter table
 
@@ -8290,7 +9589,6 @@ bool mysql_alter_table(THD *thd,char *ne
                        uint order_num, ORDER *order, bool ignore)
 {
   TABLE *table, *new_table= 0;
-  MDL_ticket *mdl_ticket;
   MDL_request target_mdl_request;
   bool has_target_mdl_lock= FALSE;
   int error= 0;
@@ -8305,6 +9603,8 @@ bool mysql_alter_table(THD *thd,char *ne
   uint fast_alter_partition= 0;
   bool partition_changed= FALSE;
 #endif
+  TABLE_LIST *parent_tables;
+  List<Foreign_key_name> fk_names_add, fk_names_drop, fk_names_replace;
   DBUG_ENTER("mysql_alter_table");
 
   /*
@@ -8439,6 +9739,39 @@ view_err:
     DBUG_RETURN(error);
   }
 
+  /*
+    Main alter table case. Do some checks and preparations
+    that can be done without any locks.
+  */

+  if (opt_fk_all_engines)
+  {
+    if (fk_check_fkey_clause(thd, alter_info->foreign_key_list,
+                             alter_info->drop_list) ||
+        fk_generate_constraint_names(thd, table_list->table_name,
+                                     alter_info->foreign_key_list))
+      DBUG_RETURN(TRUE);
+  }
+
+  if (opt_fk_all_engines)
+  {
+    /*
+      We must pre-lock names of all constraints that are being
+      created or dropped. For that, populate the pre-locking list
+      with names of constraints mentioned in the ALTER
+      specification. We can't do it earlier (e.g. in the parser),
+      because we don't know names of "anonymous" constraints, i.e.
+      those for which names were not given explicitly. These names
+      were auto-generated several lines above.
+    */
+    if (fk_add_constraint_names_to_lock(thd, thd->lex,
+                                        alter_info->foreign_key_list,
+                                        table_list,
+                                        alter_info->drop_list))
+      DBUG_RETURN(TRUE);
+  }
+
+  /* Open tables and take locks. */
+
   Ha_global_schema_lock_guard global_schema_lock_guard(thd);
   if (table_type == DB_TYPE_NDBCLUSTER ||
       (create_info->db_type && create_info->db_type->db_type == DB_TYPE_NDBCLUSTER))
@@ -8462,11 +9795,58 @@ view_err:
       global_schema_lock_guard.lock();
     }
   }
-  if (!(table= open_n_lock_single_table(thd, table_list, TL_WRITE_ALLOW_READ,
-                                        MYSQL_OPEN_TAKE_UPGRADABLE_MDL)))
+
+  /*
+    Code below can handle only base tables so ensure that we won't open a view.
+    Note that RENAME TABLE the only ALTER clause which is supported for views
+    has been already processed.
+  */
+  table_list->required_type= FRMTYPE_TABLE;
+
+  Alter_table_prelocking_strategy alter_prelocking_strategy(alter_info);
+
+  error= open_and_lock_tables_derived(thd, table_list, FALSE,
+                                      MYSQL_OPEN_TAKE_UPGRADABLE_MDL,
+                                      &alter_prelocking_strategy);
+
+  if (error)
+  {
     DBUG_RETURN(TRUE);
+  }
+
+  table= table_list->table;
   table->use_all_columns();
-  mdl_ticket= table->mdl_ticket;
+
+  /*
+    Make the metadata lock available to code handling downgrade of metadata
+    locks and to open_table() called by mysql_fast_or_online_alter_table().
+  */
+  table_list->mdl_request.ticket= table->mdl_ticket;
+
+  Foreign_key_ddl_rcontext fk_ctx(thd);
+  Parent_info alter_table_parent_info (create_info,
+                                       alter_info,
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+                                       0,
+#endif
+                                       0);
+
+  if (opt_fk_all_engines)
+  {
+    if (fk_check_parent_tables(thd, table_list->next_local,
+                               alter_info->foreign_key_list) ||
+        alter_table_prepare_parent_list(table_list, table_list->next_local,
+                                        alter_info->drop_list,
+                                        &parent_tables) ||
+        alter_table_check_parent_locks(parent_tables,
+                                       table_list->next_local) ||
+        fk_ctx.prepare_alter_table(table_list, parent_tables,
+                                   alter_info->drop_list,
+                                   alter_info->foreign_key_list,
+                                   &alter_table_parent_info))
+
+      DBUG_RETURN(TRUE);
+  }
 
   /*
     Prohibit changing of the UNION list of a non-temporary MERGE table
@@ -8474,7 +9854,8 @@ view_err:
     set of tables from the old table or to open a new TABLE object for
     an extended list and verify that they belong to locked tables.
   */
-  if (thd->locked_tables_mode &&
+  if ((thd->locked_tables_mode == LTM_LOCK_TABLES ||
+       thd->locked_tables_mode == LTM_PRELOCKED_UNDER_LOCK_TABLES) &&
       (create_info->used_fields & HA_CREATE_USED_UNION) &&
       (table->s->tmp_table == NO_TMP_TABLE))
   {
@@ -8603,6 +9984,24 @@ view_err:
   if (!(alter_info->flags & ~(ALTER_RENAME | ALTER_KEYS_ONOFF)) &&
       !table->s->tmp_table) // no need to touch frm
   {
+
+    if (! table->s->fkeys.is_empty() && (alter_info->flags & ALTER_RENAME))
+    {
+      my_error(ER_FK_STATEMENT_ILLEGAL, MYF(0),
+               "ALTER TABLE ... RENAME TABLE",
+               table_list->table_name);
+      goto err;
+    }
+
+    if (! table->s->fkeys.fkeys_child.is_empty() &&
+        alter_info->keys_onoff == DISABLE)
+    {
+      my_error(ER_FK_STATEMENT_ILLEGAL, MYF(0),
+               "ALTER TABLE ... DISABLE KEYS",
+               table_list->table_name);
+      goto err;
+    }
+
     switch (alter_info->keys_onoff) {
     case LEAVE_AS_IS:
       break;
@@ -8703,7 +10102,8 @@ view_err:
       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))
     {
       /*
         Under LOCK TABLES we should adjust meta-data locks before finishing
@@ -8714,23 +10114,25 @@ view_err:
       */
       if (new_name != table_name || new_db != db)
       {
-        thd->mdl_context.release_all_locks_for_name(mdl_ticket);
+        thd->mdl_context.release_all_locks_for_name(table_list->
+                                                    mdl_request.ticket);
       }
       else
-        mdl_ticket->downgrade_exclusive_lock();
+        table_list->mdl_request.ticket->downgrade_exclusive_lock();
     }
     DBUG_RETURN(error);
   }
 
   /* We have to do full alter table. */
 
-  /*
-    This is a temporary work-around which should go away once
-    "DDL checks and changes" milestone of WL#148 "Foreign keys"
-    is implemented.
-  */
-  if (opt_fk_all_engines)
-    alter_info->foreign_key_list.empty();
+  if (opt_fk_all_engines &&
+      table->s->tmp_table == NO_TMP_TABLE &&
+      (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,
@@ -8783,6 +10185,21 @@ view_err:
     alter_info->build_method= HA_BUILD_OFFLINE;
   }
 
+  /*
+    When executing ADD CONSTRAINT we want to check that for all values
+    of new foreign key there are matching parent key values, so such
+    ALTER TABLE should be perfomed using approach with full table
+    copying, unless FOREIGN_KEY_CHECKS mode is set to 0.
+  */
+  if (opt_fk_all_engines &&
+      (alter_info->flags & ALTER_FOREIGN_KEY) &&
+      ! (thd->options & OPTION_NO_FOREIGN_KEY_CHECKS) &&
+      ! alter_info->foreign_key_list.is_empty())
+  {
+    alter_info->build_method= HA_BUILD_OFFLINE;
+  }
+
+
   if (alter_info->build_method != HA_BUILD_OFFLINE)
   {
     TABLE *altered_table= 0;
@@ -8792,7 +10209,9 @@ view_err:
     bool need_copy_table= TRUE;
     /* Check how much the tables differ. */
     if (compare_tables(thd, table, alter_info,
-                       create_info, order_num,
+                       create_info,
+                       &alter_table_parent_info,
+                       order_num,
                        &ha_alter_flags,
                        &ha_alter_info,
                        &table_changes))
@@ -8825,6 +10244,8 @@ view_err:
     {
       Alter_info tmp_alter_info(*alter_info, thd->mem_root);
 
+      alter_table_parent_info.alter_info= &tmp_alter_info;
+
       /*
         If no table rename,
         check if table can be altered on-line
@@ -8837,6 +10258,8 @@ view_err:
                                                 !strcmp(db, new_db))))
         goto err;
 
+      alter_table_parent_info.alter_info= alter_info;
+
       switch (table->file->check_if_supported_alter(altered_table,
                                                     create_info,
                                                     &ha_alter_flags,
@@ -8853,6 +10276,18 @@ view_err:
           already now.
         */
         need_copy_table= FALSE;
+
+        /*
+          Before we proceed with fast or online alter table we have to adjust
+          descriptions of parent tables by adding information about foreign
+          keys to be created.
+        */
+        if (opt_fk_all_engines &&
+            fk_ctx.add_fks_to_parent_descriptions(tmp_alter_info.foreign_key_list))
+        {
+          close_temporary_table(thd, altered_table, 1, 1);
+          goto err;
+        }
         break;
       case HA_ALTER_NOT_SUPPORTED:
         if (alter_info->build_method == HA_BUILD_ONLINE)
@@ -8891,7 +10326,9 @@ view_err:
                                               create_info,
                                               &ha_alter_info,
                                               &ha_alter_flags,
-                                              alter_info->keys_onoff);
+                                              alter_info->keys_onoff,
+                                              parent_tables,
+                                              &fk_ctx);
       close_temporary_table(thd, altered_table, 1, 1);
 
       switch (error) {
@@ -8926,6 +10363,14 @@ view_err:
     goto err;
   }
 
+  /*
+    Adjust descriptions of parent tables by adding information about foreign
+    keys to be created.
+  */
+  if (opt_fk_all_engines &&
+      fk_ctx.add_fks_to_parent_descriptions(alter_info->foreign_key_list))
+    goto err_new_table_cleanup;
+
   /* Open the table so we need to copy the data to it. */
   if (table->s->tmp_table)
   {
@@ -8951,6 +10396,24 @@ view_err:
   if (!new_table)
     goto err_new_table_cleanup;
 
+  if (fk_check_if_can_change_engine(thd, alter_info, table_list,
+                                    new_table->file))
+    goto err_new_table_cleanup;
+
+  /*
+    ALTER TABLE should not be allowed to delete rows if there are chances
+    that these rows can be referenced by some existing foreign key.
+    So if there are foreign keys which were created before this ALTER TABLE
+    statement referencing table being altered we ignore IGNORE clause.
+  */
+  if (ignore && fk_has_previosly_existing_referencing_fks(alter_info))
+  {
+    ignore= FALSE;
+    push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
+                        ER_FK_ALTER_IGNORE_IGNORED,
+                        ER(ER_FK_ALTER_IGNORE_IGNORED));
+  }
+
   /* Copy the data if necessary. */
   thd->count_cuted_fields= CHECK_FIELD_WARN;	// calc cuted fields
   thd->cuted_fields=0L;
@@ -8961,15 +10424,26 @@ view_err:
   */
   if (!(new_table->file->ha_table_flags() & HA_NO_COPY_ON_ALTER))
   {
-    /* We don't want update TIMESTAMP fields during ALTER TABLE. */
-    new_table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET;
-    new_table->next_number_field=new_table->found_next_number_field;
-    thd_proc_info(thd, "copy to tmp table");
-    error= copy_data_between_tables(table, new_table,
-                                    alter_info->create_list, ignore,
-                                   order_num, order, &copied, &deleted,
-                                    alter_info->keys_onoff,
-                                    alter_info->error_if_not_empty);
+    Fk_constraint_list fk_list(TRG_EVENT_INSERT, NULL);
+
+    if (fk_list.prepare_alter_check_parent(thd, new_table, tmp_name,
+                                           alter_info->foreign_key_list))
+    {
+      error= 1;
+    }
+    else
+    {
+      /* We don't want update TIMESTAMP fields during ALTER TABLE. */
+      new_table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET;
+      new_table->next_number_field=new_table->found_next_number_field;
+      thd_proc_info(thd, "copy to tmp table");
+      error= copy_data_between_tables(table, new_table,
+                                      alter_info->create_list, ignore,
+                                      order_num, order, &copied, &deleted,
+                                      alter_info->keys_onoff,
+                                      alter_info->error_if_not_empty,
+                                      &fk_list);
+    }
   }
   else
   {
@@ -8985,7 +10459,9 @@ view_err:
   if (table->s->tmp_table != NO_TMP_TABLE)
   {
     /* Close lock if this is a transactional table */
-    if (thd->lock && ! thd->locked_tables_mode)
+    if (thd->lock &&
+        ! (thd->locked_tables_mode == LTM_LOCK_TABLES ||
+           thd->locked_tables_mode == LTM_PRELOCKED_UNDER_LOCK_TABLES))
     {
       mysql_unlock_tables(thd, thd->lock);
       thd->lock=0;
@@ -9032,7 +10508,8 @@ view_err:
   if (lower_case_table_names)
     my_casedn_str(files_charset_info, old_name);
 
-  if (wait_while_table_is_used(thd, table, HA_EXTRA_PREPARE_FOR_RENAME))
+  if (upgrade_mdl_for_altered_and_parent_tables(thd, table_list,
+                                                parent_tables))
     goto err_new_table_cleanup;
 
   close_all_tables_for_name(thd, table->s,
@@ -9042,6 +10519,13 @@ view_err:
   table_list->table= table= 0;                  /* Safety */
   save_old_db_type= old_db_type;
 
+
+  if (fk_ctx.install_new_frms())
+  {
+    (void) quick_rm_table(new_db_type, new_db, tmp_name, FN_IS_TMP);
+    goto err_with_mdl;
+  }
+
   /*
     This leads to the storage engine (SE) not being notified for renames in
     mysql_rename_table(), because we just juggle with the FRM and nothing
@@ -9067,8 +10551,12 @@ view_err:
 
     DEBUG_SYNC(thd, "alter_table_before_rename");
 
-    if (mysql_rename_table(new_db_type, new_db, tmp_name, new_db,
-                              new_alias, FN_FROM_IS_TMP) ||
+    DBUG_EXECUTE_IF("alter_table_rename_table_fail",
+                    { my_error(ER_UNKNOWN_ERROR, MYF(0)); error= 1; });
+
+    if (error ||
+        mysql_rename_table(new_db_type, new_db, tmp_name, new_db,
+                           new_alias, FN_FROM_IS_TMP) ||
            (new_name != table_name || new_db != db) && // we also do rename
            Table_triggers_list::change_table_name(thd, db, table_name,
                                                   new_db, new_alias))
@@ -9091,7 +10579,12 @@ view_err:
   pthread_mutex_unlock(&LOCK_open);
 
   if (error)
+  {
+    fk_ctx.restore_old_frms();
     goto err_with_mdl;
+  }
+  else
+    fk_ctx.remove_old_frms();
 
 end_online:
   if (thd->locked_tables_list.reopen_tables(thd))
@@ -9141,14 +10634,21 @@ 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)
   {
+    downgrade_mdl_for_altered_and_parent_tables(table_list, parent_tables);
+
     if ((new_name != table_name || new_db != db))
     {
-      thd->mdl_context.release_all_locks_for_name(mdl_ticket);
+      thd->mdl_context.release_all_locks_for_name(table_list->
+                                                  mdl_request.ticket);
     }
-    else
-      mdl_ticket->downgrade_exclusive_lock();
+
+    if (opt_fk_all_engines)
+      alter_table_adjust_fk_name_locks_under_lock_tables(thd, fk_names_drop,
+                                                         fk_names_add,
+                                                         fk_names_replace);
   }
 
 end_temporary:
@@ -9207,22 +10707,49 @@ 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:
   /*
-    An error happened while we were holding exclusive name metadata lock
+    An error happened while we were holding exclusive metadata lock
     on table being altered. To be safe under LOCK TABLES we should
     remove all references to the altered table from the list of locked
     tables and release the exclusive metadata lock.
+
+    Also to make cleanup simple we do the same thing for all parent tables.
+    To do that we close all parent tables which are still open by calling
+    Foreign_key_ddl_rcontext::cleanup() first.
   */
+  fk_ctx.cleanup();
+
   thd->locked_tables_list.unlink_all_closed_tables();
+
   if (has_target_mdl_lock)
   {
     thd->mdl_context.release_lock(target_mdl_request.ticket);
     thd->mdl_context.remove_request(&target_mdl_request);
   }
-  thd->mdl_context.release_all_locks_for_name(mdl_ticket);
+
+  /*
+    Under LOCK TABLES we can't rely on close_thread_tables() releasing all
+    metadata locks so we have to do it explicitly.
+  */
+  if (thd->locked_tables_mode == LTM_LOCK_TABLES ||
+      thd->locked_tables_mode == LTM_PRELOCKED_UNDER_LOCK_TABLES)
+  {
+    release_mdl_for_altered_and_parent_tables(thd, table_list, parent_tables);
+
+    if (opt_fk_all_engines)
+      alter_table_release_fk_name_locks_under_lock_tables(thd, fk_names_drop,
+                                                          fk_names_add,
+                                                          fk_names_replace);
+  }
   DBUG_RETURN(TRUE);
 }
 /* mysql_alter_table */
@@ -9235,7 +10762,8 @@ copy_data_between_tables(TABLE *from,TAB
 			 ha_rows *copied,
 			 ha_rows *deleted,
                          enum enum_enable_or_disable keys_onoff,
-                         bool error_if_not_empty)
+                         bool error_if_not_empty,
+                         Fk_constraint_list *fk_list)
 {
   int error= 1, errpos= 0;
   Copy_field *copy= NULL, *copy_end;
@@ -9272,6 +10800,10 @@ copy_data_between_tables(TABLE *from,TAB
     goto err;
   errpos= 2;
 
+  if (fk_list->lock_extra_table(thd))
+    goto err;
+  errpos= 3;
+
   /* We need external lock before we can disable/enable keys */
   alter_table_manage_keys(to, from->file->indexes_are_disabled(), keys_onoff);
 
@@ -9282,7 +10814,7 @@ copy_data_between_tables(TABLE *from,TAB
 
   from->file->info(HA_STATUS_VARIABLE | HA_STATUS_NO_LOCK);
   to->file->ha_start_bulk_insert(from->file->stats.records);
-  errpos= 3;
+  errpos= 4;
 
   copy_end=copy;
   for (Field **ptr=to->field ; *ptr ; ptr++)
@@ -9345,7 +10877,7 @@ copy_data_between_tables(TABLE *from,TAB
   /* Tell handler that we have values for all columns in the to table */
   to->use_all_columns();
   init_read_record(&info, thd, from, (SQL_SELECT *) 0, 1, 1, FALSE);
-  errpos= 4;
+  errpos= 5;
   if (ignore)
     to->file->extra(HA_EXTRA_IGNORE_DUP_KEY);
   thd->warning_info->reset_current_row_for_warning();
@@ -9408,23 +10940,29 @@ copy_data_between_tables(TABLE *from,TAB
     else
       found_count++;
     thd->warning_info->inc_current_row_for_warning();
+
+    if ((error= fk_list->check_parent_list()))
+      break;
   }
 
 err:
-  if (errpos >= 4)
+  if (errpos >= 5)
     end_read_record(&info);
   free_io_cache(from);
   delete [] copy;
 
   if (error > 0)
     to->file->extra(HA_EXTRA_PREPARE_FOR_DROP);
-  if (errpos >= 3 && to->file->ha_end_bulk_insert(error > 1) && error <= 0)
+  if (errpos >= 4 && to->file->ha_end_bulk_insert(error > 1) && error <= 0)
   {
     to->file->print_error(my_errno,MYF(0));
     error= 1;
   }
   to->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY);
 
+  if (error < 0)
+    error= fk_list->eos_check();
+
   if (errpos >= 1 && ha_enable_transaction(thd, TRUE))
     error= 1;
 
@@ -9442,6 +10980,9 @@ err:
   *copied= found_count;
   *deleted=delete_count;
   to->file->ha_release_auto_increment();
+
+  if (errpos >= 3 && fk_list->unlock_extra_table(thd))
+    error= 1;
   if (errpos >= 2 && to->file->ha_external_lock(thd,F_UNLCK))
     error=1;
   if (error < 0 && to->file->extra(HA_EXTRA_PREPARE_FOR_RENAME))
@@ -9467,12 +11008,20 @@ bool mysql_recreate_table(THD *thd, TABL
   Alter_info alter_info;
 
   DBUG_ENTER("mysql_recreate_table");
-  DBUG_ASSERT(!table_list->next_global);
+
+  /*
+    Code which opens table in mysql_alter_table() assumes that it gets table
+    list, which was prepared in the same way as if ordinary ALTER TABLE is
+    executed.
+  */
+  DBUG_ASSERT(! table_list->next_global && ! table_list->next_local &&
+              thd->lex->query_tables == table_list);
   /*
     table_list->table has been closed and freed. Do not reference
     uninitialized data. open_tables() could fail.
   */
   table_list->table= NULL;
+  table_list->lock_type= TL_WRITE_ALLOW_READ;
 
   bzero((char*) &create_info, sizeof(create_info));
   create_info.row_type=ROW_TYPE_NOT_USED;

=== modified file 'sql/sql_trigger.cc'
--- a/sql/sql_trigger.cc	2009-05-18 04:18:37 +0000
+++ b/sql/sql_trigger.cc	2009-07-31 15:10:25 +0000
@@ -19,6 +19,7 @@
 #include "sp_head.h"
 #include "sql_trigger.h"
 #include "parse_file.h"
+#include "sp.h"
 
 /*************************************************************************/
 
@@ -2063,6 +2064,56 @@ Table_triggers_list::accept_visitor(THD 
 }
 
 
+/**
+  Add triggers for table to the set of routines used by statement.
+  Add tables used by them to statement table list. Do the same for
+  routines used by triggers.
+
+  @param thd             Thread context.
+  @param prelocking_ctx  Prelocking context of the statement.
+  @param table_list      Table list element for table with trigger.
+
+  @retval FALSE  Success.
+  @retval TRUE   Failure.
+*/
+
+bool
+Table_triggers_list::
+add_tables_and_routines_for_triggers(THD *thd,
+                                     Query_tables_list *prelocking_ctx,
+                                     TABLE_LIST *table_list)
+{
+  DBUG_ASSERT(static_cast<int>(table_list->lock_type) >=
+              static_cast<int>(TL_WRITE_ALLOW_WRITE));
+
+  for (int i= 0; i < (int)TRG_EVENT_MAX; i++)
+  {
+    if (table_list->trg_event_map &
+        static_cast<uint8>(1 << static_cast<int>(i)))
+    {
+      for (int j= 0; j < (int)TRG_ACTION_MAX; j++)
+      {
+        /* We can have only one trigger per action type currently */
+        sp_head *trigger= table_list->table->triggers->bodies[i][j];
+
+        if (trigger && sp_add_used_routine(prelocking_ctx, thd->stmt_arena,
+                                           &trigger->m_sroutines_key,
+                                           table_list->belong_to_view))
+        {
+          trigger->add_used_tables_to_table_list(thd,
+                                            &prelocking_ctx->query_tables_last,
+                                            table_list->belong_to_view);
+          sp_update_stmt_used_routines(thd, prelocking_ctx,
+                                       &trigger->m_sroutines,
+                                       table_list->belong_to_view);
+          trigger->propagate_attributes(prelocking_ctx);
+        }
+      }
+    }
+  }
+  return FALSE;
+}
+
 
 /**
   Mark fields of subject table which we read/set in its triggers

=== modified file 'sql/sql_trigger.h'
--- a/sql/sql_trigger.h	2009-02-11 08:51:44 +0000
+++ b/sql/sql_trigger.h	2009-07-31 15:10:25 +0000

@@ -152,11 +152,14 @@ public:
   }
 
   friend class Item_trigger_field;
-  friend int sp_cache_routines_and_add_tables_for_triggers(THD *thd, LEX *lex,
-                                                            TABLE_LIST *table);
+
   bool accept_visitor(THD *thd, Execution_tree_visitor *visitor,
                       uint8 trg_event_map);
 
+  bool add_tables_and_routines_for_triggers(THD *thd,
+                                            Query_tables_list *prelocking_ctx,
+                                            TABLE_LIST *table_list);
+
 private:
   bool prepare_for_trigger_fields(TABLE *table);
   LEX_STRING* change_table_name_in_trignames(const char *old_db_name,

=== modified file 'sql/sql_yacc.yy'
--- a/sql/sql_yacc.yy	2009-05-18 04:18:37 +0000
+++ b/sql/sql_yacc.yy	2009-07-31 15:10:25 +0000
@@ -599,10 +599,10 @@ bool my_yyoverflow(short **a, YYSTYPE **
 
 %pure_parser                                    /* We have threads */
 /*
-  Currently there are 169 shift/reduce conflicts.
+  Currently there are 167 shift/reduce conflicts.
   We should not introduce new conflicts any more.
 */
-%expect 169
+%expect 167
 
 /*
    Comments for TOKENS.
@@ -1886,7 +1886,7 @@ create:
             lex->create_info.default_table_charset= NULL;
             lex->name.str= 0;
             lex->name.length= 0;
-            lex->create_last_non_select_table= lex->query_tables;
+            lex->create_last_non_select_table= lex->last_table();
           }
           create2
           {
@@ -1910,7 +1910,8 @@ create:
             lex->sql_command= SQLCOM_CREATE_INDEX;
             if (!lex->current_select->add_table_to_list(lex->thd, $8,
                                                         NULL,
-                                                        TL_OPTION_UPDATING))
+                                                        TL_OPTION_UPDATING,
+                                                        TL_WRITE_ALLOW_READ))
               MYSQL_YYABORT;
             lex->alter_info.reset();
             lex->alter_info.flags= ALTER_ADD_INDEX;
@@ -4044,7 +4045,7 @@ size_number:
 create2:
           '(' create2a {}
         | opt_create_table_options
-          opt_create_partitioning
+          opt_partitioning
           create3 {}
         | LIKE table_ident
           {
@@ -4078,9 +4079,9 @@ create2:
 
 create2a:
           create_field_list ')' opt_create_table_options
-          opt_create_partitioning
+          opt_partitioning
           create3 {}
-        |  opt_create_partitioning
+        |  opt_partitioning
            create_select ')'
            { Select->set_braces(1);}
            union_opt {}
@@ -4096,19 +4097,6 @@ create3:
           union_opt {}
         ;
 
-opt_create_partitioning:
-          opt_partitioning
-          {
-            /*
-              Remove all tables used in PARTITION clause from the global table
-              list. Partitioning with subqueries is not allowed anyway.
-            */
-            TABLE_LIST *last_non_sel_table= Lex->create_last_non_select_table;
-            last_non_sel_table->next_global= 0;
-            Lex->query_tables_last= &last_non_sel_table->next_global;
-          }
-        ;
-
 /*
  This part of the parser is about handling of the partition information.
 
@@ -4136,6 +4124,15 @@ opt_create_partitioning:
 opt_partitioning:
           /* empty */ {}
         | partitioning
+          {
+            /*
+              Remove all tables used in PARTITION clause from the global table
+              list. Partitioning with subqueries is not allowed anyway.
+            */
+            TABLE_LIST *last_non_sel_table= Lex->create_last_non_select_table;
+            last_non_sel_table->next_global= 0;
+            Lex->query_tables_last= &last_non_sel_table->next_global;
+          }
         ;
 
 partitioning:
@@ -4863,19 +4860,30 @@ create_table_option:
             Lex->create_info.used_fields|= HA_CREATE_USED_ROW_FORMAT;
             Lex->alter_info.flags|= ALTER_ROW_FORMAT;
           }
-        | UNION_SYM opt_equal '(' opt_table_list ')'
+        | UNION_SYM opt_equal
           {
-            /* Move the union list to the merge_list */
+            Lex->select_lex.table_list.save_and_clear(&Lex->save_list);
+          }
+          '(' opt_table_list ')'
+          {
+            /*
+              Move the union list to the merge_list and exclude its tables
+              from the global list.
+            */
             LEX *lex=Lex;
-            TABLE_LIST *table_list= lex->select_lex.get_table_list();
             lex->create_info.merge_list= lex->select_lex.table_list;
-            lex->create_info.merge_list.elements--;
-            lex->create_info.merge_list.first=
-              (uchar*) (table_list->next_local);
-            lex->select_lex.table_list.elements=1;
-            lex->select_lex.table_list.next=
-              (uchar**) &(table_list->next_local);
-            table_list->next_local= 0;
+            lex->select_lex.table_list= lex->save_list;
+            /*
+              When excluding union list from the global list we assume that
+              elements of the former immediately follow elements which represent
+              table being created/altered and parent tables.
+            */
+            TABLE_LIST *last_non_sel_table= lex->create_last_non_select_table;
+            DBUG_ASSERT(last_non_sel_table->next_global ==
+                        (TABLE_LIST *)lex->create_info.merge_list.first);
+            last_non_sel_table->next_global= 0;
+            Lex->query_tables_last= &last_non_sel_table->next_global;
+
             lex->create_info.used_fields|= HA_CREATE_USED_UNION;
           }
         | default_charset
@@ -5039,11 +5047,7 @@ udf_type:
 create_field_list:
         field_list
         {
-          /* Don't use offsetof() macro in order to avoid warnings. */
-          Lex->create_last_non_select_table=
-            (TABLE_LIST*) ((char*) Lex->query_tables_last -
-                           ((char*) &(Lex->query_tables->next_global) -
-                            (char*) Lex->query_tables));
+          Lex->create_last_non_select_table= Lex->last_table();
         }
         ;
 
@@ -5095,7 +5099,8 @@ column_ref_constraint_def:
                                                        lex->fk_delete_opt,
                                                        lex->fk_update_opt,
                                                        lex->fk_match_option,
-                                                       FALSE, FALSE, NULL);
+                                                       FALSE, FALSE, FALSE,
+                                                       NULL);
             if (key == NULL)
               MYSQL_YYABORT;
             key->set_missing_options();
@@ -5153,7 +5158,8 @@ key_def:
                                                        lex->fk_delete_opt,
                                                        lex->fk_update_opt,
                                                        lex->fk_match_option,
-                                                       FALSE, FALSE, NULL);
+                                                       FALSE, FALSE, FALSE,
+                                                       NULL);
             if (key == NULL)
               MYSQL_YYABORT;
             key->set_missing_options();
@@ -6051,7 +6057,8 @@ alter:
             lex->sql_command= SQLCOM_ALTER_TABLE;
             lex->duplicates= DUP_ERROR; 
             if (!lex->select_lex.add_table_to_list(thd, $5, NULL,
-                                                   TL_OPTION_UPDATING))
+                                                   TL_OPTION_UPDATING,
+                                                   TL_WRITE_ALLOW_READ))
               MYSQL_YYABORT;
             lex->alter_info.reset();
             lex->col_list.empty();
@@ -6066,9 +6073,17 @@ alter:
             lex->no_write_to_binlog= 0;
             lex->create_info.default_storage_media= HA_SM_DEFAULT;
             lex->alter_info.build_method= $2;
+            lex->create_last_non_select_table= lex->last_table();
           }
           alter_commands
-          {}
+          {
+            /*
+              To be backwards compatible in --foreign-key-all-engines=0 mode
+              we also have to remove all parent tables from the global table
+              list.
+            */
+            alter_table_adjust_tables(Lex);
+          }
         | ALTER DATABASE ident_or_empty
           {
             Lex->create_info.default_table_charset= NULL;
@@ -6265,7 +6280,21 @@ alter_commands:
           opt_partitioning
         | alter_list
           remove_partitioning
-        | remove_partitioning
+        | alter_partitioning_commands
+          {
+            /*
+              Remove tables that are used in various partitioning-related
+              clauses from the global table list. Partitioning with subqueries
+              is not allowed anyway.
+            */
+            TABLE_LIST *last_non_sel_table= Lex->create_last_non_select_table;
+            last_non_sel_table->next_global= 0;
+            Lex->query_tables_last= &last_non_sel_table->next_global;
+          }
+        ;
+
+alter_partitioning_commands:
+          remove_partitioning
         | partitioning
 /*
   This part was added for release 5.1 by Mikael Ronström.
@@ -6461,12 +6490,16 @@ add_column:
         ;
 
 alter_list_item:
-          add_column column_def opt_place { }
+          add_column column_def opt_place
+          {
+            Lex->create_last_non_select_table= Lex->last_table();
+          }
         | ADD key_def
           {
+            Lex->create_last_non_select_table= Lex->last_table();
             Lex->alter_info.flags|= ALTER_ADD_INDEX;
           }
-        | add_column '(' field_list ')'
+        | add_column '(' create_field_list ')'
           {
             Lex->alter_info.flags|= ALTER_ADD_COLUMN | ALTER_ADD_INDEX;
           }
@@ -6481,6 +6514,9 @@ alter_list_item:
             Lex->ident= $5;
           }
           opt_column_ref_constraint_list opt_place
+          {
+            Lex->create_last_non_select_table= Lex->last_table();
+          }
         | MODIFY_SYM opt_column field_ident
           {
             LEX *lex=Lex;
@@ -6507,10 +6543,13 @@ alter_list_item:
             lex->ident= $3;
           }
           opt_column_ref_constraint_list opt_place
+          {
+            Lex->create_last_non_select_table= Lex->last_table();
+          }
         | DROP opt_column field_ident opt_restrict
           {
             LEX *lex=Lex;
-            Alter_drop *ad= new Alter_drop(Alter_drop::COLUMN, $3.str);
+            Alter_drop *ad= new Alter_drop(Alter_drop::COLUMN, $3.str, FALSE);
             if (ad == NULL)
               MYSQL_YYABORT;
             lex->alter_info.drop_list.push_back(ad);
@@ -6521,18 +6560,20 @@ alter_list_item:
             THD *thd= YYTHD;
             LEX *lex= thd->lex;
             Alter_drop *alter_drop=
-              new (thd->mem_root) Alter_drop(Alter_drop::FOREIGN_KEY, $3.str);
+              new (thd->mem_root) Alter_drop(Alter_drop::FOREIGN_KEY, $3.str,
+                                             FALSE);
             if (alter_drop == NULL)
               MYSQL_YYABORT;
             lex->alter_info.drop_list.push_back(alter_drop);
             lex->alter_info.flags|= ALTER_FOREIGN_KEY;
           }
-        | DROP FOREIGN KEY_SYM opt_ident
+        | DROP FOREIGN KEY_SYM field_ident
           {
             THD *thd= YYTHD;
             LEX *lex= thd->lex;
             Alter_drop *alter_drop=
-              new (thd->mem_root) Alter_drop(Alter_drop::FOREIGN_KEY, $4.str);
+              new (thd->mem_root) Alter_drop(Alter_drop::FOREIGN_KEY, $4.str,
+                                             TRUE);
             if (alter_drop == NULL)
               MYSQL_YYABORT;
             lex->alter_info.drop_list.push_back(alter_drop);
@@ -6542,7 +6583,7 @@ alter_list_item:
           {
             LEX *lex=Lex;
             Alter_drop *ad= new Alter_drop(Alter_drop::KEY,
-                                           primary_key_name.str);
+                                           primary_key_name.str, FALSE);
             if (ad == NULL)
               MYSQL_YYABORT;
             lex->alter_info.drop_list.push_back(ad);
@@ -6551,7 +6592,7 @@ alter_list_item:
         | DROP key_or_index field_ident
           {
             LEX *lex=Lex;
-            Alter_drop *ad= new Alter_drop(Alter_drop::KEY, $3.str);
+            Alter_drop *ad= new Alter_drop(Alter_drop::KEY, $3.str, FALSE);
             if (ad == NULL)
               MYSQL_YYABORT;
             lex->alter_info.drop_list.push_back(ad);
@@ -10161,7 +10202,7 @@ drop:
         | DROP build_method INDEX_SYM ident ON table_ident {}
           {
             LEX *lex=Lex;
-            Alter_drop *ad= new Alter_drop(Alter_drop::KEY, $4.str);
+            Alter_drop *ad= new Alter_drop(Alter_drop::KEY, $4.str, FALSE);
             if (ad == NULL)
               MYSQL_YYABORT;
             lex->sql_command= SQLCOM_DROP_INDEX;
@@ -10170,7 +10211,8 @@ drop:
             lex->alter_info.build_method= $2;
             lex->alter_info.drop_list.push_back(ad);
             if (!lex->current_select->add_table_to_list(lex->thd, $6, NULL,
-                                                        TL_OPTION_UPDATING))
+                                                        TL_OPTION_UPDATING,
+                                                        TL_WRITE_ALLOW_READ))
               MYSQL_YYABORT;
           }
         | DROP DATABASE if_exists ident

=== modified file 'sql/table.h'
--- a/sql/table.h	2009-05-27 03:08:49 +0000
+++ b/sql/table.h	2009-07-31 15:10:25 +0000
@@ -32,6 +32,8 @@ class Foreign_key_parent;
 class Foreign_key_child;
 class Foreign_key_child_share;
 class Foreign_key_parent_share;
+class Query_tables_list;
+class Alter_drop;
 struct LEX;
 class Execution_tree_visitor;
 
@@ -325,7 +327,12 @@ public:
                              List<Foreign_key_child> *new_fkey_list,
                              List<Foreign_key_parent> *new_parent_fkey_list);
 
-  bool add_tables_for_fks(THD *thd, LEX *lex, TABLE_LIST *table);
+  bool add_tables_for_fks(THD *thd, Query_tables_list *prelocking_ctx,
+                          TABLE_LIST *table);
+  bool add_fk_names(THD *thd, Query_tables_list *prelocking_ctx,
+                    const char *db);
+  bool add_parent_tables(THD *thd, Query_tables_list *prelocking_ctx,
+                         List<Alter_drop> &drop_list);
 
   bool restore_from_frm_section(THD *thd, TABLE_SHARE *share,
                                 const char **buff_p, const char *buff_end);

=== modified file 'storage/falcon/ha_falcon.cpp'
--- a/storage/falcon/ha_falcon.cpp	2009-07-02 04:47:16 +0000
+++ b/storage/falcon/ha_falcon.cpp	2009-07-31 15:10:25 +0000
@@ -2490,7 +2490,7 @@ int StorageInterface::check_if_supported
 	tempTable = (create_info->options & HA_LEX_CREATE_TMP_TABLE) ? true : false;
 	HA_ALTER_FLAGS supported;
 	supported = supported | HA_ADD_INDEX | HA_DROP_INDEX | HA_ADD_UNIQUE_INDEX | HA_DROP_UNIQUE_INDEX
-	                      | HA_ADD_PK_INDEX | HA_DROP_PK_INDEX | HA_ADD_COLUMN;
+	                      | HA_ADD_PK_INDEX | HA_DROP_PK_INDEX | HA_ADD_COLUMN | HA_ALTER_FOREIGN_KEY;
 	HA_ALTER_FLAGS notSupported = ~(supported);
 	
 #ifndef ONLINE_ALTER

=== modified file 'storage/innobase/handler/ha_innodb.cc'
--- a/storage/innobase/handler/ha_innodb.cc	2009-05-18 04:18:37 +0000
+++ b/storage/innobase/handler/ha_innodb.cc	2009-07-31 15:10:25 +0000
@@ -5816,7 +5816,8 @@ ha_innobase::rename_table(
 
 	/* Rename the table in InnoDB */
 
-	error = row_rename_table_for_mysql(norm_from, norm_to, trx);
+	error = row_rename_table_for_mysql(norm_from, norm_to, trx,
+					   !opt_fk_all_engines);
 
 	/* Flush the log to reduce probability that the .frm files and
 	the InnoDB data dictionary get out-of-sync if the user runs

=== modified file 'storage/innobase/include/row0mysql.h'
--- a/storage/innobase/include/row0mysql.h	2009-04-15 13:06:16 +0000
+++ b/storage/innobase/include/row0mysql.h	2009-07-31 15:10:25 +0000
@@ -453,7 +453,11 @@ row_rename_table_for_mysql(
 					/* out: error code or DB_SUCCESS */
 	const char*	old_name,	/* in: old table name */
 	const char*	new_name,	/* in: new table name */
-	trx_t*		trx);		/* in: transaction handle */
+	trx_t*		trx,		/* in: transaction handle */
+	ibool           drop_cons);     /* in: TRUE if this routine should
+					parse DROP FOREIGN KEY clauses when
+					executing ALTER TABLE and try to
+					drop constraints mentioned in them */
 /*************************************************************************
 Checks a table for corruption. */
 

=== modified file 'storage/innobase/row/row0mysql.c'
--- a/storage/innobase/row/row0mysql.c	2009-04-15 13:06:16 +0000
+++ b/storage/innobase/row/row0mysql.c	2009-07-31 15:10:25 +0000
@@ -3560,7 +3560,11 @@ row_rename_table_for_mysql(
 					/* out: error code or DB_SUCCESS */
 	const char*	old_name,	/* in: old table name */
 	const char*	new_name,	/* in: new table name */
-	trx_t*		trx)		/* in: transaction handle */
+	trx_t*		trx,		/* in: transaction handle */
+	ibool           drop_cons)      /* in: TRUE if this routine should
+					parse DROP FOREIGN KEY clauses when
+					executing ALTER TABLE and try to
+					drop constraints mentioned in them */
 {
 	dict_table_t*	table;
 	ulint		err;
@@ -3648,7 +3652,7 @@ row_rename_table_for_mysql(
 		goto funct_exit;
 	}
 
-	if (new_is_tmp) {
+	if (new_is_tmp && drop_cons) {
 		/* MySQL is doing an ALTER TABLE command and it renames the
 		original table to a temporary table name. We want to preserve
 		the original foreign key constraint definitions despite the

Attachment: [text/bzr-bundle] bzr/dlenev@mysql.com-20090731151025-v0r0j8jp3wza7zjf.bundle
Thread
bzr commit into mysql-6.1-fk branch (dlenev:2725) WL#148Dmitry Lenev31 Jul
  • Re: bzr commit into mysql-6.1-fk branch (dlenev:2725) WL#148Konstantin Osipov31 Jul