#At file:///home/dlenev/src/bzr/mysql-6.1-mil4/
2680 Dmitry Lenev 2008-07-11
Tentative patch for the 4th milestone of WL#148 "Foreign keys"
("Implement meta-data locking for foreign key definitions
and make prelocking algorithm aware of foreign keys").
Extended algortihm which calculates list of prelocked tables
for the statement to take into account foreign keys.
As a pre-requisite changed code responsible for table creation
to store information about foreign keys on table being created
also in .FRMs of parent tables (with appropriate meta-data
locking).
As usual questions for reviewer is marked by QQ.
modified:
mysql-test/r/foreign_key_acceptance.result
mysql-test/r/foreign_key_all_engines_2.result
mysql-test/t/foreign_key_acceptance.test
mysql-test/t/foreign_key_all_engines_2.test
sql/fk_dd.cc
sql/fk_dd.h
sql/item.cc
sql/item.h
sql/mysql_priv.h
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_list.h
sql/sql_parse.cc
sql/sql_show.cc
sql/sql_table.cc
sql/sql_trigger.cc
sql/sql_trigger.h
sql/sql_yacc.yy
sql/table.cc
sql/table.h
per-file messages:
mysql-test/r/foreign_key_acceptance.result
Adjusted test case for one of earlier milestones to avoid error
about missing parent table which started to be emitted on this
milestone.
mysql-test/r/foreign_key_all_engines_2.result
Added basic coverage for 4th milestone "Implement meta-data
locking for foreign key definitions and make prelocking
algorithm aware of foreign keys".
mysql-test/t/foreign_key_acceptance.test
Adjusted test case for one of earlier milestones to avoid error
about missing parent table which started to be emitted on this
milestone.
mysql-test/t/foreign_key_all_engines_2.test
Added basic coverage for 4th milestone "Implement meta-data
locking for foreign key definitions and make prelocking
algorithm aware of foreign keys".
sql/fk_dd.cc
Changed code responsible for saving/restoring information
about foreign keys in/from .FRM files to support storing
description of foreign keys in .FRMs of parent tables.
Introduced fk_init_column_sets() function for initialization
of set of used columns in Foreign_key objects for particular
table share.
Added fk_add_tables_for_fks() function to be used by prelocking
algorithm for adding tables required for checks/actions for
foreign keys which are associated with particular table (also
added auxiliary functions used by above function).
sql/fk_dd.h
fk_check_constraint_added() no longer needs "db" argument
since the same information now can be obtained from Foreign_key
object.
Introduced fk_init_column_sets() function for initialization
of set of used columns in Foreign_key objects for particular
table share.
Added fk_add_tables_for_fks() function to be used by prelocking
algorithm for adding tables required for checks/actions for
foreign keys which are associated with particular table.
sql/item.cc
Now Item_trigger_field::setup_field() is responsible for
marking found column as used in the sets of used columns
which are passed to it as parameter. Also simplified its
code by using new find_field_idx_in_table() function.
sql/item.h
Now Item_trigger_field::setup_field() is responsible for
marking found column as used in the sets of used columns
which are passed to it as parameter.
sql/mysql_priv.h
Now mysql_create_table() also takes list of tables referenced
by table's foreign keys as its argument.
Added find_field_idx_in_table() function for looking up field
index by its name using table's share.
sql/sp.cc
Renamed add_used_routine() to sp_add_used_routine() and
made it available outside of sp.cc.
sql/sp.h
Renamed add_used_routine() to sp_add_used_routine() and
made it available outside of sp.cc.
sql/sp_head.h
Introduced TYPE_ENUM_FOREIGN_KEY type of "routine" so
prelocking algorithm can distinguish foreign keys that
it has processed from other kinds of objects.
sql/sql_base.cc
open_tables():
Extended algorithm which calculates list of prelocked tables
to handle foreign keys.
find_field_idx_in_table():
Introduced new function for looking up field index by its
name using table's share.
sql/sql_class.cc
Foreign_key:
Introduced "table", "is_parent_table" and "column_set" members.
sql/sql_class.h
Foreign_key:
Added "table" member for storing information about foreign
key's referencing table, adjusted some methods accordingly.
Added "is_parent_table" member to be able distinguish objects
in table's description which represent foreign keys for which
this table serves as parent from those for which this table
is child table.
Introduced "column_set" member for storing information about
columns of the child or parent table (depending on value of
is_parent_table) involved in this foreign key constraint.
Added method for initialization of this set.
sql/sql_lex.cc
Changed LEX::unlink_all_tables_except_first() method to
properly set TABLE_LIST::prev_global pointer for tables
being unlinked.
sql/sql_lex.h
Added LEX::create_foreign_key_tables member for storing
list of tables referenced by foreign keys in the table
being created.
Changed LEX::unlink_all_tables_except_first() method to
properly set TABLE_LIST::prev_global pointer for tables
being unlinked.
sql/sql_list.h
Fixed List<T>::swap() method to properly handle empty lists.
QQ: Should we backport this change to earlier versions?
sql/sql_parse.cc
Now mysql_create_table() also takes list of tables referenced
by table's foreign keys as its argument.
sql/sql_show.cc
Now I_S code has to ignore Foreign_key objects from
TABLE_SHARE::fkeys list which represent foreign keys that
reference this table.
sql/sql_table.cc
mysql_create_table():
Now when we create table with foreign key constraints
in --foreign-key-all-engines mode we also add information
about these foreign keys to .FRM of parent tables. Indeed
to do this safely we have to acquire exclusive metadata
locks on these tables. To implement above changes added
several auxiliary functions.
We no longer need to pass information about database where
we are going to create our table to mysql_prepare_create_table()
and other similar function since now this information, which
is needed for proper handling of foreign keys, is available
directly from Foreign_key objects.
sql/sql_trigger.cc
Changed approach to calculating set of table columns used
by trigger on this table. Instead of calculating it each
time when Table_triggers_list::mark_fields_used() is called
we pre-calculate these sets for each kind of trigger event
during loading trigger and then them in mark_fields_used().
In order to do this added Table_triggers_list::read/write_set
members and changed prepare_record1_accessors() method to
prepare_for_trigger_fields().
As nice side-effect of this change now we can use write_set
for update triggers in optimization in part of prelocking
algorithm which handles foreign keys.
Also got rid of unused Table_triggers_list::set_table()
method.
sql/sql_trigger.h
Changed approach to calculating set of table columns used
by trigger on this table. Instead of calculating it each
time when Table_triggers_list::mark_fields_used() is called
we pre-calculate these sets for each kind of trigger event
during loading trigger and then them in mark_fields_used().
In order to do this added Table_triggers_list::read/write_set
members and changed prepare_record1_accessors() method to
prepare_for_trigger_fields().
As nice side-effect of this change now we can use write_set
for update triggers in optimization in part of prelocking
algorithm which handles foreign keys.
Also got rid of unused Table_triggers_list::set_table()
method.
sql/sql_yacc.yy
Store list of tables referenced by foreign keys in the table
being created in LEX::create_foreign_key_tables member.
Pass table element for the table being created to Foreign_key
constructor.
sql/table.cc
At the final stage of creation of TABLE_SHARE initialize sets
of used columns in Foreign_key objects for this share.
sql/table.h
Added TABLE_LIST::cols_to_be_updated_known/cols_to_be_updated
members for storing information about columns to be affected
by update on the in cases when list of such columns is known.
=== modified file 'mysql-test/r/foreign_key_acceptance.result'
--- a/mysql-test/r/foreign_key_acceptance.result 2008-07-03 05:41:57 +0000
+++ b/mysql-test/r/foreign_key_acceptance.result 2008-07-11 07:06:24 +0000
@@ -16,6 +16,7 @@ Warnings:
Warning 1674 Foreign key warning: Constraint 'y': Syntax 'FOREIGN KEY [id]' is old style, changing to CONSTRAINT [id]
DROP TABLE IF EXISTS t4,t3,t2,t1;
DROP TABLE IF EXISTS t2,t1;
+CREATE TABLE t1 (s1 INT PRIMARY KEY);
CREATE TABLE t2 (
s1 INT,
CONSTRAINT `PRIMARY` FOREIGN KEY (s1) REFERENCES t1 (s1));
=== modified file 'mysql-test/r/foreign_key_all_engines_2.result'
--- a/mysql-test/r/foreign_key_all_engines_2.result 2008-06-23 07:14:00 +0000
+++ b/mysql-test/r/foreign_key_all_engines_2.result 2008-07-11 07:06:24 +0000
@@ -1,3 +1,93 @@
+drop tables if exists t1, t2, t3;
+create table t1 (pk int primary key) engine=myisam;
+insert into t1 values (1), (2), (3);
+lock tables t1 read;
+create table t2 (fk int references t1 (pk)) engine=myisam;
+unlock tables;
+lock tables t1 write;
+insert into t2 values (1);
+unlock tables;
+lock table t1 read;
+insert into t2 values (2);
+unlock tables;
+lock table t2 write;
+update t1 set pk= 4 where pk=3;
+unlock tables;
+lock table t2 read;
+update t1 set pk= 5 where pk=4;
+unlock tables;
+drop tables t1, t2;
+create table t1 (pk int primary key) engine=myisam;
+insert into t1 values (1), (2), (3);
+create table t2 (fk int references t1 (pk) on delete cascade) engine=myisam;
+lock tables t2 read;
+delete from t1 where pk=3;
+unlock tables;
+drop tables t1, t2;
+create table t1 (pk int primary key) engine=myisam;
+insert into t1 values (1);
+create table t2 (pk int primary key) engine=myisam;
+insert into t2 values (1);
+create table t3 (fk1 int references t1 (pk) on delete set null,
+fk2 int references t2 (pk)) engine=myisam;
+insert into t3 values (1, 1);
+lock table t1 write;
+insert into t2 values (2);
+unlock tables;
+drop tables t1, t2, t3;
+create table t1 (pk int primary key) engine=myisam;
+insert into t1 values (1);
+create table t2 (pk1 int, pk2 int, primary key(pk1, pk2)) engine=myisam;
+insert into t2 values (1, 1);
+create table t3 (fk1 int references t1 (pk) on delete set null,
+fk2 int,
+foreign key (fk1, fk2) references t2 (pk1, pk2)) engine=myisam;
+insert into t3 values (1, 1);
+lock table t1 write;
+insert into t2 values (2, 2);
+unlock tables;
+drop tables t1, t2, t3;
+create table t1 (pk int primary key) engine=myisam;
+insert into t1 values (1);
+create table t2 (pk int primary key,
+fk int references t1 (pk) on delete set null) engine=myisam;
+insert into t2 values (1, 1);
+create table t3 (fk int references t2 (pk) on delete cascade) engine=myisam;
+insert into t3 values (1);
+lock table t3 read;
+delete from t1 where pk= 1;
+unlock tables;
+drop tables t1, t2, t3;
+create table t1 (pk int primary key) engine=myisam;
+insert into t1 values (1);
+create table t2 (pk1 int,
+pk2_fk int references t1 (pk) on update cascade) engine=myisam;
+insert into t2 values (1, 1);
+create table t3 (fk1 int, fk2 int,
+foreign key (fk1, fk2) references t2 (pk1, pk2_fk)
+on update set null) engine=myisam;
+insert into t3 values (1, 1);
+lock table t3 read;
+update t1 set pk= 2;
+unlock tables;
+drop tables t1, t2, t3;
+create table t1 (pk int primary key) engine=myisam;
+insert into t1 values (1), (2);
+create table t2 (pk int primary key,
+fk int references t1 (pk) on delete set null) engine=myisam;
+insert into t2 values (1, 1), (2, 2);
+create table t3 (fk int references t2 (pk) on update set null) engine=myisam;
+insert into t3 values (1), (2);
+create trigger t2_bu before update on t2 for each row set @a:= 1;
+lock table t3 read;
+delete from t1 where pk=1;
+unlock tables;
+drop trigger t2_bu;
+create trigger t2_bu before update on t2 for each row set new.pk=3;
+lock table t3 read;
+delete from t1 where pk=2;
+unlock tables;
+drop tables t1, t2, t3;
drop tables if exists t1, t2;
create table t1 (s1 int primary key) engine=innodb;
create table t2 (s1 int, foreign key (s1) references t1 (s1)) engine=innodb;
=== modified file 'mysql-test/t/foreign_key_acceptance.test'
--- a/mysql-test/t/foreign_key_acceptance.test 2008-07-03 05:41:57 +0000
+++ b/mysql-test/t/foreign_key_acceptance.test 2008-07-11 07:06:24 +0000
@@ -47,6 +47,7 @@ DROP TABLE IF EXISTS t4,t3,t2,t1;
--disable_warnings
DROP TABLE IF EXISTS t2,t1;
--enable_warnings
+CREATE TABLE t1 (s1 INT PRIMARY KEY);
--error ER_FK_CONSTRAINT_NAME_ILLEGAL
CREATE TABLE t2 (
s1 INT,
=== modified file 'mysql-test/t/foreign_key_all_engines_2.test'
--- a/mysql-test/t/foreign_key_all_engines_2.test 2008-06-23 07:14:00 +0000
+++ b/mysql-test/t/foreign_key_all_engines_2.test 2008-07-11 07:06:24 +0000
@@ -5,6 +5,227 @@
#
--source include/have_innodb.inc
--source include/have_partition.inc
+--source include/not_embedded.inc
+
+#
+# Some simple tests for 4th milestone of WL#148 ("Implement meta-data
+# locking for foreign key definitions and make prelocking algorithm
+# aware of foreign keys").
+#
+--disable_warnings
+drop tables if exists t1, t2, t3;
+--enable_warnings
+connect (blocker, localhost, root,,);
+connection default;
+create table t1 (pk int primary key) engine=myisam;
+insert into t1 values (1), (2), (3);
+connection blocker;
+# Take table-level lock and thus shared metadata lock on parent table
+lock tables t1 read;
+connection default;
+# This statement takes exclusive metadata lock on parent so it
+# it should be blocked
+--send create table t2 (fk int references t1 (pk)) engine=myisam
+connection blocker;
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and
+ info = "create table t2 (fk int references t1 (pk)) engine=myisam";
+--source include/wait_condition.inc
+unlock tables;
+connection default;
+--reap
+connection blocker;
+# Take table-level write lock on parent table
+lock tables t1 write;
+connection default;
+# This statement should take read lock on parent table and
+# therefore should be blocked.
+--send insert into t2 values (1)
+connection blocker;
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Table lock" and info = "insert into t2 values (1)";
+--source include/wait_condition.inc
+unlock tables;
+connection default;
+--reap
+connection blocker;
+# Let us check that our check for presence of row in the parent is
+# compatible with table-level read lock.
+lock table t1 read;
+connection default;
+insert into t2 values (2);
+connection blocker;
+unlock tables;
+lock table t2 write;
+connection default;
+# Should be blocked since it has to obtain read lock on t2
+--send update t1 set pk= 4 where pk=3
+connection blocker;
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Table lock" and info = "update t1 set pk= 4 where pk=3";
+--source include/wait_condition.inc
+unlock tables;
+connection default;
+--reap
+connection blocker;
+lock table t2 read;
+connection default;
+# Should not be blocked
+update t1 set pk= 5 where pk=4;
+connection blocker;
+unlock tables;
+connection default;
+drop tables t1, t2;
+# This part of test might start failing once we fully implement all checks
+# in CREATE TABLE
+create table t1 (pk int primary key) engine=myisam;
+insert into t1 values (1), (2), (3);
+create table t2 (fk int references t1 (pk) on delete cascade) engine=myisam;
+connection blocker;
+lock tables t2 read;
+connection default;
+# Should be blocked since it has to obtain write lock on t1
+--send delete from t1 where pk=3
+connection blocker;
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Table lock" and info = "delete from t1 where pk=3";
+--source include/wait_condition.inc
+unlock tables;
+connection default;
+--reap
+drop tables t1, t2;
+#
+# Test that column-based locking optimizations work
+#
+# Tests where column-based optimization works for updates on child table.
+create table t1 (pk int primary key) engine=myisam;
+insert into t1 values (1);
+create table t2 (pk int primary key) engine=myisam;
+insert into t2 values (1);
+create table t3 (fk1 int references t1 (pk) on delete set null,
+ fk2 int references t2 (pk)) engine=myisam;
+insert into t3 values (1, 1);
+connection blocker;
+# This will implicitly lock t3 for update, but should not
+# lock t2 as cascading change to t3 won't affect t3.fk2.
+lock table t1 write;
+connection default;
+# Should not be blocked
+insert into t2 values (2);
+connection blocker;
+unlock tables;
+connection default;
+drop tables t1, t2, t3;
+create table t1 (pk int primary key) engine=myisam;
+insert into t1 values (1);
+create table t2 (pk1 int, pk2 int, primary key(pk1, pk2)) engine=myisam;
+insert into t2 values (1, 1);
+create table t3 (fk1 int references t1 (pk) on delete set null,
+ fk2 int,
+ foreign key (fk1, fk2) references t2 (pk1, pk2)) engine=myisam;
+insert into t3 values (1, 1);
+connection blocker;
+# This will implicitly lock t3 for update and t2 for read,
+# since cascading change to t3 affects t3.fk2 and thus new
+# value of foreign key should be checked.
+lock table t1 write;
+connection default;
+--send insert into t2 values (2, 2)
+connection blocker;
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Table lock" and info = "insert into t2 values (2, 2)";
+--source include/wait_condition.inc
+unlock tables;
+connection default;
+--reap
+drop tables t1, t2, t3;
+#
+# Tests where column-based optimization works for updates on parent table.
+#
+create table t1 (pk int primary key) engine=myisam;
+insert into t1 values (1);
+create table t2 (pk int primary key,
+ fk int references t1 (pk) on delete set null) engine=myisam;
+insert into t2 values (1, 1);
+create table t3 (fk int references t2 (pk) on delete cascade) engine=myisam;
+insert into t3 values (1);
+connection blocker;
+lock table t3 read;
+connection default;
+# Should not be blocked since we are not updating t2.pk and
+# thus should not do any checks/actions to t3.
+delete from t1 where pk= 1;
+connection blocker;
+unlock tables;
+connection default;
+drop tables t1, t2, t3;
+create table t1 (pk int primary key) engine=myisam;
+insert into t1 values (1);
+create table t2 (pk1 int,
+ pk2_fk int references t1 (pk) on update cascade) engine=myisam;
+insert into t2 values (1, 1);
+create table t3 (fk1 int, fk2 int,
+ foreign key (fk1, fk2) references t2 (pk1, pk2_fk)
+ on update set null) engine=myisam;
+insert into t3 values (1, 1);
+connection blocker;
+lock table t3 read;
+connection default;
+# Should be blocked since we implicitly change primary
+# key in t2 and thus should update t3.
+--send update t1 set pk= 2
+connection blocker;
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Table lock" and info = "update t1 set pk= 2";
+--source include/wait_condition.inc
+unlock tables;
+connection default;
+--reap
+drop tables t1, t2, t3;
+#
+# Test that column based optimization properly works with triggers.
+#
+create table t1 (pk int primary key) engine=myisam;
+insert into t1 values (1), (2);
+create table t2 (pk int primary key,
+ fk int references t1 (pk) on delete set null) engine=myisam;
+insert into t2 values (1, 1), (2, 2);
+create table t3 (fk int references t2 (pk) on update set null) engine=myisam;
+insert into t3 values (1), (2);
+create trigger t2_bu before update on t2 for each row set @a:= 1;
+connection blocker;
+lock table t3 read;
+connection default;
+# Should not be blocked since trigger on t2 doesn't change its
+# primary key and therefore t3 won't be updated.
+delete from t1 where pk=1;
+connection blocker;
+unlock tables;
+connection default;
+# Now scenario in which we should block
+drop trigger t2_bu;
+create trigger t2_bu before update on t2 for each row set new.pk=3;
+connection blocker;
+lock table t3 read;
+connection default;
+# Should be blocked since trigger on t2 will change its primary key
+# and therefore t3 should be updated.
+--send delete from t1 where pk=2
+connection blocker;
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Table lock" and info = "delete from t1 where pk=2";
+--source include/wait_condition.inc
+unlock tables;
+connection default;
+--reap
+drop tables t1, t2, t3;
#
=== modified file 'sql/fk_dd.cc'
--- a/sql/fk_dd.cc 2008-06-25 20:11:52 +0000
+++ b/sql/fk_dd.cc 2008-07-11 07:06:24 +0000
@@ -14,7 +14,10 @@
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
#include "mysql_priv.h"
+#include "sp.h"
#include "fk_dd.h"
+#include "sql_trigger.h"
+#include "sp_head.h"
/* Sizes of various fields in .FRM section describing all foreign keys. */
@@ -73,13 +76,17 @@ uint Foreign_key::get_frm_description_le
length+= FK_FRM_NAMES_SEP_SIZE;
length+= name.length + FK_FRM_NAMES_SEP_SIZE;
/*
+ In .FRM of child table we save parent's database and name, in case
+ 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 (!is_within_one_db)
- length+= ref_table->db_length;
+ length+= is_parent_table ? table->db_length : ref_table->db_length;
length+= FK_FRM_NAMES_SEP_SIZE;
- length+= ref_table->table_name_length + FK_FRM_NAMES_SEP_SIZE;
+ length+= (is_parent_table ? table->table_name_length :
+ ref_table->table_name_length) +
+ FK_FRM_NAMES_SEP_SIZE;
while ((col= col_it1++))
length+= col->field_name.length + FK_FRM_NAMES_SEP_SIZE;
while ((col= col_it2++))
@@ -111,6 +118,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_PARENT_TABLE= 4;
/**
@@ -142,7 +150,8 @@ void Foreign_key::save_to_frm(char **buf
its name.
*/
*cur_pos= (is_table_constraint ? FK_FRM_FLAG_TABLE_CONSTRAINT : 0) |
- (is_within_one_db ? FK_FRM_FLAG_WITHIN_ONE_DATABASE : 0);
+ (is_within_one_db ? FK_FRM_FLAG_WITHIN_ONE_DATABASE : 0) |
+ (is_parent_table ? FK_FRM_FLAG_PARENT_TABLE : 0);
cur_pos+= FK_FRM_FLAGS_SIZE;
*cur_pos= match_opt;
cur_pos+= FK_FRM_MATCH_OPT_SIZE;
@@ -163,15 +172,19 @@ void Foreign_key::save_to_frm(char **buf
*cur_pos= NAMES_SEP_CHAR;
cur_pos+= FK_FRM_NAMES_SEP_SIZE;
/*
+ In .FRM of child table we save parent's database and name,
+ in case of parent table we save database and name of child.
+
By storing lowercased version of db and table name when
lowercase_table_names option is enabled we follow InnoDB
implementation.
*/
if (!is_within_one_db)
- cur_pos= strmov(cur_pos, ref_table->db);
+ cur_pos= strmov(cur_pos, is_parent_table ? table->db : ref_table->db);
*cur_pos= NAMES_SEP_CHAR;
cur_pos+= FK_FRM_NAMES_SEP_SIZE;
- cur_pos= strmov(cur_pos, ref_table->table_name);
+ cur_pos= strmov(cur_pos, is_parent_table ? table->table_name :
+ ref_table->table_name);
*cur_pos= NAMES_SEP_CHAR;
cur_pos+= FK_FRM_NAMES_SEP_SIZE;
while ((col= col_it1++))
@@ -257,6 +270,7 @@ bool fk_save_to_frm_section(THD *thd, Fi
Foreign_key *Foreign_key::restore_from_frm(THD *thd, MEM_ROOT *mem_root,
const LEX_STRING &db,
+ const LEX_STRING &table_name,
const char **buff_p)
{
TYPELIB dummy_type;
@@ -264,10 +278,10 @@ Foreign_key *Foreign_key::restore_from_f
char *names_cols_buff;
const char **names_cols_arr, **names_cols_ptr;
unsigned int *names_cols_lengths, *names_cols_lengths_ptr;
- TABLE_LIST *table;
+ TABLE_LIST *table, *ref_table, *this_table, *another_table;
List<Key_part_spec> cols, ref_cols;
uint i;
- bool is_within_one_db;
+ bool is_within_one_db, is_parent_table;
const char *cur_pos= *buff_p;
const char *fkey_descr_start= cur_pos;
@@ -276,6 +290,7 @@ Foreign_key *Foreign_key::restore_from_f
cur_pos+= FK_FRM_FK_DESCR_LENGTH_SIZE;
is_within_one_db= (*cur_pos) & FK_FRM_FLAG_WITHIN_ONE_DATABASE;
+ is_parent_table= (*cur_pos) & FK_FRM_FLAG_PARENT_TABLE;
cur_pos+= FK_FRM_FLAGS_SIZE;
/* Skip space where info about MATCH and ON UPDATE/DELETE are stored. */
@@ -322,22 +337,39 @@ Foreign_key *Foreign_key::restore_from_f
if (thd->is_fatal_error)
return NULL;
- if (!(table= (TABLE_LIST *)alloc_root(mem_root, sizeof(TABLE_LIST))))
+ if (!multi_alloc_root(mem_root, &table, sizeof(TABLE_LIST),
+ &ref_table, sizeof(TABLE_LIST),
+ NULL, 0))
return NULL;
bzero(table, sizeof(TABLE_LIST));
+ bzero(ref_table, sizeof(TABLE_LIST));
+ if (is_parent_table)
+ {
+ this_table= ref_table;
+ another_table= table;
+ }
+ else
+ {
+ this_table= table;
+ another_table= ref_table;
+ }
+ this_table->db= db.str;
+ this_table->db_length= db.length;
+ this_table->table_name= table_name.str;
+ this_table->table_name_length= table_name.length;
if (is_within_one_db)
{
- table->db= db.str;
- table->db_length= db.length;
+ another_table->db= db.str;
+ another_table->db_length= db.length;
}
else
{
- table->db= (char *)names_cols_arr[1];
- table->db_length= names_cols_lengths[1];
+ another_table->db= (char *)names_cols_arr[1];
+ another_table->db_length= names_cols_lengths[1];
}
- table->table_name= (char *)names_cols_arr[2];
- table->table_name_length= names_cols_lengths[2];
+ another_table->table_name= (char *)names_cols_arr[2];
+ another_table->table_name_length= names_cols_lengths[2];
*buff_p= fkey_descr_start + FK_FRM_FK_DESCR_LENGTH_SIZE + fkey_descr_length;
@@ -345,10 +377,11 @@ Foreign_key *Foreign_key::restore_from_f
names_cols_lengths[0],
(fkey_descr_start[FK_FRM_FLAGS_OFFSET] &
FK_FRM_FLAG_TABLE_CONSTRAINT),
- is_within_one_db, cols, table, ref_cols,
+ is_within_one_db, table, cols, ref_table, ref_cols,
fkey_descr_start[FK_FRM_ON_DELETE_OPT_OFFSET],
fkey_descr_start[FK_FRM_ON_UPDATE_OPT_OFFSET],
fkey_descr_start[FK_FRM_MATCH_OPT_OFFSET],
+ is_parent_table,
TRUE);
}
@@ -389,7 +422,8 @@ bool fk_restore_from_frm_section(THD *th
while ((*buff_p) < buff_end)
{
if (!(fkey= Foreign_key::restore_from_frm(thd, &share->mem_root,
- share->db, buff_p)) ||
+ share->db, share->table_name,
+ buff_p)) ||
share->fkeys.push_back(fkey, &share->mem_root))
return TRUE;
}
@@ -429,17 +463,17 @@ const LEX_STRING fk_match_type_names[]=
@param thd [in] Thread context.
@param str [in/out] String to which clause should be appended.
- @param db [in] Name of database where the table resides.
*/
-void Foreign_key::get_create_statement_clause(THD *thd, String *str,
- const char *db)
+void Foreign_key::get_create_statement_clause(THD *thd, String *str)
{
List_iterator<Key_part_spec> col_it1(columns);
List_iterator<Key_part_spec> col_it2(ref_columns);
Key_part_spec *col;
bool first;
+ DBUG_ASSERT(!is_parent_table);
+
if (is_table_constraint)
str->append(STRING_WITH_LEN(",\n "));
@@ -526,11 +560,11 @@ void fk_get_column_constraint_for_create
Foreign_key *fkey;
while ((fkey= fkey_it++))
{
- if (!fkey->is_table_constraint &&
+ if (!fkey->is_table_constraint && !fkey->is_parent_table &&
!my_strcasecmp(system_charset_info, field_name,
fkey->columns.head()->field_name.str))
{
- fkey->get_create_statement_clause(thd, str, table->s->db.str);
+ fkey->get_create_statement_clause(thd, str);
continue;
}
}
@@ -551,8 +585,8 @@ void fk_get_table_constraints_for_create
List_iterator<Foreign_key> fkey_it(table->s->fkeys);
Foreign_key *fkey;
while ((fkey= fkey_it++))
- if (fkey->is_table_constraint)
- fkey->get_create_statement_clause(thd, str, table->s->db.str);
+ if (fkey->is_table_constraint && !fkey->is_parent_table)
+ fkey->get_create_statement_clause(thd, str);
}
@@ -565,6 +599,8 @@ void fk_get_table_constraints_for_create
void Foreign_key::generate_name_if_needed(THD *thd, const char *table_name)
{
+ DBUG_ASSERT(!is_parent_table);
+
if (!name.str || is_name_generated)
{
const uint MAX_AFFIX_LEN= 3 + 1 + 5;
@@ -574,6 +610,7 @@ void Foreign_key::generate_name_if_neede
ulong randval;
char *pos;
+
if (tab_name_len <= NAME_CHAR_LEN - MAX_AFFIX_LEN)
{
/*
@@ -748,12 +785,26 @@ static bool fk_create_constraint_name(co
bool fk_create_constraint_names(List<Foreign_key> &fkey_list, const char *db)
{
List_iterator<Foreign_key> fkey_iterator(fkey_list);
- Foreign_key *fkey;
+ Foreign_key *fkey, *fkey_err;
while ((fkey= fkey_iterator++))
- if ((!fkey->exists) && fk_create_constraint_name(db, fkey->name.str))
- return TRUE;
+ {
+ DBUG_ASSERT(!fkey->exists);
+ if (!fkey->is_parent_table &&
+ fk_create_constraint_name(db, fkey->name.str))
+ goto err;
+ }
return FALSE;
+
+err:
+ fkey_iterator.rewind();
+ fkey_err= fkey;
+ while ((fkey= fkey_iterator++) && fkey != fkey_err)
+ {
+ if (!fkey->is_parent_table)
+ (void) fk_drop_constraint_name(db, fkey->name.str);
+ }
+ return TRUE;
}
@@ -820,7 +871,8 @@ bool fk_drop_all_constraint_names_for_ta
Foreign_key *fkey;
while ((fkey= fkey_iterator++))
- if (fk_drop_constraint_name(table->db, fkey->name.str))
+ if (!fkey->is_parent_table &&
+ fk_drop_constraint_name(table->db, fkey->name.str))
{
free_table_share(&share);
return TRUE;
@@ -837,15 +889,13 @@ bool fk_drop_all_constraint_names_for_ta
@param thd Thread context
@param fkey Foreign key to be created.
@param fkey_list List of all foreign keys for the table.
- @param db Database of child table for this FK.
@retval FALSE Success, all checks passed.
@retval TRUE Some error discovered.
*/
bool fk_check_constraint_added(THD *thd, Foreign_key *fkey,
- List<Foreign_key> &fkey_list,
- const char *db)
+ List<Foreign_key> &fkey_list)
{
List_iterator<Foreign_key> fkey_it2(fkey_list);
Foreign_key *fkey2;
@@ -857,7 +907,7 @@ bool fk_check_constraint_added(THD *thd,
about already existing foreign keys in .FRM (e.g. during ALTER).
This might change once we reach later milestones of WL#148.
*/
- if (fkey->exists)
+ if (fkey->exists || fkey->is_parent_table)
DBUG_RETURN(FALSE);
if (fkey->match_opt == Foreign_key::FK_MATCH_PARTIAL)
@@ -914,7 +964,7 @@ 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(db, fkey->name.str))
+ if (fk_check_if_constraint_name_exists(fkey->table->db, fkey->name.str))
{
my_error(ER_FK_CONSTRAINT_NAME_DUPLICATE, MYF(0), fkey->name.str);
DBUG_RETURN(TRUE);
@@ -926,7 +976,8 @@ bool fk_check_constraint_added(THD *thd,
fkey_it2.rewind();
while ((fkey2= fkey_it2++) != fkey)
{
- if (!my_strcasecmp(system_charset_info, fkey->name.str, fkey2->name.str))
+ if (!fkey->is_parent_table &&
+ !my_strcasecmp(system_charset_info, fkey->name.str, fkey2->name.str))
{
my_error(ER_FK_CONSTRAINT_NAME_DUPLICATE, MYF(0), fkey->name.str);
DBUG_RETURN(TRUE);
@@ -934,4 +985,344 @@ bool fk_check_constraint_added(THD *thd,
}
DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Initialize set of the table columns participating in the foreign key.
+
+ @param share Share for child table if this Foreign_key object represents
+ constraint in child table description, share for parent table
+ if it represents constraint in parent table.
+
+ @retval FALSE Success.
+ @retval TRUE Failure (OOM).
+*/
+
+bool Foreign_key::init_column_set(TABLE_SHARE *share)
+{
+ List_iterator<Key_part_spec> col_it(is_parent_table ? ref_columns : columns);
+ Key_part_spec *col;
+ my_bitmap_map *bitmap_buff;
+ uint col_idx;
+ if (!(bitmap_buff= (my_bitmap_map*) alloc_root(&share->mem_root,
+ share->column_bitmap_size)))
+ return TRUE;
+ bitmap_init(&column_set, bitmap_buff, share->fields, FALSE);
+ bitmap_clear_all(&column_set);
+ while ((col= col_it++))
+ {
+ if ((col_idx= find_field_idx_in_table(share, col->field_name.str,
+ col->field_name.length)) != UINT_MAX)
+ bitmap_set_bit(&column_set, col_idx);
+ }
+ return FALSE;
+}
+
+
+/**
+ Initialize sets of the table columns for all table's foreign keys.
+
+ @param share Table's share.
+
+ @retval FALSE Success.
+ @retval TRUE Failure (OOM).
+*/
+
+bool fk_init_column_sets(TABLE_SHARE *share)
+{
+ List_iterator<Foreign_key> it(share->fkeys);
+ Foreign_key *fkey;
+
+ while ((fkey= it++))
+ if (fkey->init_column_set(share))
+ return TRUE;
+ return FALSE;
+}
+
+
+/**
+ Types of actions on child or parent table which should be distinguished
+ by prelocking algorithm when processing foreign key.
+*/
+
+enum fk_action_type {CHILD_INSERT= 0, PARENT_UPDATE, PARENT_DELETE};
+
+
+/**
+ Add foreign key constraint + action combination to the set of "stored
+ routines" used by statement.
+
+ @note By doing this we ensure that each foreign key/action pair is
+ handled by prelocking algortithm only once for each statement.
+
+ @todo In future this function will be responsible for creating list
+ of constraints used by statement.
+
+ QQ: May be it is better to rename this function ?
+
+ @param thd Thread context.
+ @param lex LEX of this statement
+ @param fk Foreign key
+ @param action Foreign key action to be added
+
+ @retval TRUE - constraint/action pair was added.
+ @retval FALSE - constraint/action pair was not added since
+ it is already present in the set.
+*/
+
+static bool fk_add_to_constraint_list(THD *thd, LEX *lex, Foreign_key *fk,
+ fk_action_type action)
+{
+ LEX_STRING key;
+ char key_buff[1+1+2*NAME_LEN+1+1];
+ key_buff[0]= TYPE_ENUM_FOREIGN_KEY;
+ key_buff[1]= action;
+ key.str= key_buff;
+ key.length= strxnmov(key_buff+2, sizeof(key_buff)-3, fk->table->db,
+ ".", fk->name.str, NULL) - key_buff;
+ return sp_add_used_routine(lex, thd->stmt_arena, &key, 0);
+}
+
+
+/**
+ Add table needed for foreign key constraint check or action to the
+ table list of statement.
+
+ @param thd Thread context
+ @param lex LEX for the statement
+ @param tab TABLE_LIST for the table from the foreign key
+ description
+ @param lock_type Type of lock
+ @param trg_event_map Bitmap with operations to be performed on table
+ @param belong_to_view Uppermost view which uses this foreign key
+ (0 if foreign key is not used by view).
+ @param columns Pointer to the list of columns which are going
+ to be updated by the action (0 - if check/action
+ won't involve update).
+
+ QQ: Again there are several subtle differences between this function,
+ sp_add_to_query_tables() and other similar functions. Does it make
+ sense to extract a common uber-function?
+
+ @retval FALSE Success.
+ @retval TRUE Failure (OOM).
+*/
+
+static bool fk_add_table_to_table_list(THD *thd, LEX *lex, TABLE_LIST *tab,
+ thr_lock_type lock_type,
+ uint8 trg_event_map,
+ TABLE_LIST *belong_to_view,
+ List<Key_part_spec> *columns)
+{
+ TABLE_LIST *table;
+ char *db_buff, *table_name_buff;
+ Query_arena *arena, backup;
+
+ arena= thd->activate_stmt_arena_if_needed(&backup);
+
+ if (!multi_alloc_root(thd->mem_root, &table, sizeof(TABLE_LIST),
+ &db_buff, tab->db_length + 1,
+ &table_name_buff, tab->table_name_length + 1,
+ NULL, 0))
+ return TRUE;
+
+ bzero(table, sizeof(TABLE_LIST));
+ strmov(db_buff, tab->db);
+ table->db= db_buff;
+ table->db_length= tab->db_length;
+ strmov(table_name_buff, tab->table_name);
+ table->table_name= table_name_buff;
+ table->table_name_length= tab->table_name_length;
+ table->alias= table->table_name;
+ table->lock_type= lock_type;
+ table->lock_timeout= -1;
+ table->lock_transactional= FALSE;
+ table->cacheable_table= 1;
+ table->prelocking_placeholder= 1;
+ table->belong_to_view= belong_to_view;
+ table->trg_event_map= trg_event_map;
+ table->mdl_lock_data= mdl_alloc_lock(0, table->db, table->table_name,
+ thd->locked_tables_root ?
+ thd->locked_tables_root :
+ thd->mem_root);
+
+ if (!table->mdl_lock_data)
+ return TRUE;
+
+ if (columns)
+ {
+ List_iterator<Key_part_spec> it_col(*columns);
+ Key_part_spec *col;
+ LEX_STRING *field_name;
+ char *field_name_buff;
+ table->cols_to_be_updated_known= TRUE;
+ table->cols_to_be_updated.empty();
+ while ((col= it_col++))
+ {
+ if (!multi_alloc_root(thd->mem_root,
+ &field_name, sizeof(LEX_STRING),
+ &field_name_buff, col->field_name.length + 1,
+ NULL, 0))
+ return TRUE;
+ field_name->str= field_name_buff;
+ field_name->length= col->field_name.length;
+ memcpy(field_name_buff, col->field_name.str, col->field_name.length + 1);
+ if (table->cols_to_be_updated.push_back(field_name, thd->mem_root))
+ return TRUE;
+ }
+ }
+
+ /* Everything else should be zeroed. */
+ if (arena)
+ thd->restore_active_arena(arena, &backup);
+
+ lex->add_to_query_tables(table);
+
+ return FALSE;
+}
+
+
+/**
+ Mark columns from the list as used in the set of used table columns.
+
+ @param columns List of column names
+ @param share Table's share
+ @param bitmap Set of used columns
+*/
+
+static void fk_colnames_to_set(List<LEX_STRING> &columns, TABLE_SHARE *share,
+ MY_BITMAP *bitmap)
+{
+ List_iterator<LEX_STRING> col_it(columns);
+ LEX_STRING *col;
+ uint col_idx;
+ while((col= col_it++))
+ {
+ col_idx= find_field_idx_in_table(share, col->str, col->length);
+ if (col_idx != UINT_MAX)
+ bitmap_set_bit(bitmap, col_idx);
+ }
+}
+
+
+/**
+ Process all foreign key constraints which are going to be involved
+ 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
+
+ @retval FALSE - Success
+ @retval TRUE - Failure (OOM)
+*/
+
+bool fk_add_tables_for_fks(THD *thd, LEX *lex, TABLE_LIST *table)
+{
+ MY_BITMAP *cols_to_be_updated_set;
+ bool is_insert= (table->trg_event_map &
+ static_cast<uint8>(1 << static_cast<int>(TRG_EVENT_INSERT)));
+ bool is_update= (table->trg_event_map &
+ static_cast<uint8>(1 << static_cast<int>(TRG_EVENT_UPDATE)));
+ bool is_delete= (table->trg_event_map &
+ static_cast<uint8>(1 << static_cast<int>(TRG_EVENT_DELETE)));
+
+ if (table->cols_to_be_updated_known)
+ {
+ /*
+ For this table we know list of columns which are going
+ to be updated. Let us convert it to set representation.
+ */
+ cols_to_be_updated_set= &table->table->tmp_set;
+ bitmap_clear_all(cols_to_be_updated_set);
+ fk_colnames_to_set(table->cols_to_be_updated, table->table->s,
+ cols_to_be_updated_set);
+ /* Add set of columns which can be changed by ON UPDATE trigger. */
+ if (table->table->triggers)
+ {
+ bitmap_union(cols_to_be_updated_set,
+ table->table->triggers->get_update_triggers_write_set());
+ }
+ }
+
+ if (is_insert || is_update)
+ {
+ List_iterator<Foreign_key> it(table->table->s->fkeys);
+ Foreign_key *fk;
+ while ((fk= it++))
+ {
+ if (!fk->is_parent_table &&
+ (is_insert ||
+ !table->cols_to_be_updated_known ||
+ bitmap_is_overlapping(&fk->column_set, cols_to_be_updated_set)) &&
+ fk_add_to_constraint_list(thd, lex, fk, CHILD_INSERT))
+ {
+ if (fk_add_table_to_table_list(thd, lex, fk->ref_table,
+ TL_READ_NO_INSERT, 0,
+ table->belong_to_view, 0))
+ return TRUE;
+ }
+ }
+ }
+ if (is_update || is_delete)
+ {
+ List_iterator<Foreign_key> it(table->table->s->fkeys);
+ Foreign_key *fk;
+ while ((fk= it++))
+ {
+ if (fk->is_parent_table)
+ {
+ if (is_update &&
+ (!table->cols_to_be_updated_known ||
+ bitmap_is_overlapping(&fk->column_set, cols_to_be_updated_set)) &&
+ fk_add_to_constraint_list(thd, lex, fk, PARENT_UPDATE))
+ {
+ if (fk->update_opt != Foreign_key::FK_OPTION_NO_ACTION &&
+ fk->update_opt != Foreign_key::FK_OPTION_RESTRICT)
+ {
+ if (fk_add_table_to_table_list(thd, lex, fk->table, TL_WRITE,
+ static_cast<uint8>(1 << static_cast<int>(TRG_EVENT_UPDATE)),
+ table->belong_to_view, &fk->columns))
+ return TRUE;
+ }
+ else
+ {
+ if (fk_add_table_to_table_list(thd, lex, fk->table,
+ TL_READ_NO_INSERT, 0,
+ table->belong_to_view, 0))
+ return TRUE;
+ }
+ }
+ if (is_delete &&
+ fk_add_to_constraint_list(thd, lex, fk, PARENT_DELETE))
+ {
+ if (fk->delete_opt == Foreign_key::FK_OPTION_NO_ACTION ||
+ fk->delete_opt == Foreign_key::FK_OPTION_RESTRICT)
+ {
+ if (fk_add_table_to_table_list(thd, lex, fk->table,
+ TL_READ_NO_INSERT, 0,
+ table->belong_to_view, 0))
+ return TRUE;
+ }
+ else if (fk->delete_opt == Foreign_key::FK_OPTION_CASCADE)
+ {
+ if (fk_add_table_to_table_list(thd, lex, fk->table, TL_WRITE,
+ static_cast<uint8>(1 << static_cast<int>(TRG_EVENT_DELETE)),
+ table->belong_to_view, 0))
+ return TRUE;
+ }
+ else
+ {
+ if (fk_add_table_to_table_list(thd, lex, fk->table, TL_WRITE,
+ static_cast<uint8>(1 << static_cast<int>(TRG_EVENT_UPDATE)),
+ table->belong_to_view, &fk->columns))
+ return TRUE;
+ }
+ }
+ }
+ }
+ }
+ return FALSE;
}
=== modified file 'sql/fk_dd.h'
--- a/sql/fk_dd.h 2008-06-25 19:52:25 +0000
+++ b/sql/fk_dd.h 2008-07-11 07:06:24 +0000
@@ -39,7 +39,10 @@ bool fk_drop_constraint_name(const char
bool fk_drop_all_constraint_names_for_table(THD *thd, TABLE_LIST *table);
bool fk_check_constraint_added(THD *thd, Foreign_key *fkey,
- List<Foreign_key> &fkey_list,
- const char *db);
+ List<Foreign_key> &fkey_list);
+
+bool fk_init_column_sets(TABLE_SHARE *share);
+
+bool fk_add_tables_for_fks(THD *thd, LEX *lex, TABLE_LIST *table);
#endif
=== modified file 'sql/item.cc'
--- a/sql/item.cc 2008-05-27 17:53:05 +0000
+++ b/sql/item.cc 2008-07-11 07:06:24 +0000
@@ -6333,44 +6333,52 @@ void Item_insert_value::print(String *st
/**
- Find index of Field object which will be appropriate for item
- representing field of row being changed in trigger.
+ Find index of Field object corresponding to the item representing
+ field of row being changed in trigger, mark this field as used in
+ appropriate set.
@param thd current thread context
@param table table of trigger (and where we looking for fields)
@param table_grant_info GRANT_INFO of the subject table
+ @param read_set set of fields read by triggers
+ @param write_set set of fields written by triggers
@note
- This function does almost the same as fix_fields() for Item_field
- but is invoked right after trigger definition parsing. Since at
- this stage we can't say exactly what Field object (corresponding
- to TABLE::record[0] or TABLE::record[1]) should be bound to this
- Item, we only find out index of the Field and then select concrete
- Field object in fix_fields() (by that time Table_trigger_list::old_field/
- new_field should point to proper array of Fields).
- It also binds Item_trigger_field to Table_triggers_list object for
- table of trigger which uses this item.
+ This function does almost the same thing as fix_fields() for
+ Item_field but is invoked right after trigger definition parsing.
+ Since at this stage we can't say exactly what Field object
+ (corresponding to TABLE::record[0] or TABLE::record[1]) should be
+ bound to this Item, we only find out index of the Field and then
+ select concrete Field object in fix_fields() (by that time
+ Table_trigger_list::old_field/new_field should point to proper
+ array of Fields). We also mark this field as read/written in
+ sets of fields used by triggers for particular event type.
+ Finally we bind Item_trigger_field to Table_triggers_list object
+ for table of trigger which uses this item.
*/
void Item_trigger_field::setup_field(THD *thd, TABLE *table,
- GRANT_INFO *table_grant_info)
+ GRANT_INFO *table_grant_info,
+ MY_BITMAP *read_set,
+ MY_BITMAP *write_set)
{
- /*
- It is too early to mark fields used here, because before execution
- of statement that will invoke trigger other statements may use same
- TABLE object, so all such mark-up will be wiped out.
- So instead we do it in Table_triggers_list::mark_fields_used()
- method which is called during execution of these statements.
- */
- enum_mark_columns save_mark_used_columns= thd->mark_used_columns;
- thd->mark_used_columns= MARK_COLUMNS_NONE;
- /*
- Try to find field by its name and if it will be found
- set field_idx properly.
- */
- (void)find_field_in_table(thd, table, field_name, (uint) strlen(field_name),
- 0, &field_idx);
- thd->mark_used_columns= save_mark_used_columns;
+ field_idx= find_field_idx_in_table(table->s, field_name,
+ (uint)strlen(field_name));
+ if (field_idx != UINT_MAX && read_set)
+ {
+ /*
+ Mark fields as used in sets of read/written fields for particular
+ event type. It is too early to mark them as used in TABLE::write_set
+ and read_set, because before execution of statement that will invoke
+ trigger other statements may use same TABLE object, so all such
+ mark-up will be wiped out. So instead we use sets in Table_triggers_list
+ and then update TABLE::write/read_set in mark_fields_used() method
+ which is called during execution of statements invoking triggers.
+ */
+ bitmap_set_bit(read_set, field_idx);
+ if (!read_only)
+ bitmap_set_bit(write_set, field_idx);
+ }
triggers= table->triggers;
table_grants= table_grant_info;
}
@@ -6416,7 +6424,7 @@ bool Item_trigger_field::fix_fields(THD
/* Set field. */
- if (field_idx != (uint)-1)
+ if (field_idx != UINT_MAX)
{
#ifndef NO_EMBEDDED_ACCESS_CHECKS
/*
=== modified file 'sql/item.h'
--- a/sql/item.h 2008-05-22 18:40:15 +0000
+++ b/sql/item.h 2008-07-11 07:06:24 +0000
@@ -2730,10 +2730,11 @@ public:
ulong priv, const bool ro)
:Item_field(context_arg,
(const char *)NULL, (const char *)NULL, field_name_arg),
- row_version(row_ver_arg), field_idx((uint)-1), original_privilege(priv),
+ row_version(row_ver_arg), field_idx(UINT_MAX), original_privilege(priv),
want_privilege(priv), table_grants(NULL), read_only (ro)
{}
- void setup_field(THD *thd, TABLE *table, GRANT_INFO *table_grant_info);
+ void setup_field(THD *thd, TABLE *table, GRANT_INFO *table_grant_info,
+ MY_BITMAP *read_set, MY_BITMAP *write_set);
enum Type type() const { return TRIGGER_FIELD_ITEM; }
bool eq(const Item *item, bool binary_cmp) const;
bool fix_fields(THD *, Item **);
=== modified file 'sql/mysql_priv.h'
--- a/sql/mysql_priv.h 2008-06-23 13:26:17 +0000
+++ b/sql/mysql_priv.h 2008-07-11 07:06:24 +0000
@@ -1256,7 +1256,8 @@ int prepare_create_field(Create_field *s
uint *blob_columns,
int *timestamps, int *timestamps_with_niladic,
longlong table_flags);
-bool mysql_create_table(THD *thd,const char *db, const char *table_name,
+bool mysql_create_table(THD *thd, TABLE_LIST *create_table,
+ TABLE_LIST **fkey_tables,
HA_CREATE_INFO *create_info,
Alter_info *alter_info,
bool tmp_table, uint select_field_count);
@@ -1359,6 +1360,8 @@ find_field_in_table(THD *thd, TABLE *tab
bool allow_rowid, uint *cached_field_index_ptr);
Field *
find_field_in_table_sef(TABLE *table, const char *name);
+uint
+find_field_idx_in_table(TABLE_SHARE *share, const char *name, uint length);
#endif /* MYSQL_SERVER */
=== modified file 'sql/sp.cc'
--- a/sql/sp.cc 2008-06-04 11:18:52 +0000
+++ b/sql/sp.cc 2008-07-11 07:06:24 +0000
@@ -1486,9 +1486,9 @@ void sp_get_prelocking_info(THD *thd, bo
the set).
*/
-static bool add_used_routine(LEX *lex, Query_arena *arena,
- const LEX_STRING *key,
- TABLE_LIST *belong_to_view)
+bool sp_add_used_routine(LEX *lex, Query_arena *arena,
+ const LEX_STRING *key,
+ TABLE_LIST *belong_to_view)
{
hash_init_opt(&lex->sroutines, system_charset_info,
Query_tables_list::START_SROUTINES_HASH_SIZE,
@@ -1535,7 +1535,7 @@ void sp_add_used_routine(LEX *lex, Query
sp_name *rt, char rt_type)
{
rt->set_routine_type(rt_type);
- (void)add_used_routine(lex, arena, &rt->m_sroutines_key, 0);
+ (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;
}
@@ -1616,7 +1616,7 @@ sp_update_stmt_used_routines(THD *thd, L
for (uint i=0 ; i < src->records ; i++)
{
Sroutine_hash_entry *rt= (Sroutine_hash_entry *)hash_element(src, i);
- (void)add_used_routine(lex, thd->stmt_arena, &rt->key, belong_to_view);
+ (void)sp_add_used_routine(lex, thd->stmt_arena, &rt->key, belong_to_view);
}
}
@@ -1640,7 +1640,7 @@ static void sp_update_stmt_used_routines
{
for (Sroutine_hash_entry *rt= (Sroutine_hash_entry *)src->first;
rt; rt= rt->next)
- (void)add_used_routine(lex, thd->stmt_arena, &rt->key, belong_to_view);
+ (void)sp_add_used_routine(lex, thd->stmt_arena, &rt->key, belong_to_view);
}
@@ -1845,8 +1845,9 @@ sp_cache_routines_and_add_tables_for_tri
/* We can have only one trigger per action type currently */
sp_head *trigger= table->table->triggers->bodies[i][j];
if (trigger &&
- add_used_routine(lex, thd->stmt_arena, &trigger->m_sroutines_key,
- table->belong_to_view))
+ 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 */
=== modified file 'sql/sp.h'
--- a/sql/sp.h 2008-04-08 16:36:01 +0000
+++ b/sql/sp.h 2008-07-11 07:06:24 +0000
@@ -93,6 +93,9 @@ void sp_get_prelocking_info(THD *thd, bo
bool *first_no_prelocking);
void sp_add_used_routine(LEX *lex, 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);
void sp_update_sp_used_routines(HASH *dst, HASH *src);
int sp_cache_routines_and_add_tables(THD *thd, LEX *lex,
=== modified file 'sql/sp_head.h'
--- a/sql/sp_head.h 2008-05-16 13:55:54 +0000
+++ b/sql/sp_head.h 2008-07-11 07:06:24 +0000
@@ -33,6 +33,7 @@
#define TYPE_ENUM_FUNCTION 1
#define TYPE_ENUM_PROCEDURE 2
#define TYPE_ENUM_TRIGGER 3
+#define TYPE_ENUM_FOREIGN_KEY 4
Item_result
sp_map_result_type(enum enum_field_types type);
=== modified file 'sql/sql_base.cc'
--- a/sql/sql_base.cc 2008-06-25 16:40:20 +0000
+++ b/sql/sql_base.cc 2008-07-11 07:06:24 +0000
@@ -21,6 +21,7 @@
#include "sp_head.h"
#include "sp.h"
#include "sql_trigger.h"
+#include "fk_dd.h"
#include <m_ctype.h>
#include <my_dir.h>
#include <hash.h>
@@ -4098,24 +4099,43 @@ int open_tables(THD *thd, TABLE_LIST **s
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.
*/
if (thd->locked_tables_mode <= LTM_LOCK_TABLES &&
!thd->lex->requires_prelocking() &&
- tables->trg_event_map && tables->table->triggers &&
+ tables->trg_event_map &&
tables->lock_type >= TL_WRITE_ALLOW_WRITE)
{
- 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))
+ if (tables->table->triggers)
{
- /*
- 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;
+ 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;
+ }
+ }
+ 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 (fk_add_tables_for_fks(thd, thd->lex, tables))
+ {
+ result= -1;
+ goto err;
+ }
}
}
@@ -5314,6 +5334,53 @@ find_field_in_table(THD *thd, TABLE *tab
update_field_dependencies(thd, field, table);
DBUG_RETURN(field);
+}
+
+
+/**
+ Find field index by field name in a base table.
+
+ @param share TABLE_SHARE object for the table.
+ @param name Field name.
+ @param length Length of field name.
+
+ @note Unlike find_field_in_table() requires properly initialized
+ TABLE_SHARE::field array and therefore does not work for
+ various temporary tables. Also unlike find_field_in_table()
+ does not have any side-effects.
+
+ QQ: It would be nice to merge this code with find_field_in_table()
+ and find_field_in_table_sef(), but this means that for all TABLE
+ objects we need to properly initialize TABLE_SHARE. Does it worth?
+
+ @retval non-UINT_MAX - Index of the field if field was found.
+ @retval UINT_MAX - If field was not found.
+*/
+
+uint find_field_idx_in_table(TABLE_SHARE *share, const char *name, uint length)
+{
+ Field **field_ptr;
+ uint result= UINT_MAX;
+ DBUG_ENTER("find_field_idx_in_table");
+
+ if (share->name_hash.records)
+ {
+ field_ptr= (Field**) hash_search(&share->name_hash, (uchar*) name, length);
+ if (field_ptr)
+ result= field_ptr - share->field;
+ }
+ else
+ {
+ for (field_ptr= share->field; *field_ptr; ++field_ptr)
+ {
+ if (!my_strcasecmp(system_charset_info, (*field_ptr)->field_name, name))
+ {
+ result= field_ptr - share->field;
+ break;
+ }
+ }
+ }
+ DBUG_RETURN(result);
}
=== modified file 'sql/sql_class.cc'
--- a/sql/sql_class.cc 2008-06-25 19:52:25 +0000
+++ b/sql/sql_class.cc 2008-07-11 07:06:24 +0000
@@ -123,6 +123,7 @@ Foreign_key::Foreign_key(const Foreign_k
:
name(rhs.name),
columns(rhs.columns),
+ table(rhs.table),
ref_table(rhs.ref_table),
ref_columns(rhs.ref_columns),
delete_opt(rhs.delete_opt),
@@ -132,7 +133,12 @@ Foreign_key::Foreign_key(const Foreign_k
is_table_constraint(rhs.is_table_constraint),
is_name_generated(rhs.is_name_generated),
is_within_one_db(rhs.is_within_one_db),
+ is_parent_table(rhs.is_parent_table),
exists(rhs.exists)
+ /*
+ Don't copy "column_set" since it is relevant only for
+ Foreign_key bound to particular TABLE_SHARE instance.
+ */
{
list_copy_and_replace_each_value(columns, mem_root);
list_copy_and_replace_each_value(ref_columns, mem_root);
=== modified file 'sql/sql_class.h'
--- a/sql/sql_class.h 2008-06-25 19:52:25 +0000
+++ b/sql/sql_class.h 2008-07-11 07:06:24 +0000
@@ -246,6 +246,7 @@ public:
LEX_STRING name;
List<Key_part_spec> columns;
+ TABLE_LIST *table;
TABLE_LIST *ref_table;
List<Key_part_spec> ref_columns;
uint delete_opt, update_opt, match_opt;
@@ -271,34 +272,49 @@ public:
*/
bool is_within_one_db;
/**
+ Indicates that this object represents constraint in .FRM file
+ of parent table.
+ */
+ bool is_parent_table;
+ /**
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;
+ /**
+ Set of table columns participating in foreign key relationship.
+ Note that if this Foreign_key object represents constraint in
+ description of parent table then it is going to be set of columns
+ in parent table and not in child.
+ */
+ MY_BITMAP column_set;
+
Foreign_key(const LEX_STRING &name_arg,
const LEX_STRING &fkey_clause_name_arg,
- bool table_con_arg, List<Key_part_spec> &cols,
- TABLE_LIST *table, List<Key_part_spec> &ref_cols,
+ bool table_con_arg, TABLE_LIST *tab, List<Key_part_spec> &cols,
+ TABLE_LIST *ref_tab, List<Key_part_spec> &ref_cols,
uint delete_opt_arg, uint update_opt_arg, uint match_opt_arg)
: name(name_arg), columns(cols),
- ref_table(table), ref_columns(ref_cols),
+ table(tab), ref_table(ref_tab), ref_columns(ref_cols),
delete_opt(delete_opt_arg), update_opt(update_opt_arg),
match_opt(match_opt_arg), fkey_clause_name(fkey_clause_name_arg),
is_table_constraint(table_con_arg), is_name_generated(FALSE),
- is_within_one_db(FALSE), exists(FALSE)
+ is_within_one_db(FALSE), is_parent_table(FALSE), exists(FALSE)
{}
Foreign_key(const char *name_arg, size_t name_len_arg, bool table_con_arg,
- bool within_one_db_arg, List<Key_part_spec> &cols,
- TABLE_LIST *table, List<Key_part_spec> &ref_cols,
+ bool within_one_db_arg, TABLE_LIST *tab,
+ List<Key_part_spec> &cols, TABLE_LIST *ref_tab,
+ List<Key_part_spec> &ref_cols,
uint delete_opt_arg, uint update_opt_arg,
- uint match_opt_arg, bool exists_arg)
+ uint match_opt_arg, bool par_arg, bool exists_arg)
: columns(cols),
- ref_table(table), ref_columns(ref_cols),
+ table(tab), ref_table(ref_tab), ref_columns(ref_cols),
delete_opt(delete_opt_arg), update_opt(update_opt_arg),
match_opt(match_opt_arg),
is_table_constraint(table_con_arg), is_name_generated(FALSE),
- is_within_one_db(within_one_db_arg), exists(exists_arg)
+ is_within_one_db(within_one_db_arg), is_parent_table(par_arg),
+ exists(exists_arg)
{
name.str= (char *)name_arg;
name.length= name_len_arg;
@@ -318,7 +334,7 @@ public:
to their default value, also set value of is_within_one_db
attribute..
*/
- void set_missing_options(const char *db)
+ void set_missing_options()
{
if (match_opt == FK_MATCH_UNDEF)
match_opt= FK_MATCH_SIMPLE;
@@ -327,7 +343,7 @@ public:
if (delete_opt == FK_OPTION_UNDEF)
delete_opt= FK_OPTION_NO_ACTION;
- if (!strcmp(db, ref_table->db))
+ if (!strcmp(table->db, ref_table->db))
is_within_one_db= TRUE;
}
@@ -335,10 +351,11 @@ public:
void save_to_frm(char **buff_p);
static Foreign_key* restore_from_frm(THD *thd, MEM_ROOT *mem_root,
const LEX_STRING &db,
+ const LEX_STRING &table_name,
const char **buff_p);
+ bool init_column_set(TABLE_SHARE *share);
- void get_create_statement_clause(THD *thd, String *str,
- const char *db);
+ void get_create_statement_clause(THD *thd, String *str);
void generate_name_if_needed(THD *thd, const char *table_name);
};
=== modified file 'sql/sql_lex.cc'
--- a/sql/sql_lex.cc 2008-06-27 05:11:56 +0000
+++ b/sql/sql_lex.cc 2008-07-11 07:06:24 +0000
@@ -2775,10 +2775,11 @@ void st_lex::link_first_table_back(TABLE
@note We assume that the first table in outer select and global
list exist and are the same.
- @return Head of the list with unlinked tables.
+ @param link Pointer to the head of the list to which unlinked
+ tables should be attached.
*/
-TABLE_LIST *st_lex::unlink_all_tables_except_first()
+void st_lex::unlink_all_tables_except_first(TABLE_LIST **link)
{
TABLE_LIST *first_table= select_lex.get_table_list();
TABLE_LIST *rest_tables= first_table->next_local;
@@ -2790,7 +2791,10 @@ TABLE_LIST *st_lex::unlink_all_tables_ex
DBUG_ASSERT(first_table == query_tables);
first_table->next_global= 0;
query_tables_last= &(first_table->next_global);
- return rest_tables;
+ /* Attach removed tables to the specified list. */
+ if (rest_tables)
+ rest_tables->prev_global= link;
+ *link= rest_tables;
}
=== modified file 'sql/sql_lex.h'
--- a/sql/sql_lex.h 2008-06-27 05:11:56 +0000
+++ b/sql/sql_lex.h 2008-07-11 07:06:24 +0000
@@ -1680,6 +1680,8 @@ typedef struct st_lex : public Query_tab
bool subqueries, ignore;
st_parsing_options parsing_options;
Alter_info alter_info;
+ /* List of tables referenced by foreign keys in the table being created. */
+ TABLE_LIST *create_foreign_key_tables;
/* Prepared statements SQL syntax:*/
LEX_STRING prepared_stmt_name; /* Statement name (in all queries) */
/*
@@ -1793,7 +1795,7 @@ typedef struct st_lex : public Query_tab
TABLE_LIST *unlink_first_table(bool *link_to_local);
void link_first_table_back(TABLE_LIST *first, bool link_to_local);
void first_lists_tables_same();
- TABLE_LIST *unlink_all_tables_except_first();
+ void unlink_all_tables_except_first(TABLE_LIST **link);
bool can_be_merged();
bool can_use_merged();
=== modified file 'sql/sql_list.h'
--- a/sql/sql_list.h 2008-03-28 17:47:53 +0000
+++ b/sql/sql_list.h 2008-07-11 07:06:24 +0000
@@ -214,6 +214,10 @@ public:
swap_variables(list_node *, first, rhs.first);
swap_variables(list_node **, last, rhs.last);
swap_variables(uint, elements, rhs.elements);
+ if (is_empty())
+ last= &first;
+ if (rhs.is_empty())
+ rhs.last= &rhs.first;
}
inline list_node* last_node() { return *last; }
inline list_node* first_node() { return first;}
=== modified file 'sql/sql_parse.cc'
--- a/sql/sql_parse.cc 2008-06-23 13:26:17 +0000
+++ b/sql/sql_parse.cc 2008-07-11 07:06:24 +0000
@@ -2437,9 +2437,9 @@ mysql_execute_command(THD *thd)
&create_info);
else
{
- res= mysql_create_table(thd, create_table->db,
- create_table->table_name, &create_info,
- &alter_info, 0, 0);
+ res= mysql_create_table(thd, create_table,
+ &lex->create_foreign_key_tables,
+ &create_info, &alter_info, 0, 0);
}
if (!res)
my_ok(thd);
=== modified file 'sql/sql_show.cc'
--- a/sql/sql_show.cc 2008-06-23 13:26:17 +0000
+++ b/sql/sql_show.cc 2008-07-11 07:06:24 +0000
@@ -4788,6 +4788,13 @@ static int get_schema_constraints_record
Foreign_key *fkey;
while ((fkey= fkey_it++))
{
+ /*
+ QQ: Does it make sense to split TABLE_SHARE::fkeys in two lists?
+ This will simplify some code... What about list in Alter_info?
+ */
+ if (fkey->is_parent_table)
+ continue;
+
if (store_constraints(thd, table, db_name, table_name,
fkey->name.str, fkey->name.length,
"FOREIGN KEY", 11))
@@ -4990,6 +4997,9 @@ static int get_schema_key_column_usage_r
Foreign_key *fkey;
while ((fkey= fkey_it++))
{
+ if (fkey->is_parent_table)
+ continue;
+
List_iterator<Key_part_spec> it(fkey->columns);
List_iterator<Key_part_spec> it1(fkey->ref_columns);
Key_part_spec *f_col, *r_col;
@@ -5736,6 +5746,9 @@ get_referential_constraints_record(THD *
Foreign_key *fkey;
while ((fkey= fkey_it++))
{
+ if (fkey->is_parent_table)
+ continue;
+
restore_record(table, s->default_values);
table->field[1]->store(db_name->str, db_name->length, cs);
table->field[2]->store(fkey->name.str, fkey->name.length, cs);
=== modified file 'sql/sql_table.cc'
--- a/sql/sql_table.cc 2008-06-25 19:52:25 +0000
+++ b/sql/sql_table.cc 2008-07-11 07:06:24 +0000
@@ -44,8 +44,7 @@ static int copy_data_between_tables(TABL
static bool prepare_blob_field(THD *thd, Create_field *sql_field);
static bool check_engine(THD *, const char *, HA_CREATE_INFO *);
static int
-mysql_prepare_create_table(THD *thd, const char *db,
- HA_CREATE_INFO *create_info,
+mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info,
Alter_info *alter_info,
bool tmp_table,
uint *db_options,
@@ -1272,7 +1271,7 @@ bool mysql_write_frm(ALTER_PARTITION_PAR
strxmov(shadow_frm_name, shadow_path, reg_ext, NullS);
if (flags & WFRM_WRITE_SHADOW)
{
- if (mysql_prepare_create_table(lpt->thd, lpt->db, lpt->create_info,
+ if (mysql_prepare_create_table(lpt->thd, lpt->create_info,
lpt->alter_info,
/*tmp_table*/ 1,
&lpt->db_options,
@@ -2238,7 +2237,6 @@ int prepare_create_field(Create_field *s
SYNOPSIS
mysql_prepare_create_table()
thd Thread object.
- db Name of database for table to being created.
create_info Create information (like MAX_ROWS).
alter_info List of columns and indexes to create
tmp_table If a temporary table is to be created.
@@ -2260,7 +2258,7 @@ int prepare_create_field(Create_field *s
*/
static int
-mysql_prepare_create_table(THD *thd, const char *db,
+mysql_prepare_create_table(THD *thd,
HA_CREATE_INFO *create_info,
Alter_info *alter_info,
bool tmp_table,
@@ -2624,12 +2622,11 @@ mysql_prepare_create_table(THD *thd, con
fkey->name.str : "(none)"));
fk_key_count++;
- fkey->set_missing_options(db);
+ fkey->set_missing_options();
if (opt_fk_all_engines)
{
- if (fk_check_constraint_added(thd, fkey, alter_info->foreign_key_list,
- db))
+ if (fk_check_constraint_added(thd, fkey, alter_info->foreign_key_list))
DBUG_RETURN(TRUE);
}
else
@@ -3554,7 +3551,7 @@ bool mysql_create_table_no_lock(THD *thd
set_table_default_charset(thd, create_info, (char*) db);
- if (mysql_prepare_create_table(thd, db, create_info, alter_info,
+ if (mysql_prepare_create_table(thd, create_info, alter_info,
internal_tmp_table,
&db_options, file,
&key_info_buffer, &key_count,
@@ -3784,24 +3781,316 @@ static bool lock_table_name_if_not_cache
}
-/*
- Database locking aware wrapper for mysql_create_table_no_lock(),
+/**
+ Fix list of tables referenced by foreign keys in table being created
+ by removing duplicate elements.
+
+ @param fkey_tables [in/out] List of referenced tables
+ @param create_table [in] Table being created
+ @param alter_info [in] Alter_info for table being created
+*/
+
+static void fix_foreign_key_tables_list(TABLE_LIST **fkey_tables,
+ TABLE_LIST *create_table,
+ Alter_info *alter_info)
+{
+ TABLE_LIST *tab= *fkey_tables;
+ TABLE_LIST *dup;
+
+ while (tab)
+ {
+ dup= create_table;
+ if (strcmp(dup->db, tab->db) || strcmp(dup->table_name, tab->table_name))
+ {
+ for (dup= *fkey_tables;
+ dup != tab && (strcmp(dup->db, tab->db) ||
+ strcmp(dup->table_name, tab->table_name));
+ dup= dup->next_global)
+ {}
+ }
+ if (dup != tab)
+ {
+ /*
+ We have found a duplicate element in the list so we can remove
+ current element from the list.
+ */
+ *(tab->prev_global)= tab->next_global;
+ if (tab->next_global)
+ tab->next_global->prev_global= tab->prev_global;
+ /*
+ Let us adjust pointers in Foreign_key objects to point to
+ TABLE_LIST instance which is left in the list.
+ */
+ List_iterator<Foreign_key> fkey_it(alter_info->foreign_key_list);
+ Foreign_key *fkey;
+ while ((fkey= fkey_it++))
+ if (fkey->ref_table == tab)
+ fkey->ref_table= dup;
+ }
+ tab= tab->next_global;
+ }
+}
+
+
+/**
+ Auxiliary function which creates new version of .FRM for table
+ which is referenced via foreign key from table being created.
+
+ @param thd Thread context
+ @param table Table for which we need new .FRM
+ @param dst_path Name for the new .FRM
+ @param fk_list List of foreign keys some of which reference
+ this table.
+
+ @retval FALSE Success
+ @retval TRUE Failure
*/
-bool mysql_create_table(THD *thd, const char *db, const char *table_name,
+static bool create_frm_with_ref_fks(THD *thd, TABLE_LIST *table,
+ char *dst_path,
+ List<Foreign_key> &fk_list)
+{
+ HA_CREATE_INFO create_info;
+ Alter_info alter_info;
+ KEY *key_info_buffer;
+ uint db_options= 0, key_count;
+ DBUG_ENTER("create_frm_with_ref_fks");
+
+ bzero((char*) &create_info, sizeof(create_info));
+ create_info.db_type= table->table->s->db_type();
+ create_info.row_type= table->table->s->row_type;
+ alter_info.flags= (ALTER_CHANGE_COLUMN | ALTER_RECREATE);
+ table->table->use_all_columns();
+ if (mysql_prepare_alter_table(thd, table->table, &create_info,
+ &alter_info))
+ DBUG_RETURN(TRUE);
+
+ List_iterator<Foreign_key> fkey_it(fk_list);
+ Foreign_key *fkey;
+
+ /*
+ Select foreign keys which reference this table and add their
+ descriptions to the list foreign keys for this table.
+ */
+ while ((fkey= fkey_it++))
+ {
+ if (fkey->ref_table == table)
+ {
+ if (!(fkey= fkey->clone(thd->mem_root)))
+ DBUG_RETURN(TRUE);
+ fkey->is_parent_table= TRUE;
+ if (alter_info.foreign_key_list.push_back(fkey))
+ DBUG_RETURN(TRUE);
+ }
+ }
+
+ if (mysql_prepare_create_table(thd, &create_info,
+ &alter_info,
+ FALSE, &db_options,
+ table->table->file,
+ &key_info_buffer,
+ &key_count, 0))
+ DBUG_RETURN(TRUE);
+
+ if (mysql_create_frm(thd, dst_path, NullS, NullS,
+ &create_info, alter_info.create_list,
+ key_count, key_info_buffer,
+ alter_info.foreign_key_list,
+ table->table->file))
+ DBUG_RETURN(TRUE);
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Prepare for creation of table with foreign keys by opening referenced
+ tables and adjusting definition of this table if has self-references.
+
+ @param thd Thread context
+ @param create_table Table being created
+ @param fkey_tables List of referenced tables
+ @param alter_info Alter_info for the table being created
+
+ @retval FALSE Success
+ @retval TRUE Failure
+*/
+
+static bool prepare_create_table_with_fks(THD *thd,
+ TABLE_LIST *create_table,
+ TABLE_LIST *fkey_tables,
+ Alter_info *alter_info)
+{
+ List_iterator<Foreign_key> fkey_it(alter_info->foreign_key_list);
+ TABLE_LIST *tab;
+ Foreign_key *fkey;
+
+ /*
+ Open referenced tables so they are ready for checking if we can
+ create foreign key referencing them and also for creation of new
+ version of .FRM file.
+ */
+ for (tab= fkey_tables; tab; tab= tab->next_global)
+ {
+ enum enum_open_table_action ot_action_unused;
+ if (open_table(thd, tab, thd->mem_root, &ot_action_unused,
+ MYSQL_OPEN_REOPEN))
+ return TRUE;
+ }
+
+ /*
+ Foreign keys for which table being created serves as both child and
+ parent should present in table description twice. First as foreign
+ key on this child table and second as foreign key which references
+ to this parent table.
+ */
+ while ((fkey= fkey_it++) && !fkey->is_parent_table)
+ {
+ if (fkey->ref_table == create_table)
+ {
+ if (!(fkey= fkey->clone(thd->mem_root)))
+ return TRUE;
+ fkey->is_parent_table= TRUE;
+ if (alter_info->foreign_key_list.push_back(fkey))
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+
+/**
+ Finalize creation of table with foreign keys by updating .FRM files
+ of referenced tables and creating constraint names.
+
+ @param thd Thread context
+ @param create_table Table being created
+ @param fkey_tables List of referenced tables
+ @param alter_info Alter_info for the table being created
+
+ @retval FALSE Success
+ @retval TRUE Failure
+*/
+
+static bool finish_create_table_with_fks(THD *thd,
+ TABLE_LIST *create_table,
+ TABLE_LIST *fkey_tables,
+ Alter_info *alter_info)
+{
+ char new_ver_name[NAME_LEN+1], old_ver_name[NAME_LEN+1],
+ dst_path[FN_REFLEN];
+ uint new_ver_var_part, old_ver_var_part;
+ TABLE_LIST *tab, *err_tab;
+ ulong i;
+
+ new_ver_var_part= my_snprintf(new_ver_name, sizeof(new_ver_name),
+ "%s-%lx_%lx_", tmp_file_prefix,
+ current_pid, thd->thread_id);
+ old_ver_var_part= my_snprintf(old_ver_name, sizeof(new_ver_name),
+ "%s2-%lx_%lx_", tmp_file_prefix,
+ current_pid, thd->thread_id);
+ if (lower_case_table_names)
+ new_ver_var_part= my_casedn_str(files_charset_info, new_ver_name);
+ if (lower_case_table_names)
+ old_ver_var_part= my_casedn_str(files_charset_info, old_ver_name);
+
+ for (tab= fkey_tables, i= 0; tab; tab= tab->next_global, i++)
+ {
+ int10_to_str(i, new_ver_name + new_ver_var_part, 10);
+ build_table_filename(dst_path, sizeof(dst_path), tab->db,
+ new_ver_name, reg_ext, FN_IS_TMP);
+ if (create_frm_with_ref_fks(thd, tab, dst_path,
+ alter_info->foreign_key_list))
+ {
+ err_tab= tab;
+ goto error_remove_new_frm;
+ }
+ }
+
+ for (tab= fkey_tables, i= 0; tab; tab= tab->next_global, i++)
+ {
+ close_all_tables_for_name(thd, tab->table->s, FALSE);
+ pthread_mutex_lock(&LOCK_open);
+ tdc_remove_table(thd, TDC_RT_REMOVE_ALL, tab->db, tab->table_name);
+ pthread_mutex_unlock(&LOCK_open);
+
+ int10_to_str(i, new_ver_name + new_ver_var_part, 10);
+ int10_to_str(i, old_ver_name + old_ver_var_part, 10);
+
+ if (mysql_rename_table(NULL, tab->db, tab->table_name, tab->db,
+ old_ver_name, FN_TO_IS_TMP))
+ {
+ err_tab= tab;
+ goto error_revert_renames;
+ }
+ if (mysql_rename_table(NULL, tab->db, new_ver_name, tab->db,
+ tab->table_name, FN_FROM_IS_TMP))
+ {
+ (void) mysql_rename_table(NULL, tab->db, old_ver_name, tab->db,
+ tab->table_name, FN_FROM_IS_TMP);
+ err_tab= tab;
+ goto error_revert_renames;
+ }
+ }
+
+ if (fk_create_constraint_names(alter_info->foreign_key_list,
+ create_table->db))
+ {
+ err_tab= 0;
+ goto error_revert_renames;
+ }
+
+ for (tab= fkey_tables, i= 0; tab; tab= tab->next_global, i++)
+ {
+ int10_to_str(i, old_ver_name + old_ver_var_part, 10);
+ (void) quick_rm_table(NULL, tab->db, old_ver_name, FN_IS_TMP);
+ }
+ return FALSE;
+
+error_revert_renames:
+ for (tab= fkey_tables, i= 0; tab != err_tab; tab= tab->next_global, i++)
+ {
+ int10_to_str(i, new_ver_name + new_ver_var_part, 10);
+ int10_to_str(i, old_ver_name + old_ver_var_part, 10);
+ (void) mysql_rename_table(NULL, tab->db, tab->table_name, tab->db,
+ new_ver_name, FN_TO_IS_TMP);
+ (void) mysql_rename_table(NULL, tab->db, old_ver_name, tab->db,
+ tab->table_name, FN_FROM_IS_TMP);
+ }
+ err_tab= 0;
+
+error_remove_new_frm:
+ for (tab= fkey_tables, i= 0; tab != err_tab; tab= tab->next_global, i++)
+ {
+ int10_to_str(i, new_ver_name + new_ver_var_part, 10);
+ (void) quick_rm_table(NULL, tab->db, new_ver_name, FN_IS_TMP);
+ }
+ return TRUE;
+}
+
+
+/**
+ Database locking and foreign keys aware wrapper for
+ mysql_create_table_no_lock().
+*/
+
+bool mysql_create_table(THD *thd, TABLE_LIST *create_table,
+ TABLE_LIST **fkey_tables,
HA_CREATE_INFO *create_info,
Alter_info *alter_info,
bool internal_tmp_table,
uint select_field_count)
{
MDL_LOCK_DATA *target_lock_data= 0;
+ TABLE_LIST *tab;
bool result;
DBUG_ENTER("mysql_create_table");
/* Wait for any database locks */
pthread_mutex_lock(&LOCK_lock_db);
while (!thd->killed &&
- hash_search(&lock_db_cache,(uchar*) db, strlen(db)))
+ hash_search(&lock_db_cache, (uchar*)create_table->db,
+ create_table->db_length))
{
wait_for_condition(thd, &LOCK_lock_db, &COND_refresh);
pthread_mutex_lock(&LOCK_lock_db);
@@ -3817,40 +4106,110 @@ bool mysql_create_table(THD *thd, const
if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE))
{
- if (lock_table_name_if_not_cached(thd, db, table_name, &target_lock_data))
+ if (opt_fk_all_engines)
{
- result= TRUE;
- goto unlock;
+ /*
+ QQ: It seems that best approach to solve the problem of
+ potential deadlocks is to simply prohibit creation
+ of new non-temporary tables under LOCK TABLES.
+ Any better idea ?
+ */
+
+ if (thd->locked_tables_mode)
+ {
+ my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0));
+ result= TRUE;
+ goto unlock;
+ }
+
+ fk_generate_constraint_names(thd, create_table->table_name,
+ alter_info->foreign_key_list);
+
+ /*
+ We need to acquire exclusive meta-data locks on table to be
+ created and on all tables referenced by its foreign keys.
+ But first let us get rid of duplicates among the latter.
+ */
+ fix_foreign_key_tables_list(fkey_tables, create_table, alter_info);
+
+ mdl_set_lock_type(create_table->mdl_lock_data, MDL_EXCLUSIVE);
+ mdl_add_lock(&thd->mdl_context, create_table->mdl_lock_data);
+
+ for (tab= *fkey_tables; tab; tab= tab->next_global)
+ {
+ mdl_set_lock_type(tab->mdl_lock_data, MDL_EXCLUSIVE);
+ mdl_add_lock(&thd->mdl_context, tab->mdl_lock_data);
+ }
+
+ if (mdl_acquire_exclusive_locks(&thd->mdl_context))
+ {
+ mdl_remove_all_locks(&thd->mdl_context);
+ result= TRUE;
+ goto unlock;
+ }
+
+ if (prepare_create_table_with_fks(thd, create_table, *fkey_tables,
+ alter_info))
+ {
+ result= TRUE;
+ goto close_and_unlock;
+ }
}
- if (!target_lock_data)
+ else
{
- if (create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS)
+ if (lock_table_name_if_not_cached(thd, create_table->db,
+ create_table->table_name,
+ &target_lock_data))
{
- push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE,
- ER_TABLE_EXISTS_ERROR, ER(ER_TABLE_EXISTS_ERROR),
- table_name);
- create_info->table_existed= 1;
- result= FALSE;
+ result= TRUE;
+ goto unlock;
}
- else
+ if (!target_lock_data)
{
- my_error(ER_TABLE_EXISTS_ERROR,MYF(0),table_name);
- result= TRUE;
+ if (create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS)
+ {
+ push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE,
+ ER_TABLE_EXISTS_ERROR, ER(ER_TABLE_EXISTS_ERROR),
+ create_table->table_name);
+ create_info->table_existed= 1;
+ result= FALSE;
+ }
+ else
+ {
+ my_error(ER_TABLE_EXISTS_ERROR,MYF(0), create_table->table_name);
+ result= TRUE;
+ }
+ goto unlock;
}
- goto unlock;
}
}
- if (opt_fk_all_engines)
- fk_generate_constraint_names(thd, table_name, alter_info->foreign_key_list);
-
- result= mysql_create_table_no_lock(thd, db, table_name, create_info,
+ result= mysql_create_table_no_lock(thd, create_table->db,
+ create_table->table_name,
+ create_info,
alter_info,
internal_tmp_table,
select_field_count);
- if (!result && opt_fk_all_engines)
- result= fk_create_constraint_names(alter_info->foreign_key_list, db);
+ if (opt_fk_all_engines &&
+ !(create_info->options & HA_LEX_CREATE_TMP_TABLE) &&
+ !result && !create_info->table_existed &&
+ finish_create_table_with_fks(thd, create_table, *fkey_tables, alter_info))
+ {
+ (void) quick_rm_table(create_info->db_type, create_table->db,
+ create_table->table_name, 0);
+ result= TRUE;
+ }
+
+close_and_unlock:
+ if (opt_fk_all_engines && !(create_info->options & HA_LEX_CREATE_TMP_TABLE))
+ {
+ /*
+ close_thread_tables() takes care about both closing open tables (which
+ might be still around in case of error) and releasing metadata locks.
+ */
+ close_thread_tables(thd);
+ }
unlock:
if (target_lock_data)
@@ -4758,7 +5117,6 @@ bool mysql_preload_keys(THD* thd, TABLE_
@param[in] thd thread handler
@param[in] schema_table I_S table
- @param[in] db database for table being created
@param[in] dst_path path where frm should be created
@param[in] create_info Create info
@@ -4770,7 +5128,6 @@ bool mysql_preload_keys(THD* thd, TABLE_
bool mysql_create_like_schema_frm(THD* thd,
TABLE_LIST* schema_table,
- const char *db,
char *dst_path, HA_CREATE_INFO *create_info)
{
HA_CREATE_INFO local_create_info;
@@ -4789,7 +5146,7 @@ bool mysql_create_like_schema_frm(THD* t
if (mysql_prepare_alter_table(thd, schema_table->table,
&local_create_info, &alter_info))
DBUG_RETURN(1);
- if (mysql_prepare_create_table(thd, db, &local_create_info, &alter_info,
+ if (mysql_prepare_create_table(thd, &local_create_info, &alter_info,
tmp_table, &db_options,
schema_table->table->file,
&schema_table->table->s->key_info, &keys, 0))
@@ -4897,7 +5254,7 @@ bool mysql_create_like_table(THD* thd, T
pthread_mutex_lock(&LOCK_open);
if (src_table->schema_table)
{
- if (mysql_create_like_schema_frm(thd, src_table, db, dst_path,
+ if (mysql_create_like_schema_frm(thd, src_table, dst_path,
create_info))
{
pthread_mutex_unlock(&LOCK_open);
@@ -5188,9 +5545,6 @@ void setup_ha_alter_flags(Alter_info *al
/**
@param thd Thread
@param table The original table.
- @param db Database for new table.
- QQ: Do we really need this argument or we
- can use some dummy value ?
@param alter_info Alter options, fields and keys for the new
table.
@param create_info Create options for the new table.
@@ -5223,7 +5577,6 @@ static
bool
compare_tables(THD *thd,
TABLE *table,
- const char *db,
Alter_info *alter_info,
HA_CREATE_INFO *create_info,
uint order_num,
@@ -5264,7 +5617,7 @@ compare_tables(THD *thd,
THD *thd= table->in_use;
uint db_options= 0; /* not used */
/* Create the prepared information. */
- if (mysql_prepare_create_table(thd, db, create_info,
+ if (mysql_prepare_create_table(thd, create_info,
&tmp_alter_info,
(table->s->tmp_table != NO_TMP_TABLE),
&db_options,
@@ -6864,7 +7217,7 @@ view_err:
uint table_changes= IS_EQUAL_YES;
bool need_copy_table= TRUE;
/* Check how much the tables differ. */
- if (compare_tables(thd, table, new_db, alter_info,
+ if (compare_tables(thd, table, alter_info,
create_info, order_num,
&ha_alter_flags,
&ha_alter_info,
=== modified file 'sql/sql_trigger.cc'
--- a/sql/sql_trigger.cc 2008-06-11 11:49:58 +0000
+++ b/sql/sql_trigger.cc 2008-07-11 07:06:24 +0000
@@ -651,7 +651,7 @@ bool Table_triggers_list::create_trigger
NOTE: now we do not check privileges at CREATE TRIGGER time. This will
be changed in the future.
*/
- trg_field->setup_field(thd, table, NULL);
+ trg_field->setup_field(thd, table, NULL, NULL, NULL);
if (!trg_field->fixed &&
trg_field->fix_fields(thd, (Item **)0))
@@ -986,9 +986,15 @@ Table_triggers_list::~Table_triggers_lis
/**
- Prepare array of Field objects referencing to TABLE::record[1] instead
- of record[0] (they will represent OLD.* row values in ON UPDATE trigger
- and in ON DELETE trigger which will be called during REPLACE execution).
+ Prepare structures needed for executing triggers with references to
+ OLD.* and NEW.* pseudo-variables.
+
+ @note Prepares array of Field objects referencing to TABLE::record[1]
+ instead of record[0] (they will represent OLD.* row values in
+ ON UPDATE trigger and in ON DELETE trigger which will be called
+ during REPLACE execution).
+ @note Allocates memory and provides initial values for sets of table
+ columns to be read and updated by triggers via pseudo-variables.
@param table pointer to TABLE object for which we are creating fields.
@@ -997,9 +1003,11 @@ Table_triggers_list::~Table_triggers_lis
@retval
True error
*/
-bool Table_triggers_list::prepare_record1_accessors(TABLE *table)
+bool Table_triggers_list::prepare_for_trigger_fields(TABLE *table)
{
+ uchar *bitmap;
Field **fld, **old_fld;
+ int i;
if (!(record1_field= (Field **)alloc_root(&table->mem_root,
(table->s->fields + 1) *
@@ -1020,24 +1028,28 @@ bool Table_triggers_list::prepare_record
}
*old_fld= 0;
- return 0;
-}
-
-
-/**
- Adjust Table_triggers_list with new TABLE pointer.
-
- @param new_table new pointer to TABLE instance
-*/
+ /*
+ Allocate memory and provide initial state for sets of subject
+ table columns to be read and updated by triggers via OLD/NEW.*
+ pseudo-variables. Initialization of these sets will be finished
+ in Item_trigger_field::setup_field().
+ */
+ if (!(bitmap= (uchar*) alloc_root(&table->mem_root,
+ (table->s->column_bitmap_size * 2 *
+ TRG_EVENT_MAX))))
+ return 1;
-void Table_triggers_list::set_table(TABLE *new_table)
-{
- trigger_table= new_table;
- for (Field **field= new_table->triggers->record1_field ; *field ; field++)
+ for (i= 0; i < (int)TRG_EVENT_MAX; i++)
{
- (*field)->table= (*field)->orig_table= new_table;
- (*field)->table_name= &new_table->alias;
+ bitmap_init(&read_set[i], (my_bitmap_map*)bitmap, table->s->fields, FALSE);
+ bitmap_clear_all(&read_set[i]);
+ bitmap+= table->s->column_bitmap_size;
+ bitmap_init(&write_set[i], (my_bitmap_map*)bitmap, table->s->fields, FALSE);
+ bitmap_clear_all(&write_set[i]);
+ bitmap+= table->s->column_bitmap_size;
}
+
+ return 0;
}
@@ -1264,7 +1276,7 @@ bool Table_triggers_list::check_n_load(T
TODO: This could be avoided if there is no triggers
for UPDATE and DELETE.
*/
- if (!names_only && triggers->prepare_record1_accessors(table))
+ if (!names_only && triggers->prepare_for_trigger_fields(table))
DBUG_RETURN(1);
List_iterator_fast<ulonglong> itm(triggers->definition_modes_list);
@@ -1408,7 +1420,9 @@ bool Table_triggers_list::check_n_load(T
{
trg_field->setup_field(thd, table,
&triggers->subject_table_grants[lex.trg_chistics.event]
- [lex.trg_chistics.action_time]);
+ [lex.trg_chistics.action_time],
+ &triggers->read_set[lex.trg_chistics.event],
+ &triggers->write_set[lex.trg_chistics.event]);
}
lex_end(&lex);
@@ -1989,23 +2003,8 @@ bool Table_triggers_list::process_trigge
void Table_triggers_list::mark_fields_used(trg_event_type event)
{
- int action_time;
- Item_trigger_field *trg_field;
-
- for (action_time= 0; action_time < (int)TRG_ACTION_MAX; action_time++)
- {
- for (trg_field= trigger_fields[event][action_time]; trg_field;
- trg_field= trg_field->next_trg_field)
- {
- /* We cannot mark fields which does not present in table. */
- if (trg_field->field_idx != (uint)-1)
- {
- bitmap_set_bit(trigger_table->read_set, trg_field->field_idx);
- if (trg_field->get_settable_routine_parameter())
- bitmap_set_bit(trigger_table->write_set, trg_field->field_idx);
- }
- }
- }
+ bitmap_union(trigger_table->read_set, &read_set[event]);
+ bitmap_union(trigger_table->write_set, &write_set[event]);
trigger_table->file->column_bitmaps_signal();
}
=== modified file 'sql/sql_trigger.h'
--- a/sql/sql_trigger.h 2007-10-16 20:11:50 +0000
+++ b/sql/sql_trigger.h 2008-07-11 07:06:24 +0000
@@ -38,10 +38,16 @@ class Table_triggers_list: public Sql_al
/**
During execution of trigger new_field and old_field should point to the
array of fields representing new or old version of row correspondingly
- (so it can point to TABLE::field or to Tale_triggers_list::record1_field)
+ (so it can point to TABLE::field or to Table_triggers_list::record1_field)
*/
Field **new_field;
Field **old_field;
+ /**
+ Sets of subject table columns read and updated by triggers for
+ respective event types.
+ */
+ MY_BITMAP read_set[TRG_EVENT_MAX];
+ MY_BITMAP write_set[TRG_EVENT_MAX];
/* TABLE instance for which this triggers list object was created */
TABLE *trigger_table;
@@ -136,16 +142,19 @@ public:
bodies[TRG_EVENT_DELETE][TRG_ACTION_AFTER]);
}
- void set_table(TABLE *new_table);
-
void mark_fields_used(trg_event_type event);
+ const MY_BITMAP *get_update_triggers_write_set()
+ {
+ return &write_set[TRG_EVENT_UPDATE];
+ }
+
friend class Item_trigger_field;
friend int sp_cache_routines_and_add_tables_for_triggers(THD *thd, LEX *lex,
TABLE_LIST *table);
private:
- bool prepare_record1_accessors(TABLE *table);
+ bool prepare_for_trigger_fields(TABLE *table);
LEX_STRING* change_table_name_in_trignames(const char *db_name,
LEX_STRING *new_table_name,
LEX_STRING *stopper);
=== modified file 'sql/sql_yacc.yy'
--- a/sql/sql_yacc.yy 2008-06-27 05:11:56 +0000
+++ b/sql/sql_yacc.yy 2008-07-11 07:06:24 +0000
@@ -1825,6 +1825,7 @@ create:
lex->create_info.default_table_charset= NULL;
lex->name.str= 0;
lex->name.length= 0;
+ lex->create_foreign_key_tables= 0;
}
create2
{
@@ -4716,11 +4717,10 @@ create_field_list:
field_list
{
/*
- Until one of the later milestones of WL#148 "Foreign key" is
- implemented we should parent tables for foreign key constraints
- from the table lists.
+ Move parent tables for foreign key constraints to the separate
+ table list.
*/
- Lex->unlink_all_tables_except_first();
+ Lex->unlink_all_tables_except_first(&Lex->create_foreign_key_tables);
}
;
@@ -4761,6 +4761,7 @@ column_ref_constraint_def:
TL_IGNORE)))
YYABORT;
key= new (thd->mem_root) Foreign_key($1, null_lex_str, FALSE,
+ lex->query_tables,
lex->col_list,
table,
lex->ref_list,
@@ -4808,7 +4809,9 @@ key_def:
TL_IGNORE)))
YYABORT;
key= new (thd->mem_root) Foreign_key($1.str ? $1 : $4, $4,
- TRUE, lex->col_list,
+ TRUE,
+ lex->query_tables,
+ lex->col_list,
table,
lex->ref_list,
lex->fk_delete_opt,
=== modified file 'sql/table.cc'
--- a/sql/table.cc 2008-06-23 13:26:17 +0000
+++ b/sql/table.cc 2008-07-11 07:06:24 +0000
@@ -1663,6 +1663,9 @@ static int open_binary_frm(THD *thd, TAB
bitmap_init(&share->all_set, bitmaps, share->fields, FALSE);
bitmap_set_all(&share->all_set);
+ if (fk_init_column_sets(share))
+ goto err;
+
delete handler_file;
#ifndef DBUG_OFF
if (use_hash)
=== modified file 'sql/table.h'
--- a/sql/table.h 2008-06-23 13:26:17 +0000
+++ b/sql/table.h 2008-07-11 07:06:24 +0000
@@ -1327,6 +1327,15 @@ struct TABLE_LIST
*/
uint8 trg_event_map;
+ /**
+ In some cases when table list element represents table to be
+ updated we might know the list of columns which are going to
+ be updated. Two members below are indicator that such list
+ is known and the list itself.
+ */
+ bool cols_to_be_updated_known;
+ List<LEX_STRING> cols_to_be_updated;
+
uint i_s_requested_object;
bool has_db_lookup_value;
bool has_table_lookup_value;
| Thread |
|---|
| • bzr commit into mysql-6.1-fk branch (dlenev:2680) WL#148 | Dmitry Lenev | 11 Jul |