From b7224c0a99342ef8b608ea3f334307a5e5462c81 Mon Sep 17 00:00:00 2001 From: Karen Langford Date: Thu, 11 Aug 2011 15:31:25 +0200 Subject: [PATCH 01/19] Raise version number after cloning 5.1.59 --- configure.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.in b/configure.in index 0e336c5522b..e56a18540b5 100644 --- a/configure.in +++ b/configure.in @@ -12,7 +12,7 @@ dnl dnl When changing the major version number please also check the switch dnl statement in mysqlbinlog::check_master_version(). You may also need dnl to update version.c in ndb. -AC_INIT([MySQL Server], [5.1.59], [], [mysql]) +AC_INIT([MySQL Server], [5.1.60], [], [mysql]) AC_CONFIG_SRCDIR([sql/mysqld.cc]) AC_CANONICAL_SYSTEM From 138df47b4988aa59ddf3be74710c1537240b1ccc Mon Sep 17 00:00:00 2001 From: Georgi Kodinov Date: Wed, 17 Aug 2011 13:13:44 +0300 Subject: [PATCH 02/19] changed the old @sun.com address of security trees. --- .bzr-mysql/default.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.bzr-mysql/default.conf b/.bzr-mysql/default.conf index 4b2affc1529..3e92b5454d7 100644 --- a/.bzr-mysql/default.conf +++ b/.bzr-mysql/default.conf @@ -1,4 +1,4 @@ [MYSQL] -post_commit_to = "dbg_mysql_security@sun.com" -post_push_to = "dbg_mysql_security@sun.com" +post_commit_to = "MYSQL_COMMITS_SECURITY_WW@ORACLE.COM" +post_push_to = "MYSQL_COMMITS_SECURITY_WW@ORACLE.COM" tree_name = "mysql-5.0-security" From 0fca2269425df6a1e2224bb3185a76d3a2608fbf Mon Sep 17 00:00:00 2001 From: Mattias Jonsson Date: Tue, 23 Aug 2011 15:13:17 +0200 Subject: [PATCH 03/19] Bug#11766879/Bug#60106: DIFF BETWEEN # OF INDEXES IN MYSQL VS INNODB, PARTITONING, ON INDEX CREATE If the first partition succeeded in adding a index, but a successive partition failed, then the first partition had still the new index. The fix reverts the added indexes from previous partitions on failure. --- mysql-test/r/partition_innodb_plugin.result | 22 +++++++++++++++++ mysql-test/t/partition_innodb_plugin.test | 27 +++++++++++++++++++-- sql/ha_partition.cc | 20 ++++++++++++++- 3 files changed, 66 insertions(+), 3 deletions(-) diff --git a/mysql-test/r/partition_innodb_plugin.result b/mysql-test/r/partition_innodb_plugin.result index f6b5ce84338..081b7b94e1f 100644 --- a/mysql-test/r/partition_innodb_plugin.result +++ b/mysql-test/r/partition_innodb_plugin.result @@ -1,3 +1,25 @@ +# +# Bug#11766879/Bug#60106: DIFF BETWEEN # OF INDEXES IN MYSQL VS INNODB, +# PARTITONING, ON INDEX CREATE +# +CREATE TABLE t1 ( +id bigint NOT NULL AUTO_INCREMENT, +time date, +id2 bigint not null, +PRIMARY KEY (id,time) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 +/*!50100 PARTITION BY RANGE(TO_DAYS(time)) +(PARTITION p10 VALUES LESS THAN (734708) ENGINE = InnoDB, +PARTITION p20 VALUES LESS THAN MAXVALUE ENGINE = InnoDB) */; +INSERT INTO t1 (time,id2) VALUES ('2011-07-24',1); +INSERT INTO t1 (time,id2) VALUES ('2011-07-25',1); +INSERT INTO t1 (time,id2) VALUES ('2011-07-25',1); +CREATE UNIQUE INDEX uk_time_id2 on t1(time,id2); +ERROR 23000: Duplicate entry '2011-07-25-1' for key 'uk_time_id2' +SELECT COUNT(*) FROM t1; +COUNT(*) +3 +DROP TABLE t1; call mtr.add_suppression("nnoDB: Error: table `test`.`t1` .* Partition.* InnoDB internal"); # # Bug#55091: Server crashes on ADD PARTITION after a failed attempt diff --git a/mysql-test/t/partition_innodb_plugin.test b/mysql-test/t/partition_innodb_plugin.test index 626e5d19b99..efcf5c282a7 100644 --- a/mysql-test/t/partition_innodb_plugin.test +++ b/mysql-test/t/partition_innodb_plugin.test @@ -1,10 +1,33 @@ --source include/have_partition.inc --source include/have_innodb_plugin.inc -# Remove the line below when bug#53307 is solved. ---source include/not_valgrind.inc let $MYSQLD_DATADIR= `SELECT @@datadir`; +--echo # +--echo # Bug#11766879/Bug#60106: DIFF BETWEEN # OF INDEXES IN MYSQL VS INNODB, +--echo # PARTITONING, ON INDEX CREATE +--echo # +CREATE TABLE t1 ( + id bigint NOT NULL AUTO_INCREMENT, + time date, + id2 bigint not null, + PRIMARY KEY (id,time) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 +/*!50100 PARTITION BY RANGE(TO_DAYS(time)) +(PARTITION p10 VALUES LESS THAN (734708) ENGINE = InnoDB, + PARTITION p20 VALUES LESS THAN MAXVALUE ENGINE = InnoDB) */; + +INSERT INTO t1 (time,id2) VALUES ('2011-07-24',1); +INSERT INTO t1 (time,id2) VALUES ('2011-07-25',1); +INSERT INTO t1 (time,id2) VALUES ('2011-07-25',1); + +--error ER_DUP_ENTRY +CREATE UNIQUE INDEX uk_time_id2 on t1(time,id2); + +SELECT COUNT(*) FROM t1; + +DROP TABLE t1; + call mtr.add_suppression("nnoDB: Error: table `test`.`t1` .* Partition.* InnoDB internal"); --echo # --echo # Bug#55091: Server crashes on ADD PARTITION after a failed attempt diff --git a/sql/ha_partition.cc b/sql/ha_partition.cc index cd5d7f201a7..7c7cf5a4302 100644 --- a/sql/ha_partition.cc +++ b/sql/ha_partition.cc @@ -6490,7 +6490,25 @@ int ha_partition::add_index(TABLE *table_arg, KEY *key_info, uint num_of_keys) */ for (file= m_file; *file; file++) if ((ret= (*file)->add_index(table_arg, key_info, num_of_keys))) - break; + goto err; + return ret; +err: + if (file > m_file) + { + uint *key_numbers= (uint*) ha_thd()->alloc(sizeof(uint) * num_of_keys); + KEY *old_key_info= table_arg->key_info; + uint i; + /* Use the newly added key_info as table->key_info to remove them. */ + for (i= 0; i < num_of_keys; i++) + key_numbers[i]= i; + table_arg->key_info= key_info; + while (--file >= m_file) + { + (void) (*file)->prepare_drop_index(table_arg, key_numbers, num_of_keys); + (void) (*file)->final_drop_index(table_arg); + } + table_arg->key_info= old_key_info; + } return ret; } From 1922d65fd8881836b57ac925596dac4cb753e29b Mon Sep 17 00:00:00 2001 From: Alexander Nozdrin Date: Thu, 22 Sep 2011 18:31:16 +0400 Subject: [PATCH 04/19] Fix for Bug#13001491: MYSQL_REFRESH CRASHES WHEN STORED ROUTINES ARE RUN CONCURRENTLY. The main problem was that lex_start() was forgotten to be called before processing COM_REFRESH. Another problem discovered was that if failures to flush the error log were not properly handled, which resulted in the server crash. The user-visible effect of these problems were: - if COM_REFRESH command was sent after SQL-queries of some sort, the server would crash. - if COM_REFRESH was requested with REFRESH_LOG only, and the error log failed to flush, the server would crash. The error log fails to flush when it points to unavailable file (for example, due to restricted permissions). The fixes are: - call lex_start() in the beginning of COM_REFRESH; - handle failures to flush the error log properly, i.e. raise ER_UNKNOWN_ERROR. sql/sql_parse.cc: Fix for Bug#13001491: MYSQL_REFRESH CRASHES WHEN STORED ROUTINES ARE RUN CONCURRENTLY. tests/mysql_client_test.c: A test case for Bug#13001491: MYSQL_REFRESH CRASHES WHEN STORED ROUTINES ARE RUN CONCURRENTLY. --- sql/sql_parse.cc | 44 ++++++++++++++++++--- tests/mysql_client_test.c | 80 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+), 5 deletions(-) diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 2eeabc2f0c1..b5864fef19d 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -1491,6 +1491,14 @@ bool dispatch_command(enum enum_server_command command, THD *thd, case COM_REFRESH: { int not_used; + + /* + Initialize thd->lex since it's used in many base functions, such as + open_tables(). Otherwise, it remains unitialized and may cause crash + during execution of COM_REFRESH. + */ + lex_start(thd); + status_var_increment(thd->status_var.com_stat[SQLCOM_FLUSH]); ulong options= (ulong) (uchar) packet[0]; if (check_global_access(thd,RELOAD_ACL)) @@ -6986,7 +6994,14 @@ bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables, if (ha_flush_logs(NULL)) result=1; if (flush_error_log()) - result=1; + { + /* + When flush_error_log() failed, my_error() has not been called. + So, we have to do it here to keep the protocol. + */ + my_error(ER_UNKNOWN_ERROR, MYF(0)); + result= 1; + } } #ifdef HAVE_QUERY_CACHE if (options & REFRESH_QUERY_CACHE_FREE) @@ -7035,7 +7050,13 @@ bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables, return 1; // Killed if (close_cached_tables(thd, tables, FALSE, (options & REFRESH_FAST) ? FALSE : TRUE, TRUE)) - result= 1; + { + /* + NOTE: my_error() has been already called by reopen_tables() within + close_cached_tables(). + */ + result= 1; + } if (make_global_read_lock_block_commit(thd)) // Killed { @@ -7048,7 +7069,13 @@ bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables, { if (close_cached_tables(thd, tables, FALSE, (options & REFRESH_FAST) ? FALSE : TRUE, FALSE)) + { + /* + NOTE: my_error() has been already called by reopen_tables() within + close_cached_tables(). + */ result= 1; + } } my_dbopt_cleanup(); } @@ -7065,7 +7092,8 @@ bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables, tmp_write_to_binlog= 0; if (reset_master(thd)) { - result=1; + /* NOTE: my_error() has been already called by reset_master(). */ + result= 1; } } #endif @@ -7073,7 +7101,10 @@ bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables, if (options & REFRESH_DES_KEY_FILE) { if (des_key_file && load_des_key_file(des_key_file)) - result= 1; + { + /* NOTE: my_error() has been already called by load_des_key_file(). */ + result= 1; + } } #endif #ifdef HAVE_REPLICATION @@ -7082,7 +7113,10 @@ bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables, tmp_write_to_binlog= 0; pthread_mutex_lock(&LOCK_active_mi); if (reset_slave(thd, active_mi)) - result=1; + { + /* NOTE: my_error() has been already called by reset_slave(). */ + result= 1; + } pthread_mutex_unlock(&LOCK_active_mi); } #endif diff --git a/tests/mysql_client_test.c b/tests/mysql_client_test.c index 5880e734d32..2472a0b6df6 100644 --- a/tests/mysql_client_test.c +++ b/tests/mysql_client_test.c @@ -18513,6 +18513,85 @@ static void test_bug56976() DBUG_VOID_RETURN; } +/* + Bug#13001491: MYSQL_REFRESH CRASHES WHEN STORED ROUTINES ARE RUN CONCURRENTLY. +*/ +static void test_bug13001491() +{ + int rc; + char query[MAX_TEST_QUERY_LENGTH]; + MYSQL *c; + + myheader("test_bug13001491"); + + snprintf(query, MAX_TEST_QUERY_LENGTH, + "GRANT ALL PRIVILEGES ON *.* TO mysqltest_u1@%s", + opt_host ? opt_host : "'localhost'"); + + rc= mysql_query(mysql, query); + myquery(rc); + + snprintf(query, MAX_TEST_QUERY_LENGTH, + "GRANT RELOAD ON *.* TO mysqltest_u1@%s", + opt_host ? opt_host : "'localhost'"); + + rc= mysql_query(mysql, query); + myquery(rc); + + c= mysql_client_init(NULL); + + DIE_UNLESS(mysql_real_connect(c, opt_host, "mysqltest_u1", NULL, + current_db, opt_port, opt_unix_socket, + CLIENT_MULTI_STATEMENTS | + CLIENT_MULTI_RESULTS)); + + rc= mysql_query(c, "DROP PROCEDURE IF EXISTS p1"); + myquery(rc); + + rc= mysql_query(c, + "CREATE PROCEDURE p1() " + "BEGIN " + " DECLARE CONTINUE HANDLER FOR SQLEXCEPTION BEGIN END; " + " SELECT COUNT(*) " + " FROM INFORMATION_SCHEMA.PROCESSLIST " + " GROUP BY user " + " ORDER BY NULL " + " INTO @a; " + "END"); + myquery(rc); + + rc= mysql_query(c, "CALL p1()"); + myquery(rc); + + mysql_free_result(mysql_store_result(c)); + + /* Check that mysql_refresh() succeeds without REFRESH_LOG. */ + rc= mysql_refresh(c, REFRESH_GRANT | + REFRESH_TABLES | REFRESH_HOSTS | + REFRESH_STATUS | REFRESH_THREADS); + myquery(rc); + + /* + Check that mysql_refresh(REFRESH_LOG) does not crash the server even if it + fails. mysql_refresh(REFRESH_LOG) fails when error log points to unavailable + location. + */ + mysql_refresh(c, REFRESH_LOG); + + rc= mysql_query(c, "DROP PROCEDURE p1"); + myquery(rc); + + mysql_close(c); + c= NULL; + + snprintf(query, MAX_TEST_QUERY_LENGTH, + "DROP USER mysqltest_u1@%s", + opt_host ? opt_host : "'localhost'"); + + rc= mysql_query(mysql, query); + myquery(rc); +} + /* Read and parse arguments and MySQL options from my.cnf @@ -18842,6 +18921,7 @@ static struct my_tests_st my_tests[]= { { "test_bug47485", test_bug47485 }, { "test_bug58036", test_bug58036 }, { "test_bug56976", test_bug56976 }, + { "test_bug13001491", test_bug13001491 }, { 0, 0 } }; From 8932ae216674f08880a6f914b9651262037175be Mon Sep 17 00:00:00 2001 From: Tatjana Azundris Nuernberg Date: Thu, 29 Sep 2011 10:47:11 +0100 Subject: [PATCH 05/19] Bug#11765687 (MySQL58677): No privilege on table / view, but can know #rows / underlying table's name 1 - If a user had SHOW VIEW and SELECT privileges on a view and this view was referencing another view, EXPLAIN SELECT on the outer view (that the user had privileges on) could reveal the structure of the underlying "inner" view as well as the number of rows in the underlying tables, even if the user had privileges on none of these referenced objects. This happened because we used DEFINER's UID ("SUID") not just for the view given in EXPLAIN, but also when checking privileges on the underlying views (where we should use the UID of the EXPLAIN's INVOKER instead). We no longer run the EXPLAIN SUID (with DEFINER's privileges). This prevents a possible exploit and makes permissions more orthogonal. 2 - EXPLAIN SELECT would reveal a view's structure even if the user did not have SHOW VIEW privileges for that view, as long as they had SELECT privilege on the underlying tables. Instead of requiring both SHOW VIEW privilege on a view and SELECT privilege on all underlying tables, we were checking for presence of either of them. We now explicitly require SHOW VIEW and SELECT privileges on the view we run EXPLAIN SELECT on, as well as all its underlying views. We also require SELECT on all relevant tables. mysql-test/r/view_grant.result: add extensive tests to illustrate desired behavior and prevent regressions (as always). mysql-test/t/view_grant.test: add extensive tests to illustrate desired behavior and prevent regressions (as always). sql/sql_view.cc: We no longer run the EXPLAIN SUID (with DEFINER's privileges). To achieve this, we use a temporary, SUID-less TABLE_LIST for the views while checking privileges. --- mysql-test/r/view_grant.result | 224 +++++++++++++++++++- mysql-test/t/view_grant.test | 368 ++++++++++++++++++++++++++++++++- sql/sql_view.cc | 34 ++- 3 files changed, 608 insertions(+), 18 deletions(-) diff --git a/mysql-test/r/view_grant.result b/mysql-test/r/view_grant.result index e7a50451cec..1febb54fbf4 100644 --- a/mysql-test/r/view_grant.result +++ b/mysql-test/r/view_grant.result @@ -64,10 +64,12 @@ create view mysqltest.v1 (c,d) as select a+1,b+1 from mysqltest.t1; create algorithm=temptable view mysqltest.v2 (c,d) as select a+1,b+1 from mysqltest.t1; create view mysqltest.v3 (c,d) as select a+1,b+1 from mysqltest.t2; create algorithm=temptable view mysqltest.v4 (c,d) as select a+1,b+1 from mysqltest.t2; +create view mysqltest.v5 (c,d) as select a+1,b+1 from mysqltest.t1; grant select on mysqltest.v1 to mysqltest_1@localhost; grant select on mysqltest.v2 to mysqltest_1@localhost; grant select on mysqltest.v3 to mysqltest_1@localhost; grant select on mysqltest.v4 to mysqltest_1@localhost; +grant show view on mysqltest.v5 to mysqltest_1@localhost; select c from mysqltest.v1; c select c from mysqltest.v2; @@ -76,6 +78,8 @@ select c from mysqltest.v3; c select c from mysqltest.v4; c +select c from mysqltest.v5; +ERROR 42000: SELECT command denied to user 'mysqltest_1'@'localhost' for table 'v5' show columns from mysqltest.v1; Field Type Null Key Default Extra c bigint(12) YES NULL @@ -100,16 +104,25 @@ explain select c from mysqltest.v4; ERROR HY000: EXPLAIN/SHOW can not be issued; lacking privileges for underlying table show create view mysqltest.v4; ERROR 42000: SHOW VIEW command denied to user 'mysqltest_1'@'localhost' for table 'v4' +explain select c from mysqltest.v5; +ERROR 42000: SELECT command denied to user 'mysqltest_1'@'localhost' for table 'v5' +show create view mysqltest.v5; +View Create View +v5 CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `mysqltest`.`v5` AS select (`mysqltest`.`t1`.`a` + 1) AS `c`,(`mysqltest`.`t1`.`b` + 1) AS `d` from `mysqltest`.`t1` +explain select c from mysqltest.v1; +ERROR HY000: EXPLAIN/SHOW can not be issued; lacking privileges for underlying table +show create view mysqltest.v1; +ERROR 42000: SHOW VIEW command denied to user 'mysqltest_1'@'localhost' for table 'v1' +grant show view on mysqltest.v1 to mysqltest_1@localhost; grant select on mysqltest.t1 to mysqltest_1@localhost; explain select c from mysqltest.v1; id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE t1 system NULL NULL NULL NULL 0 const row not found show create view mysqltest.v1; -ERROR 42000: SHOW VIEW command denied to user 'mysqltest_1'@'localhost' for table 'v1' +View Create View +v1 CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `mysqltest`.`v1` AS select (`mysqltest`.`t1`.`a` + 1) AS `c`,(`mysqltest`.`t1`.`b` + 1) AS `d` from `mysqltest`.`t1` explain select c from mysqltest.v2; -id select_type table type possible_keys key key_len ref rows Extra -1 PRIMARY system NULL NULL NULL NULL 0 const row not found -2 DERIVED NULL NULL NULL NULL NULL NULL NULL no matching row in const table +ERROR HY000: EXPLAIN/SHOW can not be issued; lacking privileges for underlying table show create view mysqltest.v2; ERROR 42000: SHOW VIEW command denied to user 'mysqltest_1'@'localhost' for table 'v2' explain select c from mysqltest.v3; @@ -120,6 +133,11 @@ explain select c from mysqltest.v4; ERROR HY000: EXPLAIN/SHOW can not be issued; lacking privileges for underlying table show create view mysqltest.v4; ERROR 42000: SHOW VIEW command denied to user 'mysqltest_1'@'localhost' for table 'v4' +explain select c from mysqltest.v5; +ERROR 42000: SELECT command denied to user 'mysqltest_1'@'localhost' for table 'v5' +show create view mysqltest.v5; +View Create View +v5 CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `mysqltest`.`v5` AS select (`mysqltest`.`t1`.`a` + 1) AS `c`,(`mysqltest`.`t1`.`b` + 1) AS `d` from `mysqltest`.`t1` grant show view on mysqltest.* to mysqltest_1@localhost; explain select c from mysqltest.v1; id select_type table type possible_keys key key_len ref rows Extra @@ -135,15 +153,12 @@ show create view mysqltest.v2; View Create View v2 CREATE ALGORITHM=TEMPTABLE DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `mysqltest`.`v2` AS select (`mysqltest`.`t1`.`a` + 1) AS `c`,(`mysqltest`.`t1`.`b` + 1) AS `d` from `mysqltest`.`t1` explain select c from mysqltest.v3; -id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE t2 system NULL NULL NULL NULL 0 const row not found +ERROR HY000: EXPLAIN/SHOW can not be issued; lacking privileges for underlying table show create view mysqltest.v3; View Create View v3 CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `mysqltest`.`v3` AS select (`mysqltest`.`t2`.`a` + 1) AS `c`,(`mysqltest`.`t2`.`b` + 1) AS `d` from `mysqltest`.`t2` explain select c from mysqltest.v4; -id select_type table type possible_keys key key_len ref rows Extra -1 PRIMARY system NULL NULL NULL NULL 0 const row not found -2 DERIVED NULL NULL NULL NULL NULL NULL NULL no matching row in const table +ERROR HY000: EXPLAIN/SHOW can not be issued; lacking privileges for underlying table show create view mysqltest.v4; View Create View v4 CREATE ALGORITHM=TEMPTABLE DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `mysqltest`.`v4` AS select (`mysqltest`.`t2`.`a` + 1) AS `c`,(`mysqltest`.`t2`.`b` + 1) AS `d` from `mysqltest`.`t2` @@ -945,4 +960,195 @@ DROP USER foo; DROP VIEW db1.v1; DROP TABLE db1.t1; DROP DATABASE db1; +Bug #11765687/#58677: +No privilege on table/view, but can know #rows / underlying table's name +create database mysqltest1; +create table mysqltest1.t1 (i int); +create table mysqltest1.t2 (j int); +create table mysqltest1.t3 (k int, secret int); +create user alice@localhost; +create user bob@localhost; +create user cecil@localhost; +create user dan@localhost; +create user eugene@localhost; +create user fiona@localhost; +create user greg@localhost; +create user han@localhost; +create user inga@localhost; +create user jamie@localhost; +create user karl@localhost; +create user lena@localhost; +create user mhairi@localhost; +create user noam@localhost; +create user olga@localhost; +create user pjotr@localhost; +create user quintessa@localhost; +grant all privileges on mysqltest1.* to alice@localhost with grant option; +... as alice +create view v1 as select * from t1; +create view v2 as select * from v1, t2; +create view v3 as select k from t3; +grant select on mysqltest1.v1 to bob@localhost; +grant show view on mysqltest1.v1 to cecil@localhost; +grant select, show view on mysqltest1.v1 to dan@localhost; +grant select on mysqltest1.t1 to dan@localhost; +grant select on mysqltest1.* to eugene@localhost; +grant select, show view on mysqltest1.v2 to fiona@localhost; +grant select, show view on mysqltest1.v2 to greg@localhost; +grant show view on mysqltest1.v1 to greg@localhost; +grant select(k) on mysqltest1.t3 to han@localhost; +grant select, show view on mysqltest1.v3 to han@localhost; +grant select on mysqltest1.t1 to inga@localhost; +grant select on mysqltest1.t2 to inga@localhost; +grant select on mysqltest1.v1 to inga@localhost; +grant select, show view on mysqltest1.v2 to inga@localhost; +grant select on mysqltest1.t1 to jamie@localhost; +grant select on mysqltest1.t2 to jamie@localhost; +grant show view on mysqltest1.v1 to jamie@localhost; +grant select, show view on mysqltest1.v2 to jamie@localhost; +grant select on mysqltest1.t1 to karl@localhost; +grant select on mysqltest1.t2 to karl@localhost; +grant select, show view on mysqltest1.v1 to karl@localhost; +grant select on mysqltest1.v2 to karl@localhost; +grant select on mysqltest1.t1 to lena@localhost; +grant select on mysqltest1.t2 to lena@localhost; +grant select, show view on mysqltest1.v1 to lena@localhost; +grant show view on mysqltest1.v2 to lena@localhost; +grant select on mysqltest1.t1 to mhairi@localhost; +grant select on mysqltest1.t2 to mhairi@localhost; +grant select, show view on mysqltest1.v1 to mhairi@localhost; +grant select, show view on mysqltest1.v2 to mhairi@localhost; +grant select on mysqltest1.t1 to noam@localhost; +grant select, show view on mysqltest1.v1 to noam@localhost; +grant select, show view on mysqltest1.v2 to noam@localhost; +grant select on mysqltest1.t2 to olga@localhost; +grant select, show view on mysqltest1.v1 to olga@localhost; +grant select, show view on mysqltest1.v2 to olga@localhost; +grant select on mysqltest1.t1 to pjotr@localhost; +grant select on mysqltest1.t2 to pjotr@localhost; +grant select, show view on mysqltest1.v2 to pjotr@localhost; +grant select, show view on mysqltest1.v1 to quintessa@localhost; +... as bob +select * from v1; +i +explain select * from v1; +ERROR HY000: EXPLAIN/SHOW can not be issued; lacking privileges for underlying table +... as cecil +select * from v1; +ERROR 42000: SELECT command denied to user 'cecil'@'localhost' for table 'v1' +explain select * from v1; +ERROR 42000: SELECT command denied to user 'cecil'@'localhost' for table 'v1' +... as dan +select * from v1; +i +explain select * from v1; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 system NULL NULL NULL NULL 0 const row not found +... as eugene +select * from v1; +i +explain select * from v1; +ERROR HY000: EXPLAIN/SHOW can not be issued; lacking privileges for underlying table +... as fiona +select * from v2; +i j +show create view v2; +View Create View +v2 CREATE ALGORITHM=UNDEFINED DEFINER=`alice`@`localhost` SQL SECURITY DEFINER VIEW `v2` AS select `v1`.`i` AS `i`,`t2`.`j` AS `j` from (`v1` join `t2`) +explain select * from t1; +ERROR 42000: SELECT command denied to user 'fiona'@'localhost' for table 't1' +explain select * from v1; +ERROR 42000: SELECT command denied to user 'fiona'@'localhost' for table 'v1' +explain select * from t2; +ERROR 42000: SELECT command denied to user 'fiona'@'localhost' for table 't2' +explain select * from v2; +ERROR HY000: EXPLAIN/SHOW can not be issued; lacking privileges for underlying table +... as greg +select * from v2; +i j +explain select * from v1; +ERROR 42000: SELECT command denied to user 'greg'@'localhost' for table 'v1' +explain select * from v2; +ERROR HY000: EXPLAIN/SHOW can not be issued; lacking privileges for underlying table +... as han +select * from t3; +ERROR 42000: SELECT command denied to user 'han'@'localhost' for table 't3' +explain select * from t3; +ERROR 42000: SELECT command denied to user 'han'@'localhost' for table 't3' +select k from t3; +k +explain select k from t3; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t3 system NULL NULL NULL NULL 0 const row not found +select * from v3; +k +explain select * from v3; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t3 system NULL NULL NULL NULL 0 const row not found +... as inga +select * from v2; +i j +explain select * from v2; +ERROR HY000: EXPLAIN/SHOW can not be issued; lacking privileges for underlying table +... as jamie +select * from v2; +i j +explain select * from v2; +ERROR HY000: EXPLAIN/SHOW can not be issued; lacking privileges for underlying table +... as karl +select * from v2; +i j +explain select * from v2; +ERROR HY000: EXPLAIN/SHOW can not be issued; lacking privileges for underlying table +... as lena +select * from v2; +ERROR 42000: SELECT command denied to user 'lena'@'localhost' for table 'v2' +explain select * from v2; +ERROR 42000: SELECT command denied to user 'lena'@'localhost' for table 'v2' +... as mhairi +select * from v2; +i j +explain select * from v2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 system NULL NULL NULL NULL 0 const row not found +1 SIMPLE t2 system NULL NULL NULL NULL 0 const row not found +... as noam +select * from v2; +i j +explain select * from v2; +ERROR HY000: EXPLAIN/SHOW can not be issued; lacking privileges for underlying table +... as olga +select * from v2; +i j +explain select * from v2; +ERROR HY000: EXPLAIN/SHOW can not be issued; lacking privileges for underlying table +... as pjotr +select * from v2; +i j +explain select * from v2; +ERROR HY000: EXPLAIN/SHOW can not be issued; lacking privileges for underlying table +... as quintessa +select * from v1; +i +explain select * from v1; +ERROR HY000: EXPLAIN/SHOW can not be issued; lacking privileges for underlying table +... as root again at last: clean-up time! +drop user alice@localhost; +drop user bob@localhost; +drop user cecil@localhost; +drop user dan@localhost; +drop user eugene@localhost; +drop user fiona@localhost; +drop user greg@localhost; +drop user han@localhost; +drop user inga@localhost; +drop user jamie@localhost; +drop user karl@localhost; +drop user lena@localhost; +drop user mhairi@localhost; +drop user noam@localhost; +drop user olga@localhost; +drop user pjotr@localhost; +drop user quintessa@localhost; +drop database mysqltest1; End of 5.0 tests. diff --git a/mysql-test/t/view_grant.test b/mysql-test/t/view_grant.test index ff17cde5184..f06f0d58c8a 100644 --- a/mysql-test/t/view_grant.test +++ b/mysql-test/t/view_grant.test @@ -121,21 +121,26 @@ create view mysqltest.v1 (c,d) as select a+1,b+1 from mysqltest.t1; create algorithm=temptable view mysqltest.v2 (c,d) as select a+1,b+1 from mysqltest.t1; create view mysqltest.v3 (c,d) as select a+1,b+1 from mysqltest.t2; create algorithm=temptable view mysqltest.v4 (c,d) as select a+1,b+1 from mysqltest.t2; +# v5: SHOW VIEW, but no SELECT +create view mysqltest.v5 (c,d) as select a+1,b+1 from mysqltest.t1; grant select on mysqltest.v1 to mysqltest_1@localhost; grant select on mysqltest.v2 to mysqltest_1@localhost; grant select on mysqltest.v3 to mysqltest_1@localhost; grant select on mysqltest.v4 to mysqltest_1@localhost; +grant show view on mysqltest.v5 to mysqltest_1@localhost; connection user1; -# all selects works +# all SELECTs works, except v5 which lacks SELECT privs select c from mysqltest.v1; select c from mysqltest.v2; select c from mysqltest.v3; select c from mysqltest.v4; +--error ER_TABLEACCESS_DENIED_ERROR +select c from mysqltest.v5; # test of show coluns show columns from mysqltest.v1; show columns from mysqltest.v2; -# but explain/show do not +# explain/show fail --error ER_VIEW_NO_EXPLAIN explain select c from mysqltest.v1; --error ER_TABLEACCESS_DENIED_ERROR @@ -152,15 +157,26 @@ show create view mysqltest.v3; explain select c from mysqltest.v4; --error ER_TABLEACCESS_DENIED_ERROR show create view mysqltest.v4; +--error ER_TABLEACCESS_DENIED_ERROR +explain select c from mysqltest.v5; +show create view mysqltest.v5; -# allow to see one of underlying table -connection root; -grant select on mysqltest.t1 to mysqltest_1@localhost; -connection user1; -# EXPLAIN of view on above table works +# missing SELECT on underlying t1, no SHOW VIEW on v1 either. +--error ER_VIEW_NO_EXPLAIN explain select c from mysqltest.v1; +# missing SHOW VIEW --error ER_TABLEACCESS_DENIED_ERROR show create view mysqltest.v1; +# allow to see one of underlying table +connection root; +grant show view on mysqltest.v1 to mysqltest_1@localhost; +grant select on mysqltest.t1 to mysqltest_1@localhost; +connection user1; +# EXPLAIN works +explain select c from mysqltest.v1; +show create view mysqltest.v1; +# missing SHOW VIEW +--error ER_VIEW_NO_EXPLAIN explain select c from mysqltest.v2; --error ER_TABLEACCESS_DENIED_ERROR show create view mysqltest.v2; @@ -173,6 +189,11 @@ show create view mysqltest.v3; explain select c from mysqltest.v4; --error ER_TABLEACCESS_DENIED_ERROR show create view mysqltest.v4; +# we have SHOW VIEW on v5, and SELECT on t1 -- not enough +--error ER_TABLEACCESS_DENIED_ERROR +explain select c from mysqltest.v5; +# we can SHOW CREATE VIEW though +show create view mysqltest.v5; # allow to see any view in mysqltest database connection root; @@ -182,8 +203,12 @@ explain select c from mysqltest.v1; show create view mysqltest.v1; explain select c from mysqltest.v2; show create view mysqltest.v2; +# have SHOW VIEW | SELECT on v3, but no SELECT on t2 +--error ER_VIEW_NO_EXPLAIN explain select c from mysqltest.v3; show create view mysqltest.v3; +# have SHOW VIEW | SELECT on v4, but no SELECT on t2 +--error ER_VIEW_NO_EXPLAIN explain select c from mysqltest.v4; show create view mysqltest.v4; @@ -1232,7 +1257,336 @@ DROP TABLE db1.t1; DROP DATABASE db1; connection default; + + +--echo Bug #11765687/#58677: +--echo No privilege on table/view, but can know #rows / underlying table's name + +# As a root-like user +connect (root,localhost,root,,test); +connection root; + +create database mysqltest1; +create table mysqltest1.t1 (i int); +create table mysqltest1.t2 (j int); +create table mysqltest1.t3 (k int, secret int); + +create user alice@localhost; +create user bob@localhost; +create user cecil@localhost; +create user dan@localhost; +create user eugene@localhost; +create user fiona@localhost; +create user greg@localhost; +create user han@localhost; +create user inga@localhost; +create user jamie@localhost; +create user karl@localhost; +create user lena@localhost; +create user mhairi@localhost; +create user noam@localhost; +create user olga@localhost; +create user pjotr@localhost; +create user quintessa@localhost; + +grant all privileges on mysqltest1.* to alice@localhost with grant option; + +# +--echo ... as alice +connect (test11765687,localhost,alice,,mysqltest1); +connection test11765687; + +create view v1 as select * from t1; +create view v2 as select * from v1, t2; +create view v3 as select k from t3; + +grant select on mysqltest1.v1 to bob@localhost; + +grant show view on mysqltest1.v1 to cecil@localhost; + +grant select, show view on mysqltest1.v1 to dan@localhost; +grant select on mysqltest1.t1 to dan@localhost; + +grant select on mysqltest1.* to eugene@localhost; + +grant select, show view on mysqltest1.v2 to fiona@localhost; + +grant select, show view on mysqltest1.v2 to greg@localhost; +grant show view on mysqltest1.v1 to greg@localhost; + +grant select(k) on mysqltest1.t3 to han@localhost; +grant select, show view on mysqltest1.v3 to han@localhost; + +grant select on mysqltest1.t1 to inga@localhost; +grant select on mysqltest1.t2 to inga@localhost; +grant select on mysqltest1.v1 to inga@localhost; +grant select, show view on mysqltest1.v2 to inga@localhost; + +grant select on mysqltest1.t1 to jamie@localhost; +grant select on mysqltest1.t2 to jamie@localhost; +grant show view on mysqltest1.v1 to jamie@localhost; +grant select, show view on mysqltest1.v2 to jamie@localhost; + +grant select on mysqltest1.t1 to karl@localhost; +grant select on mysqltest1.t2 to karl@localhost; +grant select, show view on mysqltest1.v1 to karl@localhost; +grant select on mysqltest1.v2 to karl@localhost; + +grant select on mysqltest1.t1 to lena@localhost; +grant select on mysqltest1.t2 to lena@localhost; +grant select, show view on mysqltest1.v1 to lena@localhost; +grant show view on mysqltest1.v2 to lena@localhost; + +grant select on mysqltest1.t1 to mhairi@localhost; +grant select on mysqltest1.t2 to mhairi@localhost; +grant select, show view on mysqltest1.v1 to mhairi@localhost; +grant select, show view on mysqltest1.v2 to mhairi@localhost; + +grant select on mysqltest1.t1 to noam@localhost; +grant select, show view on mysqltest1.v1 to noam@localhost; +grant select, show view on mysqltest1.v2 to noam@localhost; + +grant select on mysqltest1.t2 to olga@localhost; +grant select, show view on mysqltest1.v1 to olga@localhost; +grant select, show view on mysqltest1.v2 to olga@localhost; + +grant select on mysqltest1.t1 to pjotr@localhost; +grant select on mysqltest1.t2 to pjotr@localhost; +grant select, show view on mysqltest1.v2 to pjotr@localhost; + +grant select, show view on mysqltest1.v1 to quintessa@localhost; + +disconnect test11765687; + +# +--echo ... as bob +connect (test11765687,localhost,bob,,mysqltest1); +connection test11765687; + +select * from v1; # Should succeed. +--error ER_VIEW_NO_EXPLAIN +explain select * from v1; # fail, no SHOW_VIEW + +disconnect test11765687; + +# +--echo ... as cecil +connect (test11765687,localhost,cecil,,mysqltest1); +connection test11765687; + +--error ER_TABLEACCESS_DENIED_ERROR +select * from v1; # fail, no SELECT +--error ER_TABLEACCESS_DENIED_ERROR +explain select * from v1; # fail, no SELECT + +disconnect test11765687; + +# +--echo ... as dan +connect (test11765687,localhost,dan,,mysqltest1); +connection test11765687; + +select * from v1; # Should succeed. +explain select * from v1; # Should succeed. + +disconnect test11765687; + +# +--echo ... as eugene +connect (test11765687,localhost,eugene,,mysqltest1); +connection test11765687; + +select * from v1; # Should succeed. +--error ER_VIEW_NO_EXPLAIN +explain select * from v1; # fail, no SHOW_VIEW + +disconnect test11765687; + +# +--echo ... as fiona +connect (test11765687,localhost,fiona,,mysqltest1); +connection test11765687; + +select * from v2; # Should succeed. +show create view v2; # Should succeed, but... +--error ER_TABLEACCESS_DENIED_ERROR +explain select * from t1; # fail, shouldn't see t1! +--error ER_TABLEACCESS_DENIED_ERROR +# err msg must give view name, no table names!! +explain select * from v1; # fail, have no privs on v1! +--error ER_TABLEACCESS_DENIED_ERROR +explain select * from t2; # fail, have no privs on t2! +--error ER_VIEW_NO_EXPLAIN +explain select * from v2; # fail, shouldn't see t2! + +disconnect test11765687; + +# +--echo ... as greg +connect (test11765687,localhost,greg,,mysqltest1); +connection test11765687; + +select * from v2; # Should succeed. +--error ER_TABLEACCESS_DENIED_ERROR +explain select * from v1; # fail; no SELECT on v1! +--error ER_VIEW_NO_EXPLAIN +explain select * from v2; # fail; no SELECT on v1! + +disconnect test11765687; + +# +--echo ... as han +connect (test11765687,localhost,han,,mysqltest1); +connection test11765687; + +--error ER_TABLEACCESS_DENIED_ERROR +select * from t3; # don't have privs on all columns, +--error ER_TABLEACCESS_DENIED_ERROR +explain select * from t3; # so EXPLAIN on "forbidden" columns should fail. +select k from t3; # but we do have SELECT on column k though, +explain select k from t3; # so EXPLAIN just on k should work, +select * from v3; # and so should SELECT on view only using allowed columns +explain select * from v3; # as should the associated EXPLAIN + +disconnect test11765687; + +# +--echo ... as inga +connect (test11765687,localhost,inga,,mysqltest1); +connection test11765687; + +select * from v2; +# has sel/show on v2, sel on t1/t2, only sel v1 +# fail: lacks show on v1 +--error ER_VIEW_NO_EXPLAIN +explain select * from v2; +disconnect test11765687; + +# +--echo ... as jamie +connect (test11765687,localhost,jamie,,mysqltest1); +connection test11765687; + +select * from v2; +# has sel/show on v2, sel on t1/t2, only show v1 +# fail: lacks sel on v1 +--error ER_VIEW_NO_EXPLAIN +explain select * from v2; +disconnect test11765687; + +# +--echo ... as karl +connect (test11765687,localhost,karl,,mysqltest1); +connection test11765687; + +select * from v2; +# has sel only on v2, sel on t1/t2, sel/show v1 +# fail: lacks show on v2 +--error ER_VIEW_NO_EXPLAIN +explain select * from v2; +disconnect test11765687; + +# +--echo ... as lena + +connect (test11765687,localhost,lena,,mysqltest1); +connection test11765687; +--error ER_TABLEACCESS_DENIED_ERROR +select * from v2; +# has show only on v2, sel on t1/t2, sel/show v1 +# fail: lacks sel on v2 +--error ER_TABLEACCESS_DENIED_ERROR +explain select * from v2; +disconnect test11765687; + +# +--echo ... as mhairi +connect (test11765687,localhost,mhairi,,mysqltest1); +connection test11765687; + +select * from v2; +# has sel/show on v2, sel on t1/t2, sel/show v1 +explain select * from v2; +disconnect test11765687; + +# +--echo ... as noam +connect (test11765687,localhost,noam,,mysqltest1); +connection test11765687; + +select * from v2; +# has sel/show on v2, sel only on t1, sel/show v1 (no sel on t2!) +--error ER_VIEW_NO_EXPLAIN +explain select * from v2; +disconnect test11765687; + +# +--echo ... as olga +connect (test11765687,localhost,olga,,mysqltest1); +connection test11765687; + +select * from v2; +# has sel/show on v2, sel only on t2, sel/show v1 (no sel on t1!) +--error ER_VIEW_NO_EXPLAIN +explain select * from v2; +disconnect test11765687; + +# +--echo ... as pjotr +connect (test11765687,localhost,pjotr,,mysqltest1); +connection test11765687; + +select * from v2; +# has sel/show on v2, sel only on t2, nothing on v1 +# fail: lacks show on v1 +--error ER_VIEW_NO_EXPLAIN +explain select * from v2; +disconnect test11765687; + +# +--echo ... as quintessa +connect (test11765687,localhost,quintessa,,mysqltest1); +connection test11765687; + +select * from v1; # Should succeed. +--error ER_VIEW_NO_EXPLAIN +explain select * from v1; # fail: lacks select on t1 + +disconnect test11765687; + +# cleanup + +# +--echo ... as root again at last: clean-up time! +connection root; + +drop user alice@localhost; +drop user bob@localhost; +drop user cecil@localhost; +drop user dan@localhost; +drop user eugene@localhost; +drop user fiona@localhost; +drop user greg@localhost; +drop user han@localhost; +drop user inga@localhost; +drop user jamie@localhost; +drop user karl@localhost; +drop user lena@localhost; +drop user mhairi@localhost; +drop user noam@localhost; +drop user olga@localhost; +drop user pjotr@localhost; +drop user quintessa@localhost; + +drop database mysqltest1; + +disconnect root; + +connection default; + --echo End of 5.0 tests. + + # Wait till we reached the initial number of concurrent sessions --source include/wait_until_count_sessions.inc diff --git a/sql/sql_view.cc b/sql/sql_view.cc index 07580663f24..c318f79eec1 100644 --- a/sql/sql_view.cc +++ b/sql/sql_view.cc @@ -1148,8 +1148,38 @@ bool mysql_make_view(THD *thd, File_parser *parser, TABLE_LIST *table, if (!table->prelocking_placeholder && (old_lex->sql_command == SQLCOM_SELECT && old_lex->describe)) { - if (check_table_access(thd, SELECT_ACL, view_tables, 1) && - check_table_access(thd, SHOW_VIEW_ACL, table, 1)) + /* + The user we run EXPLAIN as (either the connected user who issued + the EXPLAIN statement, or the definer of a SUID stored routine + which contains the EXPLAIN) should have both SHOW_VIEW_ACL and + SELECT_ACL on the view being opened as well as on all underlying + views since EXPLAIN will disclose their structure. This user also + should have SELECT_ACL on all underlying tables of the view since + this EXPLAIN will disclose information about the number of rows in it. + + To perform this privilege check we create auxiliary TABLE_LIST object + for the view in order a) to avoid trashing "table->grant" member for + original table list element, which contents can be important at later + stage for column-level privilege checking b) get TABLE_LIST object + with "security_ctx" member set to 0, i.e. forcing check_table_access() + to use active user's security context. + + There is no need for creating similar copies of table list elements + for underlying tables since they are just have been constructed and + thus have TABLE_LIST::security_ctx == 0 and fresh TABLE_LIST::grant + member. + + Finally at this point making sure we have SHOW_VIEW_ACL on the views + will suffice as we implicitly require SELECT_ACL anyway. + */ + + TABLE_LIST view; + bzero((char *)&view, sizeof(TABLE_LIST)); + view.db= table->db; + view.table_name= table->table_name; + + if (check_table_access(thd, SELECT_ACL, view_tables, 1) || + check_table_access(thd, SHOW_VIEW_ACL, &view, 1)) { my_message(ER_VIEW_NO_EXPLAIN, ER(ER_VIEW_NO_EXPLAIN), MYF(0)); goto err; From d740f9e603378c2ba9bbfacd111f6fe9c6728b78 Mon Sep 17 00:00:00 2001 From: Tatjana Azundris Nuernberg Date: Thu, 6 Oct 2011 11:23:46 +0100 Subject: [PATCH 06/19] additional clean-up for 11765687 --- sql/sql_view.cc | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/sql/sql_view.cc b/sql/sql_view.cc index c318f79eec1..3ae35e5cfe0 100644 --- a/sql/sql_view.cc +++ b/sql/sql_view.cc @@ -1164,22 +1164,23 @@ bool mysql_make_view(THD *thd, File_parser *parser, TABLE_LIST *table, with "security_ctx" member set to 0, i.e. forcing check_table_access() to use active user's security context. - There is no need for creating similar copies of table list elements - for underlying tables since they are just have been constructed and - thus have TABLE_LIST::security_ctx == 0 and fresh TABLE_LIST::grant - member. + There is no need for creating similar copies of TABLE_LIST elements + for underlying tables since they just have been constructed and thus + have TABLE_LIST::security_ctx == 0 and fresh TABLE_LIST::grant member. Finally at this point making sure we have SHOW_VIEW_ACL on the views will suffice as we implicitly require SELECT_ACL anyway. */ - TABLE_LIST view; - bzero((char *)&view, sizeof(TABLE_LIST)); - view.db= table->db; - view.table_name= table->table_name; + TABLE_LIST view_no_suid; + bzero(static_cast(&view_no_suid), sizeof(TABLE_LIST)); + view_no_suid.db= table->db; + view_no_suid.table_name= table->table_name; + + DBUG_ASSERT(view_tables == NULL || view_tables->security_ctx == NULL); if (check_table_access(thd, SELECT_ACL, view_tables, 1) || - check_table_access(thd, SHOW_VIEW_ACL, &view, 1)) + check_table_access(thd, SHOW_VIEW_ACL, &view_no_suid, 1)) { my_message(ER_VIEW_NO_EXPLAIN, ER(ER_VIEW_NO_EXPLAIN), MYF(0)); goto err; From f36e854ac6ce19e7018addbb8701796006a27134 Mon Sep 17 00:00:00 2001 From: Magne Mahre Date: Fri, 7 Oct 2011 14:08:31 +0200 Subject: [PATCH 07/19] BUG#12589870 CRASHES WITH MULTIQUERY PACKET + USE + QUERY CACHE A buffer large enough to hold the query _plus_ some additional data is allocated before parsing is started. The additional data is used by the query cache, and consists of the name of the current database and a set of flags. When a packet containing multiple SQL statements is sent to the server and one of the statements changes the current database (a "USE " statement), and the name of the new current database is longer than of the previous, there is not enough space in the buffer for the new name, and we write out over the buffer boundary. The fix adds an extra field to store the number of bytes allocated to the database name in the buffer. If the current database name changes, and the new name is longer than the previous one, we refuse to cache the query. --- sql/sp_head.cc | 12 +++++++++++- sql/sql_cache.cc | 33 ++++++++++++++++++++++++++++----- sql/sql_parse.cc | 21 +++++++++++++++++++-- 3 files changed, 58 insertions(+), 8 deletions(-) diff --git a/sql/sp_head.cc b/sql/sp_head.cc index 95a7eda0540..70ffb0a36ab 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -1029,12 +1029,22 @@ subst_spvars(THD *thd, sp_instr *instr, LEX_STRING *query_str) /* Allocate additional space at the end of the new query string for the query_cache_send_result_to_client function. + + The query buffer layout is: + buffer :== + The input statement(s) + '\0' Terminating null char + Length of following current database name (size_t) + Name of current database + Flags struct */ - buf_len= qbuf.length() + thd->db_length + 1 + QUERY_CACHE_FLAGS_SIZE + 1; + buf_len= qbuf.length() + 1 + sizeof(size_t) + thd->db_length + + QUERY_CACHE_FLAGS_SIZE + 1; if ((pbuf= (char *) alloc_root(thd->mem_root, buf_len))) { memcpy(pbuf, qbuf.ptr(), qbuf.length()); pbuf[qbuf.length()]= 0; + *(size_t *)(pbuf+qbuf.length()+1)= thd->db_length; } else DBUG_RETURN(TRUE); diff --git a/sql/sql_cache.cc b/sql/sql_cache.cc index b791428eef0..4800fdedbe5 100644 --- a/sql/sql_cache.cc +++ b/sql/sql_cache.cc @@ -1241,8 +1241,8 @@ def_week_frmt: %lu, in_trans: %d, autocommit: %d", /* Key is query + database + flag */ if (thd->db_length) { - memcpy(thd->query() + thd->query_length() + 1, thd->db, - thd->db_length); + memcpy(thd->query() + thd->query_length() + 1 + sizeof(size_t), + thd->db, thd->db_length); DBUG_PRINT("qcache", ("database: %s length: %u", thd->db, (unsigned) thd->db_length)); } @@ -1251,7 +1251,7 @@ def_week_frmt: %lu, in_trans: %d, autocommit: %d", DBUG_PRINT("qcache", ("No active database")); } tot_length= thd->query_length() + thd->db_length + 1 + - QUERY_CACHE_FLAGS_SIZE; + sizeof(size_t) + QUERY_CACHE_FLAGS_SIZE; /* We should only copy structure (don't use it location directly) because of alignment issue @@ -1462,7 +1462,28 @@ Query_cache::send_result_to_client(THD *thd, char *sql, uint query_length) goto err; } } + { + /* + We have allocated buffer space (in alloc_query) to hold the + SQL statement(s) + the current database name + a flags struct. + If the database name has changed during execution, which might + happen if there are multiple statements, we need to make + sure the new current database has a name with the same length + as the previous one. + */ + size_t *db_len= (size_t *) (sql + query_length + 1); + if (thd->db_length != *db_len) + { + /* + We should probably reallocate the buffer in this case, + but for now we just leave it uncached + */ + DBUG_PRINT("qcache", + ("Current database has changed since start of query")); + goto err; + } + } /* Try to obtain an exclusive lock on the query cache. If the cache is disabled or if a full cache flush is in progress, the attempt to @@ -1484,10 +1505,12 @@ Query_cache::send_result_to_client(THD *thd, char *sql, uint query_length) Query_cache_block *query_block; - tot_length= query_length + thd->db_length + 1 + QUERY_CACHE_FLAGS_SIZE; + tot_length= query_length + 1 + sizeof(size_t) + + thd->db_length + QUERY_CACHE_FLAGS_SIZE; + if (thd->db_length) { - memcpy(sql+query_length+1, thd->db, thd->db_length); + memcpy(sql + query_length + 1 + sizeof(size_t), thd->db, thd->db_length); DBUG_PRINT("qcache", ("database: '%s' length: %u", thd->db, (unsigned)thd->db_length)); } diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index b5864fef19d..55509a31f77 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -1912,13 +1912,30 @@ bool alloc_query(THD *thd, const char *packet, uint packet_length) pos--; packet_length--; } - /* We must allocate some extra memory for query cache */ + /* We must allocate some extra memory for query cache + + The query buffer layout is: + buffer :== + The input statement(s) + '\0' Terminating null char (1 byte) + Length of following current database name (size_t) + Name of current database + Flags struct + */ if (! (query= (char*) thd->memdup_w_gap(packet, packet_length, - 1 + thd->db_length + + 1 + sizeof(size_t) + thd->db_length + QUERY_CACHE_FLAGS_SIZE))) return TRUE; query[packet_length]= '\0'; + /* + Space to hold the name of the current database is allocated. We + also store this length, in case current database is changed during + execution. We might need to reallocate the 'query' buffer + */ + size_t *len = (size_t *) (query + packet_length + 1); + *len= thd->db_length; + thd->set_query(query, packet_length); /* Reclaim some memory */ From a6145f4b62bd264ad32d2dade98eda0cc6de0dba Mon Sep 17 00:00:00 2001 From: Tor Didriksen Date: Fri, 14 Oct 2011 10:09:53 +0200 Subject: [PATCH 08/19] Bug#12563865 ROUNDED,TMP_BUF,DECIMAL_VALUE STACK CORRUPTION IN ALL VERSIONS >=5.0 Buffer over-run on all platforms, crash on windows, wrong result on other platforms, when rounding numbers which start with 999999999 and have precision = 9 or 18 or 27 or 36 ... mysql-test/r/type_newdecimal.result: New test cases. mysql-test/t/type_newdecimal.test: New test cases. sql/my_decimal.h: Add sanity checking code, to catch buffer over/under-run. strings/decimal.c: The original initialization of intg1 (add 1 if buf[0] == DIG_MAX) will set p1 to point outside the buffer, and the loop to copy the original value while (buf0 < p0) *(--p1) = *(--p0); will overwrite memory outside the my_decimal object. --- mysql-test/r/type_newdecimal.result | 44 +++++++++++++++++++++++++++++ mysql-test/t/type_newdecimal.test | 21 ++++++++++++++ sql/my_decimal.h | 30 ++++++++++++++++++++ strings/decimal.c | 21 ++++++++------ 4 files changed, 108 insertions(+), 8 deletions(-) diff --git a/mysql-test/r/type_newdecimal.result b/mysql-test/r/type_newdecimal.result index 90b6f524692..43caaa2239b 100644 --- a/mysql-test/r/type_newdecimal.result +++ b/mysql-test/r/type_newdecimal.result @@ -1539,4 +1539,48 @@ select * from t1; 5.05 / 0.014 360.714286 DROP TABLE t1; +# +# Bug#12563865 +# ROUNDED,TMP_BUF,DECIMAL_VALUE STACK CORRUPTION IN ALL VERSIONS >=5.0 +# +SELECT substring(('M') FROM (999999999999999999999999999999999999999999999999999999999999999999999999999999999)) AS foo; +foo + +Warnings: +Error 1292 Truncated incorrect DECIMAL value: '' +Error 1292 Truncated incorrect DECIMAL value: '' +SELECT min(999999999999999999999999999999999999999999999999999999999999999999999999999999999) AS foo; +foo +999999999999999999999999999999999999999999999999999999999999999999999999999999999 +SELECT multipolygonfromtext(('4294967294.1'),(999999999999999999999999999999999999999999999999999999999999999999999999999999999)) AS foo; +foo +NULL +Warnings: +Error 1292 Truncated incorrect DECIMAL value: '' +SELECT convert((999999999999999999999999999999999999999999999999999999999999999999999999999999999), decimal(30,30)) AS foo; +foo +0.999999999999999999999999999999 +Warnings: +Error 1264 Out of range value adjusted for column 'foo' at row 1 +SELECT bit_xor(999999999999999999999999999999999999999999999999999999999999999999999999999999999) AS foo; +foo +9223372036854775807 +Warnings: +Error 1292 Truncated incorrect DECIMAL value: '' +SELECT -(999999999999999999999999999999999999999999999999999999999999999999999999999999999) AS foo; +foo +-999999999999999999999999999999999999999999999999999999999999999999999999999999999 +SELECT date_sub((999999999999999999999999999999999999999999999999999999999999999999999999999999999), +interval ((SELECT date_add((0x77500000), +interval ('Oml') second))) +day_minute) +AS foo; +foo +NULL +Warnings: +Error 1292 Truncated incorrect DECIMAL value: '' +Warning 1292 Truncated incorrect datetime value: '9223372036854775807' +SELECT truncate(999999999999999999999999999999999999999999999999999999999999999999999999999999999, 28) AS foo; +foo +999999999999999999999999999999999999999999999999999999999999999999999999999999999 End of 5.0 tests diff --git a/mysql-test/t/type_newdecimal.test b/mysql-test/t/type_newdecimal.test index a5331582df6..a55951caf63 100644 --- a/mysql-test/t/type_newdecimal.test +++ b/mysql-test/t/type_newdecimal.test @@ -1235,4 +1235,25 @@ show create table t1; select * from t1; DROP TABLE t1; +--echo # +--echo # Bug#12563865 +--echo # ROUNDED,TMP_BUF,DECIMAL_VALUE STACK CORRUPTION IN ALL VERSIONS >=5.0 +--echo # + +let $nine_81= +999999999999999999999999999999999999999999999999999999999999999999999999999999999; + +eval SELECT substring(('M') FROM ($nine_81)) AS foo; +eval SELECT min($nine_81) AS foo; +eval SELECT multipolygonfromtext(('4294967294.1'),($nine_81)) AS foo; +eval SELECT convert(($nine_81), decimal(30,30)) AS foo; +eval SELECT bit_xor($nine_81) AS foo; +eval SELECT -($nine_81) AS foo; +eval SELECT date_sub(($nine_81), + interval ((SELECT date_add((0x77500000), + interval ('Oml') second))) + day_minute) +AS foo; +eval SELECT truncate($nine_81, 28) AS foo; + --echo End of 5.0 tests diff --git a/sql/my_decimal.h b/sql/my_decimal.h index 6a0d05921ec..ee023438f20 100644 --- a/sql/my_decimal.h +++ b/sql/my_decimal.h @@ -86,12 +86,31 @@ inline int my_decimal_int_part(uint precision, uint decimals) class my_decimal :public decimal_t { + /* + Several of the routines in strings/decimal.c have had buffer + overrun/underrun problems. These are *not* caught by valgrind. + To catch them, we allocate dummy fields around the buffer, + and test that their values do not change. + */ +#if !defined(DBUG_OFF) + int foo1; +#endif + decimal_digit_t buffer[DECIMAL_BUFF_LENGTH]; +#if !defined(DBUG_OFF) + int foo2; + static const int test_value= 123; +#endif + public: void init() { +#if !defined(DBUG_OFF) + foo1= test_value; + foo2= test_value; +#endif len= DECIMAL_BUFF_LENGTH; buf= buffer; #if !defined (HAVE_purify) && !defined(DBUG_OFF) @@ -104,6 +123,17 @@ public: { init(); } + ~my_decimal() + { + sanity_check(); + } + + void sanity_check() + { + DBUG_ASSERT(foo1 == test_value); + DBUG_ASSERT(foo2 == test_value); + } + void fix_buffer_pointer() { buf= buffer; } bool sign() const { return decimal_t::sign; } diff --git a/strings/decimal.c b/strings/decimal.c index 1498aec15a1..87faff9b4cd 100644 --- a/strings/decimal.c +++ b/strings/decimal.c @@ -1480,9 +1480,8 @@ decimal_round(decimal_t *from, decimal_t *to, int scale, { int frac0=scale>0 ? ROUND_UP(scale) : scale/DIG_PER_DEC1, frac1=ROUND_UP(from->frac), UNINIT_VAR(round_digit), - intg0=ROUND_UP(from->intg), error=E_DEC_OK, len=to->len, - intg1=ROUND_UP(from->intg + - (((intg0 + frac0)>0) && (from->buf[0] == DIG_MAX))); + intg0=ROUND_UP(from->intg), error=E_DEC_OK, len=to->len; + dec1 *buf0=from->buf, *buf1=to->buf, x, y, carry=0; int first_dig; @@ -1497,6 +1496,12 @@ decimal_round(decimal_t *from, decimal_t *to, int scale, default: DBUG_ASSERT(0); } + /* + For my_decimal we always use len == DECIMAL_BUFF_LENGTH == 9 + For internal testing here (ifdef MAIN) we always use len == 100/4 + */ + DBUG_ASSERT(from->len == to->len); + if (unlikely(frac0+intg0 > len)) { frac0=len-intg0; @@ -1510,17 +1515,17 @@ decimal_round(decimal_t *from, decimal_t *to, int scale, return E_DEC_OK; } - if (to != from || intg1>intg0) + if (to != from) { dec1 *p0= buf0+intg0+max(frac1, frac0); - dec1 *p1= buf1+intg1+max(frac1, frac0); + dec1 *p1= buf1+intg0+max(frac1, frac0); + + DBUG_ASSERT(p0 - buf0 <= len); + DBUG_ASSERT(p1 - buf1 <= len); while (buf0 < p0) *(--p1) = *(--p0); - if (unlikely(intg1 > intg0)) - to->buf[0]= 0; - intg0= intg1; buf0=to->buf; buf1=to->buf; to->sign=from->sign; From 8444b6a1149aad457f8d584e0d410c3be9cd4336 Mon Sep 17 00:00:00 2001 From: Tatjana Azundris Nuernberg Date: Wed, 19 Oct 2011 03:21:31 +0100 Subject: [PATCH 09/19] Bug12589870 post-merge fixes for Sparc64 and friends sql/sp_head.cc: alignment-safe copy sql/sql_cache.cc: alignment-safe copy sql/sql_parse.cc: alignment-safe copy --- sql/sp_head.cc | 2 +- sql/sql_cache.cc | 5 +++-- sql/sql_parse.cc | 6 ++++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/sql/sp_head.cc b/sql/sp_head.cc index 70ffb0a36ab..8bea0be0f56 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -1044,7 +1044,7 @@ subst_spvars(THD *thd, sp_instr *instr, LEX_STRING *query_str) { memcpy(pbuf, qbuf.ptr(), qbuf.length()); pbuf[qbuf.length()]= 0; - *(size_t *)(pbuf+qbuf.length()+1)= thd->db_length; + memcpy(pbuf+qbuf.length()+1, (char *) &thd->db_length, sizeof(size_t)); } else DBUG_RETURN(TRUE); diff --git a/sql/sql_cache.cc b/sql/sql_cache.cc index 4800fdedbe5..8a6d4bf9802 100644 --- a/sql/sql_cache.cc +++ b/sql/sql_cache.cc @@ -1471,8 +1471,9 @@ Query_cache::send_result_to_client(THD *thd, char *sql, uint query_length) sure the new current database has a name with the same length as the previous one. */ - size_t *db_len= (size_t *) (sql + query_length + 1); - if (thd->db_length != *db_len) + size_t db_len; + memcpy((char *) &db_len, (sql + query_length + 1), sizeof(size_t)); + if (thd->db_length != db_len) { /* We should probably reallocate the buffer in this case, diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index a88cc2fa8e5..989c3e0f42f 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -492,6 +492,8 @@ static void handle_bootstrap_impl(THD *thd) query= (char *) thd->memdup_w_gap(buff, length + 1, thd->db_length + 1 + QUERY_CACHE_FLAGS_SIZE); + size_t db_len= 0; + memcpy(query + length + 1, (char *) &db_len, sizeof(size_t)); thd->set_query(query, length); DBUG_PRINT("query",("%-.4096s", thd->query())); #if defined(ENABLED_PROFILING) && defined(COMMUNITY_SERVER) @@ -1933,8 +1935,8 @@ bool alloc_query(THD *thd, const char *packet, uint packet_length) also store this length, in case current database is changed during execution. We might need to reallocate the 'query' buffer */ - size_t *len = (size_t *) (query + packet_length + 1); - *len= thd->db_length; + char *len_pos = (query + packet_length + 1); + memcpy(len_pos, (char *) &thd->db_length, sizeof(size_t)); thd->set_query(query, packet_length); From ca881510f21b9b0787079ec478d563cf52d38264 Mon Sep 17 00:00:00 2001 From: Alexander Nozdrin Date: Thu, 20 Oct 2011 18:06:41 +0400 Subject: [PATCH 10/19] Fix Windows-build failure: use my_snprintf() instead of snprintf(). --- tests/mysql_client_test.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/mysql_client_test.c b/tests/mysql_client_test.c index 2472a0b6df6..f553eb530ae 100644 --- a/tests/mysql_client_test.c +++ b/tests/mysql_client_test.c @@ -18524,14 +18524,14 @@ static void test_bug13001491() myheader("test_bug13001491"); - snprintf(query, MAX_TEST_QUERY_LENGTH, + my_snprintf(query, MAX_TEST_QUERY_LENGTH, "GRANT ALL PRIVILEGES ON *.* TO mysqltest_u1@%s", opt_host ? opt_host : "'localhost'"); rc= mysql_query(mysql, query); myquery(rc); - snprintf(query, MAX_TEST_QUERY_LENGTH, + my_snprintf(query, MAX_TEST_QUERY_LENGTH, "GRANT RELOAD ON *.* TO mysqltest_u1@%s", opt_host ? opt_host : "'localhost'"); @@ -18584,7 +18584,7 @@ static void test_bug13001491() mysql_close(c); c= NULL; - snprintf(query, MAX_TEST_QUERY_LENGTH, + my_snprintf(query, MAX_TEST_QUERY_LENGTH, "DROP USER mysqltest_u1@%s", opt_host ? opt_host : "'localhost'"); From dce337406ea07b4581bf724133d5262fddd365f3 Mon Sep 17 00:00:00 2001 From: Vasil Dimov Date: Tue, 25 Oct 2011 16:46:38 +0300 Subject: [PATCH 11/19] Fix Bug#12661768 UPDATE IGNORE CRASHES SERVER IF TABLE IS INNODB AND IT IS PARENT FOR OTHER ONE Do not try to lookup key_nr'th key in 'table' because there may not be such a key there. key_nr is the number of the key in the _child_ table name, not in the parent table. Instead just print the fields of the record that are covered by the first key defined on the parent table. This bug gets a better fix in MySQL 5.6, which is too risky for 5.1 and 5.5. Approved by: Jon Olav Hauglid (via IM) --- .../suite/innodb/r/innodb_bug12661768.result | 2 + .../suite/innodb/t/innodb_bug12661768.test | 50 +++++++++++++++++++ sql/handler.cc | 8 ++- 3 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 mysql-test/suite/innodb/r/innodb_bug12661768.result create mode 100644 mysql-test/suite/innodb/t/innodb_bug12661768.test diff --git a/mysql-test/suite/innodb/r/innodb_bug12661768.result b/mysql-test/suite/innodb/r/innodb_bug12661768.result new file mode 100644 index 00000000000..1f2401ddd47 --- /dev/null +++ b/mysql-test/suite/innodb/r/innodb_bug12661768.result @@ -0,0 +1,2 @@ +SET SESSION foreign_key_checks=0; +ERROR 23000: Upholding foreign key constraints for table 'bug12661768_1', entry '3-bbb', key 2 would lead to a duplicate entry diff --git a/mysql-test/suite/innodb/t/innodb_bug12661768.test b/mysql-test/suite/innodb/t/innodb_bug12661768.test new file mode 100644 index 00000000000..01549a7e9e9 --- /dev/null +++ b/mysql-test/suite/innodb/t/innodb_bug12661768.test @@ -0,0 +1,50 @@ +# +# Bug#12661768 UPDATE IGNORE CRASHES SERVER IF TABLE IS INNODB AND IT IS +# PARENT FOR OTHER ONE +# + +-- source include/have_innodb.inc + +SET SESSION foreign_key_checks=0; + +# only interested that the "UPDATE IGNORE" at the end does not crash the server + +-- disable_query_log +-- disable_result_log + +SET NAMES utf8; + +-- let $t1_name = bug12661768_1 +-- let $t2_name = bug12661768_2 +-- let $fk_name = ab_on_2 +-- let $key_str = 'bbb' + +eval DROP TABLE IF EXISTS `$t2_name`, `$t1_name`; + +eval CREATE TABLE `$t1_name` ( + a INT, + b VARCHAR(512), + PRIMARY KEY (a, b) +) ENGINE=INNODB; + +eval CREATE TABLE `$t2_name` ( + id INT, + a INT, + b VARCHAR(512), + PRIMARY KEY (id), + UNIQUE KEY `$fk_name` (a, b), + FOREIGN KEY (a, b) REFERENCES `$t1_name` (a, b) + ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=INNODB; + +eval INSERT INTO `$t1_name` VALUES (1, $key_str); +eval INSERT INTO `$t2_name` VALUES (100, 1, $key_str), (101, 3, $key_str); + +SET SESSION foreign_key_checks=1; + +-- enable_result_log + +-- error ER_FOREIGN_DUPLICATE_KEY +eval UPDATE IGNORE `$t1_name` SET a = 3; + +eval DROP TABLE `$t2_name`, `$t1_name`; diff --git a/sql/handler.cc b/sql/handler.cc index 82f5f2ee841..895e7cc4df0 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -2692,7 +2692,13 @@ void handler::print_error(int error, myf errflag) char key[MAX_KEY_LENGTH]; String str(key,sizeof(key),system_charset_info); /* Table is opened and defined at this point */ - key_unpack(&str,table,(uint) key_nr); + key_unpack(&str,table,0 /* Use 0 instead of key_nr because key_nr + is a key number in the child FK table, not in our 'table'. See + Bug#12661768 UPDATE IGNORE CRASHES SERVER IF TABLE IS INNODB + AND IT IS PARENT FOR OTHER ONE + This bug gets a better fix in MySQL 5.6, but it is too risky + to get that in 5.1 and 5.5 (extending the handler interface + and adding new error message codes */); max_length= (MYSQL_ERRMSG_SIZE- (uint) strlen(ER(ER_FOREIGN_DUPLICATE_KEY))); if (str.length() >= max_length) From 013ba71dfdd6f9d49730c1a918d1d089976faefd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Tue, 25 Oct 2011 17:33:38 +0300 Subject: [PATCH 12/19] Bug#13002783 PARTIALLY UNINITIALIZED CASCADE UPDATE VECTOR In the ON UPDATE CASCADE clause of FOREIGN KEY constraints, the calculated update vector was not fully initialized. This bug was introduced in the InnoDB Plugin when implementing support for ROW_FORMAT=DYNAMIC. Additionally, the data type information was not initialized, but apparently it has never been needed in this case. Nevertheless, it is not good programming practice to pass uninitialized values around. calc_row_difference(): Declare the update field uninitialized in Valgrind. Copy the data type information as well, except when the field is SQL NULL. In the built-in InnoDB, initialize ufield->extern_storage = FALSE (an initialization bug that had gone unnoticed this far). The InnoDB Plugin and later have this flag to dfield_t and have always initialized it properly. row_ins_cascade_calc_update_vec(): Reduce the scope of some pointers. Initialize orig_len. (This caused the bug in InnoDB Plugin and later.) row_ins_foreign_check_on_constraint(): Simplify a condition. Declare the update vector uninitialized. rb:771 approved by Jimmy Yang --- mysql-test/suite/innodb/r/innodb.result | 16 +++++++++++++++- mysql-test/suite/innodb/t/innodb.test | 18 ++++++++++++++++++ mysql-test/suite/innodb_plugin/r/innodb.result | 16 +++++++++++++++- mysql-test/suite/innodb_plugin/t/innodb.test | 18 ++++++++++++++++++ storage/innobase/handler/ha_innodb.cc | 10 +++++++--- storage/innobase/row/row0ins.c | 15 ++++++++------- storage/innodb_plugin/ChangeLog | 7 ++++++- storage/innodb_plugin/handler/ha_innodb.cc | 9 +++++---- storage/innodb_plugin/row/row0ins.c | 17 ++++++++++------- 9 files changed, 102 insertions(+), 24 deletions(-) diff --git a/mysql-test/suite/innodb/r/innodb.result b/mysql-test/suite/innodb/r/innodb.result index c6f6f352743..edf3c7d2753 100644 --- a/mysql-test/suite/innodb/r/innodb.result +++ b/mysql-test/suite/innodb/r/innodb.result @@ -1293,6 +1293,20 @@ ERROR 23000: Cannot delete or update a parent row: a foreign key constraint fail update t3 set t3.id=7 where t1.id =1 and t2.id = t1.id and t3.id = t2.id; ERROR 42S22: Unknown column 't1.id' in 'where clause' drop table t3,t2,t1; +CREATE TABLE t1 ( +c1 VARCHAR(8), c2 VARCHAR(8), +PRIMARY KEY (c1, c2) +) ENGINE=InnoDB; +CREATE TABLE t2 ( +c0 INT PRIMARY KEY, +c1 VARCHAR(8) UNIQUE, +FOREIGN KEY (c1) REFERENCES t1 (c1) ON UPDATE CASCADE +) ENGINE=InnoDB; +INSERT INTO t1 VALUES ('old', 'somevalu'), ('other', 'anyvalue'); +INSERT INTO t2 VALUES (10, 'old'), (20, 'other'); +UPDATE t1 SET c1 = 'other' WHERE c1 = 'old'; +ERROR 23000: Upholding foreign key constraints for table 't1', entry '', key 2 would lead to a duplicate entry +DROP TABLE t2,t1; create table t1( id int primary key, pid int, @@ -1671,7 +1685,7 @@ variable_value - @innodb_rows_deleted_orig 71 SELECT variable_value - @innodb_rows_inserted_orig FROM information_schema.global_status WHERE LOWER(variable_name) = 'innodb_rows_inserted'; variable_value - @innodb_rows_inserted_orig -1063 +1067 SELECT variable_value - @innodb_rows_updated_orig FROM information_schema.global_status WHERE LOWER(variable_name) = 'innodb_rows_updated'; variable_value - @innodb_rows_updated_orig 865 diff --git a/mysql-test/suite/innodb/t/innodb.test b/mysql-test/suite/innodb/t/innodb.test index 480183b9e2d..ef050390b48 100644 --- a/mysql-test/suite/innodb/t/innodb.test +++ b/mysql-test/suite/innodb/t/innodb.test @@ -1021,6 +1021,24 @@ update t1,t2,t3 set t3.id=5, t2.id=6, t1.id=7 where t1.id =1 and t2.id = t1.id update t3 set t3.id=7 where t1.id =1 and t2.id = t1.id and t3.id = t2.id; drop table t3,t2,t1; +# test ON UPDATE CASCADE +CREATE TABLE t1 ( + c1 VARCHAR(8), c2 VARCHAR(8), + PRIMARY KEY (c1, c2) +) ENGINE=InnoDB; + +CREATE TABLE t2 ( + c0 INT PRIMARY KEY, + c1 VARCHAR(8) UNIQUE, + FOREIGN KEY (c1) REFERENCES t1 (c1) ON UPDATE CASCADE +) ENGINE=InnoDB; + +INSERT INTO t1 VALUES ('old', 'somevalu'), ('other', 'anyvalue'); +INSERT INTO t2 VALUES (10, 'old'), (20, 'other'); +-- error ER_FOREIGN_DUPLICATE_KEY +UPDATE t1 SET c1 = 'other' WHERE c1 = 'old'; +DROP TABLE t2,t1; + # # test for recursion depth limit # diff --git a/mysql-test/suite/innodb_plugin/r/innodb.result b/mysql-test/suite/innodb_plugin/r/innodb.result index d3f7a0b9fff..a550b9702bb 100644 --- a/mysql-test/suite/innodb_plugin/r/innodb.result +++ b/mysql-test/suite/innodb_plugin/r/innodb.result @@ -1301,6 +1301,20 @@ ERROR 23000: Cannot delete or update a parent row: a foreign key constraint fail update t3 set t3.id=7 where t1.id =1 and t2.id = t1.id and t3.id = t2.id; ERROR 42S22: Unknown column 't1.id' in 'where clause' drop table t3,t2,t1; +CREATE TABLE t1 ( +c1 VARCHAR(8), c2 VARCHAR(8), +PRIMARY KEY (c1, c2) +) ENGINE=InnoDB; +CREATE TABLE t2 ( +c0 INT PRIMARY KEY, +c1 VARCHAR(8) UNIQUE, +FOREIGN KEY (c1) REFERENCES t1 (c1) ON UPDATE CASCADE +) ENGINE=InnoDB; +INSERT INTO t1 VALUES ('old', 'somevalu'), ('other', 'anyvalue'); +INSERT INTO t2 VALUES (10, 'old'), (20, 'other'); +UPDATE t1 SET c1 = 'other' WHERE c1 = 'old'; +ERROR 23000: Upholding foreign key constraints for table 't1', entry '', key 2 would lead to a duplicate entry +DROP TABLE t2,t1; create table t1( id int primary key, pid int, @@ -1679,7 +1693,7 @@ variable_value - @innodb_rows_deleted_orig 71 SELECT variable_value - @innodb_rows_inserted_orig FROM information_schema.global_status WHERE LOWER(variable_name) = 'innodb_rows_inserted'; variable_value - @innodb_rows_inserted_orig -1067 +1071 SELECT variable_value - @innodb_rows_updated_orig FROM information_schema.global_status WHERE LOWER(variable_name) = 'innodb_rows_updated'; variable_value - @innodb_rows_updated_orig 866 diff --git a/mysql-test/suite/innodb_plugin/t/innodb.test b/mysql-test/suite/innodb_plugin/t/innodb.test index cfc2277d501..1480492cf2a 100644 --- a/mysql-test/suite/innodb_plugin/t/innodb.test +++ b/mysql-test/suite/innodb_plugin/t/innodb.test @@ -1040,6 +1040,24 @@ update t1,t2,t3 set t3.id=5, t2.id=6, t1.id=7 where t1.id =1 and t2.id = t1.id update t3 set t3.id=7 where t1.id =1 and t2.id = t1.id and t3.id = t2.id; drop table t3,t2,t1; +# test ON UPDATE CASCADE +CREATE TABLE t1 ( + c1 VARCHAR(8), c2 VARCHAR(8), + PRIMARY KEY (c1, c2) +) ENGINE=InnoDB; + +CREATE TABLE t2 ( + c0 INT PRIMARY KEY, + c1 VARCHAR(8) UNIQUE, + FOREIGN KEY (c1) REFERENCES t1 (c1) ON UPDATE CASCADE +) ENGINE=InnoDB; + +INSERT INTO t1 VALUES ('old', 'somevalu'), ('other', 'anyvalue'); +INSERT INTO t2 VALUES (10, 'old'), (20, 'other'); +-- error ER_FOREIGN_DUPLICATE_KEY +UPDATE t1 SET c1 = 'other' WHERE c1 = 'old'; +DROP TABLE t2,t1; + # # test for recursion depth limit # diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index 2d230e1c297..44b9a7fba36 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -4264,14 +4264,16 @@ calc_row_difference( /* The field has changed */ ufield = uvect->fields + n_changed; + UNIV_MEM_INVALID(ufield, sizeof *ufield); /* Let us use a dummy dfield to make the conversion from the MySQL column format to the InnoDB format */ - dict_col_copy_type_noninline(prebuilt->table->cols + i, - &dfield.type); - if (n_len != UNIV_SQL_NULL) { + dict_col_copy_type_noninline( + prebuilt->table->cols + i, + &dfield.type); + buf = row_mysql_store_col_in_innobase_format( &dfield, (byte*)buf, @@ -4282,11 +4284,13 @@ calc_row_difference( prebuilt->table)); ufield->new_val.data = dfield.data; ufield->new_val.len = dfield.len; + ufield->new_val.type = dfield.type; } else { ufield->new_val.data = NULL; ufield->new_val.len = UNIV_SQL_NULL; } + ufield->extern_storage = FALSE; ufield->exp = NULL; ufield->field_no = dict_col_get_clust_pos_noninline( &prebuilt->table->cols[i], clust_index); diff --git a/storage/innobase/row/row0ins.c b/storage/innobase/row/row0ins.c index 6366beb6b47..f6e6c81534b 100644 --- a/storage/innobase/row/row0ins.c +++ b/storage/innobase/row/row0ins.c @@ -425,11 +425,9 @@ row_ins_cascade_calc_update_vec( dict_table_t* table = foreign->foreign_table; dict_index_t* index = foreign->foreign_index; upd_t* update; - upd_field_t* ufield; dict_table_t* parent_table; dict_index_t* parent_index; upd_t* parent_update; - upd_field_t* parent_ufield; ulint n_fields_updated; ulint parent_field_no; ulint i; @@ -465,12 +463,14 @@ row_ins_cascade_calc_update_vec( dict_index_get_nth_col_no(parent_index, i)); for (j = 0; j < parent_update->n_fields; j++) { - parent_ufield = parent_update->fields + j; + const upd_field_t* parent_ufield + = &parent_update->fields[j]; if (parent_ufield->field_no == parent_field_no) { ulint min_size; const dict_col_t* col; + upd_field_t* ufield; col = dict_index_get_nth_col(index, i); @@ -975,10 +975,9 @@ row_ins_foreign_check_on_constraint( goto nonstandard_exit_func; } - if ((node->is_delete - && (foreign->type & DICT_FOREIGN_ON_DELETE_SET_NULL)) - || (!node->is_delete - && (foreign->type & DICT_FOREIGN_ON_UPDATE_SET_NULL))) { + if (node->is_delete + ? (foreign->type & DICT_FOREIGN_ON_DELETE_SET_NULL) + : (foreign->type & DICT_FOREIGN_ON_UPDATE_SET_NULL)) { /* Build the appropriate update vector which sets foreign->n_fields first fields in rec to SQL NULL */ @@ -987,6 +986,8 @@ row_ins_foreign_check_on_constraint( update->info_bits = 0; update->n_fields = foreign->n_fields; + UNIV_MEM_INVALID(update->fields, + update->n_fields * sizeof *update->fields); for (i = 0; i < foreign->n_fields; i++) { (update->fields + i)->field_no diff --git a/storage/innodb_plugin/ChangeLog b/storage/innodb_plugin/ChangeLog index 68f60c37f1f..4e6e2be615a 100644 --- a/storage/innodb_plugin/ChangeLog +++ b/storage/innodb_plugin/ChangeLog @@ -1,6 +1,11 @@ +2011-10-25 The InnoDB Team + + * handler/ha_innodb.cc, row/row0ins.c: + Fix Bug#13002783 PARTIALLY UNINITIALIZED CASCADE UPDATE VECTOR + 2011-10-20 The InnoDB Team - * btr/brt0cur.c: + * btr/btr0cur.c: Fix Bug#13116045 Compilation failure using GCC 4.6.1 in btr/btr0cur.c 2011-10-12 The InnoDB Team diff --git a/storage/innodb_plugin/handler/ha_innodb.cc b/storage/innodb_plugin/handler/ha_innodb.cc index fc1193e55bb..566c54e0175 100644 --- a/storage/innodb_plugin/handler/ha_innodb.cc +++ b/storage/innodb_plugin/handler/ha_innodb.cc @@ -4971,14 +4971,15 @@ calc_row_difference( /* The field has changed */ ufield = uvect->fields + n_changed; + UNIV_MEM_INVALID(ufield, sizeof *ufield); /* Let us use a dummy dfield to make the conversion from the MySQL column format to the InnoDB format */ - dict_col_copy_type(prebuilt->table->cols + i, - dfield_get_type(&dfield)); - if (n_len != UNIV_SQL_NULL) { + dict_col_copy_type(prebuilt->table->cols + i, + dfield_get_type(&dfield)); + buf = row_mysql_store_col_in_innobase_format( &dfield, (byte*)buf, @@ -4986,7 +4987,7 @@ calc_row_difference( new_mysql_row_col, col_pack_len, dict_table_is_comp(prebuilt->table)); - dfield_copy_data(&ufield->new_val, &dfield); + dfield_copy(&ufield->new_val, &dfield); } else { dfield_set_null(&ufield->new_val); } diff --git a/storage/innodb_plugin/row/row0ins.c b/storage/innodb_plugin/row/row0ins.c index 0f158cdc706..cd135e8ba8f 100644 --- a/storage/innodb_plugin/row/row0ins.c +++ b/storage/innodb_plugin/row/row0ins.c @@ -434,11 +434,9 @@ row_ins_cascade_calc_update_vec( dict_table_t* table = foreign->foreign_table; dict_index_t* index = foreign->foreign_index; upd_t* update; - upd_field_t* ufield; dict_table_t* parent_table; dict_index_t* parent_index; upd_t* parent_update; - upd_field_t* parent_ufield; ulint n_fields_updated; ulint parent_field_no; ulint i; @@ -474,13 +472,15 @@ row_ins_cascade_calc_update_vec( dict_index_get_nth_col_no(parent_index, i)); for (j = 0; j < parent_update->n_fields; j++) { - parent_ufield = parent_update->fields + j; + const upd_field_t* parent_ufield + = &parent_update->fields[j]; if (parent_ufield->field_no == parent_field_no) { ulint min_size; const dict_col_t* col; ulint ufield_len; + upd_field_t* ufield; col = dict_index_get_nth_col(index, i); @@ -493,6 +493,8 @@ row_ins_cascade_calc_update_vec( ufield->field_no = dict_table_get_nth_col_pos( table, dict_col_get_no(col)); + + ufield->orig_len = 0; ufield->exp = NULL; ufield->new_val = parent_ufield->new_val; @@ -993,10 +995,9 @@ row_ins_foreign_check_on_constraint( goto nonstandard_exit_func; } - if ((node->is_delete - && (foreign->type & DICT_FOREIGN_ON_DELETE_SET_NULL)) - || (!node->is_delete - && (foreign->type & DICT_FOREIGN_ON_UPDATE_SET_NULL))) { + if (node->is_delete + ? (foreign->type & DICT_FOREIGN_ON_DELETE_SET_NULL) + : (foreign->type & DICT_FOREIGN_ON_UPDATE_SET_NULL)) { /* Build the appropriate update vector which sets foreign->n_fields first fields in rec to SQL NULL */ @@ -1005,6 +1006,8 @@ row_ins_foreign_check_on_constraint( update->info_bits = 0; update->n_fields = foreign->n_fields; + UNIV_MEM_INVALID(update->fields, + update->n_fields * sizeof *update->fields); for (i = 0; i < foreign->n_fields; i++) { upd_field_t* ufield = &update->fields[i]; From e8d793f8709ce2beab3cff6a184e0c959f44e6b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Tue, 25 Oct 2011 17:58:34 +0300 Subject: [PATCH 13/19] Fix results after Bug#12661768 fix. --- mysql-test/suite/innodb_plugin/r/innodb.result | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mysql-test/suite/innodb_plugin/r/innodb.result b/mysql-test/suite/innodb_plugin/r/innodb.result index a550b9702bb..cdb61f3ba98 100644 --- a/mysql-test/suite/innodb_plugin/r/innodb.result +++ b/mysql-test/suite/innodb_plugin/r/innodb.result @@ -1313,7 +1313,7 @@ FOREIGN KEY (c1) REFERENCES t1 (c1) ON UPDATE CASCADE INSERT INTO t1 VALUES ('old', 'somevalu'), ('other', 'anyvalue'); INSERT INTO t2 VALUES (10, 'old'), (20, 'other'); UPDATE t1 SET c1 = 'other' WHERE c1 = 'old'; -ERROR 23000: Upholding foreign key constraints for table 't1', entry '', key 2 would lead to a duplicate entry +ERROR 23000: Upholding foreign key constraints for table 't1', entry 'other-somevalu', key 2 would lead to a duplicate entry DROP TABLE t2,t1; create table t1( id int primary key, From 86c72a3fad6fdd0868d47168c306b873b3aed7cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Wed, 26 Oct 2011 09:34:32 +0300 Subject: [PATCH 14/19] Fix results after Bug#12661768 fix. --- mysql-test/suite/innodb/r/innodb.result | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mysql-test/suite/innodb/r/innodb.result b/mysql-test/suite/innodb/r/innodb.result index edf3c7d2753..ad140cc59be 100644 --- a/mysql-test/suite/innodb/r/innodb.result +++ b/mysql-test/suite/innodb/r/innodb.result @@ -1305,7 +1305,7 @@ FOREIGN KEY (c1) REFERENCES t1 (c1) ON UPDATE CASCADE INSERT INTO t1 VALUES ('old', 'somevalu'), ('other', 'anyvalue'); INSERT INTO t2 VALUES (10, 'old'), (20, 'other'); UPDATE t1 SET c1 = 'other' WHERE c1 = 'old'; -ERROR 23000: Upholding foreign key constraints for table 't1', entry '', key 2 would lead to a duplicate entry +ERROR 23000: Upholding foreign key constraints for table 't1', entry 'other-somevalu', key 2 would lead to a duplicate entry DROP TABLE t2,t1; create table t1( id int primary key, From 5cd2cb0c4f6f63d44f9d130d4f2e1f11b5d4064e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Wed, 26 Oct 2011 09:38:28 +0300 Subject: [PATCH 15/19] Copy and adapt innodb_misc1.test from innodb to innodb_plugin. --- .../suite/innodb_plugin/r/innodb_misc1.result | 882 ++++++++++++ .../innodb_plugin/t/innodb_misc1-master.opt | 1 + .../suite/innodb_plugin/t/innodb_misc1.test | 1178 +++++++++++++++++ 3 files changed, 2061 insertions(+) create mode 100644 mysql-test/suite/innodb_plugin/r/innodb_misc1.result create mode 100644 mysql-test/suite/innodb_plugin/t/innodb_misc1-master.opt create mode 100644 mysql-test/suite/innodb_plugin/t/innodb_misc1.test diff --git a/mysql-test/suite/innodb_plugin/r/innodb_misc1.result b/mysql-test/suite/innodb_plugin/r/innodb_misc1.result new file mode 100644 index 00000000000..5b1774c6e99 --- /dev/null +++ b/mysql-test/suite/innodb_plugin/r/innodb_misc1.result @@ -0,0 +1,882 @@ +drop table if exists t1,t2,t3,t4; +drop database if exists mysqltest; +create table t1 (v varchar(16384)) engine=innodb; +drop table t1; +create table t1 (a char(1), b char(1), key(a, b)) engine=innodb; +insert into t1 values ('8', '6'), ('4', '7'); +select min(a) from t1; +min(a) +4 +select min(b) from t1 where a='8'; +min(b) +6 +drop table t1; +CREATE TABLE t1 ( `a` int(11) NOT NULL auto_increment, `b` int(11) default NULL,PRIMARY KEY (`a`),UNIQUE KEY `b` (`b`)) ENGINE=innodb; +insert into t1 (b) values (1); +replace into t1 (b) values (2), (1), (3); +select * from t1; +a b +3 1 +2 2 +4 3 +truncate table t1; +insert into t1 (b) values (1); +replace into t1 (b) values (2); +replace into t1 (b) values (1); +replace into t1 (b) values (3); +select * from t1; +a b +3 1 +2 2 +4 3 +drop table t1; +create table t1 (rowid int not null auto_increment, val int not null,primary +key (rowid), unique(val)) engine=innodb; +replace into t1 (val) values ('1'),('2'); +replace into t1 (val) values ('1'),('2'); +insert into t1 (val) values ('1'),('2'); +ERROR 23000: Duplicate entry '1' for key 'val' +select * from t1; +rowid val +3 1 +4 2 +drop table t1; +create table t1 (a int not null auto_increment primary key, val int) engine=InnoDB; +insert into t1 (val) values (1); +update t1 set a=2 where a=1; +insert into t1 (val) values (1); +ERROR 23000: Duplicate entry '2' for key 'PRIMARY' +select * from t1; +a val +2 1 +drop table t1; +CREATE TABLE t1 (GRADE DECIMAL(4) NOT NULL, PRIMARY KEY (GRADE)) ENGINE=INNODB; +INSERT INTO t1 (GRADE) VALUES (151),(252),(343); +SELECT GRADE FROM t1 WHERE GRADE > 160 AND GRADE < 300; +GRADE +252 +SELECT GRADE FROM t1 WHERE GRADE= 151; +GRADE +151 +DROP TABLE t1; +create table t1 (f1 varchar(10), f2 varchar(10), primary key (f1,f2)) engine=innodb; +create table t2 (f3 varchar(10), f4 varchar(10), key (f4)) engine=innodb; +insert into t2 values ('aa','cc'); +insert into t1 values ('aa','bb'),('aa','cc'); +delete t1 from t1,t2 where f1=f3 and f4='cc'; +select * from t1; +f1 f2 +drop table t1,t2; +CREATE TABLE t1 ( +id INTEGER NOT NULL AUTO_INCREMENT, PRIMARY KEY (id) +) ENGINE=InnoDB; +CREATE TABLE t2 ( +id INTEGER NOT NULL, +FOREIGN KEY (id) REFERENCES t1 (id) +) ENGINE=InnoDB; +INSERT INTO t1 (id) VALUES (NULL); +SELECT * FROM t1; +id +1 +TRUNCATE t1; +INSERT INTO t1 (id) VALUES (NULL); +SELECT * FROM t1; +id +1 +DELETE FROM t1; +TRUNCATE t1; +INSERT INTO t1 (id) VALUES (NULL); +SELECT * FROM t1; +id +1 +DROP TABLE t2, t1; +CREATE TABLE t1 +( +id INT PRIMARY KEY +) ENGINE=InnoDB; +CREATE TEMPORARY TABLE t2 +( +id INT NOT NULL PRIMARY KEY, +b INT, +FOREIGN KEY (b) REFERENCES test.t1(id) +) ENGINE=InnoDB; +Got one of the listed errors +DROP TABLE t1; +create table t1 (col1 varchar(2000), index (col1(767))) +character set = latin1 engine = innodb; +create table t2 (col1 char(255), index (col1)) +character set = latin1 engine = innodb; +create table t3 (col1 binary(255), index (col1)) +character set = latin1 engine = innodb; +create table t4 (col1 varchar(767), index (col1)) +character set = latin1 engine = innodb; +create table t5 (col1 varchar(767) primary key) +character set = latin1 engine = innodb; +create table t6 (col1 varbinary(767) primary key) +character set = latin1 engine = innodb; +create table t7 (col1 text, index(col1(767))) +character set = latin1 engine = innodb; +create table t8 (col1 blob, index(col1(767))) +character set = latin1 engine = innodb; +create table t9 (col1 varchar(512), col2 varchar(512), index(col1, col2)) +character set = latin1 engine = innodb; +show create table t9; +Table Create Table +t9 CREATE TABLE `t9` ( + `col1` varchar(512) DEFAULT NULL, + `col2` varchar(512) DEFAULT NULL, + KEY `col1` (`col1`,`col2`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +drop table t1, t2, t3, t4, t5, t6, t7, t8, t9; +create table t1 (col1 varchar(768), index(col1)) +character set = latin1 engine = innodb; +Warnings: +Warning 1071 Specified key was too long; max key length is 767 bytes +create table t2 (col1 varbinary(768), index(col1)) +character set = latin1 engine = innodb; +Warnings: +Warning 1071 Specified key was too long; max key length is 767 bytes +create table t3 (col1 text, index(col1(768))) +character set = latin1 engine = innodb; +Warnings: +Warning 1071 Specified key was too long; max key length is 767 bytes +create table t4 (col1 blob, index(col1(768))) +character set = latin1 engine = innodb; +Warnings: +Warning 1071 Specified key was too long; max key length is 767 bytes +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `col1` varchar(768) DEFAULT NULL, + KEY `col1` (`col1`(767)) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +drop table t1, t2, t3, t4; +create table t1 (col1 varchar(768) primary key) +character set = latin1 engine = innodb; +ERROR 42000: Specified key was too long; max key length is 767 bytes +create table t2 (col1 varbinary(768) primary key) +character set = latin1 engine = innodb; +ERROR 42000: Specified key was too long; max key length is 767 bytes +create table t3 (col1 text, primary key(col1(768))) +character set = latin1 engine = innodb; +ERROR 42000: Specified key was too long; max key length is 767 bytes +create table t4 (col1 blob, primary key(col1(768))) +character set = latin1 engine = innodb; +ERROR 42000: Specified key was too long; max key length is 767 bytes +CREATE TABLE t1 +( +id INT PRIMARY KEY +) ENGINE=InnoDB; +CREATE TABLE t2 +( +v INT, +CONSTRAINT c1 FOREIGN KEY (v) REFERENCES t1(id) +) ENGINE=InnoDB; +INSERT INTO t2 VALUES(2); +ERROR 23000: Cannot add or update a child row: a foreign key constraint fails (`test`.`t2`, CONSTRAINT `c1` FOREIGN KEY (`v`) REFERENCES `t1` (`id`)) +INSERT INTO t1 VALUES(1); +INSERT INTO t2 VALUES(1); +DELETE FROM t1 WHERE id = 1; +ERROR 23000: Cannot delete or update a parent row: a foreign key constraint fails (`test`.`t2`, CONSTRAINT `c1` FOREIGN KEY (`v`) REFERENCES `t1` (`id`)) +DROP TABLE t1; +ERROR 23000: Cannot delete or update a parent row: a foreign key constraint fails +SET FOREIGN_KEY_CHECKS=0; +DROP TABLE t1; +SET FOREIGN_KEY_CHECKS=1; +INSERT INTO t2 VALUES(3); +ERROR 23000: Cannot add or update a child row: a foreign key constraint fails (`test`.`t2`, CONSTRAINT `c1` FOREIGN KEY (`v`) REFERENCES `t1` (`id`)) +DROP TABLE t2; +create table t1(a int not null) engine=innodb DEFAULT CHARSET=latin1; +insert into t1 values (1),(2); +set autocommit=0; +checksum table t1; +Table Checksum +test.t1 1531596814 +insert into t1 values(3); +checksum table t1; +Table Checksum +test.t1 1531596814 +commit; +checksum table t1; +Table Checksum +test.t1 2050879373 +commit; +drop table t1; +create table t1(a int not null) engine=innodb DEFAULT CHARSET=latin1; +insert into t1 values (1),(2); +set autocommit=1; +checksum table t1; +Table Checksum +test.t1 1531596814 +set autocommit=1; +insert into t1 values(3); +checksum table t1; +Table Checksum +test.t1 2050879373 +drop table t1; +set foreign_key_checks=0; +create table t2 (a int primary key, b int, foreign key (b) references t1(a)) engine = innodb; +create table t1(a char(10) primary key, b varchar(20)) engine = innodb; +ERROR HY000: Can't create table 'test.t1' (errno: 150) +set foreign_key_checks=1; +drop table t2; +set foreign_key_checks=0; +create table t1(a varchar(10) primary key) engine = innodb DEFAULT CHARSET=latin1; +create table t2 (a varchar(10), foreign key (a) references t1(a)) engine = innodb DEFAULT CHARSET=utf8; +ERROR HY000: Can't create table 'test.t2' (errno: 150) +set foreign_key_checks=1; +drop table t1; +set foreign_key_checks=0; +create table t2 (a varchar(10), foreign key (a) references t1(a)) engine = innodb; +create table t1(a varchar(10) primary key) engine = innodb; +alter table t1 modify column a int; +Got one of the listed errors +set foreign_key_checks=1; +drop table t2,t1; +set foreign_key_checks=0; +create table t2 (a varchar(10), foreign key (a) references t1(a)) engine = innodb DEFAULT CHARSET=latin1; +create table t1(a varchar(10) primary key) engine = innodb DEFAULT CHARSET=latin1; +alter table t1 convert to character set utf8; +set foreign_key_checks=1; +drop table t2,t1; +set foreign_key_checks=0; +create table t2 (a varchar(10), foreign key (a) references t1(a)) engine = innodb DEFAULT CHARSET=latin1; +create table t3(a varchar(10) primary key) engine = innodb DEFAULT CHARSET=utf8; +rename table t3 to t1; +ERROR HY000: Error on rename of './test/t3' to './test/t1' (errno: 150) +set foreign_key_checks=1; +drop table t2,t3; +create table t1(a int primary key) row_format=redundant engine=innodb; +create table t2(a int primary key,constraint foreign key(a)references t1(a)) row_format=compact engine=innodb; +create table t3(a int primary key) row_format=compact engine=innodb; +create table t4(a int primary key,constraint foreign key(a)references t3(a)) row_format=redundant engine=innodb; +insert into t1 values(1); +insert into t3 values(1); +insert into t2 values(2); +ERROR 23000: Cannot add or update a child row: a foreign key constraint fails (`test`.`t2`, CONSTRAINT `t2_ibfk_1` FOREIGN KEY (`a`) REFERENCES `t1` (`a`)) +insert into t4 values(2); +ERROR 23000: Cannot add or update a child row: a foreign key constraint fails (`test`.`t4`, CONSTRAINT `t4_ibfk_1` FOREIGN KEY (`a`) REFERENCES `t3` (`a`)) +insert into t2 values(1); +insert into t4 values(1); +update t1 set a=2; +ERROR 23000: Cannot delete or update a parent row: a foreign key constraint fails (`test`.`t2`, CONSTRAINT `t2_ibfk_1` FOREIGN KEY (`a`) REFERENCES `t1` (`a`)) +update t2 set a=2; +ERROR 23000: Cannot add or update a child row: a foreign key constraint fails (`test`.`t2`, CONSTRAINT `t2_ibfk_1` FOREIGN KEY (`a`) REFERENCES `t1` (`a`)) +update t3 set a=2; +ERROR 23000: Cannot delete or update a parent row: a foreign key constraint fails (`test`.`t4`, CONSTRAINT `t4_ibfk_1` FOREIGN KEY (`a`) REFERENCES `t3` (`a`)) +update t4 set a=2; +ERROR 23000: Cannot add or update a child row: a foreign key constraint fails (`test`.`t4`, CONSTRAINT `t4_ibfk_1` FOREIGN KEY (`a`) REFERENCES `t3` (`a`)) +truncate t1; +ERROR 23000: Cannot delete or update a parent row: a foreign key constraint fails (`test`.`t2`, CONSTRAINT `t2_ibfk_1` FOREIGN KEY (`a`) REFERENCES `t1` (`a`)) +truncate t3; +ERROR 23000: Cannot delete or update a parent row: a foreign key constraint fails (`test`.`t4`, CONSTRAINT `t4_ibfk_1` FOREIGN KEY (`a`) REFERENCES `t3` (`a`)) +truncate t2; +truncate t4; +truncate t1; +truncate t3; +drop table t4,t3,t2,t1; +create table t1 (a varchar(255) character set utf8, +b varchar(255) character set utf8, +c varchar(255) character set utf8, +d varchar(255) character set utf8, +key (a,b,c,d)) engine=innodb; +drop table t1; +create table t1 (a varchar(255) character set utf8, +b varchar(255) character set utf8, +c varchar(255) character set utf8, +d varchar(255) character set utf8, +e varchar(255) character set utf8, +key (a,b,c,d,e)) engine=innodb; +ERROR 42000: Specified key was too long; max key length is 3072 bytes +create table t1 (s1 varbinary(2),primary key (s1)) engine=innodb; +create table t2 (s1 binary(2),primary key (s1)) engine=innodb; +create table t3 (s1 varchar(2) binary,primary key (s1)) engine=innodb; +create table t4 (s1 char(2) binary,primary key (s1)) engine=innodb; +insert into t1 values (0x41),(0x4120),(0x4100); +insert into t2 values (0x41),(0x4120),(0x4100); +ERROR 23000: Duplicate entry 'A' for key 'PRIMARY' +insert into t2 values (0x41),(0x4120); +insert into t3 values (0x41),(0x4120),(0x4100); +ERROR 23000: Duplicate entry 'A ' for key 'PRIMARY' +insert into t3 values (0x41),(0x4100); +insert into t4 values (0x41),(0x4120),(0x4100); +ERROR 23000: Duplicate entry 'A' for key 'PRIMARY' +insert into t4 values (0x41),(0x4100); +select hex(s1) from t1; +hex(s1) +41 +4100 +4120 +select hex(s1) from t2; +hex(s1) +4100 +4120 +select hex(s1) from t3; +hex(s1) +4100 +41 +select hex(s1) from t4; +hex(s1) +4100 +41 +drop table t1,t2,t3,t4; +create table t1 (a int primary key,s1 varbinary(3) not null unique) engine=innodb; +create table t2 (s1 binary(2) not null, constraint c foreign key(s1) references t1(s1) on update cascade) engine=innodb; +insert into t1 values(1,0x4100),(2,0x41),(3,0x4120),(4,0x42); +insert into t2 values(0x42); +ERROR 23000: Cannot add or update a child row: a foreign key constraint fails (`test`.`t2`, CONSTRAINT `c` FOREIGN KEY (`s1`) REFERENCES `t1` (`s1`) ON UPDATE CASCADE) +insert into t2 values(0x41); +select hex(s1) from t2; +hex(s1) +4100 +update t1 set s1=0x123456 where a=2; +select hex(s1) from t2; +hex(s1) +4100 +update t1 set s1=0x12 where a=1; +ERROR 23000: Cannot delete or update a parent row: a foreign key constraint fails (`test`.`t2`, CONSTRAINT `c` FOREIGN KEY (`s1`) REFERENCES `t1` (`s1`) ON UPDATE CASCADE) +update t1 set s1=0x12345678 where a=1; +ERROR 23000: Cannot delete or update a parent row: a foreign key constraint fails (`test`.`t2`, CONSTRAINT `c` FOREIGN KEY (`s1`) REFERENCES `t1` (`s1`) ON UPDATE CASCADE) +update t1 set s1=0x123457 where a=1; +ERROR 23000: Cannot delete or update a parent row: a foreign key constraint fails (`test`.`t2`, CONSTRAINT `c` FOREIGN KEY (`s1`) REFERENCES `t1` (`s1`) ON UPDATE CASCADE) +update t1 set s1=0x1220 where a=1; +select hex(s1) from t2; +hex(s1) +1220 +update t1 set s1=0x1200 where a=1; +select hex(s1) from t2; +hex(s1) +1200 +update t1 set s1=0x4200 where a=1; +select hex(s1) from t2; +hex(s1) +4200 +delete from t1 where a=1; +ERROR 23000: Cannot delete or update a parent row: a foreign key constraint fails (`test`.`t2`, CONSTRAINT `c` FOREIGN KEY (`s1`) REFERENCES `t1` (`s1`) ON UPDATE CASCADE) +delete from t1 where a=2; +update t2 set s1=0x4120; +delete from t1; +ERROR 23000: Cannot delete or update a parent row: a foreign key constraint fails (`test`.`t2`, CONSTRAINT `c` FOREIGN KEY (`s1`) REFERENCES `t1` (`s1`) ON UPDATE CASCADE) +delete from t1 where a!=3; +select a,hex(s1) from t1; +a hex(s1) +3 4120 +select hex(s1) from t2; +hex(s1) +4120 +drop table t2,t1; +create table t1 (a int primary key,s1 varchar(2) binary not null unique) engine=innodb; +create table t2 (s1 char(2) binary not null, constraint c foreign key(s1) references t1(s1) on update cascade) engine=innodb; +insert into t1 values(1,0x4100),(2,0x41); +insert into t2 values(0x41); +select hex(s1) from t2; +hex(s1) +41 +update t1 set s1=0x1234 where a=1; +select hex(s1) from t2; +hex(s1) +41 +update t1 set s1=0x12 where a=2; +select hex(s1) from t2; +hex(s1) +12 +delete from t1 where a=1; +delete from t1 where a=2; +ERROR 23000: Cannot delete or update a parent row: a foreign key constraint fails (`test`.`t2`, CONSTRAINT `c` FOREIGN KEY (`s1`) REFERENCES `t1` (`s1`) ON UPDATE CASCADE) +select a,hex(s1) from t1; +a hex(s1) +2 12 +select hex(s1) from t2; +hex(s1) +12 +drop table t2,t1; +CREATE TABLE t1(a INT, PRIMARY KEY(a)) ENGINE=InnoDB; +CREATE TABLE t2(a INT) ENGINE=InnoDB; +ALTER TABLE t2 ADD FOREIGN KEY (a) REFERENCES t1(a); +ALTER TABLE t2 DROP FOREIGN KEY t2_ibfk_1; +ALTER TABLE t2 ADD CONSTRAINT t2_ibfk_0 FOREIGN KEY (a) REFERENCES t1(a); +ALTER TABLE t2 DROP FOREIGN KEY t2_ibfk_0; +SHOW CREATE TABLE t2; +Table Create Table +t2 CREATE TABLE `t2` ( + `a` int(11) DEFAULT NULL, + KEY `t2_ibfk_0` (`a`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +DROP TABLE t2,t1; +create table t1(a int not null, b int, c int, d int, primary key(a)) engine=innodb; +insert into t1(a) values (1),(2),(3); +commit; +set autocommit = 0; +update t1 set b = 5 where a = 2; +create trigger t1t before insert on t1 for each row begin set NEW.b = NEW.a * 10 + 5, NEW.c = NEW.a / 10; end | +set autocommit = 0; +insert into t1(a) values (10),(20),(30),(40),(50),(60),(70),(80),(90),(100), +(11),(21),(31),(41),(51),(61),(71),(81),(91),(101), +(12),(22),(32),(42),(52),(62),(72),(82),(92),(102), +(13),(23),(33),(43),(53),(63),(73),(83),(93),(103), +(14),(24),(34),(44),(54),(64),(74),(84),(94),(104); +commit; +commit; +drop trigger t1t; +drop table t1; +create table t1(a int not null, b int, c int, d int, primary key(a)) engine=innodb; +create table t2(a int not null, b int, c int, d int, primary key(a)) engine=innodb; +create table t3(a int not null, b int, c int, d int, primary key(a)) engine=innodb; +create table t4(a int not null, b int, c int, d int, primary key(a)) engine=innodb; +create table t5(a int not null, b int, c int, d int, primary key(a)) engine=innodb; +insert into t1(a) values (1),(2),(3); +insert into t2(a) values (1),(2),(3); +insert into t3(a) values (1),(2),(3); +insert into t4(a) values (1),(2),(3); +insert into t3(a) values (5),(7),(8); +insert into t4(a) values (5),(7),(8); +insert into t5(a) values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12); +create trigger t1t before insert on t1 for each row begin +INSERT INTO t2 SET a = NEW.a; +end | +create trigger t2t before insert on t2 for each row begin +DELETE FROM t3 WHERE a = NEW.a; +end | +create trigger t3t before delete on t3 for each row begin +UPDATE t4 SET b = b + 1 WHERE a = OLD.a; +end | +create trigger t4t before update on t4 for each row begin +UPDATE t5 SET b = b + 1 where a = NEW.a; +end | +commit; +set autocommit = 0; +update t1 set b = b + 5 where a = 1; +update t2 set b = b + 5 where a = 1; +update t3 set b = b + 5 where a = 1; +update t4 set b = b + 5 where a = 1; +insert into t5(a) values(20); +set autocommit = 0; +insert into t1(a) values(7); +insert into t2(a) values(8); +delete from t2 where a = 3; +update t4 set b = b + 1 where a = 3; +commit; +drop trigger t1t; +drop trigger t2t; +drop trigger t3t; +drop trigger t4t; +drop table t1, t2, t3, t4, t5; +CREATE TABLE t1 ( +field1 varchar(8) NOT NULL DEFAULT '', +field2 varchar(8) NOT NULL DEFAULT '', +PRIMARY KEY (field1, field2) +) ENGINE=InnoDB; +CREATE TABLE t2 ( +field1 varchar(8) NOT NULL DEFAULT '' PRIMARY KEY, +FOREIGN KEY (field1) REFERENCES t1 (field1) +ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB; +INSERT INTO t1 VALUES ('old', 'somevalu'); +INSERT INTO t1 VALUES ('other', 'anyvalue'); +INSERT INTO t2 VALUES ('old'); +INSERT INTO t2 VALUES ('other'); +UPDATE t1 SET field1 = 'other' WHERE field2 = 'somevalu'; +ERROR 23000: Upholding foreign key constraints for table 't1', entry 'other-somevalu', key 1 would lead to a duplicate entry +DROP TABLE t2; +DROP TABLE t1; +create table t1 ( +c1 bigint not null, +c2 bigint not null, +primary key (c1), +unique key (c2) +) engine=innodb; +create table t2 ( +c1 bigint not null, +primary key (c1) +) engine=innodb; +alter table t1 add constraint c2_fk foreign key (c2) +references t2(c1) on delete cascade; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `c1` bigint(20) NOT NULL, + `c2` bigint(20) NOT NULL, + PRIMARY KEY (`c1`), + UNIQUE KEY `c2` (`c2`), + CONSTRAINT `c2_fk` FOREIGN KEY (`c2`) REFERENCES `t2` (`c1`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +alter table t1 drop foreign key c2_fk; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `c1` bigint(20) NOT NULL, + `c2` bigint(20) NOT NULL, + PRIMARY KEY (`c1`), + UNIQUE KEY `c2` (`c2`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +drop table t1, t2; +create table t1(a date) engine=innodb; +create table t2(a date, key(a)) engine=innodb; +insert into t1 values('2005-10-01'); +insert into t2 values('2005-10-01'); +select * from t1, t2 +where t2.a between t1.a - interval 2 day and t1.a + interval 2 day; +a a +2005-10-01 2005-10-01 +drop table t1, t2; +create table t1 (id int not null, f_id int not null, f int not null, +primary key(f_id, id)) engine=innodb; +create table t2 (id int not null,s_id int not null,s varchar(200), +primary key(id)) engine=innodb; +INSERT INTO t1 VALUES (8, 1, 3); +INSERT INTO t1 VALUES (1, 2, 1); +INSERT INTO t2 VALUES (1, 0, ''); +INSERT INTO t2 VALUES (8, 1, ''); +commit; +DELETE ml.* FROM t1 AS ml LEFT JOIN t2 AS mm ON (mm.id=ml.id) +WHERE mm.id IS NULL; +select ml.* from t1 as ml left join t2 as mm on (mm.id=ml.id) +where mm.id is null lock in share mode; +id f_id f +drop table t1,t2; +create table t1(a int not null, b int, primary key(a)) engine=innodb; +insert into t1 values(1,1),(2,2),(3,1),(4,2),(5,1),(6,2),(7,3); +commit; +SET binlog_format='MIXED'; +set autocommit = 0; +SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; +update t1 set b = 5 where b = 1; +SET binlog_format='MIXED'; +set autocommit = 0; +SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; +select * from t1 where a = 7 and b = 3 for update; +a b +7 3 +commit; +commit; +drop table t1; +create table t1(a int not null, b int, primary key(a)) engine=innodb; +insert into t1 values(1,1),(2,2),(3,1),(4,2),(5,1),(6,2); +commit; +set autocommit = 0; +select * from t1 lock in share mode; +a b +1 1 +2 2 +3 1 +4 2 +5 1 +6 2 +update t1 set b = 5 where b = 1; +set autocommit = 0; +select * from t1 where a = 2 and b = 2 for update; +ERROR HY000: Lock wait timeout exceeded; try restarting transaction +commit; +commit; +drop table t1; +create table t1(a int not null, b int, primary key(a)) engine=innodb; +insert into t1 values (1,2),(5,3),(4,2); +create table t2(d int not null, e int, primary key(d)) engine=innodb; +insert into t2 values (8,6),(12,1),(3,1); +commit; +set autocommit = 0; +select * from t2 for update; +d e +3 1 +8 6 +12 1 +SET binlog_format='MIXED'; +set autocommit = 0; +SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; +insert into t1 select * from t2; +update t1 set b = (select e from t2 where a = d); +create table t3(d int not null, e int, primary key(d)) engine=innodb +select * from t2; +commit; +commit; +drop table t1, t2, t3; +create table t1(a int not null, b int, primary key(a)) engine=innodb; +insert into t1 values (1,2),(5,3),(4,2); +create table t2(a int not null, b int, primary key(a)) engine=innodb; +insert into t2 values (8,6),(12,1),(3,1); +create table t3(d int not null, b int, primary key(d)) engine=innodb; +insert into t3 values (8,6),(12,1),(3,1); +create table t5(a int not null, b int, primary key(a)) engine=innodb; +insert into t5 values (1,2),(5,3),(4,2); +create table t6(d int not null, e int, primary key(d)) engine=innodb; +insert into t6 values (8,6),(12,1),(3,1); +create table t8(a int not null, b int, primary key(a)) engine=innodb; +insert into t8 values (1,2),(5,3),(4,2); +create table t9(d int not null, e int, primary key(d)) engine=innodb; +insert into t9 values (8,6),(12,1),(3,1); +commit; +set autocommit = 0; +select * from t2 for update; +a b +3 1 +8 6 +12 1 +SET binlog_format='MIXED'; +set autocommit = 0; +SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE; +insert into t1 select * from t2; +SET binlog_format='MIXED'; +set autocommit = 0; +SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE; +update t3 set b = (select b from t2 where a = d); +SET binlog_format='MIXED'; +set autocommit = 0; +SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE; +create table t4(a int not null, b int, primary key(a)) engine=innodb select * from t2; +SET binlog_format='MIXED'; +set autocommit = 0; +SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; +insert into t5 (select * from t2 lock in share mode); +SET binlog_format='MIXED'; +set autocommit = 0; +SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; +update t6 set e = (select b from t2 where a = d lock in share mode); +SET binlog_format='MIXED'; +set autocommit = 0; +SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; +create table t7(a int not null, b int, primary key(a)) engine=innodb select * from t2 lock in share mode; +SET binlog_format='MIXED'; +set autocommit = 0; +SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; +insert into t8 (select * from t2 for update); +SET binlog_format='MIXED'; +set autocommit = 0; +SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; +update t9 set e = (select b from t2 where a = d for update); +SET binlog_format='MIXED'; +set autocommit = 0; +SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; +create table t10(a int not null, b int, primary key(a)) engine=innodb select * from t2 for update; +ERROR HY000: Lock wait timeout exceeded; try restarting transaction +ERROR HY000: Lock wait timeout exceeded; try restarting transaction +ERROR HY000: Lock wait timeout exceeded; try restarting transaction +ERROR HY000: Lock wait timeout exceeded; try restarting transaction +ERROR HY000: Lock wait timeout exceeded; try restarting transaction +ERROR HY000: Lock wait timeout exceeded; try restarting transaction +ERROR HY000: Lock wait timeout exceeded; try restarting transaction +ERROR HY000: Lock wait timeout exceeded; try restarting transaction +ERROR HY000: Lock wait timeout exceeded; try restarting transaction +commit; +drop table t1, t2, t3, t5, t6, t8, t9; +CREATE TABLE t1 (DB_ROW_ID int) engine=innodb; +ERROR 42000: Incorrect column name 'DB_ROW_ID' +CREATE TABLE t1 ( +a BIGINT(20) NOT NULL, +PRIMARY KEY (a) +) ENGINE=INNODB DEFAULT CHARSET=UTF8; +CREATE TABLE t2 ( +a BIGINT(20) NOT NULL, +b VARCHAR(128) NOT NULL, +c TEXT NOT NULL, +PRIMARY KEY (a,b), +KEY idx_t2_b_c (b,c(200)), +CONSTRAINT t_fk FOREIGN KEY (a) REFERENCES t1 (a) +ON DELETE CASCADE +) ENGINE=INNODB DEFAULT CHARSET=UTF8; +INSERT INTO t1 VALUES (1); +INSERT INTO t2 VALUES (1, 'bar', 'vbar'); +INSERT INTO t2 VALUES (1, 'BAR2', 'VBAR'); +INSERT INTO t2 VALUES (1, 'bar_bar', 'bibi'); +INSERT INTO t2 VALUES (1, 'customer_over', '1'); +SELECT * FROM t2 WHERE b = 'customer_over'; +a b c +1 customer_over 1 +SELECT * FROM t2 WHERE BINARY b = 'customer_over'; +a b c +1 customer_over 1 +SELECT DISTINCT p0.a FROM t2 p0 WHERE p0.b = 'customer_over'; +a +1 +/* Bang: Empty result set, above was expected: */ +SELECT DISTINCT p0.a FROM t2 p0 WHERE BINARY p0.b = 'customer_over'; +a +1 +SELECT p0.a FROM t2 p0 WHERE BINARY p0.b = 'customer_over'; +a +1 +drop table t2, t1; +CREATE TABLE t1 ( a int ) ENGINE=innodb; +BEGIN; +INSERT INTO t1 VALUES (1); +OPTIMIZE TABLE t1; +Table Op Msg_type Msg_text +test.t1 optimize note Table does not support optimize, doing recreate + analyze instead +test.t1 optimize status OK +DROP TABLE t1; +CREATE TABLE t1 (id int PRIMARY KEY, f int NOT NULL, INDEX(f)) ENGINE=InnoDB; +CREATE TABLE t2 (id int PRIMARY KEY, f INT NOT NULL, +CONSTRAINT t2_t1 FOREIGN KEY (id) REFERENCES t1 (id) +ON DELETE CASCADE ON UPDATE CASCADE) ENGINE=InnoDB; +ALTER TABLE t2 ADD FOREIGN KEY (f) REFERENCES t1 (f) ON +DELETE CASCADE ON UPDATE CASCADE; +SHOW CREATE TABLE t2; +Table Create Table +t2 CREATE TABLE `t2` ( + `id` int(11) NOT NULL, + `f` int(11) NOT NULL, + PRIMARY KEY (`id`), + KEY `f` (`f`), + CONSTRAINT `t2_ibfk_1` FOREIGN KEY (`f`) REFERENCES `t1` (`f`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `t2_t1` FOREIGN KEY (`id`) REFERENCES `t1` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +DROP TABLE t2, t1; +CREATE TABLE t1 (a INT, INDEX(a)) ENGINE=InnoDB; +CREATE TABLE t2 (a INT, INDEX(a)) ENGINE=InnoDB; +INSERT INTO t1 VALUES (1); +INSERT INTO t2 VALUES (1); +ALTER TABLE t2 ADD FOREIGN KEY (a) REFERENCES t1 (a) ON DELETE SET NULL; +ALTER TABLE t2 MODIFY a INT NOT NULL; +ERROR HY000: Error on rename of '#sql-temporary' to './test/t2' (errno: 150) +DELETE FROM t1; +DROP TABLE t2,t1; +CREATE TABLE t1 (a VARCHAR(5) COLLATE utf8_unicode_ci PRIMARY KEY) +ENGINE=InnoDB; +INSERT INTO t1 VALUES (0xEFBCA4EFBCA4EFBCA4); +DELETE FROM t1; +INSERT INTO t1 VALUES ('DDD'); +SELECT * FROM t1; +a +DDD +DROP TABLE t1; +CREATE TABLE t1 (id int PRIMARY KEY AUTO_INCREMENT) ENGINE=InnoDB +AUTO_INCREMENT=42; +INSERT INTO t1 VALUES (0),(347),(0); +SELECT * FROM t1; +id +42 +347 +348 +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=349 DEFAULT CHARSET=latin1 +CREATE TABLE t2 (id int PRIMARY KEY) ENGINE=InnoDB; +INSERT INTO t2 VALUES(42),(347),(348); +ALTER TABLE t1 ADD CONSTRAINT t1_t2 FOREIGN KEY (id) REFERENCES t2(id); +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + PRIMARY KEY (`id`), + CONSTRAINT `t1_t2` FOREIGN KEY (`id`) REFERENCES `t2` (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=349 DEFAULT CHARSET=latin1 +DROP TABLE t1,t2; +SET innodb_strict_mode=ON; +CREATE TABLE t1 ( +c01 CHAR(255), c02 CHAR(255), c03 CHAR(255), c04 CHAR(255), +c05 CHAR(255), c06 CHAR(255), c07 CHAR(255), c08 CHAR(255), +c09 CHAR(255), c10 CHAR(255), c11 CHAR(255), c12 CHAR(255), +c13 CHAR(255), c14 CHAR(255), c15 CHAR(255), c16 CHAR(255), +c17 CHAR(255), c18 CHAR(255), c19 CHAR(255), c20 CHAR(255), +c21 CHAR(255), c22 CHAR(255), c23 CHAR(255), c24 CHAR(255), +c25 CHAR(255), c26 CHAR(255), c27 CHAR(255), c28 CHAR(255), +c29 CHAR(255), c30 CHAR(255), c31 CHAR(255), c32 CHAR(255) +) ENGINE = InnoDB; +ERROR 42000: Row size too large. The maximum row size for the used table type, not counting BLOBs, is 8126. You have to change some columns to TEXT or BLOBs +SET innodb_strict_mode=OFF; +DROP TABLE IF EXISTS t1; +Warnings: +Note 1051 Unknown table 't1' +CREATE TABLE t1( +id BIGINT(20) NOT NULL AUTO_INCREMENT PRIMARY KEY +) ENGINE=InnoDB; +INSERT INTO t1 VALUES(-10); +SELECT * FROM t1; +id +-10 +INSERT INTO t1 VALUES(NULL); +SELECT * FROM t1; +id +-10 +1 +DROP TABLE t1; +SET binlog_format='MIXED'; +SET TX_ISOLATION='read-committed'; +SET AUTOCOMMIT=0; +DROP TABLE IF EXISTS t1, t2; +Warnings: +Note 1051 Unknown table 't1' +Note 1051 Unknown table 't2' +CREATE TABLE t1 ( a int ) ENGINE=InnoDB; +CREATE TABLE t2 LIKE t1; +SELECT * FROM t2; +a +SET binlog_format='MIXED'; +SET TX_ISOLATION='read-committed'; +SET AUTOCOMMIT=0; +INSERT INTO t1 VALUES (1); +COMMIT; +SELECT * FROM t1 WHERE a=1; +a +1 +SET binlog_format='MIXED'; +SET TX_ISOLATION='read-committed'; +SET AUTOCOMMIT=0; +SELECT * FROM t2; +a +SET binlog_format='MIXED'; +SET TX_ISOLATION='read-committed'; +SET AUTOCOMMIT=0; +INSERT INTO t1 VALUES (2); +COMMIT; +SELECT * FROM t1 WHERE a=2; +a +2 +SELECT * FROM t1 WHERE a=2; +a +2 +DROP TABLE t1; +DROP TABLE t2; +create table t1 (i int, j int) engine=innodb; +insert into t1 (i, j) values (1, 1), (2, 2); +update t1 set j = 2; +affected rows: 1 +info: Rows matched: 2 Changed: 1 Warnings: 0 +drop table t1; +create table t1 (id int) comment='this is a comment' engine=innodb; +select table_comment, data_free > 0 as data_free_is_set +from information_schema.tables +where table_schema='test' and table_name = 't1'; +table_comment data_free_is_set +this is a comment 1 +drop table t1; +CREATE TABLE t1 ( +c1 INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, +c2 VARCHAR(128) NOT NULL, +PRIMARY KEY(c1) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=100; +CREATE TABLE t2 ( +c1 INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, +c2 INT(10) UNSIGNED DEFAULT NULL, +PRIMARY KEY(c1) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=200; +SELECT AUTO_INCREMENT FROM INFORMATION_SCHEMA.TABLES WHERE table_name = 't2'; +AUTO_INCREMENT +200 +ALTER TABLE t2 ADD CONSTRAINT t1_t2_1 FOREIGN KEY(c1) REFERENCES t1(c1); +SELECT AUTO_INCREMENT FROM INFORMATION_SCHEMA.TABLES WHERE table_name = 't2'; +AUTO_INCREMENT +200 +DROP TABLE t2; +DROP TABLE t1; +CREATE TABLE t1 (c1 int default NULL, +c2 int default NULL +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +TRUNCATE TABLE t1; +affected rows: 0 +INSERT INTO t1 VALUES (1, 1), (2, 2), (3, 3), (4, 4), (5, 5); +affected rows: 5 +info: Records: 5 Duplicates: 0 Warnings: 0 +TRUNCATE TABLE t1; +affected rows: 0 +DROP TABLE t1; +Variable_name Value +Handler_update 0 +Variable_name Value +Handler_delete 0 +Variable_name Value +Handler_update 1 +Variable_name Value +Handler_delete 1 diff --git a/mysql-test/suite/innodb_plugin/t/innodb_misc1-master.opt b/mysql-test/suite/innodb_plugin/t/innodb_misc1-master.opt new file mode 100644 index 00000000000..4901efb416c --- /dev/null +++ b/mysql-test/suite/innodb_plugin/t/innodb_misc1-master.opt @@ -0,0 +1 @@ +--binlog_cache_size=32768 --innodb_lock_wait_timeout=1 diff --git a/mysql-test/suite/innodb_plugin/t/innodb_misc1.test b/mysql-test/suite/innodb_plugin/t/innodb_misc1.test new file mode 100644 index 00000000000..e9b6d72aa7d --- /dev/null +++ b/mysql-test/suite/innodb_plugin/t/innodb_misc1.test @@ -0,0 +1,1178 @@ +-- source include/have_innodb_plugin.inc + +let $MYSQLD_DATADIR= `select @@datadir`; + +# Save the original values of some variables in order to be able to +# estimate how much they have changed during the tests. Previously this +# test assumed that e.g. rows_deleted is 0 here and after deleting 23 +# rows it expected that rows_deleted will be 23. Now we do not make +# assumptions about the values of the variables at the beginning, e.g. +# rows_deleted should be 23 + "rows_deleted before the test". This allows +# the test to be run multiple times without restarting the mysqld server. +# See Bug#43309 Test main.innodb can't be run twice +-- disable_query_log +SET @innodb_thread_concurrency_orig = @@innodb_thread_concurrency; +-- enable_query_log + +--disable_warnings +drop table if exists t1,t2,t3,t4; +drop database if exists mysqltest; +--enable_warnings + +# InnoDB specific varchar tests +create table t1 (v varchar(16384)) engine=innodb; +drop table t1; + +# +# BUG#11039 Wrong key length in min() +# + +create table t1 (a char(1), b char(1), key(a, b)) engine=innodb; +insert into t1 values ('8', '6'), ('4', '7'); +select min(a) from t1; +select min(b) from t1 where a='8'; +drop table t1; + +# +# Bug #11080 & #11005 Multi-row REPLACE fails on a duplicate key error +# + +CREATE TABLE t1 ( `a` int(11) NOT NULL auto_increment, `b` int(11) default NULL,PRIMARY KEY (`a`),UNIQUE KEY `b` (`b`)) ENGINE=innodb; +insert into t1 (b) values (1); +replace into t1 (b) values (2), (1), (3); +select * from t1; +truncate table t1; +insert into t1 (b) values (1); +replace into t1 (b) values (2); +replace into t1 (b) values (1); +replace into t1 (b) values (3); +select * from t1; +drop table t1; + +create table t1 (rowid int not null auto_increment, val int not null,primary +key (rowid), unique(val)) engine=innodb; +replace into t1 (val) values ('1'),('2'); +replace into t1 (val) values ('1'),('2'); +--error ER_DUP_ENTRY +insert into t1 (val) values ('1'),('2'); +select * from t1; +drop table t1; + +# +# Test that update does not change internal auto-increment value +# + +create table t1 (a int not null auto_increment primary key, val int) engine=InnoDB; +insert into t1 (val) values (1); +update t1 set a=2 where a=1; +# We should get the following error because InnoDB does not update the counter +--error ER_DUP_ENTRY +insert into t1 (val) values (1); +select * from t1; +drop table t1; +# +# Bug #10465 +# + +--disable_warnings +CREATE TABLE t1 (GRADE DECIMAL(4) NOT NULL, PRIMARY KEY (GRADE)) ENGINE=INNODB; +--enable_warnings +INSERT INTO t1 (GRADE) VALUES (151),(252),(343); +SELECT GRADE FROM t1 WHERE GRADE > 160 AND GRADE < 300; +SELECT GRADE FROM t1 WHERE GRADE= 151; +DROP TABLE t1; + +# +# Bug #12340 multitable delete deletes only one record +# +create table t1 (f1 varchar(10), f2 varchar(10), primary key (f1,f2)) engine=innodb; +create table t2 (f3 varchar(10), f4 varchar(10), key (f4)) engine=innodb; +insert into t2 values ('aa','cc'); +insert into t1 values ('aa','bb'),('aa','cc'); +delete t1 from t1,t2 where f1=f3 and f4='cc'; +select * from t1; +drop table t1,t2; + +# +# Test that the slow TRUNCATE implementation resets autoincrement columns +# (bug #11946) +# + +CREATE TABLE t1 ( +id INTEGER NOT NULL AUTO_INCREMENT, PRIMARY KEY (id) +) ENGINE=InnoDB; + +CREATE TABLE t2 ( +id INTEGER NOT NULL, +FOREIGN KEY (id) REFERENCES t1 (id) +) ENGINE=InnoDB; + +INSERT INTO t1 (id) VALUES (NULL); +SELECT * FROM t1; +TRUNCATE t1; +INSERT INTO t1 (id) VALUES (NULL); +SELECT * FROM t1; + +# continued from above; test that doing a slow TRUNCATE on a table with 0 +# rows resets autoincrement columns +DELETE FROM t1; +TRUNCATE t1; +INSERT INTO t1 (id) VALUES (NULL); +SELECT * FROM t1; +DROP TABLE t2, t1; + +# Test that foreign keys in temporary tables are not accepted (bug #12084) +CREATE TABLE t1 +( + id INT PRIMARY KEY +) ENGINE=InnoDB; + +--error 1005,1005 +CREATE TEMPORARY TABLE t2 +( + id INT NOT NULL PRIMARY KEY, + b INT, + FOREIGN KEY (b) REFERENCES test.t1(id) +) ENGINE=InnoDB; +DROP TABLE t1; + +# +# Test that index column max sizes are honored (bug #13315) +# + +# prefix index +create table t1 (col1 varchar(2000), index (col1(767))) + character set = latin1 engine = innodb; + +# normal indexes +create table t2 (col1 char(255), index (col1)) + character set = latin1 engine = innodb; +create table t3 (col1 binary(255), index (col1)) + character set = latin1 engine = innodb; +create table t4 (col1 varchar(767), index (col1)) + character set = latin1 engine = innodb; +create table t5 (col1 varchar(767) primary key) + character set = latin1 engine = innodb; +create table t6 (col1 varbinary(767) primary key) + character set = latin1 engine = innodb; +create table t7 (col1 text, index(col1(767))) + character set = latin1 engine = innodb; +create table t8 (col1 blob, index(col1(767))) + character set = latin1 engine = innodb; + +# multi-column indexes are allowed to be longer +create table t9 (col1 varchar(512), col2 varchar(512), index(col1, col2)) + character set = latin1 engine = innodb; + +show create table t9; + +drop table t1, t2, t3, t4, t5, t6, t7, t8, t9; + +# these should have their index length trimmed +create table t1 (col1 varchar(768), index(col1)) + character set = latin1 engine = innodb; +create table t2 (col1 varbinary(768), index(col1)) + character set = latin1 engine = innodb; +create table t3 (col1 text, index(col1(768))) + character set = latin1 engine = innodb; +create table t4 (col1 blob, index(col1(768))) + character set = latin1 engine = innodb; + +show create table t1; + +drop table t1, t2, t3, t4; + +# these should be refused +--error 1071 +create table t1 (col1 varchar(768) primary key) + character set = latin1 engine = innodb; +--error 1071 +create table t2 (col1 varbinary(768) primary key) + character set = latin1 engine = innodb; +--error 1071 +create table t3 (col1 text, primary key(col1(768))) + character set = latin1 engine = innodb; +--error 1071 +create table t4 (col1 blob, primary key(col1(768))) + character set = latin1 engine = innodb; + +# +# Test improved foreign key error messages (bug #3443) +# + +CREATE TABLE t1 +( + id INT PRIMARY KEY +) ENGINE=InnoDB; + +CREATE TABLE t2 +( + v INT, + CONSTRAINT c1 FOREIGN KEY (v) REFERENCES t1(id) +) ENGINE=InnoDB; + +--error 1452 +INSERT INTO t2 VALUES(2); + +INSERT INTO t1 VALUES(1); +INSERT INTO t2 VALUES(1); + +--error 1451 +DELETE FROM t1 WHERE id = 1; + +--error 1217 +DROP TABLE t1; + +SET FOREIGN_KEY_CHECKS=0; +DROP TABLE t1; +SET FOREIGN_KEY_CHECKS=1; + +--error 1452 +INSERT INTO t2 VALUES(3); + +DROP TABLE t2; +# +# Test that checksum table uses a consistent read Bug #12669 +# +connect (a,localhost,root,,); +connect (b,localhost,root,,); +connection a; +create table t1(a int not null) engine=innodb DEFAULT CHARSET=latin1; +insert into t1 values (1),(2); +set autocommit=0; +checksum table t1; +connection b; +insert into t1 values(3); +connection a; +# +# Here checksum should not see insert +# +checksum table t1; +connection a; +commit; +checksum table t1; +commit; +drop table t1; +# +# autocommit = 1 +# +connection a; +create table t1(a int not null) engine=innodb DEFAULT CHARSET=latin1; +insert into t1 values (1),(2); +set autocommit=1; +checksum table t1; +connection b; +set autocommit=1; +insert into t1 values(3); +connection a; +# +# Here checksum sees insert +# +checksum table t1; +drop table t1; + +connection default; +disconnect a; +disconnect b; + +# tests for bugs #9802 and #13778 + +# test that FKs between invalid types are not accepted + +set foreign_key_checks=0; +create table t2 (a int primary key, b int, foreign key (b) references t1(a)) engine = innodb; +# Embedded server doesn't chdir to data directory +--replace_result $MYSQLTEST_VARDIR . mysqld.1/data/ '' +-- error 1005 +create table t1(a char(10) primary key, b varchar(20)) engine = innodb; +set foreign_key_checks=1; +drop table t2; + +# test that FKs between different charsets are not accepted in CREATE even +# when f_k_c is 0 + +set foreign_key_checks=0; +create table t1(a varchar(10) primary key) engine = innodb DEFAULT CHARSET=latin1; +# Embedded server doesn't chdir to data directory +--replace_result $MYSQLTEST_VARDIR . mysqld.1/data/ '' +-- error 1005 +create table t2 (a varchar(10), foreign key (a) references t1(a)) engine = innodb DEFAULT CHARSET=utf8; +set foreign_key_checks=1; +drop table t1; + +# test that invalid datatype conversions with ALTER are not allowed + +set foreign_key_checks=0; +create table t2 (a varchar(10), foreign key (a) references t1(a)) engine = innodb; +create table t1(a varchar(10) primary key) engine = innodb; +-- error 1025,1025 +alter table t1 modify column a int; +set foreign_key_checks=1; +drop table t2,t1; + +# test that charset conversions with ALTER are allowed when f_k_c is 0 + +set foreign_key_checks=0; +create table t2 (a varchar(10), foreign key (a) references t1(a)) engine = innodb DEFAULT CHARSET=latin1; +create table t1(a varchar(10) primary key) engine = innodb DEFAULT CHARSET=latin1; +alter table t1 convert to character set utf8; +set foreign_key_checks=1; +drop table t2,t1; + +# test that RENAME does not allow invalid charsets when f_k_c is 0 + +set foreign_key_checks=0; +create table t2 (a varchar(10), foreign key (a) references t1(a)) engine = innodb DEFAULT CHARSET=latin1; +create table t3(a varchar(10) primary key) engine = innodb DEFAULT CHARSET=utf8; +# Embedded server doesn't chdir to data directory +--replace_result $MYSQLD_DATADIR ./ master-data/ '' +-- error 1025 +rename table t3 to t1; +set foreign_key_checks=1; +drop table t2,t3; + +# test that foreign key errors are reported correctly (Bug #15550) + +create table t1(a int primary key) row_format=redundant engine=innodb; +create table t2(a int primary key,constraint foreign key(a)references t1(a)) row_format=compact engine=innodb; +create table t3(a int primary key) row_format=compact engine=innodb; +create table t4(a int primary key,constraint foreign key(a)references t3(a)) row_format=redundant engine=innodb; + +insert into t1 values(1); +insert into t3 values(1); +-- error 1452 +insert into t2 values(2); +-- error 1452 +insert into t4 values(2); +insert into t2 values(1); +insert into t4 values(1); +-- error 1451 +update t1 set a=2; +-- error 1452 +update t2 set a=2; +-- error 1451 +update t3 set a=2; +-- error 1452 +update t4 set a=2; +-- error 1451 +truncate t1; +-- error 1451 +truncate t3; +truncate t2; +truncate t4; +truncate t1; +truncate t3; + +drop table t4,t3,t2,t1; + + +# +# Test that we can create a large (>1K) key +# +create table t1 (a varchar(255) character set utf8, + b varchar(255) character set utf8, + c varchar(255) character set utf8, + d varchar(255) character set utf8, + key (a,b,c,d)) engine=innodb; +drop table t1; +--error ER_TOO_LONG_KEY +create table t1 (a varchar(255) character set utf8, + b varchar(255) character set utf8, + c varchar(255) character set utf8, + d varchar(255) character set utf8, + e varchar(255) character set utf8, + key (a,b,c,d,e)) engine=innodb; + + +# test the padding of BINARY types and collations (Bug #14189) + +create table t1 (s1 varbinary(2),primary key (s1)) engine=innodb; +create table t2 (s1 binary(2),primary key (s1)) engine=innodb; +create table t3 (s1 varchar(2) binary,primary key (s1)) engine=innodb; +create table t4 (s1 char(2) binary,primary key (s1)) engine=innodb; + +insert into t1 values (0x41),(0x4120),(0x4100); +-- error ER_DUP_ENTRY +insert into t2 values (0x41),(0x4120),(0x4100); +insert into t2 values (0x41),(0x4120); +-- error ER_DUP_ENTRY +insert into t3 values (0x41),(0x4120),(0x4100); +insert into t3 values (0x41),(0x4100); +-- error ER_DUP_ENTRY +insert into t4 values (0x41),(0x4120),(0x4100); +insert into t4 values (0x41),(0x4100); +select hex(s1) from t1; +select hex(s1) from t2; +select hex(s1) from t3; +select hex(s1) from t4; +drop table t1,t2,t3,t4; + +create table t1 (a int primary key,s1 varbinary(3) not null unique) engine=innodb; +create table t2 (s1 binary(2) not null, constraint c foreign key(s1) references t1(s1) on update cascade) engine=innodb; + +insert into t1 values(1,0x4100),(2,0x41),(3,0x4120),(4,0x42); +-- error 1452 +insert into t2 values(0x42); +insert into t2 values(0x41); +select hex(s1) from t2; +update t1 set s1=0x123456 where a=2; +select hex(s1) from t2; +-- error 1451 +update t1 set s1=0x12 where a=1; +-- error 1451 +update t1 set s1=0x12345678 where a=1; +-- error 1451 +update t1 set s1=0x123457 where a=1; +update t1 set s1=0x1220 where a=1; +select hex(s1) from t2; +update t1 set s1=0x1200 where a=1; +select hex(s1) from t2; +update t1 set s1=0x4200 where a=1; +select hex(s1) from t2; +-- error 1451 +delete from t1 where a=1; +delete from t1 where a=2; +update t2 set s1=0x4120; +-- error 1451 +delete from t1; +delete from t1 where a!=3; +select a,hex(s1) from t1; +select hex(s1) from t2; + +drop table t2,t1; + +create table t1 (a int primary key,s1 varchar(2) binary not null unique) engine=innodb; +create table t2 (s1 char(2) binary not null, constraint c foreign key(s1) references t1(s1) on update cascade) engine=innodb; + +insert into t1 values(1,0x4100),(2,0x41); +insert into t2 values(0x41); +select hex(s1) from t2; +update t1 set s1=0x1234 where a=1; +select hex(s1) from t2; +update t1 set s1=0x12 where a=2; +select hex(s1) from t2; +delete from t1 where a=1; +-- error 1451 +delete from t1 where a=2; +select a,hex(s1) from t1; +select hex(s1) from t2; + +drop table t2,t1; +# Ensure that _ibfk_0 is not mistreated as a +# generated foreign key identifier. (Bug #16387) + +CREATE TABLE t1(a INT, PRIMARY KEY(a)) ENGINE=InnoDB; +CREATE TABLE t2(a INT) ENGINE=InnoDB; +ALTER TABLE t2 ADD FOREIGN KEY (a) REFERENCES t1(a); +ALTER TABLE t2 DROP FOREIGN KEY t2_ibfk_1; +ALTER TABLE t2 ADD CONSTRAINT t2_ibfk_0 FOREIGN KEY (a) REFERENCES t1(a); +ALTER TABLE t2 DROP FOREIGN KEY t2_ibfk_0; +SHOW CREATE TABLE t2; +DROP TABLE t2,t1; + +# +# Test case for bug #16229: MySQL/InnoDB uses full explicit table locks in trigger processing +# + +connect (a,localhost,root,,); +connect (b,localhost,root,,); +connection a; +create table t1(a int not null, b int, c int, d int, primary key(a)) engine=innodb; +insert into t1(a) values (1),(2),(3); +commit; +connection b; +set autocommit = 0; +update t1 set b = 5 where a = 2; +connection a; +delimiter |; +create trigger t1t before insert on t1 for each row begin set NEW.b = NEW.a * 10 + 5, NEW.c = NEW.a / 10; end | +delimiter ;| +set autocommit = 0; +connection a; +insert into t1(a) values (10),(20),(30),(40),(50),(60),(70),(80),(90),(100), +(11),(21),(31),(41),(51),(61),(71),(81),(91),(101), +(12),(22),(32),(42),(52),(62),(72),(82),(92),(102), +(13),(23),(33),(43),(53),(63),(73),(83),(93),(103), +(14),(24),(34),(44),(54),(64),(74),(84),(94),(104); +connection b; +commit; +connection a; +commit; +drop trigger t1t; +drop table t1; +disconnect a; +disconnect b; +# +# Another trigger test +# +connect (a,localhost,root,,); +connect (b,localhost,root,,); +connection a; +create table t1(a int not null, b int, c int, d int, primary key(a)) engine=innodb; +create table t2(a int not null, b int, c int, d int, primary key(a)) engine=innodb; +create table t3(a int not null, b int, c int, d int, primary key(a)) engine=innodb; +create table t4(a int not null, b int, c int, d int, primary key(a)) engine=innodb; +create table t5(a int not null, b int, c int, d int, primary key(a)) engine=innodb; +insert into t1(a) values (1),(2),(3); +insert into t2(a) values (1),(2),(3); +insert into t3(a) values (1),(2),(3); +insert into t4(a) values (1),(2),(3); +insert into t3(a) values (5),(7),(8); +insert into t4(a) values (5),(7),(8); +insert into t5(a) values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12); + +delimiter |; +create trigger t1t before insert on t1 for each row begin + INSERT INTO t2 SET a = NEW.a; +end | + +create trigger t2t before insert on t2 for each row begin + DELETE FROM t3 WHERE a = NEW.a; +end | + +create trigger t3t before delete on t3 for each row begin + UPDATE t4 SET b = b + 1 WHERE a = OLD.a; +end | + +create trigger t4t before update on t4 for each row begin + UPDATE t5 SET b = b + 1 where a = NEW.a; +end | +delimiter ;| +commit; +set autocommit = 0; +update t1 set b = b + 5 where a = 1; +update t2 set b = b + 5 where a = 1; +update t3 set b = b + 5 where a = 1; +update t4 set b = b + 5 where a = 1; +insert into t5(a) values(20); +connection b; +set autocommit = 0; +insert into t1(a) values(7); +insert into t2(a) values(8); +delete from t2 where a = 3; +update t4 set b = b + 1 where a = 3; +commit; +drop trigger t1t; +drop trigger t2t; +drop trigger t3t; +drop trigger t4t; +drop table t1, t2, t3, t4, t5; +connection default; +disconnect a; +disconnect b; + +# +# Test that cascading updates leading to duplicate keys give the correct +# error message (bug #9680) +# + +CREATE TABLE t1 ( + field1 varchar(8) NOT NULL DEFAULT '', + field2 varchar(8) NOT NULL DEFAULT '', + PRIMARY KEY (field1, field2) +) ENGINE=InnoDB; + +CREATE TABLE t2 ( + field1 varchar(8) NOT NULL DEFAULT '' PRIMARY KEY, + FOREIGN KEY (field1) REFERENCES t1 (field1) + ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB; + +INSERT INTO t1 VALUES ('old', 'somevalu'); +INSERT INTO t1 VALUES ('other', 'anyvalue'); + +INSERT INTO t2 VALUES ('old'); +INSERT INTO t2 VALUES ('other'); + +--error ER_FOREIGN_DUPLICATE_KEY +UPDATE t1 SET field1 = 'other' WHERE field2 = 'somevalu'; + +DROP TABLE t2; +DROP TABLE t1; + +# +# Bug#18477 - MySQL/InnoDB Ignoring Foreign Keys in ALTER TABLE +# +create table t1 ( + c1 bigint not null, + c2 bigint not null, + primary key (c1), + unique key (c2) +) engine=innodb; +# +create table t2 ( + c1 bigint not null, + primary key (c1) +) engine=innodb; +# +alter table t1 add constraint c2_fk foreign key (c2) + references t2(c1) on delete cascade; +show create table t1; +# +alter table t1 drop foreign key c2_fk; +show create table t1; +# +drop table t1, t2; + +# +# Bug #14360: problem with intervals +# + +create table t1(a date) engine=innodb; +create table t2(a date, key(a)) engine=innodb; +insert into t1 values('2005-10-01'); +insert into t2 values('2005-10-01'); +select * from t1, t2 + where t2.a between t1.a - interval 2 day and t1.a + interval 2 day; +drop table t1, t2; + +create table t1 (id int not null, f_id int not null, f int not null, +primary key(f_id, id)) engine=innodb; +create table t2 (id int not null,s_id int not null,s varchar(200), +primary key(id)) engine=innodb; +INSERT INTO t1 VALUES (8, 1, 3); +INSERT INTO t1 VALUES (1, 2, 1); +INSERT INTO t2 VALUES (1, 0, ''); +INSERT INTO t2 VALUES (8, 1, ''); +commit; +DELETE ml.* FROM t1 AS ml LEFT JOIN t2 AS mm ON (mm.id=ml.id) +WHERE mm.id IS NULL; +select ml.* from t1 as ml left join t2 as mm on (mm.id=ml.id) +where mm.id is null lock in share mode; +drop table t1,t2; + +# +# Test case where X-locks on unused rows should be released in a +# update (because READ COMMITTED isolation level) +# + +connect (a,localhost,root,,); +connect (b,localhost,root,,); +connection a; +create table t1(a int not null, b int, primary key(a)) engine=innodb; +insert into t1 values(1,1),(2,2),(3,1),(4,2),(5,1),(6,2),(7,3); +commit; +SET binlog_format='MIXED'; +set autocommit = 0; +SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; +update t1 set b = 5 where b = 1; +connection b; +SET binlog_format='MIXED'; +set autocommit = 0; +SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; +# +# X-lock to record (7,3) should be released in a update +# +select * from t1 where a = 7 and b = 3 for update; +connection a; +commit; +connection b; +commit; +drop table t1; +connection default; +disconnect a; +disconnect b; + +# +# Test case where no locks should be released (because we are not +# using READ COMMITTED isolation level) +# + +connect (a,localhost,root,,); +connect (b,localhost,root,,); +connection a; +create table t1(a int not null, b int, primary key(a)) engine=innodb; +insert into t1 values(1,1),(2,2),(3,1),(4,2),(5,1),(6,2); +commit; +set autocommit = 0; +select * from t1 lock in share mode; +update t1 set b = 5 where b = 1; +connection b; +set autocommit = 0; +# +# S-lock to records (2,2),(4,2), and (6,2) should not be released in a update +# +--error 1205 +select * from t1 where a = 2 and b = 2 for update; +# +# X-lock to record (1,1),(3,1),(5,1) should not be released in a update +# +--error 1205 +connection a; +commit; +connection b; +commit; +connection default; +disconnect a; +disconnect b; +drop table t1; + +# +# Consistent read should be used in following selects +# +# 1) INSERT INTO ... SELECT +# 2) UPDATE ... = ( SELECT ...) +# 3) CREATE ... SELECT + +connect (a,localhost,root,,); +connect (b,localhost,root,,); +connection a; +create table t1(a int not null, b int, primary key(a)) engine=innodb; +insert into t1 values (1,2),(5,3),(4,2); +create table t2(d int not null, e int, primary key(d)) engine=innodb; +insert into t2 values (8,6),(12,1),(3,1); +commit; +set autocommit = 0; +select * from t2 for update; +connection b; +SET binlog_format='MIXED'; +set autocommit = 0; +SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; +insert into t1 select * from t2; +update t1 set b = (select e from t2 where a = d); +create table t3(d int not null, e int, primary key(d)) engine=innodb +select * from t2; +commit; +connection a; +commit; +connection default; +disconnect a; +disconnect b; +drop table t1, t2, t3; + +# +# Consistent read should not be used if +# +# (a) isolation level is serializable OR +# (b) select ... lock in share mode OR +# (c) select ... for update +# +# in following queries: +# +# 1) INSERT INTO ... SELECT +# 2) UPDATE ... = ( SELECT ...) +# 3) CREATE ... SELECT + +connect (a,localhost,root,,); +connect (b,localhost,root,,); +connect (c,localhost,root,,); +connect (d,localhost,root,,); +connect (e,localhost,root,,); +connect (f,localhost,root,,); +connect (g,localhost,root,,); +connect (h,localhost,root,,); +connect (i,localhost,root,,); +connect (j,localhost,root,,); +connection a; +create table t1(a int not null, b int, primary key(a)) engine=innodb; +insert into t1 values (1,2),(5,3),(4,2); +create table t2(a int not null, b int, primary key(a)) engine=innodb; +insert into t2 values (8,6),(12,1),(3,1); +create table t3(d int not null, b int, primary key(d)) engine=innodb; +insert into t3 values (8,6),(12,1),(3,1); +create table t5(a int not null, b int, primary key(a)) engine=innodb; +insert into t5 values (1,2),(5,3),(4,2); +create table t6(d int not null, e int, primary key(d)) engine=innodb; +insert into t6 values (8,6),(12,1),(3,1); +create table t8(a int not null, b int, primary key(a)) engine=innodb; +insert into t8 values (1,2),(5,3),(4,2); +create table t9(d int not null, e int, primary key(d)) engine=innodb; +insert into t9 values (8,6),(12,1),(3,1); +commit; +set autocommit = 0; +select * from t2 for update; +connection b; +SET binlog_format='MIXED'; +set autocommit = 0; +SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE; +--send +insert into t1 select * from t2; +connection c; +SET binlog_format='MIXED'; +set autocommit = 0; +SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE; +--send +update t3 set b = (select b from t2 where a = d); +connection d; +SET binlog_format='MIXED'; +set autocommit = 0; +SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE; +--send +create table t4(a int not null, b int, primary key(a)) engine=innodb select * from t2; +connection e; +SET binlog_format='MIXED'; +set autocommit = 0; +SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; +--send +insert into t5 (select * from t2 lock in share mode); +connection f; +SET binlog_format='MIXED'; +set autocommit = 0; +SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; +--send +update t6 set e = (select b from t2 where a = d lock in share mode); +connection g; +SET binlog_format='MIXED'; +set autocommit = 0; +SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; +--send +create table t7(a int not null, b int, primary key(a)) engine=innodb select * from t2 lock in share mode; +connection h; +SET binlog_format='MIXED'; +set autocommit = 0; +SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; +--send +insert into t8 (select * from t2 for update); +connection i; +SET binlog_format='MIXED'; +set autocommit = 0; +SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; +--send +update t9 set e = (select b from t2 where a = d for update); +connection j; +SET binlog_format='MIXED'; +set autocommit = 0; +SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; +--send +create table t10(a int not null, b int, primary key(a)) engine=innodb select * from t2 for update; + +connection b; +--error 1205 +reap; + +connection c; +--error 1205 +reap; + +connection d; +--error 1205 +reap; + +connection e; +--error 1205 +reap; + +connection f; +--error 1205 +reap; + +connection g; +--error 1205 +reap; + +connection h; +--error 1205 +reap; + +connection i; +--error 1205 +reap; + +connection j; +--error 1205 +reap; + +connection a; +commit; + +connection default; +disconnect a; +disconnect b; +disconnect c; +disconnect d; +disconnect e; +disconnect f; +disconnect g; +disconnect h; +disconnect i; +disconnect j; +drop table t1, t2, t3, t5, t6, t8, t9; + +# bug 18934, "InnoDB crashes when table uses column names like DB_ROW_ID" +--error ER_WRONG_COLUMN_NAME +CREATE TABLE t1 (DB_ROW_ID int) engine=innodb; + +# +# Bug #17152: Wrong result with BINARY comparison on aliased column +# + +CREATE TABLE t1 ( + a BIGINT(20) NOT NULL, + PRIMARY KEY (a) + ) ENGINE=INNODB DEFAULT CHARSET=UTF8; + +CREATE TABLE t2 ( + a BIGINT(20) NOT NULL, + b VARCHAR(128) NOT NULL, + c TEXT NOT NULL, + PRIMARY KEY (a,b), + KEY idx_t2_b_c (b,c(200)), + CONSTRAINT t_fk FOREIGN KEY (a) REFERENCES t1 (a) + ON DELETE CASCADE + ) ENGINE=INNODB DEFAULT CHARSET=UTF8; + +INSERT INTO t1 VALUES (1); +INSERT INTO t2 VALUES (1, 'bar', 'vbar'); +INSERT INTO t2 VALUES (1, 'BAR2', 'VBAR'); +INSERT INTO t2 VALUES (1, 'bar_bar', 'bibi'); +INSERT INTO t2 VALUES (1, 'customer_over', '1'); + +SELECT * FROM t2 WHERE b = 'customer_over'; +SELECT * FROM t2 WHERE BINARY b = 'customer_over'; +SELECT DISTINCT p0.a FROM t2 p0 WHERE p0.b = 'customer_over'; +/* Bang: Empty result set, above was expected: */ +SELECT DISTINCT p0.a FROM t2 p0 WHERE BINARY p0.b = 'customer_over'; +SELECT p0.a FROM t2 p0 WHERE BINARY p0.b = 'customer_over'; + +drop table t2, t1; + +# +# Test optimize on table with open transaction +# + +CREATE TABLE t1 ( a int ) ENGINE=innodb; +BEGIN; +INSERT INTO t1 VALUES (1); +OPTIMIZE TABLE t1; +DROP TABLE t1; + +# +# Bug #24741 (existing cascade clauses disappear when adding foreign keys) +# + +CREATE TABLE t1 (id int PRIMARY KEY, f int NOT NULL, INDEX(f)) ENGINE=InnoDB; + +CREATE TABLE t2 (id int PRIMARY KEY, f INT NOT NULL, + CONSTRAINT t2_t1 FOREIGN KEY (id) REFERENCES t1 (id) + ON DELETE CASCADE ON UPDATE CASCADE) ENGINE=InnoDB; + +ALTER TABLE t2 ADD FOREIGN KEY (f) REFERENCES t1 (f) ON +DELETE CASCADE ON UPDATE CASCADE; + +SHOW CREATE TABLE t2; +DROP TABLE t2, t1; + +# +# Bug #25927: Prevent ALTER TABLE ... MODIFY ... NOT NULL on columns +# for which there is a foreign key constraint ON ... SET NULL. +# + +CREATE TABLE t1 (a INT, INDEX(a)) ENGINE=InnoDB; +CREATE TABLE t2 (a INT, INDEX(a)) ENGINE=InnoDB; +INSERT INTO t1 VALUES (1); +INSERT INTO t2 VALUES (1); +ALTER TABLE t2 ADD FOREIGN KEY (a) REFERENCES t1 (a) ON DELETE SET NULL; +# mysqltest first does replace_regex, then replace_result +--replace_regex /'[^']*test\/#sql-[0-9a-f_]*'/'#sql-temporary'/ +# Embedded server doesn't chdir to data directory +--replace_result $MYSQLD_DATADIR ./ master-data/ '' +--error 1025 +ALTER TABLE t2 MODIFY a INT NOT NULL; +DELETE FROM t1; +DROP TABLE t2,t1; + +# +# Bug #26835: table corruption after delete+insert +# + +CREATE TABLE t1 (a VARCHAR(5) COLLATE utf8_unicode_ci PRIMARY KEY) +ENGINE=InnoDB; +INSERT INTO t1 VALUES (0xEFBCA4EFBCA4EFBCA4); +DELETE FROM t1; +INSERT INTO t1 VALUES ('DDD'); +SELECT * FROM t1; +DROP TABLE t1; + +# +# Bug #23313 (AUTO_INCREMENT=# not reported back for InnoDB tables) +# Bug #21404 (AUTO_INCREMENT value reset when Adding FKEY (or ALTER?)) +# + +CREATE TABLE t1 (id int PRIMARY KEY AUTO_INCREMENT) ENGINE=InnoDB +AUTO_INCREMENT=42; + +INSERT INTO t1 VALUES (0),(347),(0); +SELECT * FROM t1; + +SHOW CREATE TABLE t1; + +CREATE TABLE t2 (id int PRIMARY KEY) ENGINE=InnoDB; +INSERT INTO t2 VALUES(42),(347),(348); +ALTER TABLE t1 ADD CONSTRAINT t1_t2 FOREIGN KEY (id) REFERENCES t2(id); +SHOW CREATE TABLE t1; + +DROP TABLE t1,t2; + +# +# Bug #21101 (Prints wrong error message if max row size is too large) +# +SET innodb_strict_mode=ON; +--error 1118 +CREATE TABLE t1 ( + c01 CHAR(255), c02 CHAR(255), c03 CHAR(255), c04 CHAR(255), + c05 CHAR(255), c06 CHAR(255), c07 CHAR(255), c08 CHAR(255), + c09 CHAR(255), c10 CHAR(255), c11 CHAR(255), c12 CHAR(255), + c13 CHAR(255), c14 CHAR(255), c15 CHAR(255), c16 CHAR(255), + c17 CHAR(255), c18 CHAR(255), c19 CHAR(255), c20 CHAR(255), + c21 CHAR(255), c22 CHAR(255), c23 CHAR(255), c24 CHAR(255), + c25 CHAR(255), c26 CHAR(255), c27 CHAR(255), c28 CHAR(255), + c29 CHAR(255), c30 CHAR(255), c31 CHAR(255), c32 CHAR(255) + ) ENGINE = InnoDB; +SET innodb_strict_mode=OFF; + +# +# Bug #31860 InnoDB assumes AUTOINC values can only be positive. +# +DROP TABLE IF EXISTS t1; +CREATE TABLE t1( + id BIGINT(20) NOT NULL AUTO_INCREMENT PRIMARY KEY + ) ENGINE=InnoDB; +INSERT INTO t1 VALUES(-10); +SELECT * FROM t1; +# +# NOTE: The server really needs to be restarted at this point +# for the test to be useful. +# +# Without the fix InnoDB would trip over an assertion here. +INSERT INTO t1 VALUES(NULL); +# The next value should be 1 and not -9 or a -ve number +SELECT * FROM t1; +DROP TABLE t1; + +# +# Bug #21409 Incorrect result returned when in READ-COMMITTED with +# query_cache ON +# +CONNECT (c1,localhost,root,,); +CONNECT (c2,localhost,root,,); +CONNECTION c1; +SET binlog_format='MIXED'; +SET TX_ISOLATION='read-committed'; +SET AUTOCOMMIT=0; +DROP TABLE IF EXISTS t1, t2; +CREATE TABLE t1 ( a int ) ENGINE=InnoDB; +CREATE TABLE t2 LIKE t1; +SELECT * FROM t2; +CONNECTION c2; +SET binlog_format='MIXED'; +SET TX_ISOLATION='read-committed'; +SET AUTOCOMMIT=0; +INSERT INTO t1 VALUES (1); +COMMIT; +CONNECTION c1; +SELECT * FROM t1 WHERE a=1; +DISCONNECT c1; +DISCONNECT c2; +CONNECT (c1,localhost,root,,); +CONNECT (c2,localhost,root,,); +CONNECTION c1; +SET binlog_format='MIXED'; +SET TX_ISOLATION='read-committed'; +SET AUTOCOMMIT=0; +SELECT * FROM t2; +CONNECTION c2; +SET binlog_format='MIXED'; +SET TX_ISOLATION='read-committed'; +SET AUTOCOMMIT=0; +INSERT INTO t1 VALUES (2); +COMMIT; +CONNECTION c1; +# The result set below should be the same for both selects +SELECT * FROM t1 WHERE a=2; +SELECT * FROM t1 WHERE a=2; +DROP TABLE t1; +DROP TABLE t2; +DISCONNECT c1; +DISCONNECT c2; +CONNECTION default; + +# +# Bug #29157 UPDATE, changed rows incorrect +# +create table t1 (i int, j int) engine=innodb; +insert into t1 (i, j) values (1, 1), (2, 2); +--enable_info +update t1 set j = 2; +--disable_info +drop table t1; + +# +# Bug #32440 InnoDB free space info does not appear in SHOW TABLE STATUS or +# I_S +# +create table t1 (id int) comment='this is a comment' engine=innodb; +select table_comment, data_free > 0 as data_free_is_set + from information_schema.tables + where table_schema='test' and table_name = 't1'; +drop table t1; + +# +# Bug 34920 test +# +CONNECTION default; +CREATE TABLE t1 ( + c1 INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, + c2 VARCHAR(128) NOT NULL, + PRIMARY KEY(c1) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=100; + +CREATE TABLE t2 ( + c1 INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, + c2 INT(10) UNSIGNED DEFAULT NULL, + PRIMARY KEY(c1) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=200; + +SELECT AUTO_INCREMENT FROM INFORMATION_SCHEMA.TABLES WHERE table_name = 't2'; +ALTER TABLE t2 ADD CONSTRAINT t1_t2_1 FOREIGN KEY(c1) REFERENCES t1(c1); +SELECT AUTO_INCREMENT FROM INFORMATION_SCHEMA.TABLES WHERE table_name = 't2'; +DROP TABLE t2; +DROP TABLE t1; +# End 34920 test +# +# Bug #29507 TRUNCATE shows to many rows effected +# +CONNECTION default; +CREATE TABLE t1 (c1 int default NULL, + c2 int default NULL +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +--enable_info +TRUNCATE TABLE t1; + +INSERT INTO t1 VALUES (1, 1), (2, 2), (3, 3), (4, 4), (5, 5); +TRUNCATE TABLE t1; + +--disable_info +DROP TABLE t1; +# +# Bug#35537 Innodb doesn't increment handler_update and handler_delete. +# +-- disable_query_log +-- disable_result_log + +CONNECT (c1,localhost,root,,); + +DROP TABLE IF EXISTS bug35537; +CREATE TABLE bug35537 ( + c1 int +) ENGINE=InnoDB; + +INSERT INTO bug35537 VALUES (1); + +-- enable_result_log + +SHOW SESSION STATUS LIKE 'Handler_update%'; +SHOW SESSION STATUS LIKE 'Handler_delete%'; + +UPDATE bug35537 SET c1 = 2 WHERE c1 = 1; +DELETE FROM bug35537 WHERE c1 = 2; + +SHOW SESSION STATUS LIKE 'Handler_update%'; +SHOW SESSION STATUS LIKE 'Handler_delete%'; + +DROP TABLE bug35537; + +DISCONNECT c1; +CONNECTION default; + +SET GLOBAL innodb_thread_concurrency = @innodb_thread_concurrency_orig; From 91b5e9352a82b096197aa9f24f149cf3bf892b8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Wed, 26 Oct 2011 11:44:28 +0300 Subject: [PATCH 16/19] Revert most of revno 3560.9.1 (Bug#12704861) This was an attempt to address problems with the Bug#12612184 fix. Even with this follow-up fix, crash recovery can be broken. Let us fix the bug later. --- .../suite/innodb_plugin/r/innodb-index.result | 14 ++ .../suite/innodb_plugin/t/innodb-index.test | 19 +- storage/innobase/btr/btr0btr.c | 211 ++--------------- storage/innobase/btr/btr0cur.c | 92 +++---- storage/innobase/fsp/fsp0fsp.c | 159 ++++--------- storage/innobase/include/btr0btr.h | 31 +-- storage/innobase/include/btr0cur.h | 14 +- storage/innobase/include/fsp0fsp.h | 6 +- storage/innobase/include/mtr0mtr.h | 7 +- storage/innobase/include/mtr0mtr.ic | 4 +- storage/innobase/mtr/mtr0mtr.c | 10 +- storage/innobase/row/row0ins.c | 32 +-- storage/innobase/row/row0row.c | 32 ++- storage/innobase/row/row0upd.c | 21 +- storage/innobase/trx/trx0undo.c | 2 +- storage/innodb_plugin/ChangeLog | 9 - storage/innodb_plugin/btr/btr0btr.c | 211 ++--------------- storage/innodb_plugin/btr/btr0cur.c | 114 +++++---- storage/innodb_plugin/fsp/fsp0fsp.c | 224 +++++++----------- storage/innodb_plugin/include/btr0btr.h | 34 +-- storage/innodb_plugin/include/btr0cur.h | 38 +-- storage/innodb_plugin/include/fsp0fsp.h | 25 +- storage/innodb_plugin/include/mtr0mtr.h | 11 +- storage/innodb_plugin/include/mtr0mtr.ic | 4 +- storage/innodb_plugin/mtr/mtr0mtr.c | 5 +- storage/innodb_plugin/row/row0ins.c | 31 +-- storage/innodb_plugin/row/row0row.c | 25 +- storage/innodb_plugin/row/row0upd.c | 21 +- storage/innodb_plugin/trx/trx0undo.c | 2 +- 29 files changed, 441 insertions(+), 967 deletions(-) diff --git a/mysql-test/suite/innodb_plugin/r/innodb-index.result b/mysql-test/suite/innodb_plugin/r/innodb-index.result index 8640ff94d9e..9600d0a79b4 100644 --- a/mysql-test/suite/innodb_plugin/r/innodb-index.result +++ b/mysql-test/suite/innodb_plugin/r/innodb-index.result @@ -1094,6 +1094,20 @@ COMMIT; UPDATE bug12547647 SET c = REPEAT('b',16928); ERROR 42000: Row size too large. The maximum row size for the used table type, not counting BLOBs, is 8126. You have to change some columns to TEXT or BLOBs DROP TABLE bug12547647; +SET @r=REPEAT('a',500); +CREATE TABLE t1(a INT, +v1 VARCHAR(500), v2 VARCHAR(500), v3 VARCHAR(500), +v4 VARCHAR(500), v5 VARCHAR(500), v6 VARCHAR(500), +v7 VARCHAR(500), v8 VARCHAR(500), v9 VARCHAR(500), +v10 VARCHAR(500), v11 VARCHAR(500), v12 VARCHAR(500), +v13 VARCHAR(500), v14 VARCHAR(500), v15 VARCHAR(500), +v16 VARCHAR(500), v17 VARCHAR(500), v18 VARCHAR(500) +) ENGINE=InnoDB ROW_FORMAT=DYNAMIC; +CREATE INDEX idx1 ON t1(a,v1); +INSERT INTO t1 VALUES(9,@r,@r,@r,@r,@r,@r,@r,@r,@r,@r,@r,@r,@r,@r,@r,@r,@r,@r); +UPDATE t1 SET a=1000; +DELETE FROM t1; +DROP TABLE t1; set global innodb_file_per_table=0; set global innodb_file_format=Antelope; set global innodb_file_format_check=Antelope; diff --git a/mysql-test/suite/innodb_plugin/t/innodb-index.test b/mysql-test/suite/innodb_plugin/t/innodb-index.test index 568661c67f1..0d891939d6e 100644 --- a/mysql-test/suite/innodb_plugin/t/innodb-index.test +++ b/mysql-test/suite/innodb_plugin/t/innodb-index.test @@ -527,13 +527,30 @@ CREATE TABLE bug12547647( a INT NOT NULL, b BLOB NOT NULL, c TEXT, PRIMARY KEY (b(10), a), INDEX (c(10)) ) ENGINE=InnoDB ROW_FORMAT=DYNAMIC; - INSERT INTO bug12547647 VALUES (5,repeat('khdfo5AlOq',1900),repeat('g',7731)); COMMIT; # The following used to cause infinite undo log allocation. --error ER_TOO_BIG_ROWSIZE UPDATE bug12547647 SET c = REPEAT('b',16928); DROP TABLE bug12547647; +# Bug#12637786 +SET @r=REPEAT('a',500); +CREATE TABLE t1(a INT, + v1 VARCHAR(500), v2 VARCHAR(500), v3 VARCHAR(500), + v4 VARCHAR(500), v5 VARCHAR(500), v6 VARCHAR(500), + v7 VARCHAR(500), v8 VARCHAR(500), v9 VARCHAR(500), + v10 VARCHAR(500), v11 VARCHAR(500), v12 VARCHAR(500), + v13 VARCHAR(500), v14 VARCHAR(500), v15 VARCHAR(500), + v16 VARCHAR(500), v17 VARCHAR(500), v18 VARCHAR(500) +) ENGINE=InnoDB ROW_FORMAT=DYNAMIC; + +CREATE INDEX idx1 ON t1(a,v1); +INSERT INTO t1 VALUES(9,@r,@r,@r,@r,@r,@r,@r,@r,@r,@r,@r,@r,@r,@r,@r,@r,@r,@r); +UPDATE t1 SET a=1000; +DELETE FROM t1; +# Let the purge thread clean up this file. +-- sleep 10 +DROP TABLE t1; eval set global innodb_file_per_table=$per_table; eval set global innodb_file_format=$format; diff --git a/storage/innobase/btr/btr0btr.c b/storage/innobase/btr/btr0btr.c index ad99913cf3b..e8e065a3116 100644 --- a/storage/innobase/btr/btr0btr.c +++ b/storage/innobase/btr/btr0btr.c @@ -300,30 +300,29 @@ btr_page_alloc_for_ibuf( /****************************************************************** Allocates a new file page to be used in an index tree. NOTE: we assume that the caller has made the reservation for free extents! */ -static -ulint -btr_page_alloc_low( -/*===============*/ - /* out: allocated page number, - FIL_NULL if out of space */ + +page_t* +btr_page_alloc( +/*===========*/ + /* out: new allocated page, x-latched; + NULL if out of space */ dict_index_t* index, /* in: index */ ulint hint_page_no, /* in: hint of a good page */ byte file_direction, /* in: direction where a possible page split is made */ ulint level, /* in: level where the page is placed in the tree */ - mtr_t* mtr, /* in/out: mini-transaction - for the allocation */ - mtr_t* init_mtr) /* in/out: mini-transaction - in which the page should be - initialized (may be the same - as mtr), or NULL if it should - not be initialized (the page - at hint was previously freed - in mtr) */ + mtr_t* mtr) /* in: mtr */ { fseg_header_t* seg_header; page_t* root; + page_t* new_page; + ulint new_page_no; + + if (index->type & DICT_IBUF) { + + return(btr_page_alloc_for_ibuf(index, mtr)); + } root = btr_root_get(index, mtr); @@ -337,61 +336,19 @@ btr_page_alloc_low( reservation for free extents, and thus we know that a page can be allocated: */ - return(fseg_alloc_free_page_general(seg_header, hint_page_no, - file_direction, TRUE, - mtr, init_mtr)); -} - -/**************************************************************//** -Allocates a new file page to be used in an index tree. NOTE: we assume -that the caller has made the reservation for free extents! */ - -page_t* -btr_page_alloc( -/*===========*/ - /* out: new allocated block, x-latched; - NULL if out of space */ - dict_index_t* index, /* in: index */ - ulint hint_page_no, /* in: hint of a good page */ - byte file_direction, /* in: direction where a possible - page split is made */ - ulint level, /* in: level where the page is placed - in the tree */ - mtr_t* mtr, /* in/out: mini-transaction - for the allocation */ - mtr_t* init_mtr) /* in/out: mini-transaction - for x-latching and initializing - the page */ -{ - page_t* new_page; - ulint new_page_no; - - if (index->type & DICT_IBUF) { - - return(btr_page_alloc_for_ibuf(index, mtr)); - } - - new_page_no = btr_page_alloc_low( - index, hint_page_no, file_direction, level, mtr, init_mtr); - + new_page_no = fseg_alloc_free_page_general(seg_header, hint_page_no, + file_direction, TRUE, mtr); if (new_page_no == FIL_NULL) { return(NULL); } new_page = buf_page_get(dict_index_get_space(index), new_page_no, - RW_X_LATCH, init_mtr); + RW_X_LATCH, mtr); #ifdef UNIV_SYNC_DEBUG buf_page_dbg_add_level(new_page, SYNC_TREE_NODE_NEW); #endif /* UNIV_SYNC_DEBUG */ - if (mtr->freed_clust_leaf) { - mtr_memo_release(mtr, new_page, MTR_MEMO_FREE_CLUST_LEAF); - ut_ad(!mtr_memo_contains(mtr, buf_block_align(new_page), - MTR_MEMO_FREE_CLUST_LEAF)); - } - - ut_ad(btr_freed_leaves_validate(mtr)); return(new_page); } @@ -538,138 +495,8 @@ btr_page_free( level = btr_page_get_level(page, mtr); btr_page_free_low(index, page, level, mtr); - - /* The handling of MTR_MEMO_FREE_CLUST_LEAF assumes this. */ - ut_ad(mtr_memo_contains(mtr, buf_block_align(page), - MTR_MEMO_PAGE_X_FIX)); - - if (level == 0 && (index->type & DICT_CLUSTERED)) { - /* We may have to call btr_mark_freed_leaves() to - temporarily mark the block nonfree for invoking - btr_store_big_rec_extern_fields() after an - update. Remember that the block was freed. */ - mtr->freed_clust_leaf = TRUE; - mtr_memo_push(mtr, buf_block_align(page), - MTR_MEMO_FREE_CLUST_LEAF); - } - - ut_ad(btr_freed_leaves_validate(mtr)); } -/**************************************************************//** -Marks all MTR_MEMO_FREE_CLUST_LEAF pages nonfree or free. -For invoking btr_store_big_rec_extern_fields() after an update, -we must temporarily mark freed clustered index pages allocated, so -that off-page columns will not be allocated from them. Between the -btr_store_big_rec_extern_fields() and mtr_commit() we have to -mark the pages free again, so that no pages will be leaked. */ - -void -btr_mark_freed_leaves( -/*==================*/ - dict_index_t* index, /* in/out: clustered index */ - mtr_t* mtr, /* in/out: mini-transaction */ - ibool nonfree)/* in: TRUE=mark nonfree, FALSE=mark freed */ -{ - /* This is loosely based on mtr_memo_release(). */ - - ulint offset; - - ut_ad(index->type & DICT_CLUSTERED); - ut_ad(mtr->magic_n == MTR_MAGIC_N); - ut_ad(mtr->state == MTR_ACTIVE); - - if (!mtr->freed_clust_leaf) { - return; - } - - offset = dyn_array_get_data_size(&mtr->memo); - - while (offset > 0) { - mtr_memo_slot_t* slot; - buf_block_t* block; - - offset -= sizeof *slot; - - slot = dyn_array_get_element(&mtr->memo, offset); - - if (slot->type != MTR_MEMO_FREE_CLUST_LEAF) { - continue; - } - - /* Because btr_page_alloc() does invoke - mtr_memo_release on MTR_MEMO_FREE_CLUST_LEAF, all - blocks tagged with MTR_MEMO_FREE_CLUST_LEAF in the - memo must still be clustered index leaf tree pages. */ - block = slot->object; - ut_a(buf_block_get_space(block) - == dict_index_get_space(index)); - ut_a(fil_page_get_type(buf_block_get_frame(block)) - == FIL_PAGE_INDEX); - ut_a(btr_page_get_level(buf_block_get_frame(block), mtr) == 0); - - if (nonfree) { - /* Allocate the same page again. */ - ulint page_no; - page_no = btr_page_alloc_low( - index, buf_block_get_page_no(block), - FSP_NO_DIR, 0, mtr, NULL); - ut_a(page_no == buf_block_get_page_no(block)); - } else { - /* Assert that the page is allocated and free it. */ - btr_page_free_low(index, buf_block_get_frame(block), - 0, mtr); - } - } - - ut_ad(btr_freed_leaves_validate(mtr)); -} - -#ifdef UNIV_DEBUG -/**************************************************************//** -Validates all pages marked MTR_MEMO_FREE_CLUST_LEAF. -See btr_mark_freed_leaves(). */ - -ibool -btr_freed_leaves_validate( -/*======================*/ - /* out: TRUE if valid */ - mtr_t* mtr) /* in: mini-transaction */ -{ - ulint offset; - - ut_ad(mtr->magic_n == MTR_MAGIC_N); - ut_ad(mtr->state == MTR_ACTIVE); - - offset = dyn_array_get_data_size(&mtr->memo); - - while (offset > 0) { - mtr_memo_slot_t* slot; - buf_block_t* block; - - offset -= sizeof *slot; - - slot = dyn_array_get_element(&mtr->memo, offset); - - if (slot->type != MTR_MEMO_FREE_CLUST_LEAF) { - continue; - } - - ut_a(mtr->freed_clust_leaf); - /* Because btr_page_alloc() does invoke - mtr_memo_release on MTR_MEMO_FREE_CLUST_LEAF, all - blocks tagged with MTR_MEMO_FREE_CLUST_LEAF in the - memo must still be clustered index leaf tree pages. */ - block = slot->object; - ut_a(fil_page_get_type(buf_block_get_frame(block)) - == FIL_PAGE_INDEX); - ut_a(btr_page_get_level(buf_block_get_frame(block), mtr) == 0); - } - - return(TRUE); -} -#endif /* UNIV_DEBUG */ - /****************************************************************** Sets the child node file address in a node pointer. */ UNIV_INLINE @@ -1199,7 +1026,7 @@ btr_root_raise_and_insert( a node pointer to the new page, and then splitting the new page. */ new_page = btr_page_alloc(index, 0, FSP_NO_DIR, - btr_page_get_level(root, mtr), mtr, mtr); + btr_page_get_level(root, mtr), mtr); btr_page_create(new_page, index, mtr); @@ -1820,7 +1647,7 @@ func_start: /* 2. Allocate a new page to the index */ new_page = btr_page_alloc(cursor->index, hint_page_no, direction, - btr_page_get_level(page, mtr), mtr, mtr); + btr_page_get_level(page, mtr), mtr); btr_page_create(new_page, cursor->index, mtr); /* 3. Calculate the first record on the upper half-page, and the diff --git a/storage/innobase/btr/btr0cur.c b/storage/innobase/btr/btr0cur.c index 37bb3188785..95d87344e93 100644 --- a/storage/innobase/btr/btr0cur.c +++ b/storage/innobase/btr/btr0cur.c @@ -2051,6 +2051,43 @@ return_after_reservations: return(err); } +/***************************************************************** +Commits and restarts a mini-transaction so that it will retain an +x-lock on index->lock and the cursor page. */ + +void +btr_cur_mtr_commit_and_start( +/*=========================*/ + btr_cur_t* cursor, /* in: cursor */ + mtr_t* mtr) /* in/out: mini-transaction */ +{ + buf_block_t* block; + + block = buf_block_align(btr_cur_get_rec(cursor)); + + ut_ad(mtr_memo_contains(mtr, dict_index_get_lock(cursor->index), + MTR_MEMO_X_LOCK)); + ut_ad(mtr_memo_contains(mtr, block, MTR_MEMO_PAGE_X_FIX)); + /* Keep the locks across the mtr_commit(mtr). */ + rw_lock_x_lock(dict_index_get_lock(cursor->index)); + rw_lock_x_lock(&block->lock); + mutex_enter(&block->mutex); +#ifdef UNIV_SYNC_DEBUG + buf_block_buf_fix_inc_debug(block, __FILE__, __LINE__); +#else + buf_block_buf_fix_inc(block); +#endif + mutex_exit(&block->mutex); + /* Write out the redo log. */ + mtr_commit(mtr); + mtr_start(mtr); + /* Reassociate the locks with the mini-transaction. + They will be released on mtr_commit(mtr). */ + mtr_memo_push(mtr, dict_index_get_lock(cursor->index), + MTR_MEMO_X_LOCK); + mtr_memo_push(mtr, block, MTR_MEMO_PAGE_X_FIX); +} + /*==================== B-TREE DELETE MARK AND UNMARK ===============*/ /******************************************************************** @@ -3449,11 +3486,6 @@ btr_store_big_rec_extern_fields( this function returns */ big_rec_t* big_rec_vec, /* in: vector containing fields to be stored externally */ - mtr_t* alloc_mtr, /* in/out: in an insert, NULL; - in an update, local_mtr for - allocating BLOB pages and - updating BLOB pointers; alloc_mtr - must not have freed any leaf pages */ mtr_t* local_mtr __attribute__((unused))) /* in: mtr containing the latch to rec and to the tree */ @@ -3474,8 +3506,6 @@ btr_store_big_rec_extern_fields( ulint i; mtr_t mtr; - ut_ad(local_mtr); - ut_ad(!alloc_mtr || alloc_mtr == local_mtr); ut_ad(rec_offs_validate(rec, index, offsets)); ut_ad(mtr_memo_contains(local_mtr, dict_index_get_lock(index), MTR_MEMO_X_LOCK)); @@ -3485,25 +3515,6 @@ btr_store_big_rec_extern_fields( space_id = buf_frame_get_space_id(rec); - if (alloc_mtr) { - /* Because alloc_mtr will be committed after - mtr, it is possible that the tablespace has been - extended when the B-tree record was updated or - inserted, or it will be extended while allocating - pages for big_rec. - - TODO: In mtr (not alloc_mtr), write a redo log record - about extending the tablespace to its current size, - and remember the current size. Whenever the tablespace - grows as pages are allocated, write further redo log - records to mtr. (Currently tablespace extension is not - covered by the redo log. If it were, the record would - only be written to alloc_mtr, which is committed after - mtr.) */ - } else { - alloc_mtr = &mtr; - } - /* We have to create a file segment to the tablespace for each field and put the pointer to the field in rec */ @@ -3530,7 +3541,7 @@ btr_store_big_rec_extern_fields( } page = btr_page_alloc(index, hint_page_no, - FSP_NO_DIR, 0, alloc_mtr, &mtr); + FSP_NO_DIR, 0, &mtr); if (page == NULL) { mtr_commit(&mtr); @@ -3584,42 +3595,37 @@ btr_store_big_rec_extern_fields( extern_len -= store_len; - if (alloc_mtr == &mtr) { #ifdef UNIV_SYNC_DEBUG - rec_page = + rec_page = #endif /* UNIV_SYNC_DEBUG */ - buf_page_get( - space_id, - buf_frame_get_page_no(data), - RW_X_LATCH, &mtr); + buf_page_get(space_id, + buf_frame_get_page_no(data), + RW_X_LATCH, &mtr); #ifdef UNIV_SYNC_DEBUG - buf_page_dbg_add_level( - rec_page, SYNC_NO_ORDER_CHECK); + buf_page_dbg_add_level(rec_page, SYNC_NO_ORDER_CHECK); #endif /* UNIV_SYNC_DEBUG */ - } - mlog_write_ulint(data + local_len + BTR_EXTERN_LEN, 0, - MLOG_4BYTES, alloc_mtr); + MLOG_4BYTES, &mtr); mlog_write_ulint(data + local_len + BTR_EXTERN_LEN + 4, big_rec_vec->fields[i].len - extern_len, - MLOG_4BYTES, alloc_mtr); + MLOG_4BYTES, &mtr); if (prev_page_no == FIL_NULL) { mlog_write_ulint(data + local_len + BTR_EXTERN_SPACE_ID, space_id, - MLOG_4BYTES, alloc_mtr); + MLOG_4BYTES, &mtr); mlog_write_ulint(data + local_len + BTR_EXTERN_PAGE_NO, page_no, - MLOG_4BYTES, alloc_mtr); + MLOG_4BYTES, &mtr); mlog_write_ulint(data + local_len + BTR_EXTERN_OFFSET, FIL_PAGE_DATA, - MLOG_4BYTES, alloc_mtr); + MLOG_4BYTES, &mtr); /* Set the bit denoting that this field in rec is stored externally */ @@ -3627,7 +3633,7 @@ btr_store_big_rec_extern_fields( rec_set_nth_field_extern_bit( rec, index, big_rec_vec->fields[i].field_no, - TRUE, alloc_mtr); + TRUE, &mtr); } prev_page_no = page_no; diff --git a/storage/innobase/fsp/fsp0fsp.c b/storage/innobase/fsp/fsp0fsp.c index d5be8fca38f..90e6ad34a9a 100644 --- a/storage/innobase/fsp/fsp0fsp.c +++ b/storage/innobase/fsp/fsp0fsp.c @@ -300,12 +300,8 @@ fseg_alloc_free_page_low( inserted there in order, into which direction they go alphabetically: FSP_DOWN, FSP_UP, FSP_NO_DIR */ - mtr_t* mtr, /* in/out: mini-transaction */ - mtr_t* init_mtr);/* in/out: mini-transaction in which the - page should be initialized - (may be the same as mtr), or NULL if it - should not be initialized (the page at hint - was previously freed in mtr) */ + mtr_t* mtr); /* in/out: mini-transaction */ + /************************************************************************** Reads the file space size stored in the header page. */ @@ -1375,43 +1371,6 @@ fsp_alloc_free_extent( return(descr); } -/**********************************************************************//** -Allocates a single free page from a space. */ -static __attribute__((nonnull)) -void -fsp_alloc_from_free_frag( -/*=====================*/ - fsp_header_t* header, /* in/out: tablespace header */ - xdes_t* descr, /* in/out: extent descriptor */ - ulint bit, /* in: slot to allocate in the extent */ - mtr_t* mtr) /* in/out: mini-transaction */ -{ - ulint frag_n_used; - - ut_ad(xdes_get_state(descr, mtr) == XDES_FREE_FRAG); - ut_a(xdes_get_bit(descr, XDES_FREE_BIT, bit, mtr)); - xdes_set_bit(descr, XDES_FREE_BIT, bit, FALSE, mtr); - - /* Update the FRAG_N_USED field */ - frag_n_used = mtr_read_ulint(header + FSP_FRAG_N_USED, MLOG_4BYTES, - mtr); - frag_n_used++; - mlog_write_ulint(header + FSP_FRAG_N_USED, frag_n_used, MLOG_4BYTES, - mtr); - if (xdes_is_full(descr, mtr)) { - /* The fragment is full: move it to another list */ - flst_remove(header + FSP_FREE_FRAG, descr + XDES_FLST_NODE, - mtr); - xdes_set_state(descr, XDES_FULL_FRAG, mtr); - - flst_add_last(header + FSP_FULL_FRAG, descr + XDES_FLST_NODE, - mtr); - mlog_write_ulint(header + FSP_FRAG_N_USED, - frag_n_used - FSP_EXTENT_SIZE, MLOG_4BYTES, - mtr); - } -} - /************************************************************************** Allocates a single free page from a space. The page is marked as used. */ static @@ -1422,22 +1381,19 @@ fsp_alloc_free_page( be allocated */ ulint space, /* in: space id */ ulint hint, /* in: hint of which page would be desirable */ - mtr_t* mtr, /* in/out: mini-transaction */ - mtr_t* init_mtr)/* in/out: mini-transaction in which the - page should be initialized - (may be the same as mtr) */ + mtr_t* mtr) /* in/out: mini-transaction */ { fsp_header_t* header; fil_addr_t first; xdes_t* descr; page_t* page; ulint free; + ulint frag_n_used; ulint page_no; ulint space_size; ibool success; ut_ad(mtr); - ut_ad(init_mtr); header = fsp_get_space_header(space, mtr); @@ -1517,21 +1473,40 @@ fsp_alloc_free_page( } } - fsp_alloc_from_free_frag(header, descr, free, mtr); + xdes_set_bit(descr, XDES_FREE_BIT, free, FALSE, mtr); + + /* Update the FRAG_N_USED field */ + frag_n_used = mtr_read_ulint(header + FSP_FRAG_N_USED, MLOG_4BYTES, + mtr); + frag_n_used++; + mlog_write_ulint(header + FSP_FRAG_N_USED, frag_n_used, MLOG_4BYTES, + mtr); + if (xdes_is_full(descr, mtr)) { + /* The fragment is full: move it to another list */ + flst_remove(header + FSP_FREE_FRAG, descr + XDES_FLST_NODE, + mtr); + xdes_set_state(descr, XDES_FULL_FRAG, mtr); + + flst_add_last(header + FSP_FULL_FRAG, descr + XDES_FLST_NODE, + mtr); + mlog_write_ulint(header + FSP_FRAG_N_USED, + frag_n_used - FSP_EXTENT_SIZE, MLOG_4BYTES, + mtr); + } /* Initialize the allocated page to the buffer pool, so that it can be obtained immediately with buf_page_get without need for a disk read. */ - buf_page_create(space, page_no, init_mtr); + buf_page_create(space, page_no, mtr); - page = buf_page_get(space, page_no, RW_X_LATCH, init_mtr); + page = buf_page_get(space, page_no, RW_X_LATCH, mtr); #ifdef UNIV_SYNC_DEBUG buf_page_dbg_add_level(page, SYNC_FSP_PAGE); #endif /* UNIV_SYNC_DEBUG */ /* Prior contents of the page should be ignored */ - fsp_init_file_page(page, init_mtr); + fsp_init_file_page(page, mtr); return(page_no); } @@ -1750,7 +1725,7 @@ fsp_alloc_seg_inode_page( space = buf_frame_get_space_id(space_header); - page_no = fsp_alloc_free_page(space, 0, mtr, mtr); + page_no = fsp_alloc_free_page(space, 0, mtr); if (page_no == FIL_NULL) { @@ -2120,8 +2095,7 @@ fseg_create_general( } if (page == 0) { - page = fseg_alloc_free_page_low(space, - inode, 0, FSP_UP, mtr, mtr); + page = fseg_alloc_free_page_low(space, inode, 0, FSP_UP, mtr); if (page == FIL_NULL) { @@ -2365,12 +2339,7 @@ fseg_alloc_free_page_low( inserted there in order, into which direction they go alphabetically: FSP_DOWN, FSP_UP, FSP_NO_DIR */ - mtr_t* mtr, /* in/out: mini-transaction */ - mtr_t* init_mtr)/* in/out: mini-transaction in which the - page should be initialized - (may be the same as mtr), or NULL if it - should not be initialized (the page at hint - was previously freed in mtr) */ + mtr_t* mtr) /* in/out: mini-transaction */ { fsp_header_t* space_header; ulint space_size; @@ -2382,6 +2351,7 @@ fseg_alloc_free_page_low( if could not be allocated */ xdes_t* ret_descr; /* the extent of the allocated page */ page_t* page; + ibool frag_page_allocated = FALSE; ibool success; ulint n; @@ -2402,8 +2372,6 @@ fseg_alloc_free_page_low( if (descr == NULL) { /* Hint outside space or too high above free limit: reset hint */ - ut_a(init_mtr); - /* The file space header page is always allocated. */ hint = 0; descr = xdes_get_descriptor(space, hint, mtr); } @@ -2415,20 +2383,15 @@ fseg_alloc_free_page_low( mtr), seg_id)) && (xdes_get_bit(descr, XDES_FREE_BIT, hint % FSP_EXTENT_SIZE, mtr) == TRUE)) { -take_hinted_page: + /* 1. We can take the hinted page =================================*/ ret_descr = descr; ret_page = hint; - /* Skip the check for extending the tablespace. If the - page hint were not within the size of the tablespace, - we would have got (descr == NULL) above and reset the hint. */ - goto got_hinted_page; /*-----------------------------------------------------------*/ - } else if (xdes_get_state(descr, mtr) == XDES_FREE - && (!init_mtr - || ((reserved - used < reserved / FSEG_FILLFACTOR) - && used >= FSEG_FRAG_LIMIT))) { + } else if ((xdes_get_state(descr, mtr) == XDES_FREE) + && ((reserved - used) < reserved / FSEG_FILLFACTOR) + && (used >= FSEG_FRAG_LIMIT)) { /* 2. We allocate the free extent from space and can take ========================================================= @@ -2446,20 +2409,8 @@ take_hinted_page: /* Try to fill the segment free list */ fseg_fill_free_list(seg_inode, space, hint + FSP_EXTENT_SIZE, mtr); - goto take_hinted_page; - /*-----------------------------------------------------------*/ - } else if (!init_mtr) { - ut_a(xdes_get_state(descr, mtr) == XDES_FREE_FRAG); - fsp_alloc_from_free_frag(space_header, descr, - hint % FSP_EXTENT_SIZE, mtr); ret_page = hint; - ret_descr = NULL; - - /* Put the page in the fragment page array of the segment */ - n = fseg_find_free_frag_page_slot(seg_inode, mtr); - ut_a(n != FIL_NULL); - fseg_set_nth_frag_page_no(seg_inode, n, ret_page, mtr); - goto got_hinted_page; + /*-----------------------------------------------------------*/ } else if ((direction != FSP_NO_DIR) && ((reserved - used) < reserved / FSEG_FILLFACTOR) && (used >= FSEG_FRAG_LIMIT) @@ -2517,9 +2468,11 @@ take_hinted_page: } else if (used < FSEG_FRAG_LIMIT) { /* 6. We allocate an individual page from the space ===================================================*/ - ret_page = fsp_alloc_free_page(space, hint, mtr, init_mtr); + ret_page = fsp_alloc_free_page(space, hint, mtr); ret_descr = NULL; + frag_page_allocated = TRUE; + if (ret_page != FIL_NULL) { /* Put the page in the fragment page array of the segment */ @@ -2529,10 +2482,6 @@ take_hinted_page: fseg_set_nth_frag_page_no(seg_inode, n, ret_page, mtr); } - - /* fsp_alloc_free_page() invoked fsp_init_file_page() - already. */ - return(ret_page); /*-----------------------------------------------------------*/ } else { /* 7. We allocate a new extent and take its first page @@ -2579,31 +2528,22 @@ take_hinted_page: } } -got_hinted_page: - { + if (!frag_page_allocated) { /* Initialize the allocated page to buffer pool, so that it can be obtained immediately with buf_page_get without need for a disk read */ - mtr_t* block_mtr = init_mtr ? init_mtr : mtr; - page = buf_page_create(space, ret_page, block_mtr); + page = buf_page_create(space, ret_page, mtr); - ut_a(page == buf_page_get(space, ret_page, RW_X_LATCH, - block_mtr)); + ut_a(page == buf_page_get(space, ret_page, RW_X_LATCH, mtr)); #ifdef UNIV_SYNC_DEBUG buf_page_dbg_add_level(page, SYNC_FSP_PAGE); #endif /* UNIV_SYNC_DEBUG */ - if (init_mtr) { - /* The prior contents of the page should be ignored */ - fsp_init_file_page(page, init_mtr); - } - } + /* The prior contents of the page should be ignored */ + fsp_init_file_page(page, mtr); - /* ret_descr == NULL if the block was allocated from free_frag - (XDES_FREE_FRAG) */ - if (ret_descr != NULL) { /* At this point we know the extent and the page offset. The extent is still in the appropriate list (FSEG_NOT_FULL or FSEG_FREE), and the page is not yet marked as used. */ @@ -2640,11 +2580,7 @@ fseg_alloc_free_page_general( with fsp_reserve_free_extents, then there is no need to do the check for this individual page */ - mtr_t* mtr, /* in/out: mini-transaction handle */ - mtr_t* init_mtr)/* in/out: mtr or another mini-transaction - in which the page should be initialized, - or NULL if this is a "fake allocation" of - a page that was previously freed in mtr */ + mtr_t* mtr) /* in/out: mini-transaction */ { fseg_inode_t* inode; ulint space; @@ -2682,8 +2618,7 @@ fseg_alloc_free_page_general( } page_no = fseg_alloc_free_page_low(buf_frame_get_space_id(inode), - inode, hint, direction, - mtr, init_mtr); + inode, hint, direction, mtr); if (!has_done_reservation) { fil_space_release_free_extents(space, n_reserved); } @@ -2711,7 +2646,7 @@ fseg_alloc_free_page( mtr_t* mtr) /* in: mtr handle */ { return(fseg_alloc_free_page_general(seg_header, hint, direction, - FALSE, mtr, mtr)); + FALSE, mtr)); } /************************************************************************** diff --git a/storage/innobase/include/btr0btr.h b/storage/innobase/include/btr0btr.h index 3988019589d..269fa355558 100644 --- a/storage/innobase/include/btr0btr.h +++ b/storage/innobase/include/btr0btr.h @@ -379,11 +379,7 @@ btr_page_alloc( page split is made */ ulint level, /* in: level where the page is placed in the tree */ - mtr_t* mtr, /* in/out: mini-transaction - for the allocation */ - mtr_t* init_mtr); /* in/out: mini-transaction - for x-latching and initializing - the page */ + mtr_t* mtr); /* in: mtr */ /****************************************************************** Frees a file page used in an index tree. NOTE: cannot free field external storage pages because the page must contain info on its level. */ @@ -406,31 +402,6 @@ btr_page_free_low( page_t* page, /* in: page to be freed, x-latched */ ulint level, /* in: page level */ mtr_t* mtr); /* in: mtr */ -/**************************************************************//** -Marks all MTR_MEMO_FREE_CLUST_LEAF pages nonfree or free. -For invoking btr_store_big_rec_extern_fields() after an update, -we must temporarily mark freed clustered index pages allocated, so -that off-page columns will not be allocated from them. Between the -btr_store_big_rec_extern_fields() and mtr_commit() we have to -mark the pages free again, so that no pages will be leaked. */ - -void -btr_mark_freed_leaves( -/*==================*/ - dict_index_t* index, /* in/out: clustered index */ - mtr_t* mtr, /* in/out: mini-transaction */ - ibool nonfree);/* in: TRUE=mark nonfree, FALSE=mark freed */ -#ifdef UNIV_DEBUG -/**************************************************************//** -Validates all pages marked MTR_MEMO_FREE_CLUST_LEAF. -See btr_mark_freed_leaves(). */ - -ibool -btr_freed_leaves_validate( -/*======================*/ - /* out: TRUE if valid */ - mtr_t* mtr); /* in: mini-transaction */ -#endif /* UNIV_DEBUG */ #ifdef UNIV_BTR_PRINT /***************************************************************** Prints size info of a B-tree. */ diff --git a/storage/innobase/include/btr0cur.h b/storage/innobase/include/btr0cur.h index c2bf84ef9cb..c068d8d3318 100644 --- a/storage/innobase/include/btr0cur.h +++ b/storage/innobase/include/btr0cur.h @@ -252,6 +252,15 @@ btr_cur_pessimistic_update( updates */ que_thr_t* thr, /* in: query thread */ mtr_t* mtr); /* in: mtr */ +/***************************************************************** +Commits and restarts a mini-transaction so that it will retain an +x-lock on index->lock and the cursor page. */ + +void +btr_cur_mtr_commit_and_start( +/*=========================*/ + btr_cur_t* cursor, /* in: cursor */ + mtr_t* mtr); /* in/out: mini-transaction */ /*************************************************************** Marks a clustered index record deleted. Writes an undo log record to undo log on this delete marking. Writes in the trx id field the id @@ -462,11 +471,6 @@ btr_store_big_rec_extern_fields( this function returns */ big_rec_t* big_rec_vec, /* in: vector containing fields to be stored externally */ - mtr_t* alloc_mtr, /* in/out: in an insert, NULL; - in an update, local_mtr for - allocating BLOB pages and - updating BLOB pointers; alloc_mtr - must not have freed any leaf pages */ mtr_t* local_mtr); /* in: mtr containing the latch to rec and to the tree */ /*********************************************************************** diff --git a/storage/innobase/include/fsp0fsp.h b/storage/innobase/include/fsp0fsp.h index 4c58d6075e6..b7322944189 100644 --- a/storage/innobase/include/fsp0fsp.h +++ b/storage/innobase/include/fsp0fsp.h @@ -179,11 +179,7 @@ fseg_alloc_free_page_general( with fsp_reserve_free_extents, then there is no need to do the check for this individual page */ - mtr_t* mtr, /* in/out: mini-transaction */ - mtr_t* init_mtr);/* in/out: mtr or another mini-transaction - in which the page should be initialized, - or NULL if this is a "fake allocation" of - a page that was previously freed in mtr */ + mtr_t* mtr); /* in/out: mini-transaction */ /************************************************************************** Reserves free pages from a tablespace. All mini-transactions which may use several pages from the tablespace should call this function beforehand diff --git a/storage/innobase/include/mtr0mtr.h b/storage/innobase/include/mtr0mtr.h index 58983063361..2b41fa0059a 100644 --- a/storage/innobase/include/mtr0mtr.h +++ b/storage/innobase/include/mtr0mtr.h @@ -36,8 +36,6 @@ first 3 values must be RW_S_LATCH, RW_X_LATCH, RW_NO_LATCH */ #define MTR_MEMO_MODIFY 54 #define MTR_MEMO_S_LOCK 55 #define MTR_MEMO_X_LOCK 56 -/* The mini-transaction freed a clustered index leaf page. */ -#define MTR_MEMO_FREE_CLUST_LEAF 57 /* Log item types: we have made them to be of the type 'byte' for the compiler to warn if val and type parameters are switched @@ -317,12 +315,9 @@ struct mtr_struct{ ulint state; /* MTR_ACTIVE, MTR_COMMITTING, MTR_COMMITTED */ dyn_array_t memo; /* memo stack for locks etc. */ dyn_array_t log; /* mini-transaction log */ - unsigned modifications:1; + ibool modifications; /* TRUE if the mtr made modifications to buffer pool pages */ - unsigned freed_clust_leaf:1; - /* TRUE if MTR_MEMO_FREE_CLUST_LEAF - was logged in the mini-transaction */ ulint n_log_recs; /* count of how many page initial log records have been written to the mtr log */ diff --git a/storage/innobase/include/mtr0mtr.ic b/storage/innobase/include/mtr0mtr.ic index 6b4cacf0766..81eec3bfc92 100644 --- a/storage/innobase/include/mtr0mtr.ic +++ b/storage/innobase/include/mtr0mtr.ic @@ -26,7 +26,6 @@ mtr_start( mtr->log_mode = MTR_LOG_ALL; mtr->modifications = FALSE; - mtr->freed_clust_leaf = FALSE; mtr->n_log_recs = 0; #ifdef UNIV_DEBUG @@ -51,8 +50,7 @@ mtr_memo_push( ut_ad(object); ut_ad(type >= MTR_MEMO_PAGE_S_FIX); - ut_ad(type <= MTR_MEMO_FREE_CLUST_LEAF); - ut_ad(type != MTR_MEMO_FREE_CLUST_LEAF || mtr->freed_clust_leaf); + ut_ad(type <= MTR_MEMO_X_LOCK); ut_ad(mtr); ut_ad(mtr->magic_n == MTR_MAGIC_N); diff --git a/storage/innobase/mtr/mtr0mtr.c b/storage/innobase/mtr/mtr0mtr.c index 33b71f0766c..728c37ce564 100644 --- a/storage/innobase/mtr/mtr0mtr.c +++ b/storage/innobase/mtr/mtr0mtr.c @@ -53,13 +53,17 @@ mtr_memo_slot_release( buf_page_release((buf_block_t*)object, type, mtr); } else if (type == MTR_MEMO_S_LOCK) { rw_lock_s_unlock((rw_lock_t*)object); - } else if (type != MTR_MEMO_X_LOCK) { - ut_ad(type == MTR_MEMO_MODIFY - || type == MTR_MEMO_FREE_CLUST_LEAF); +#ifdef UNIV_DEBUG + } else if (type == MTR_MEMO_X_LOCK) { + rw_lock_x_unlock((rw_lock_t*)object); + } else { + ut_ad(type == MTR_MEMO_MODIFY); ut_ad(mtr_memo_contains(mtr, object, MTR_MEMO_PAGE_X_FIX)); +#else } else { rw_lock_x_unlock((rw_lock_t*)object); +#endif } } diff --git a/storage/innobase/row/row0ins.c b/storage/innobase/row/row0ins.c index f6e6c81534b..c3b8f54a3c5 100644 --- a/storage/innobase/row/row0ins.c +++ b/storage/innobase/row/row0ins.c @@ -2090,20 +2090,15 @@ row_ins_index_entry_low( if (big_rec) { ut_a(err == DB_SUCCESS); /* Write out the externally stored - columns, but allocate the pages and - write the pointers using the - mini-transaction of the record update. - If any pages were freed in the update, - temporarily mark them allocated so - that off-page columns will not - overwrite them. We must do this, - because we will write the redo log for - the BLOB writes before writing the - redo log for the record update. Thus, - redo log application at crash recovery - will see BLOBs being written to free pages. */ - - btr_mark_freed_leaves(index, &mtr, TRUE); + columns while still x-latching + index->lock and block->lock. We have + to mtr_commit(mtr) first, so that the + redo log will be written in the + correct order. Otherwise, we would run + into trouble on crash recovery if mtr + freed B-tree pages on which some of + the big_rec fields will be written. */ + btr_cur_mtr_commit_and_start(&cursor, &mtr); rec = btr_cur_get_rec(&cursor); offsets = rec_get_offsets(rec, index, offsets, @@ -2111,8 +2106,7 @@ row_ins_index_entry_low( &heap); err = btr_store_big_rec_extern_fields( - index, rec, offsets, big_rec, - &mtr, &mtr); + index, rec, offsets, big_rec, &mtr); /* If writing big_rec fails (for example, because of DB_OUT_OF_FILE_SPACE), the record will be corrupted. Even if @@ -2125,9 +2119,6 @@ row_ins_index_entry_low( undo log, and thus the record cannot be rolled back. */ ut_a(err == DB_SUCCESS); - /* Free the pages again - in order to avoid a leak. */ - btr_mark_freed_leaves(index, &mtr, FALSE); goto stored_big_rec; } } else { @@ -2175,8 +2166,7 @@ function_exit: ULINT_UNDEFINED, &heap); err = btr_store_big_rec_extern_fields(index, rec, - offsets, big_rec, - NULL, &mtr); + offsets, big_rec, &mtr); stored_big_rec: if (modify) { dtuple_big_rec_free(big_rec); diff --git a/storage/innobase/row/row0row.c b/storage/innobase/row/row0row.c index ccb3c1f7781..171039e34ac 100644 --- a/storage/innobase/row/row0row.c +++ b/storage/innobase/row/row0row.c @@ -212,27 +212,23 @@ row_build( } #if defined UNIV_DEBUG || defined UNIV_BLOB_LIGHT_DEBUG - if (rec_offs_any_null_extern(rec, offsets)) { - /* This condition can occur during crash recovery - before trx_rollback_or_clean_all_without_sess() has - completed execution. + /* This condition can occur during crash recovery before + trx_rollback_or_clean_all_without_sess() has completed + execution. - This condition is possible if the server crashed - during an insert or update before - btr_store_big_rec_extern_fields() did mtr_commit() all - BLOB pointers to the clustered index record. + This condition is possible if the server crashed + during an insert or update before + btr_store_big_rec_extern_fields() did mtr_commit() all + BLOB pointers to the clustered index record. - If the record contains a null BLOB pointer, look up the - transaction that holds the implicit lock on this record, and - assert that it is active. (In this version of InnoDB, we - cannot assert that it was recovered, because there is no - trx->is_recovered field.) */ + If the record contains a null BLOB pointer, look up the + transaction that holds the implicit lock on this record, and + assert that it is active. (In this version of InnoDB, we + cannot assert that it was recovered, because there is no + trx->is_recovered field.) */ - ut_a(trx_assert_active( - row_get_rec_trx_id(rec, index, offsets))); - ut_a(trx_undo_roll_ptr_is_insert( - row_get_rec_roll_ptr(rec, index, offsets))); - } + ut_a(!rec_offs_any_null_extern(rec, offsets) + || trx_assert_active(row_get_rec_trx_id(rec, index, offsets))); #endif /* UNIV_DEBUG || UNIV_BLOB_LIGHT_DEBUG */ if (type != ROW_COPY_POINTERS) { diff --git a/storage/innobase/row/row0upd.c b/storage/innobase/row/row0upd.c index 58739edfd98..694b00ea265 100644 --- a/storage/innobase/row/row0upd.c +++ b/storage/innobase/row/row0upd.c @@ -1591,22 +1591,21 @@ row_upd_clust_rec( *offsets_ = (sizeof offsets_) / sizeof *offsets_; ut_a(err == DB_SUCCESS); - /* Write out the externally stored columns, but - allocate the pages and write the pointers using the - mini-transaction of the record update. If any pages - were freed in the update, temporarily mark them - allocated so that off-page columns will not overwrite - them. We must do this, because we write the redo log - for the BLOB writes before writing the redo log for - the record update. */ + /* Write out the externally stored columns while still + x-latching index->lock and block->lock. We have to + mtr_commit(mtr) first, so that the redo log will be + written in the correct order. Otherwise, we would run + into trouble on crash recovery if mtr freed B-tree + pages on which some of the big_rec fields will be + written. */ + btr_cur_mtr_commit_and_start(btr_cur, mtr); - btr_mark_freed_leaves(index, mtr, TRUE); rec = btr_cur_get_rec(btr_cur); err = btr_store_big_rec_extern_fields( index, rec, rec_get_offsets(rec, index, offsets_, ULINT_UNDEFINED, &heap), - big_rec, mtr, mtr); + big_rec, mtr); if (UNIV_LIKELY_NULL(heap)) { mem_heap_free(heap); } @@ -1619,8 +1618,6 @@ row_upd_clust_rec( to the undo log, and thus the record cannot be rolled back. */ ut_a(err == DB_SUCCESS); - /* Free the pages again in order to avoid a leak. */ - btr_mark_freed_leaves(index, mtr, FALSE); } mtr_commit(mtr); diff --git a/storage/innobase/trx/trx0undo.c b/storage/innobase/trx/trx0undo.c index ce09862f317..329565943c8 100644 --- a/storage/innobase/trx/trx0undo.c +++ b/storage/innobase/trx/trx0undo.c @@ -864,7 +864,7 @@ trx_undo_add_page( page_no = fseg_alloc_free_page_general(header_page + TRX_UNDO_SEG_HDR + TRX_UNDO_FSEG_HEADER, undo->top_page_no + 1, FSP_UP, - TRUE, mtr, mtr); + TRUE, mtr); fil_space_release_free_extents(undo->space, n_reserved); diff --git a/storage/innodb_plugin/ChangeLog b/storage/innodb_plugin/ChangeLog index 4e6e2be615a..e6724eb08c2 100644 --- a/storage/innodb_plugin/ChangeLog +++ b/storage/innodb_plugin/ChangeLog @@ -50,15 +50,6 @@ * include/trx0undo.h, trx/trx0rec.c, trx/trx0undo.c: Fix Bug#12547647 UPDATE LOGGING COULD EXCEED LOG PAGE SIZE -2011-08-29 The InnoDB Team - - * btr/btr0btr.c, btr/btr0cur.c, fsp/fsp0fsp.c, - include/btr0btr.h, include/btr0cur.h, include/fsp0fsp.h, - include/mtr0mtr.h, include/mtr0mtr.ic, mtr/mtr0mtr.c, - row/row0ins.c, row/row0row.c, row/row0upd.c, trx/trx0undo.c: - Fix Bug#12704861 Corruption after a crash during BLOB update - and other regressions from the fix of Bug#12612184 - 2011-08-15 The InnoDB Team * btr/btr0btr.c, btr/btr0cur.c, btr/btr0pcur.c, btr/btr0sea.c, diff --git a/storage/innodb_plugin/btr/btr0btr.c b/storage/innodb_plugin/btr/btr0btr.c index 71e1599d19e..cb94ef08cd6 100644 --- a/storage/innodb_plugin/btr/btr0btr.c +++ b/storage/innodb_plugin/btr/btr0btr.c @@ -906,29 +906,28 @@ btr_page_alloc_for_ibuf( /**************************************************************//** Allocates a new file page to be used in an index tree. NOTE: we assume that the caller has made the reservation for free extents! -@return allocated page number, FIL_NULL if out of space */ -static __attribute__((nonnull(1,5), warn_unused_result)) -ulint -btr_page_alloc_low( -/*===============*/ +@return new allocated block, x-latched; NULL if out of space */ +UNIV_INTERN +buf_block_t* +btr_page_alloc( +/*===========*/ dict_index_t* index, /*!< in: index */ ulint hint_page_no, /*!< in: hint of a good page */ byte file_direction, /*!< in: direction where a possible page split is made */ ulint level, /*!< in: level where the page is placed in the tree */ - mtr_t* mtr, /*!< in/out: mini-transaction - for the allocation */ - mtr_t* init_mtr) /*!< in/out: mini-transaction - in which the page should be - initialized (may be the same - as mtr), or NULL if it should - not be initialized (the page - at hint was previously freed - in mtr) */ + mtr_t* mtr) /*!< in: mtr */ { fseg_header_t* seg_header; page_t* root; + buf_block_t* new_block; + ulint new_page_no; + + if (dict_index_is_ibuf(index)) { + + return(btr_page_alloc_for_ibuf(index, mtr)); + } root = btr_root_get(index, mtr); @@ -942,42 +941,8 @@ btr_page_alloc_low( reservation for free extents, and thus we know that a page can be allocated: */ - return(fseg_alloc_free_page_general( - seg_header, hint_page_no, file_direction, - TRUE, mtr, init_mtr)); -} - -/**************************************************************//** -Allocates a new file page to be used in an index tree. NOTE: we assume -that the caller has made the reservation for free extents! -@return new allocated block, x-latched; NULL if out of space */ -UNIV_INTERN -buf_block_t* -btr_page_alloc( -/*===========*/ - dict_index_t* index, /*!< in: index */ - ulint hint_page_no, /*!< in: hint of a good page */ - byte file_direction, /*!< in: direction where a possible - page split is made */ - ulint level, /*!< in: level where the page is placed - in the tree */ - mtr_t* mtr, /*!< in/out: mini-transaction - for the allocation */ - mtr_t* init_mtr) /*!< in/out: mini-transaction - for x-latching and initializing - the page */ -{ - buf_block_t* new_block; - ulint new_page_no; - - if (dict_index_is_ibuf(index)) { - - return(btr_page_alloc_for_ibuf(index, mtr)); - } - - new_page_no = btr_page_alloc_low( - index, hint_page_no, file_direction, level, mtr, init_mtr); - + new_page_no = fseg_alloc_free_page_general(seg_header, hint_page_no, + file_direction, TRUE, mtr); if (new_page_no == FIL_NULL) { return(NULL); @@ -985,16 +950,9 @@ btr_page_alloc( new_block = buf_page_get(dict_index_get_space(index), dict_table_zip_size(index->table), - new_page_no, RW_X_LATCH, init_mtr); + new_page_no, RW_X_LATCH, mtr); buf_block_dbg_add_level(new_block, SYNC_TREE_NODE_NEW); - if (mtr->freed_clust_leaf) { - mtr_memo_release(mtr, new_block, MTR_MEMO_FREE_CLUST_LEAF); - ut_ad(!mtr_memo_contains(mtr, new_block, - MTR_MEMO_FREE_CLUST_LEAF)); - } - - ut_ad(btr_freed_leaves_validate(mtr)); return(new_block); } @@ -1129,140 +1087,13 @@ btr_page_free( buf_block_t* block, /*!< in: block to be freed, x-latched */ mtr_t* mtr) /*!< in: mtr */ { - const page_t* page = buf_block_get_frame(block); - ulint level = btr_page_get_level(page, mtr); + ulint level; + + level = btr_page_get_level(buf_block_get_frame(block), mtr); - ut_ad(fil_page_get_type(block->frame) == FIL_PAGE_INDEX); btr_page_free_low(index, block, level, mtr); - - /* The handling of MTR_MEMO_FREE_CLUST_LEAF assumes this. */ - ut_ad(mtr_memo_contains(mtr, block, MTR_MEMO_PAGE_X_FIX)); - - if (level == 0 && dict_index_is_clust(index)) { - /* We may have to call btr_mark_freed_leaves() to - temporarily mark the block nonfree for invoking - btr_store_big_rec_extern_fields_func() after an - update. Remember that the block was freed. */ - mtr->freed_clust_leaf = TRUE; - mtr_memo_push(mtr, block, MTR_MEMO_FREE_CLUST_LEAF); - } - - ut_ad(btr_freed_leaves_validate(mtr)); } -/**************************************************************//** -Marks all MTR_MEMO_FREE_CLUST_LEAF pages nonfree or free. -For invoking btr_store_big_rec_extern_fields() after an update, -we must temporarily mark freed clustered index pages allocated, so -that off-page columns will not be allocated from them. Between the -btr_store_big_rec_extern_fields() and mtr_commit() we have to -mark the pages free again, so that no pages will be leaked. */ -UNIV_INTERN -void -btr_mark_freed_leaves( -/*==================*/ - dict_index_t* index, /*!< in/out: clustered index */ - mtr_t* mtr, /*!< in/out: mini-transaction */ - ibool nonfree)/*!< in: TRUE=mark nonfree, FALSE=mark freed */ -{ - /* This is loosely based on mtr_memo_release(). */ - - ulint offset; - - ut_ad(dict_index_is_clust(index)); - ut_ad(mtr->magic_n == MTR_MAGIC_N); - ut_ad(mtr->state == MTR_ACTIVE); - - if (!mtr->freed_clust_leaf) { - return; - } - - offset = dyn_array_get_data_size(&mtr->memo); - - while (offset > 0) { - mtr_memo_slot_t* slot; - buf_block_t* block; - - offset -= sizeof *slot; - - slot = dyn_array_get_element(&mtr->memo, offset); - - if (slot->type != MTR_MEMO_FREE_CLUST_LEAF) { - continue; - } - - /* Because btr_page_alloc() does invoke - mtr_memo_release on MTR_MEMO_FREE_CLUST_LEAF, all - blocks tagged with MTR_MEMO_FREE_CLUST_LEAF in the - memo must still be clustered index leaf tree pages. */ - block = slot->object; - ut_a(buf_block_get_space(block) - == dict_index_get_space(index)); - ut_a(fil_page_get_type(buf_block_get_frame(block)) - == FIL_PAGE_INDEX); - ut_a(page_is_leaf(buf_block_get_frame(block))); - - if (nonfree) { - /* Allocate the same page again. */ - ulint page_no; - page_no = btr_page_alloc_low( - index, buf_block_get_page_no(block), - FSP_NO_DIR, 0, mtr, NULL); - ut_a(page_no == buf_block_get_page_no(block)); - } else { - /* Assert that the page is allocated and free it. */ - btr_page_free_low(index, block, 0, mtr); - } - } - - ut_ad(btr_freed_leaves_validate(mtr)); -} - -#ifdef UNIV_DEBUG -/**************************************************************//** -Validates all pages marked MTR_MEMO_FREE_CLUST_LEAF. -@see btr_mark_freed_leaves() -@return TRUE */ -UNIV_INTERN -ibool -btr_freed_leaves_validate( -/*======================*/ - mtr_t* mtr) /*!< in: mini-transaction */ -{ - ulint offset; - - ut_ad(mtr->magic_n == MTR_MAGIC_N); - ut_ad(mtr->state == MTR_ACTIVE); - - offset = dyn_array_get_data_size(&mtr->memo); - - while (offset > 0) { - const mtr_memo_slot_t* slot; - const buf_block_t* block; - - offset -= sizeof *slot; - - slot = dyn_array_get_element(&mtr->memo, offset); - - if (slot->type != MTR_MEMO_FREE_CLUST_LEAF) { - continue; - } - - ut_a(mtr->freed_clust_leaf); - /* Because btr_page_alloc() does invoke - mtr_memo_release on MTR_MEMO_FREE_CLUST_LEAF, all - blocks tagged with MTR_MEMO_FREE_CLUST_LEAF in the - memo must still be clustered index leaf tree pages. */ - block = slot->object; - ut_a(fil_page_get_type(buf_block_get_frame(block)) - == FIL_PAGE_INDEX); - ut_a(page_is_leaf(buf_block_get_frame(block))); - } - - return(TRUE); -} -#endif /* UNIV_DEBUG */ - /**************************************************************//** Sets the child node file address in a node pointer. */ UNIV_INLINE @@ -1984,7 +1815,7 @@ btr_root_raise_and_insert( level = btr_page_get_level(root, mtr); - new_block = btr_page_alloc(index, 0, FSP_NO_DIR, level, mtr, mtr); + new_block = btr_page_alloc(index, 0, FSP_NO_DIR, level, mtr); new_page = buf_block_get_frame(new_block); new_page_zip = buf_block_get_page_zip(new_block); ut_a(!new_page_zip == !root_page_zip); @@ -2720,7 +2551,7 @@ func_start: /* 2. Allocate a new page to the index */ new_block = btr_page_alloc(cursor->index, hint_page_no, direction, - btr_page_get_level(page, mtr), mtr, mtr); + btr_page_get_level(page, mtr), mtr); new_page = buf_block_get_frame(new_block); new_page_zip = buf_block_get_page_zip(new_block); btr_page_create(new_block, new_page_zip, cursor->index, diff --git a/storage/innodb_plugin/btr/btr0cur.c b/storage/innodb_plugin/btr/btr0cur.c index 1e603a6fc81..4c862f061c5 100644 --- a/storage/innodb_plugin/btr/btr0cur.c +++ b/storage/innodb_plugin/btr/btr0cur.c @@ -2421,6 +2421,39 @@ return_after_reservations: return(err); } +/**************************************************************//** +Commits and restarts a mini-transaction so that it will retain an +x-lock on index->lock and the cursor page. */ +UNIV_INTERN +void +btr_cur_mtr_commit_and_start( +/*=========================*/ + btr_cur_t* cursor, /*!< in: cursor */ + mtr_t* mtr) /*!< in/out: mini-transaction */ +{ + buf_block_t* block; + + block = btr_cur_get_block(cursor); + + ut_ad(mtr_memo_contains(mtr, dict_index_get_lock(cursor->index), + MTR_MEMO_X_LOCK)); + ut_ad(mtr_memo_contains(mtr, block, MTR_MEMO_PAGE_X_FIX)); + /* Keep the locks across the mtr_commit(mtr). */ + rw_lock_x_lock(dict_index_get_lock(cursor->index)); + rw_lock_x_lock(&block->lock); + mutex_enter(&block->mutex); + buf_block_buf_fix_inc(block, __FILE__, __LINE__); + mutex_exit(&block->mutex); + /* Write out the redo log. */ + mtr_commit(mtr); + mtr_start(mtr); + /* Reassociate the locks with the mini-transaction. + They will be released on mtr_commit(mtr). */ + mtr_memo_push(mtr, dict_index_get_lock(cursor->index), + MTR_MEMO_X_LOCK); + mtr_memo_push(mtr, block, MTR_MEMO_PAGE_X_FIX); +} + /*==================== B-TREE DELETE MARK AND UNMARK ===============*/ /****************************************************************//** @@ -3863,9 +3896,6 @@ btr_store_big_rec_extern_fields_func( the "external storage" flags in offsets will not correspond to rec when this function returns */ - const big_rec_t*big_rec_vec, /*!< in: vector containing fields - to be stored externally */ - #ifdef UNIV_DEBUG mtr_t* local_mtr, /*!< in: mtr containing the latch to rec and to the tree */ @@ -3874,11 +3904,9 @@ btr_store_big_rec_extern_fields_func( ibool update_in_place,/*! in: TRUE if the record is updated in place (not delete+insert) */ #endif /* UNIV_DEBUG || UNIV_BLOB_LIGHT_DEBUG */ - mtr_t* alloc_mtr) /*!< in/out: in an insert, NULL; - in an update, local_mtr for - allocating BLOB pages and - updating BLOB pointers; alloc_mtr - must not have freed any leaf pages */ + const big_rec_t*big_rec_vec) /*!< in: vector containing fields + to be stored externally */ + { ulint rec_page_no; byte* field_ref; @@ -3897,9 +3925,6 @@ btr_store_big_rec_extern_fields_func( ut_ad(rec_offs_validate(rec, index, offsets)); ut_ad(rec_offs_any_extern(offsets)); - ut_ad(local_mtr); - ut_ad(!alloc_mtr || alloc_mtr == local_mtr); - ut_ad(!update_in_place || alloc_mtr); ut_ad(mtr_memo_contains(local_mtr, dict_index_get_lock(index), MTR_MEMO_X_LOCK)); ut_ad(mtr_memo_contains(local_mtr, rec_block, MTR_MEMO_PAGE_X_FIX)); @@ -3915,25 +3940,6 @@ btr_store_big_rec_extern_fields_func( rec_page_no = buf_block_get_page_no(rec_block); ut_a(fil_page_get_type(page_align(rec)) == FIL_PAGE_INDEX); - if (alloc_mtr) { - /* Because alloc_mtr will be committed after - mtr, it is possible that the tablespace has been - extended when the B-tree record was updated or - inserted, or it will be extended while allocating - pages for big_rec. - - TODO: In mtr (not alloc_mtr), write a redo log record - about extending the tablespace to its current size, - and remember the current size. Whenever the tablespace - grows as pages are allocated, write further redo log - records to mtr. (Currently tablespace extension is not - covered by the redo log. If it were, the record would - only be written to alloc_mtr, which is committed after - mtr.) */ - } else { - alloc_mtr = &mtr; - } - if (UNIV_LIKELY_NULL(page_zip)) { int err; @@ -4010,7 +4016,7 @@ btr_store_big_rec_extern_fields_func( } block = btr_page_alloc(index, hint_page_no, - FSP_NO_DIR, 0, alloc_mtr, &mtr); + FSP_NO_DIR, 0, &mtr); if (UNIV_UNLIKELY(block == NULL)) { mtr_commit(&mtr); @@ -4137,15 +4143,11 @@ btr_store_big_rec_extern_fields_func( goto next_zip_page; } - if (alloc_mtr == &mtr) { - rec_block = buf_page_get( - space_id, zip_size, - rec_page_no, - RW_X_LATCH, &mtr); - buf_block_dbg_add_level( - rec_block, - SYNC_NO_ORDER_CHECK); - } + rec_block = buf_page_get(space_id, zip_size, + rec_page_no, + RW_X_LATCH, &mtr); + buf_block_dbg_add_level(rec_block, + SYNC_NO_ORDER_CHECK); if (err == Z_STREAM_END) { mach_write_to_4(field_ref @@ -4179,8 +4181,7 @@ btr_store_big_rec_extern_fields_func( page_zip_write_blob_ptr( page_zip, rec, index, offsets, - big_rec_vec->fields[i].field_no, - alloc_mtr); + big_rec_vec->fields[i].field_no, &mtr); next_zip_page: prev_page_no = page_no; @@ -4225,23 +4226,19 @@ next_zip_page: extern_len -= store_len; - if (alloc_mtr == &mtr) { - rec_block = buf_page_get( - space_id, zip_size, - rec_page_no, - RW_X_LATCH, &mtr); - buf_block_dbg_add_level( - rec_block, - SYNC_NO_ORDER_CHECK); - } + rec_block = buf_page_get(space_id, zip_size, + rec_page_no, + RW_X_LATCH, &mtr); + buf_block_dbg_add_level(rec_block, + SYNC_NO_ORDER_CHECK); mlog_write_ulint(field_ref + BTR_EXTERN_LEN, 0, - MLOG_4BYTES, alloc_mtr); + MLOG_4BYTES, &mtr); mlog_write_ulint(field_ref + BTR_EXTERN_LEN + 4, big_rec_vec->fields[i].len - extern_len, - MLOG_4BYTES, alloc_mtr); + MLOG_4BYTES, &mtr); if (prev_page_no == FIL_NULL) { btr_blob_dbg_add_blob( @@ -4251,19 +4248,18 @@ next_zip_page: mlog_write_ulint(field_ref + BTR_EXTERN_SPACE_ID, - space_id, MLOG_4BYTES, - alloc_mtr); + space_id, + MLOG_4BYTES, &mtr); mlog_write_ulint(field_ref + BTR_EXTERN_PAGE_NO, - page_no, MLOG_4BYTES, - alloc_mtr); + page_no, + MLOG_4BYTES, &mtr); mlog_write_ulint(field_ref + BTR_EXTERN_OFFSET, FIL_PAGE_DATA, - MLOG_4BYTES, - alloc_mtr); + MLOG_4BYTES, &mtr); } prev_page_no = page_no; diff --git a/storage/innodb_plugin/fsp/fsp0fsp.c b/storage/innodb_plugin/fsp/fsp0fsp.c index 19846b63d5b..fee7fde2e5c 100644 --- a/storage/innodb_plugin/fsp/fsp0fsp.c +++ b/storage/innodb_plugin/fsp/fsp0fsp.c @@ -312,9 +312,8 @@ fsp_fill_free_list( descriptor page and ibuf bitmap page; then we do not allocate more extents */ ulint space, /*!< in: space */ - fsp_header_t* header, /*!< in/out: space header */ - mtr_t* mtr) /*!< in/out: mini-transaction */ - __attribute__((nonnull)); + fsp_header_t* header, /*!< in: space header */ + mtr_t* mtr); /*!< in: mtr */ /**********************************************************************//** Allocates a single free page from a segment. This function implements the intelligent allocation strategy which tries to minimize file space @@ -334,13 +333,7 @@ fseg_alloc_free_page_low( inserted there in order, into which direction they go alphabetically: FSP_DOWN, FSP_UP, FSP_NO_DIR */ - mtr_t* mtr, /*!< in/out: mini-transaction */ - mtr_t* init_mtr)/*!< in/out: mini-transaction in which the - page should be initialized - (may be the same as mtr), or NULL if it - should not be initialized (the page at hint - was previously freed in mtr) */ - __attribute__((warn_unused_result, nonnull(3,6))); + mtr_t* mtr); /*!< in/out: mini-transaction */ #endif /* !UNIV_HOTBACKUP */ /**********************************************************************//** @@ -708,18 +701,17 @@ list, if not free limit == space size. This adding is necessary to make the descriptor defined, as they are uninitialized above the free limit. @return pointer to the extent descriptor, NULL if the page does not exist in the space or if the offset exceeds the free limit */ -UNIV_INLINE __attribute__((nonnull, warn_unused_result)) +UNIV_INLINE xdes_t* xdes_get_descriptor_with_space_hdr( /*===============================*/ - fsp_header_t* sp_header, /*!< in/out: space header, x-latched - in mtr */ - ulint space, /*!< in: space id */ - ulint offset, /*!< in: page offset; if equal - to the free limit, we try to - add new extents to the space - free list */ - mtr_t* mtr) /*!< in/out: mini-transaction */ + fsp_header_t* sp_header,/*!< in/out: space header, x-latched */ + ulint space, /*!< in: space id */ + ulint offset, /*!< in: page offset; + if equal to the free limit, + we try to add new extents to + the space free list */ + mtr_t* mtr) /*!< in: mtr handle */ { ulint limit; ulint size; @@ -727,9 +719,11 @@ xdes_get_descriptor_with_space_hdr( ulint descr_page_no; page_t* descr_page; + ut_ad(mtr); ut_ad(mtr_memo_contains(mtr, fil_space_get_latch(space, NULL), MTR_MEMO_X_LOCK)); - ut_ad(mtr_memo_contains_page(mtr, sp_header, MTR_MEMO_PAGE_X_FIX)); + ut_ad(mtr_memo_contains_page(mtr, sp_header, MTR_MEMO_PAGE_S_FIX) + || mtr_memo_contains_page(mtr, sp_header, MTR_MEMO_PAGE_X_FIX)); ut_ad(page_offset(sp_header) == FSP_HEADER_OFFSET); /* Read free limit and space size */ limit = mach_read_from_4(sp_header + FSP_FREE_LIMIT); @@ -779,7 +773,7 @@ is necessary to make the descriptor defined, as they are uninitialized above the free limit. @return pointer to the extent descriptor, NULL if the page does not exist in the space or if the offset exceeds the free limit */ -static __attribute__((nonnull, warn_unused_result)) +static xdes_t* xdes_get_descriptor( /*================*/ @@ -788,7 +782,7 @@ xdes_get_descriptor( or 0 for uncompressed pages */ ulint offset, /*!< in: page offset; if equal to the free limit, we try to add new extents to the space free list */ - mtr_t* mtr) /*!< in/out: mini-transaction */ + mtr_t* mtr) /*!< in: mtr handle */ { buf_block_t* block; fsp_header_t* sp_header; @@ -1166,14 +1160,14 @@ fsp_header_get_tablespace_size(void) Tries to extend a single-table tablespace so that a page would fit in the data file. @return TRUE if success */ -static __attribute__((nonnull, warn_unused_result)) +static ibool fsp_try_extend_data_file_with_pages( /*================================*/ ulint space, /*!< in: space */ ulint page_no, /*!< in: page number */ - fsp_header_t* header, /*!< in/out: space header */ - mtr_t* mtr) /*!< in/out: mini-transaction */ + fsp_header_t* header, /*!< in: space header */ + mtr_t* mtr) /*!< in: mtr */ { ibool success; ulint actual_size; @@ -1198,7 +1192,7 @@ fsp_try_extend_data_file_with_pages( /***********************************************************************//** Tries to extend the last data file of a tablespace if it is auto-extending. @return FALSE if not auto-extending */ -static __attribute__((nonnull)) +static ibool fsp_try_extend_data_file( /*=====================*/ @@ -1208,8 +1202,8 @@ fsp_try_extend_data_file( the actual file size rounded down to megabyte */ ulint space, /*!< in: space */ - fsp_header_t* header, /*!< in/out: space header */ - mtr_t* mtr) /*!< in/out: mini-transaction */ + fsp_header_t* header, /*!< in: space header */ + mtr_t* mtr) /*!< in: mtr */ { ulint size; ulint zip_size; @@ -1345,7 +1339,7 @@ fsp_fill_free_list( then we do not allocate more extents */ ulint space, /*!< in: space */ fsp_header_t* header, /*!< in/out: space header */ - mtr_t* mtr) /*!< in/out: mini-transaction */ + mtr_t* mtr) /*!< in: mtr */ { ulint limit; ulint size; @@ -1543,47 +1537,10 @@ fsp_alloc_free_extent( return(descr); } -/**********************************************************************//** -Allocates a single free page from a space. */ -static __attribute__((nonnull)) -void -fsp_alloc_from_free_frag( -/*=====================*/ - fsp_header_t* header, /*!< in/out: tablespace header */ - xdes_t* descr, /*!< in/out: extent descriptor */ - ulint bit, /*!< in: slot to allocate in the extent */ - mtr_t* mtr) /*!< in/out: mini-transaction */ -{ - ulint frag_n_used; - - ut_ad(xdes_get_state(descr, mtr) == XDES_FREE_FRAG); - ut_a(xdes_get_bit(descr, XDES_FREE_BIT, bit, mtr)); - xdes_set_bit(descr, XDES_FREE_BIT, bit, FALSE, mtr); - - /* Update the FRAG_N_USED field */ - frag_n_used = mtr_read_ulint(header + FSP_FRAG_N_USED, MLOG_4BYTES, - mtr); - frag_n_used++; - mlog_write_ulint(header + FSP_FRAG_N_USED, frag_n_used, MLOG_4BYTES, - mtr); - if (xdes_is_full(descr, mtr)) { - /* The fragment is full: move it to another list */ - flst_remove(header + FSP_FREE_FRAG, descr + XDES_FLST_NODE, - mtr); - xdes_set_state(descr, XDES_FULL_FRAG, mtr); - - flst_add_last(header + FSP_FULL_FRAG, descr + XDES_FLST_NODE, - mtr); - mlog_write_ulint(header + FSP_FRAG_N_USED, - frag_n_used - FSP_EXTENT_SIZE, MLOG_4BYTES, - mtr); - } -} - /**********************************************************************//** Allocates a single free page from a space. The page is marked as used. @return the page offset, FIL_NULL if no page could be allocated */ -static __attribute__((nonnull, warn_unused_result)) +static ulint fsp_alloc_free_page( /*================*/ @@ -1591,22 +1548,19 @@ fsp_alloc_free_page( ulint zip_size,/*!< in: compressed page size in bytes or 0 for uncompressed pages */ ulint hint, /*!< in: hint of which page would be desirable */ - mtr_t* mtr, /*!< in/out: mini-transaction */ - mtr_t* init_mtr)/*!< in/out: mini-transaction in which the - page should be initialized - (may be the same as mtr) */ + mtr_t* mtr) /*!< in/out: mini-transaction */ { fsp_header_t* header; fil_addr_t first; xdes_t* descr; buf_block_t* block; ulint free; + ulint frag_n_used; ulint page_no; ulint space_size; ibool success; ut_ad(mtr); - ut_ad(init_mtr); header = fsp_get_space_header(space, zip_size, mtr); @@ -1688,19 +1642,38 @@ fsp_alloc_free_page( } } - fsp_alloc_from_free_frag(header, descr, free, mtr); + xdes_set_bit(descr, XDES_FREE_BIT, free, FALSE, mtr); + + /* Update the FRAG_N_USED field */ + frag_n_used = mtr_read_ulint(header + FSP_FRAG_N_USED, MLOG_4BYTES, + mtr); + frag_n_used++; + mlog_write_ulint(header + FSP_FRAG_N_USED, frag_n_used, MLOG_4BYTES, + mtr); + if (xdes_is_full(descr, mtr)) { + /* The fragment is full: move it to another list */ + flst_remove(header + FSP_FREE_FRAG, descr + XDES_FLST_NODE, + mtr); + xdes_set_state(descr, XDES_FULL_FRAG, mtr); + + flst_add_last(header + FSP_FULL_FRAG, descr + XDES_FLST_NODE, + mtr); + mlog_write_ulint(header + FSP_FRAG_N_USED, + frag_n_used - FSP_EXTENT_SIZE, MLOG_4BYTES, + mtr); + } /* Initialize the allocated page to the buffer pool, so that it can be obtained immediately with buf_page_get without need for a disk read. */ - buf_page_create(space, page_no, zip_size, init_mtr); + buf_page_create(space, page_no, zip_size, mtr); - block = buf_page_get(space, zip_size, page_no, RW_X_LATCH, init_mtr); + block = buf_page_get(space, zip_size, page_no, RW_X_LATCH, mtr); buf_block_dbg_add_level(block, SYNC_FSP_PAGE); /* Prior contents of the page should be ignored */ - fsp_init_file_page(block, init_mtr); + fsp_init_file_page(block, mtr); return(page_no); } @@ -1936,7 +1909,7 @@ fsp_alloc_seg_inode_page( zip_size = dict_table_flags_to_zip_size( mach_read_from_4(FSP_SPACE_FLAGS + space_header)); - page_no = fsp_alloc_free_page(space, zip_size, 0, mtr, mtr); + page_no = fsp_alloc_free_page(space, zip_size, 0, mtr); if (page_no == FIL_NULL) { @@ -2350,7 +2323,7 @@ fseg_create_general( if (page == 0) { page = fseg_alloc_free_page_low(space, zip_size, - inode, 0, FSP_UP, mtr, mtr); + inode, 0, FSP_UP, mtr); if (page == FIL_NULL) { @@ -2606,12 +2579,7 @@ fseg_alloc_free_page_low( inserted there in order, into which direction they go alphabetically: FSP_DOWN, FSP_UP, FSP_NO_DIR */ - mtr_t* mtr, /*!< in/out: mini-transaction */ - mtr_t* init_mtr)/*!< in/out: mini-transaction in which the - page should be initialized - (may be the same as mtr), or NULL if it - should not be initialized (the page at hint - was previously freed in mtr) */ + mtr_t* mtr) /*!< in/out: mini-transaction */ { fsp_header_t* space_header; ulint space_size; @@ -2622,6 +2590,7 @@ fseg_alloc_free_page_low( ulint ret_page; /*!< the allocated page offset, FIL_NULL if could not be allocated */ xdes_t* ret_descr; /*!< the extent of the allocated page */ + ibool frag_page_allocated = FALSE; ibool success; ulint n; @@ -2643,8 +2612,6 @@ fseg_alloc_free_page_low( if (descr == NULL) { /* Hint outside space or too high above free limit: reset hint */ - ut_a(init_mtr); - /* The file space header page is always allocated. */ hint = 0; descr = xdes_get_descriptor(space, zip_size, hint, mtr); } @@ -2656,20 +2623,15 @@ fseg_alloc_free_page_low( mtr), seg_id)) && (xdes_get_bit(descr, XDES_FREE_BIT, hint % FSP_EXTENT_SIZE, mtr) == TRUE)) { -take_hinted_page: + /* 1. We can take the hinted page =================================*/ ret_descr = descr; ret_page = hint; - /* Skip the check for extending the tablespace. If the - page hint were not within the size of the tablespace, - we would have got (descr == NULL) above and reset the hint. */ - goto got_hinted_page; /*-----------------------------------------------------------*/ - } else if (xdes_get_state(descr, mtr) == XDES_FREE - && (!init_mtr - || ((reserved - used < reserved / FSEG_FILLFACTOR) - && used >= FSEG_FRAG_LIMIT))) { + } else if ((xdes_get_state(descr, mtr) == XDES_FREE) + && ((reserved - used) < reserved / FSEG_FILLFACTOR) + && (used >= FSEG_FRAG_LIMIT)) { /* 2. We allocate the free extent from space and can take ========================================================= @@ -2687,20 +2649,8 @@ take_hinted_page: /* Try to fill the segment free list */ fseg_fill_free_list(seg_inode, space, zip_size, hint + FSP_EXTENT_SIZE, mtr); - goto take_hinted_page; - /*-----------------------------------------------------------*/ - } else if (!init_mtr) { - ut_a(xdes_get_state(descr, mtr) == XDES_FREE_FRAG); - fsp_alloc_from_free_frag(space_header, descr, - hint % FSP_EXTENT_SIZE, mtr); ret_page = hint; - ret_descr = NULL; - - /* Put the page in the fragment page array of the segment */ - n = fseg_find_free_frag_page_slot(seg_inode, mtr); - ut_a(n != FIL_NULL); - fseg_set_nth_frag_page_no(seg_inode, n, ret_page, mtr); - goto got_hinted_page; + /*-----------------------------------------------------------*/ } else if ((direction != FSP_NO_DIR) && ((reserved - used) < reserved / FSEG_FILLFACTOR) && (used >= FSEG_FRAG_LIMIT) @@ -2760,10 +2710,11 @@ take_hinted_page: } else if (used < FSEG_FRAG_LIMIT) { /* 6. We allocate an individual page from the space ===================================================*/ - ret_page = fsp_alloc_free_page(space, zip_size, hint, - mtr, init_mtr); + ret_page = fsp_alloc_free_page(space, zip_size, hint, mtr); ret_descr = NULL; + frag_page_allocated = TRUE; + if (ret_page != FIL_NULL) { /* Put the page in the fragment page array of the segment */ @@ -2773,10 +2724,6 @@ take_hinted_page: fseg_set_nth_frag_page_no(seg_inode, n, ret_page, mtr); } - - /* fsp_alloc_free_page() invoked fsp_init_file_page() - already. */ - return(ret_page); /*-----------------------------------------------------------*/ } else { /* 7. We allocate a new extent and take its first page @@ -2824,34 +2771,26 @@ take_hinted_page: } } -got_hinted_page: - { + if (!frag_page_allocated) { /* Initialize the allocated page to buffer pool, so that it can be obtained immediately with buf_page_get without need for a disk read */ buf_block_t* block; ulint zip_size = dict_table_flags_to_zip_size( mach_read_from_4(FSP_SPACE_FLAGS + space_header)); - mtr_t* block_mtr = init_mtr ? init_mtr : mtr; - block = buf_page_create(space, ret_page, zip_size, block_mtr); + block = buf_page_create(space, ret_page, zip_size, mtr); buf_block_dbg_add_level(block, SYNC_FSP_PAGE); if (UNIV_UNLIKELY(block != buf_page_get(space, zip_size, ret_page, RW_X_LATCH, - block_mtr))) { + mtr))) { ut_error; } - if (init_mtr) { - /* The prior contents of the page should be ignored */ - fsp_init_file_page(block, init_mtr); - } - } + /* The prior contents of the page should be ignored */ + fsp_init_file_page(block, mtr); - /* ret_descr == NULL if the block was allocated from free_frag - (XDES_FREE_FRAG) */ - if (ret_descr != NULL) { /* At this point we know the extent and the page offset. The extent is still in the appropriate list (FSEG_NOT_FULL or FSEG_FREE), and the page is not yet marked as used. */ @@ -2888,11 +2827,7 @@ fseg_alloc_free_page_general( with fsp_reserve_free_extents, then there is no need to do the check for this individual page */ - mtr_t* mtr, /*!< in/out: mini-transaction handle */ - mtr_t* init_mtr)/*!< in/out: mtr or another mini-transaction - in which the page should be initialized, - or NULL if this is a "fake allocation" of - a page that was previously freed in mtr */ + mtr_t* mtr) /*!< in/out: mini-transaction */ { fseg_inode_t* inode; ulint space; @@ -2934,8 +2869,7 @@ fseg_alloc_free_page_general( } page_no = fseg_alloc_free_page_low(space, zip_size, - inode, hint, direction, - mtr, init_mtr); + inode, hint, direction, mtr); if (!has_done_reservation) { fil_space_release_free_extents(space, n_reserved); } @@ -2943,6 +2877,28 @@ fseg_alloc_free_page_general( return(page_no); } +/**********************************************************************//** +Allocates a single free page from a segment. This function implements +the intelligent allocation strategy which tries to minimize file space +fragmentation. +@return allocated page offset, FIL_NULL if no page could be allocated */ +UNIV_INTERN +ulint +fseg_alloc_free_page( +/*=================*/ + fseg_header_t* seg_header,/*!< in: segment header */ + ulint hint, /*!< in: hint of which page would be desirable */ + byte direction,/*!< in: if the new page is needed because + of an index page split, and records are + inserted there in order, into which + direction they go alphabetically: FSP_DOWN, + FSP_UP, FSP_NO_DIR */ + mtr_t* mtr) /*!< in: mtr handle */ +{ + return(fseg_alloc_free_page_general(seg_header, hint, direction, + FALSE, mtr)); +} + /**********************************************************************//** Checks that we have at least 2 frag pages free in the first extent of a single-table tablespace, and they are also physically initialized to the data diff --git a/storage/innodb_plugin/include/btr0btr.h b/storage/innodb_plugin/include/btr0btr.h index 476ad29adac..c0a038dd21d 100644 --- a/storage/innodb_plugin/include/btr0btr.h +++ b/storage/innodb_plugin/include/btr0btr.h @@ -557,12 +557,7 @@ btr_page_alloc( page split is made */ ulint level, /*!< in: level where the page is placed in the tree */ - mtr_t* mtr, /*!< in/out: mini-transaction - for the allocation */ - mtr_t* init_mtr) /*!< in/out: mini-transaction - for x-latching and initializing - the page */ - __attribute__((nonnull, warn_unused_result)); + mtr_t* mtr); /*!< in: mtr */ /**************************************************************//** Frees a file page used in an index tree. NOTE: cannot free field external storage pages because the page must contain info on its level. */ @@ -585,33 +580,6 @@ btr_page_free_low( buf_block_t* block, /*!< in: block to be freed, x-latched */ ulint level, /*!< in: page level */ mtr_t* mtr); /*!< in: mtr */ -/**************************************************************//** -Marks all MTR_MEMO_FREE_CLUST_LEAF pages nonfree or free. -For invoking btr_store_big_rec_extern_fields() after an update, -we must temporarily mark freed clustered index pages allocated, so -that off-page columns will not be allocated from them. Between the -btr_store_big_rec_extern_fields() and mtr_commit() we have to -mark the pages free again, so that no pages will be leaked. */ -UNIV_INTERN -void -btr_mark_freed_leaves( -/*==================*/ - dict_index_t* index, /*!< in/out: clustered index */ - mtr_t* mtr, /*!< in/out: mini-transaction */ - ibool nonfree)/*!< in: TRUE=mark nonfree, FALSE=mark freed */ - __attribute__((nonnull)); -#ifdef UNIV_DEBUG -/**************************************************************//** -Validates all pages marked MTR_MEMO_FREE_CLUST_LEAF. -@see btr_mark_freed_leaves() -@return TRUE */ -UNIV_INTERN -ibool -btr_freed_leaves_validate( -/*======================*/ - mtr_t* mtr) /*!< in: mini-transaction */ - __attribute__((nonnull, warn_unused_result)); -#endif /* UNIV_DEBUG */ #ifdef UNIV_BTR_PRINT /*************************************************************//** Prints size info of a B-tree. */ diff --git a/storage/innodb_plugin/include/btr0cur.h b/storage/innodb_plugin/include/btr0cur.h index 1d97c5b9452..6094a2a6c7a 100644 --- a/storage/innodb_plugin/include/btr0cur.h +++ b/storage/innodb_plugin/include/btr0cur.h @@ -326,6 +326,16 @@ btr_cur_pessimistic_update( que_thr_t* thr, /*!< in: query thread */ mtr_t* mtr); /*!< in: mtr; must be committed before latching any further pages */ +/***************************************************************** +Commits and restarts a mini-transaction so that it will retain an +x-lock on index->lock and the cursor page. */ +UNIV_INTERN +void +btr_cur_mtr_commit_and_start( +/*=========================*/ + btr_cur_t* cursor, /*!< in: cursor */ + mtr_t* mtr) /*!< in/out: mini-transaction */ + __attribute__((nonnull)); /***********************************************************//** Marks a clustered index record deleted. Writes an undo log record to undo log on this delete marking. Writes in the trx id field the id @@ -530,8 +540,6 @@ btr_store_big_rec_extern_fields_func( the "external storage" flags in offsets will not correspond to rec when this function returns */ - const big_rec_t*big_rec_vec, /*!< in: vector containing fields - to be stored externally */ #ifdef UNIV_DEBUG mtr_t* local_mtr, /*!< in: mtr containing the latch to rec and to the tree */ @@ -540,12 +548,9 @@ btr_store_big_rec_extern_fields_func( ibool update_in_place,/*! in: TRUE if the record is updated in place (not delete+insert) */ #endif /* UNIV_DEBUG || UNIV_BLOB_LIGHT_DEBUG */ - mtr_t* alloc_mtr) /*!< in/out: in an insert, NULL; - in an update, local_mtr for - allocating BLOB pages and - updating BLOB pointers; alloc_mtr - must not have freed any leaf pages */ - __attribute__((nonnull(1,2,3,4,5), warn_unused_result)); + const big_rec_t*big_rec_vec) /*!< in: vector containing fields + to be stored externally */ + __attribute__((nonnull)); /** Stores the fields in big_rec_vec to the tablespace and puts pointers to them in rec. The extern flags in rec will have to be set beforehand. @@ -554,22 +559,21 @@ file segment of the index tree. @param index in: clustered index; MUST be X-latched by mtr @param b in/out: block containing rec; MUST be X-latched by mtr @param rec in/out: clustered index record -@param offs in: rec_get_offsets(rec, index); +@param offsets in: rec_get_offsets(rec, index); the "external storage" flags in offsets will not be adjusted -@param big in: vector containing fields to be stored externally @param mtr in: mini-transaction that holds x-latch on index and b @param upd in: TRUE if the record is updated in place (not delete+insert) -@param rmtr in/out: in updates, the mini-transaction that holds rec +@param big in: vector containing fields to be stored externally @return DB_SUCCESS or DB_OUT_OF_FILE_SPACE */ #ifdef UNIV_DEBUG -# define btr_store_big_rec_extern_fields(index,b,rec,offs,big,mtr,upd,rmtr) \ - btr_store_big_rec_extern_fields_func(index,b,rec,offs,big,mtr,upd,rmtr) +# define btr_store_big_rec_extern_fields(index,b,rec,offsets,mtr,upd,big) \ + btr_store_big_rec_extern_fields_func(index,b,rec,offsets,mtr,upd,big) #elif defined UNIV_BLOB_LIGHT_DEBUG -# define btr_store_big_rec_extern_fields(index,b,rec,offs,big,mtr,upd,rmtr) \ - btr_store_big_rec_extern_fields_func(index,b,rec,offs,big,upd,rmtr) +# define btr_store_big_rec_extern_fields(index,b,rec,offsets,mtr,upd,big) \ + btr_store_big_rec_extern_fields_func(index,b,rec,offsets,upd,big) #else -# define btr_store_big_rec_extern_fields(index,b,rec,offs,big,mtr,upd,rmtr) \ - btr_store_big_rec_extern_fields_func(index,b,rec,offs,big,rmtr) +# define btr_store_big_rec_extern_fields(index,b,rec,offsets,mtr,upd,big) \ + btr_store_big_rec_extern_fields_func(index,b,rec,offsets,big) #endif /*******************************************************************//** diff --git a/storage/innodb_plugin/include/fsp0fsp.h b/storage/innodb_plugin/include/fsp0fsp.h index 2221380c9a2..403e1d404a8 100644 --- a/storage/innodb_plugin/include/fsp0fsp.h +++ b/storage/innodb_plugin/include/fsp0fsp.h @@ -176,18 +176,19 @@ fseg_n_reserved_pages( Allocates a single free page from a segment. This function implements the intelligent allocation strategy which tries to minimize file space fragmentation. -@param[in/out] seg_header segment header -@param[in] hint hint of which page would be desirable -@param[in] direction if the new page is needed because +@return the allocated page offset FIL_NULL if no page could be allocated */ +UNIV_INTERN +ulint +fseg_alloc_free_page( +/*=================*/ + fseg_header_t* seg_header, /*!< in: segment header */ + ulint hint, /*!< in: hint of which page would be desirable */ + byte direction, /*!< in: if the new page is needed because of an index page split, and records are inserted there in order, into which direction they go alphabetically: FSP_DOWN, - FSP_UP, FSP_NO_DIR -@param[in/out] mtr mini-transaction -@return the allocated page offset FIL_NULL if no page could be allocated */ -#define fseg_alloc_free_page(seg_header, hint, direction, mtr) \ - fseg_alloc_free_page_general(seg_header, hint, direction, \ - FALSE, mtr, mtr) + FSP_UP, FSP_NO_DIR */ + mtr_t* mtr); /*!< in: mtr handle */ /**********************************************************************//** Allocates a single free page from a segment. This function implements the intelligent allocation strategy which tries to minimize file space @@ -209,11 +210,7 @@ fseg_alloc_free_page_general( with fsp_reserve_free_extents, then there is no need to do the check for this individual page */ - mtr_t* mtr, /*!< in/out: mini-transaction */ - mtr_t* init_mtr)/*!< in/out: mtr or another mini-transaction - in which the page should be initialized, - or NULL if this is a "fake allocation" of - a page that was previously freed in mtr */ + mtr_t* mtr) /*!< in/out: mini-transaction */ __attribute__((warn_unused_result, nonnull(1,5))); /**********************************************************************//** Reserves free pages from a tablespace. All mini-transactions which may diff --git a/storage/innodb_plugin/include/mtr0mtr.h b/storage/innodb_plugin/include/mtr0mtr.h index 3529519e7f4..8a9ec8ea7f0 100644 --- a/storage/innodb_plugin/include/mtr0mtr.h +++ b/storage/innodb_plugin/include/mtr0mtr.h @@ -53,8 +53,6 @@ first 3 values must be RW_S_LATCH, RW_X_LATCH, RW_NO_LATCH */ #define MTR_MEMO_MODIFY 54 #define MTR_MEMO_S_LOCK 55 #define MTR_MEMO_X_LOCK 56 -/** The mini-transaction freed a clustered index leaf page. */ -#define MTR_MEMO_FREE_CLUST_LEAF 57 /** @name Log item types The log items are declared 'byte' so that the compiler can warn if val @@ -379,12 +377,9 @@ struct mtr_struct{ #endif dyn_array_t memo; /*!< memo stack for locks etc. */ dyn_array_t log; /*!< mini-transaction log */ - unsigned modifications:1; - /*!< TRUE if the mini-transaction - modified buffer pool pages */ - unsigned freed_clust_leaf:1; - /*!< TRUE if MTR_MEMO_FREE_CLUST_LEAF - was logged in the mini-transaction */ + ibool modifications; + /* TRUE if the mtr made modifications to + buffer pool pages */ ulint n_log_recs; /* count of how many page initial log records have been written to the mtr log */ diff --git a/storage/innodb_plugin/include/mtr0mtr.ic b/storage/innodb_plugin/include/mtr0mtr.ic index 9c0ddff9132..9f92d2b06a1 100644 --- a/storage/innodb_plugin/include/mtr0mtr.ic +++ b/storage/innodb_plugin/include/mtr0mtr.ic @@ -44,7 +44,6 @@ mtr_start( mtr->log_mode = MTR_LOG_ALL; mtr->modifications = FALSE; - mtr->freed_clust_leaf = FALSE; mtr->n_log_recs = 0; ut_d(mtr->state = MTR_ACTIVE); @@ -68,8 +67,7 @@ mtr_memo_push( ut_ad(object); ut_ad(type >= MTR_MEMO_PAGE_S_FIX); - ut_ad(type <= MTR_MEMO_FREE_CLUST_LEAF); - ut_ad(type != MTR_MEMO_FREE_CLUST_LEAF || mtr->freed_clust_leaf); + ut_ad(type <= MTR_MEMO_X_LOCK); ut_ad(mtr); ut_ad(mtr->magic_n == MTR_MAGIC_N); ut_ad(mtr->state == MTR_ACTIVE); diff --git a/storage/innodb_plugin/mtr/mtr0mtr.c b/storage/innodb_plugin/mtr/mtr0mtr.c index e3fefbedec7..5fad61b2922 100644 --- a/storage/innodb_plugin/mtr/mtr0mtr.c +++ b/storage/innodb_plugin/mtr/mtr0mtr.c @@ -58,11 +58,12 @@ mtr_memo_slot_release( buf_page_release((buf_block_t*)object, type, mtr); } else if (type == MTR_MEMO_S_LOCK) { rw_lock_s_unlock((rw_lock_t*)object); +#ifdef UNIV_DEBUG } else if (type != MTR_MEMO_X_LOCK) { - ut_ad(type == MTR_MEMO_MODIFY - || type == MTR_MEMO_FREE_CLUST_LEAF); + ut_ad(type == MTR_MEMO_MODIFY); ut_ad(mtr_memo_contains(mtr, object, MTR_MEMO_PAGE_X_FIX)); +#endif /* UNIV_DEBUG */ } else { rw_lock_x_unlock((rw_lock_t*)object); } diff --git a/storage/innodb_plugin/row/row0ins.c b/storage/innodb_plugin/row/row0ins.c index cd135e8ba8f..f0f6eca627f 100644 --- a/storage/innodb_plugin/row/row0ins.c +++ b/storage/innodb_plugin/row/row0ins.c @@ -2097,20 +2097,15 @@ row_ins_index_entry_low( if (big_rec) { ut_a(err == DB_SUCCESS); /* Write out the externally stored - columns, but allocate the pages and - write the pointers using the - mini-transaction of the record update. - If any pages were freed in the update, - temporarily mark them allocated so - that off-page columns will not - overwrite them. We must do this, - because we will write the redo log for - the BLOB writes before writing the - redo log for the record update. Thus, - redo log application at crash recovery - will see BLOBs being written to free pages. */ - - btr_mark_freed_leaves(index, &mtr, TRUE); + columns while still x-latching + index->lock and block->lock. We have + to mtr_commit(mtr) first, so that the + redo log will be written in the + correct order. Otherwise, we would run + into trouble on crash recovery if mtr + freed B-tree pages on which some of + the big_rec fields will be written. */ + btr_cur_mtr_commit_and_start(&cursor, &mtr); rec = btr_cur_get_rec(&cursor); offsets = rec_get_offsets( @@ -2119,8 +2114,7 @@ row_ins_index_entry_low( err = btr_store_big_rec_extern_fields( index, btr_cur_get_block(&cursor), - rec, offsets, big_rec, &mtr, - FALSE, &mtr); + rec, offsets, &mtr, FALSE, big_rec); /* If writing big_rec fails (for example, because of DB_OUT_OF_FILE_SPACE), the record will be corrupted. Even if @@ -2133,9 +2127,6 @@ row_ins_index_entry_low( undo log, and thus the record cannot be rolled back. */ ut_a(err == DB_SUCCESS); - /* Free the pages again - in order to avoid a leak. */ - btr_mark_freed_leaves(index, &mtr, FALSE); goto stored_big_rec; } } else { @@ -2177,7 +2168,7 @@ function_exit: err = btr_store_big_rec_extern_fields( index, btr_cur_get_block(&cursor), - rec, offsets, big_rec, &mtr, FALSE, NULL); + rec, offsets, &mtr, FALSE, big_rec); stored_big_rec: if (modify) { diff --git a/storage/innodb_plugin/row/row0row.c b/storage/innodb_plugin/row/row0row.c index e476ffae84e..9cdbbe76e04 100644 --- a/storage/innodb_plugin/row/row0row.c +++ b/storage/innodb_plugin/row/row0row.c @@ -243,20 +243,19 @@ row_build( } #if defined UNIV_DEBUG || defined UNIV_BLOB_LIGHT_DEBUG - if (rec_offs_any_null_extern(rec, offsets)) { - /* This condition can occur during crash recovery - before trx_rollback_active() has completed execution. + /* This condition can occur during crash recovery before + trx_rollback_active() has completed execution. - This condition is possible if the server crashed - during an insert or update-by-delete-and-insert before - btr_store_big_rec_extern_fields() did mtr_commit() all - BLOB pointers to the freshly inserted clustered index - record. */ - ut_a(trx_assert_recovered( - row_get_rec_trx_id(rec, index, offsets))); - ut_a(trx_undo_roll_ptr_is_insert( - row_get_rec_roll_ptr(rec, index, offsets))); - } + This condition is possible if the server crashed + during an insert or update before + btr_store_big_rec_extern_fields() did mtr_commit() all + BLOB pointers to the clustered index record. + + If the record contains a null BLOB pointer, look up the + transaction that holds the implicit lock on this record, and + assert that it was recovered (and will soon be rolled back). */ + ut_a(!rec_offs_any_null_extern(rec, offsets) + || trx_assert_recovered(row_get_rec_trx_id(rec, index, offsets))); #endif /* UNIV_DEBUG || UNIV_BLOB_LIGHT_DEBUG */ if (type != ROW_COPY_POINTERS) { diff --git a/storage/innodb_plugin/row/row0upd.c b/storage/innodb_plugin/row/row0upd.c index 05856687015..b5952ff0a78 100644 --- a/storage/innodb_plugin/row/row0upd.c +++ b/storage/innodb_plugin/row/row0upd.c @@ -1978,22 +1978,21 @@ row_upd_clust_rec( rec_offs_init(offsets_); ut_a(err == DB_SUCCESS); - /* Write out the externally stored columns, but - allocate the pages and write the pointers using the - mini-transaction of the record update. If any pages - were freed in the update, temporarily mark them - allocated so that off-page columns will not overwrite - them. We must do this, because we write the redo log - for the BLOB writes before writing the redo log for - the record update. */ + /* Write out the externally stored columns while still + x-latching index->lock and block->lock. We have to + mtr_commit(mtr) first, so that the redo log will be + written in the correct order. Otherwise, we would run + into trouble on crash recovery if mtr freed B-tree + pages on which some of the big_rec fields will be + written. */ + btr_cur_mtr_commit_and_start(btr_cur, mtr); - btr_mark_freed_leaves(index, mtr, TRUE); rec = btr_cur_get_rec(btr_cur); err = btr_store_big_rec_extern_fields( index, btr_cur_get_block(btr_cur), rec, rec_get_offsets(rec, index, offsets_, ULINT_UNDEFINED, &heap), - big_rec, mtr, TRUE, mtr); + mtr, TRUE, big_rec); /* If writing big_rec fails (for example, because of DB_OUT_OF_FILE_SPACE), the record will be corrupted. Even if we did not update any externally stored @@ -2003,8 +2002,6 @@ row_upd_clust_rec( to the undo log, and thus the record cannot be rolled back. */ ut_a(err == DB_SUCCESS); - /* Free the pages again in order to avoid a leak. */ - btr_mark_freed_leaves(index, mtr, FALSE); } mtr_commit(mtr); diff --git a/storage/innodb_plugin/trx/trx0undo.c b/storage/innodb_plugin/trx/trx0undo.c index c36f55fbd9c..746f0808643 100644 --- a/storage/innodb_plugin/trx/trx0undo.c +++ b/storage/innodb_plugin/trx/trx0undo.c @@ -912,7 +912,7 @@ trx_undo_add_page( page_no = fseg_alloc_free_page_general(header_page + TRX_UNDO_SEG_HDR + TRX_UNDO_FSEG_HEADER, undo->top_page_no + 1, FSP_UP, - TRUE, mtr, mtr); + TRUE, mtr); fil_space_release_free_extents(undo->space, n_reserved); From 2c67d5066db7d6aada4d93297918a8ff3e57aa33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Wed, 26 Oct 2011 12:23:57 +0300 Subject: [PATCH 17/19] Revert revno:3452.71.32 (Bug#12612184 fix). Bug#12612184 RACE CONDITION AFTER BTR_CUR_PESSIMISTIC_UPDATE() The fix introduced potentially more severe crash recovery problems than the bug causes. Revert the fix for now. --- storage/innobase/btr/btr0btr.c | 40 ++------ storage/innobase/btr/btr0cur.c | 106 +++++++-------------- storage/innobase/include/btr0btr.h | 12 +-- storage/innobase/include/btr0cur.h | 39 ++++---- storage/innobase/include/buf0buf.h | 19 ---- storage/innobase/include/page0page.h | 20 +--- storage/innobase/include/page0page.ic | 16 ---- storage/innobase/page/page0page.c | 31 +++--- storage/innobase/row/row0ins.c | 44 +-------- storage/innobase/row/row0umod.c | 2 - storage/innobase/row/row0upd.c | 34 ++----- storage/innodb_plugin/ChangeLog | 10 -- storage/innodb_plugin/btr/btr0btr.c | 48 +++------- storage/innodb_plugin/btr/btr0cur.c | 80 ++-------------- storage/innodb_plugin/include/btr0btr.h | 13 +-- storage/innodb_plugin/include/btr0cur.h | 24 +---- storage/innodb_plugin/include/btr0cur.ic | 2 +- storage/innodb_plugin/include/buf0buf.h | 25 ----- storage/innodb_plugin/include/buf0buf.ic | 13 +++ storage/innodb_plugin/include/page0page.h | 37 +------ storage/innodb_plugin/include/page0page.ic | 30 ------ storage/innodb_plugin/page/page0cur.c | 15 +-- storage/innodb_plugin/page/page0page.c | 50 +++++----- storage/innodb_plugin/row/row0ins.c | 46 +-------- storage/innodb_plugin/row/row0upd.c | 33 ++----- 25 files changed, 192 insertions(+), 597 deletions(-) diff --git a/storage/innobase/btr/btr0btr.c b/storage/innobase/btr/btr0btr.c index e8e065a3116..5079757272a 100644 --- a/storage/innobase/btr/btr0btr.c +++ b/storage/innobase/btr/btr0btr.c @@ -1948,7 +1948,7 @@ btr_node_ptr_delete( ut_a(err == DB_SUCCESS); if (!compressed) { - btr_cur_compress_if_useful(&cursor, FALSE, mtr); + btr_cur_compress_if_useful(&cursor, mtr); } } @@ -1956,10 +1956,9 @@ btr_node_ptr_delete( If page is the only on its level, this function moves its records to the father page, thus reducing the tree height. */ static -page_t* +void btr_lift_page_up( /*=============*/ - /* out: father page */ dict_index_t* index, /* in: index tree */ page_t* page, /* in: page which is the only on its level; must not be empty: use @@ -2035,8 +2034,6 @@ btr_lift_page_up( ibuf_reset_free_bits(index, father_page); ut_ad(page_validate(father_page, index)); ut_ad(btr_check_node_ptr(index, father_page, mtr)); - - return(father_page); } /***************************************************************** @@ -2053,13 +2050,11 @@ enough free extents so that the compression will always succeed if done! */ void btr_compress( /*=========*/ - btr_cur_t* cursor, /* in/out: cursor on the page to merge - or lift; the page must not be empty: - when deleting records, use btr_discard_page() - if the page would become empty */ - ibool adjust, /* in: TRUE if should adjust the - cursor position even if compression occurs */ - mtr_t* mtr) /* in/out: mini-transaction */ + btr_cur_t* cursor, /* in: cursor on the page to merge or lift; + the page must not be empty: in record delete + use btr_discard_page if the page would become + empty */ + mtr_t* mtr) /* in: mtr */ { dict_index_t* index; ulint space; @@ -2074,7 +2069,6 @@ btr_compress( rec_t* node_ptr; ulint data_size; ulint n_recs; - ulint nth_rec = 0; /* remove bogus warning */ ulint max_ins_size; ulint max_ins_size_reorg; ulint comp; @@ -2082,7 +2076,6 @@ btr_compress( page = btr_cur_get_page(cursor); index = btr_cur_get_index(cursor); comp = page_is_comp(page); - ut_a((ibool)!!comp == dict_table_is_comp(index->table)); ut_ad(mtr_memo_contains(mtr, dict_index_get_lock(index), @@ -2104,10 +2097,6 @@ btr_compress( father_page = buf_frame_align(node_ptr); ut_a(comp == page_is_comp(father_page)); - if (adjust) { - nth_rec = page_rec_get_n_recs_before(btr_cur_get_rec(cursor)); - } - /* Decide the page to which we try to merge and which will inherit the locks */ @@ -2132,8 +2121,9 @@ btr_compress( } else { /* The page is the only one on the level, lift the records to the father */ - merge_page = btr_lift_page_up(index, page, mtr); - goto func_exit; + btr_lift_page_up(index, page, mtr); + + return; } n_recs = page_get_n_recs(page); @@ -2209,10 +2199,6 @@ btr_compress( index, mtr); lock_update_merge_left(merge_page, orig_pred, page); - - if (adjust) { - nth_rec += page_rec_get_n_recs_before(orig_pred); - } } else { orig_succ = page_rec_get_next( page_get_infimum_rec(merge_page)); @@ -2233,12 +2219,6 @@ btr_compress( btr_page_free(index, page, mtr); ut_ad(btr_check_node_ptr(index, merge_page, mtr)); - -func_exit: - if (adjust) { - btr_cur_position(index, page_rec_get_nth(merge_page, nth_rec), - cursor); - } } /***************************************************************** diff --git a/storage/innobase/btr/btr0cur.c b/storage/innobase/btr/btr0cur.c index 95d87344e93..3c12e28feb6 100644 --- a/storage/innobase/btr/btr0cur.c +++ b/storage/innobase/btr/btr0cur.c @@ -1790,9 +1790,7 @@ btr_cur_pessimistic_update( /* out: DB_SUCCESS or error code */ ulint flags, /* in: undo logging, locking, and rollback flags */ - btr_cur_t* cursor, /* in/out: cursor on the record to update; - cursor may become invalid if *big_rec == NULL - || !(flags & BTR_KEEP_POS_FLAG) */ + btr_cur_t* cursor, /* in: cursor on the record to update */ big_rec_t** big_rec,/* out: big rec vector whose fields have to be stored externally by the caller, or NULL */ upd_t* update, /* in: update vector; this is allowed also @@ -1927,10 +1925,6 @@ btr_cur_pessimistic_update( err = DB_TOO_BIG_RECORD; goto return_after_reservations; } - - ut_ad(index->type & DICT_CLUSTERED); - ut_ad(btr_page_get_level(page, mtr) == 0); - ut_ad(flags & BTR_KEEP_POS_FLAG); } page_cursor = btr_cur_get_page_cur(cursor); @@ -1957,8 +1951,6 @@ btr_cur_pessimistic_update( ut_a(rec || optim_err != DB_UNDERFLOW); if (rec) { - page_cursor->rec = rec; - lock_rec_restore_from_page_infimum(rec, page); rec_set_field_extern_bits(rec, index, ext_vect, n_ext_vect, mtr); @@ -1972,30 +1964,12 @@ btr_cur_pessimistic_update( btr_cur_unmark_extern_fields(rec, mtr, offsets); } - btr_cur_compress_if_useful( - cursor, - big_rec_vec != NULL && (flags & BTR_KEEP_POS_FLAG), - mtr); + btr_cur_compress_if_useful(cursor, mtr); err = DB_SUCCESS; goto return_after_reservations; } - if (big_rec_vec) { - ut_ad(index->type & DICT_CLUSTERED); - ut_ad(btr_page_get_level(page, mtr) == 0); - ut_ad(flags & BTR_KEEP_POS_FLAG); - - /* btr_page_split_and_insert() in - btr_cur_pessimistic_insert() invokes - mtr_memo_release(mtr, index->lock, MTR_MEMO_X_LOCK). - We must keep the index->lock when we created a - big_rec, so that row_upd_clust_rec() can store the - big_rec in the same mini-transaction. */ - - mtr_x_lock(dict_index_get_lock(index), mtr); - } - if (page_cur_is_before_first(page_cursor)) { /* The record to be updated was positioned as the first user record on its page */ @@ -2016,7 +1990,6 @@ btr_cur_pessimistic_update( ut_a(rec); ut_a(err == DB_SUCCESS); ut_a(dummy_big_rec == NULL); - page_cursor->rec = rec; rec_set_field_extern_bits(rec, index, ext_vect, n_ext_vect, mtr); offsets = rec_get_offsets(rec, index, offsets, ULINT_UNDEFINED, &heap); @@ -2051,43 +2024,6 @@ return_after_reservations: return(err); } -/***************************************************************** -Commits and restarts a mini-transaction so that it will retain an -x-lock on index->lock and the cursor page. */ - -void -btr_cur_mtr_commit_and_start( -/*=========================*/ - btr_cur_t* cursor, /* in: cursor */ - mtr_t* mtr) /* in/out: mini-transaction */ -{ - buf_block_t* block; - - block = buf_block_align(btr_cur_get_rec(cursor)); - - ut_ad(mtr_memo_contains(mtr, dict_index_get_lock(cursor->index), - MTR_MEMO_X_LOCK)); - ut_ad(mtr_memo_contains(mtr, block, MTR_MEMO_PAGE_X_FIX)); - /* Keep the locks across the mtr_commit(mtr). */ - rw_lock_x_lock(dict_index_get_lock(cursor->index)); - rw_lock_x_lock(&block->lock); - mutex_enter(&block->mutex); -#ifdef UNIV_SYNC_DEBUG - buf_block_buf_fix_inc_debug(block, __FILE__, __LINE__); -#else - buf_block_buf_fix_inc(block); -#endif - mutex_exit(&block->mutex); - /* Write out the redo log. */ - mtr_commit(mtr); - mtr_start(mtr); - /* Reassociate the locks with the mini-transaction. - They will be released on mtr_commit(mtr). */ - mtr_memo_push(mtr, dict_index_get_lock(cursor->index), - MTR_MEMO_X_LOCK); - mtr_memo_push(mtr, block, MTR_MEMO_PAGE_X_FIX); -} - /*==================== B-TREE DELETE MARK AND UNMARK ===============*/ /******************************************************************** @@ -2455,6 +2391,30 @@ btr_cur_del_unmark_for_ibuf( /*==================== B-TREE RECORD REMOVE =========================*/ +/***************************************************************** +Tries to compress a page of the tree on the leaf level. It is assumed +that mtr holds an x-latch on the tree and on the cursor page. To avoid +deadlocks, mtr must also own x-latches to brothers of page, if those +brothers exist. NOTE: it is assumed that the caller has reserved enough +free extents so that the compression will always succeed if done! */ + +void +btr_cur_compress( +/*=============*/ + btr_cur_t* cursor, /* in: cursor on the page to compress; + cursor does not stay valid */ + mtr_t* mtr) /* in: mtr */ +{ + ut_ad(mtr_memo_contains(mtr, + dict_index_get_lock(btr_cur_get_index(cursor)), + MTR_MEMO_X_LOCK)); + ut_ad(mtr_memo_contains(mtr, buf_block_align(btr_cur_get_rec(cursor)), + MTR_MEMO_PAGE_X_FIX)); + ut_ad(btr_page_get_level(btr_cur_get_page(cursor), mtr) == 0); + + btr_compress(cursor, mtr); +} + /***************************************************************** Tries to compress a page of the tree if it seems useful. It is assumed that mtr holds an x-latch on the tree and on the cursor page. To avoid @@ -2466,12 +2426,10 @@ ibool btr_cur_compress_if_useful( /*=======================*/ /* out: TRUE if compression occurred */ - btr_cur_t* cursor, /* in/out: cursor on the page to compress; - cursor does not stay valid if !adjust and - compression occurs */ - ibool adjust, /* in: TRUE if should adjust the - cursor position even if compression occurs */ - mtr_t* mtr) /* in/out: mini-transaction */ + btr_cur_t* cursor, /* in: cursor on the page to compress; + cursor does not stay valid if compression + occurs */ + mtr_t* mtr) /* in: mtr */ { ut_ad(mtr_memo_contains(mtr, dict_index_get_lock(btr_cur_get_index(cursor)), @@ -2481,7 +2439,7 @@ btr_cur_compress_if_useful( if (btr_cur_compress_recommendation(cursor, mtr)) { - btr_compress(cursor, adjust, mtr); + btr_compress(cursor, mtr); return(TRUE); } @@ -2694,7 +2652,7 @@ return_after_reservations: mem_heap_free(heap); if (ret == FALSE) { - ret = btr_cur_compress_if_useful(cursor, FALSE, mtr); + ret = btr_cur_compress_if_useful(cursor, mtr); } if (n_extents > 0) { diff --git a/storage/innobase/include/btr0btr.h b/storage/innobase/include/btr0btr.h index 269fa355558..1573de7e818 100644 --- a/storage/innobase/include/btr0btr.h +++ b/storage/innobase/include/btr0btr.h @@ -312,13 +312,11 @@ enough free extents so that the compression will always succeed if done! */ void btr_compress( /*=========*/ - btr_cur_t* cursor, /* in/out: cursor on the page to merge - or lift; the page must not be empty: - when deleting records, use btr_discard_page() - if the page would become empty */ - ibool adjust, /* in: TRUE if should adjust the - cursor position even if compression occurs */ - mtr_t* mtr); /* in/out: mini-transaction */ + btr_cur_t* cursor, /* in: cursor on the page to merge or lift; + the page must not be empty: in record delete + use btr_discard_page if the page would become + empty */ + mtr_t* mtr); /* in: mtr */ /***************************************************************** Discards a page from a B-tree. This is used to remove the last record from a B-tree page: the whole page must be removed at the same time. This cannot diff --git a/storage/innobase/include/btr0cur.h b/storage/innobase/include/btr0cur.h index c068d8d3318..20235c55f22 100644 --- a/storage/innobase/include/btr0cur.h +++ b/storage/innobase/include/btr0cur.h @@ -23,9 +23,6 @@ Created 10/16/1994 Heikki Tuuri #define BTR_NO_LOCKING_FLAG 2 /* do no record lock checking */ #define BTR_KEEP_SYS_FLAG 4 /* sys fields will be found from the update vector or inserted entry */ -#define BTR_KEEP_POS_FLAG 8 /* btr_cur_pessimistic_update() - must keep cursor position when - moving columns to big_rec */ #define BTR_CUR_ADAPT #define BTR_CUR_HASH_ADAPT @@ -240,9 +237,7 @@ btr_cur_pessimistic_update( /* out: DB_SUCCESS or error code */ ulint flags, /* in: undo logging, locking, and rollback flags */ - btr_cur_t* cursor, /* in/out: cursor on the record to update; - cursor may become invalid if *big_rec == NULL - || !(flags & BTR_KEEP_POS_FLAG) */ + btr_cur_t* cursor, /* in: cursor on the record to update */ big_rec_t** big_rec,/* out: big rec vector whose fields have to be stored externally by the caller, or NULL */ upd_t* update, /* in: update vector; this is allowed also @@ -252,15 +247,6 @@ btr_cur_pessimistic_update( updates */ que_thr_t* thr, /* in: query thread */ mtr_t* mtr); /* in: mtr */ -/***************************************************************** -Commits and restarts a mini-transaction so that it will retain an -x-lock on index->lock and the cursor page. */ - -void -btr_cur_mtr_commit_and_start( -/*=========================*/ - btr_cur_t* cursor, /* in: cursor */ - mtr_t* mtr); /* in/out: mini-transaction */ /*************************************************************** Marks a clustered index record deleted. Writes an undo log record to undo log on this delete marking. Writes in the trx id field the id @@ -300,6 +286,19 @@ btr_cur_del_unmark_for_ibuf( rec_t* rec, /* in: record to delete unmark */ mtr_t* mtr); /* in: mtr */ /***************************************************************** +Tries to compress a page of the tree on the leaf level. It is assumed +that mtr holds an x-latch on the tree and on the cursor page. To avoid +deadlocks, mtr must also own x-latches to brothers of page, if those +brothers exist. NOTE: it is assumed that the caller has reserved enough +free extents so that the compression will always succeed if done! */ + +void +btr_cur_compress( +/*=============*/ + btr_cur_t* cursor, /* in: cursor on the page to compress; + cursor does not stay valid */ + mtr_t* mtr); /* in: mtr */ +/***************************************************************** Tries to compress a page of the tree if it seems useful. It is assumed that mtr holds an x-latch on the tree and on the cursor page. To avoid deadlocks, mtr must also own x-latches to brothers of page, if those @@ -310,12 +309,10 @@ ibool btr_cur_compress_if_useful( /*=======================*/ /* out: TRUE if compression occurred */ - btr_cur_t* cursor, /* in/out: cursor on the page to compress; - cursor does not stay valid if !adjust and - compression occurs */ - ibool adjust, /* in: TRUE if should adjust the - cursor position even if compression occurs */ - mtr_t* mtr); /* in/out: mini-transaction */ + btr_cur_t* cursor, /* in: cursor on the page to compress; + cursor does not stay valid if compression + occurs */ + mtr_t* mtr); /* in: mtr */ /*********************************************************** Removes the record on which the tree cursor is positioned. It is assumed that the mtr has an x-latch on the page where the cursor is positioned, diff --git a/storage/innobase/include/buf0buf.h b/storage/innobase/include/buf0buf.h index 50e0aa9376c..0f7553a7043 100644 --- a/storage/innobase/include/buf0buf.h +++ b/storage/innobase/include/buf0buf.h @@ -654,25 +654,6 @@ buf_page_address_fold( /* out: the folded value */ ulint space, /* in: space id */ ulint offset);/* in: offset of the page within space */ -#ifdef UNIV_SYNC_DEBUG -/*********************************************************************** -Increments the bufferfix count. */ -UNIV_INLINE -void -buf_block_buf_fix_inc_debug( -/*========================*/ - buf_block_t* block, /* in: block to bufferfix */ - const char* file __attribute__ ((unused)), /* in: file name */ - ulint line __attribute__ ((unused))); /* in: line */ -#else /* UNIV_SYNC_DEBUG */ -/*********************************************************************** -Increments the bufferfix count. */ -UNIV_INLINE -void -buf_block_buf_fix_inc( -/*==================*/ - buf_block_t* block); /* in: block to bufferfix */ -#endif /* UNIV_SYNC_DEBUG */ /********************************************************************** Returns the control block of a file page, NULL if not found. */ UNIV_INLINE diff --git a/storage/innobase/include/page0page.h b/storage/innobase/include/page0page.h index 24698557e77..273007c2778 100644 --- a/storage/innobase/include/page0page.h +++ b/storage/innobase/include/page0page.h @@ -234,21 +234,10 @@ page_get_supremum_rec( /*==================*/ /* out: the last record in record list */ page_t* page); /* in: page which must have record(s) */ -/************************************************************//** -Returns the nth record of the record list. -This is the inverse function of page_rec_get_n_recs_before(). */ +/**************************************************************** +Returns the middle record of record list. If there are an even number +of records in the list, returns the first record of upper half-list. */ -rec_t* -page_rec_get_nth( -/*=============*/ - /* out: nth record */ - page_t* page, /* in: page */ - ulint nth); /* in: nth record */ -/***************************************************************** -Returns the middle record of the records on the page. If there is an -even number of records in the list, returns the first record of the -upper half-list. */ -UNIV_INLINE rec_t* page_get_middle_rec( /*================*/ @@ -291,8 +280,7 @@ page_get_n_recs( page_t* page); /* in: index page */ /******************************************************************* Returns the number of records before the given record in chain. -The number includes infimum and supremum records. -This is the inverse function of page_rec_get_nth(). */ +The number includes infimum and supremum records. */ ulint page_rec_get_n_recs_before( diff --git a/storage/innobase/include/page0page.ic b/storage/innobase/include/page0page.ic index a019aa28515..d9e67f3eeeb 100644 --- a/storage/innobase/include/page0page.ic +++ b/storage/innobase/include/page0page.ic @@ -340,22 +340,6 @@ page_rec_is_infimum( return(page_rec_is_infimum_low(page_offset(rec))); } -/***************************************************************** -Returns the middle record of the records on the page. If there is an -even number of records in the list, returns the first record of the -upper half-list. */ -UNIV_INLINE -rec_t* -page_get_middle_rec( -/*================*/ - /* out: middle record */ - page_t* page) /* in: page */ -{ - ulint middle = (page_get_n_recs(page) + 2) / 2; - - return(page_rec_get_nth(page, middle)); -} - /***************************************************************** Compares a data tuple to a physical record. Differs from the function cmp_dtuple_rec_with_match in the way that the record must reside on an diff --git a/storage/innobase/page/page0page.c b/storage/innobase/page/page0page.c index 6a89df7de22..543cf9e34eb 100644 --- a/storage/innobase/page/page0page.c +++ b/storage/innobase/page/page0page.c @@ -1194,42 +1194,49 @@ page_dir_balance_slot( } /**************************************************************** -Returns the nth record of the record list. */ +Returns the middle record of the record list. If there are an even number +of records in the list, returns the first record of the upper half-list. */ rec_t* -page_rec_get_nth( -/*=============*/ - /* out: nth record */ - page_t* page, /* in: page */ - ulint nth) /* in: nth record */ +page_get_middle_rec( +/*================*/ + /* out: middle record */ + page_t* page) /* in: page */ { page_dir_slot_t* slot; + ulint middle; ulint i; ulint n_owned; + ulint count; rec_t* rec; - ut_ad(nth < UNIV_PAGE_SIZE / (REC_N_NEW_EXTRA_BYTES + 1)); + /* This many records we must leave behind */ + middle = (page_get_n_recs(page) + 2) / 2; + + count = 0; for (i = 0;; i++) { slot = page_dir_get_nth_slot(page, i); n_owned = page_dir_slot_get_n_owned(slot); - if (n_owned > nth) { + if (count + n_owned > middle) { break; } else { - nth -= n_owned; + count += n_owned; } } ut_ad(i > 0); slot = page_dir_get_nth_slot(page, i - 1); rec = page_dir_slot_get_rec(slot); + rec = page_rec_get_next(rec); - do { + /* There are now count records behind rec */ + + for (i = 0; i < middle - count; i++) { rec = page_rec_get_next(rec); - ut_ad(rec); - } while (nth--); + } return(rec); } diff --git a/storage/innobase/row/row0ins.c b/storage/innobase/row/row0ins.c index c3b8f54a3c5..4f5f948f218 100644 --- a/storage/innobase/row/row0ins.c +++ b/storage/innobase/row/row0ins.c @@ -259,7 +259,6 @@ row_ins_sec_index_entry_by_modify( err = btr_cur_pessimistic_update(BTR_KEEP_SYS_FLAG, cursor, &dummy_big_rec, update, 0, thr, mtr); - ut_a(!dummy_big_rec); } func_exit: mem_heap_free(heap); @@ -330,9 +329,8 @@ row_ins_clust_index_entry_by_modify( goto func_exit; } - err = btr_cur_pessimistic_update( - BTR_KEEP_POS_FLAG, cursor, big_rec, update, - 0, thr, mtr); + err = btr_cur_pessimistic_update(0, cursor, big_rec, update, + 0, thr, mtr); } func_exit: mem_heap_free(heap); @@ -2086,41 +2084,6 @@ row_ins_index_entry_low( err = row_ins_clust_index_entry_by_modify( mode, &cursor, &big_rec, entry, ext_vec, n_ext_vec, thr, &mtr); - - if (big_rec) { - ut_a(err == DB_SUCCESS); - /* Write out the externally stored - columns while still x-latching - index->lock and block->lock. We have - to mtr_commit(mtr) first, so that the - redo log will be written in the - correct order. Otherwise, we would run - into trouble on crash recovery if mtr - freed B-tree pages on which some of - the big_rec fields will be written. */ - btr_cur_mtr_commit_and_start(&cursor, &mtr); - - rec = btr_cur_get_rec(&cursor); - offsets = rec_get_offsets(rec, index, offsets, - ULINT_UNDEFINED, - &heap); - - err = btr_store_big_rec_extern_fields( - index, rec, offsets, big_rec, &mtr); - /* If writing big_rec fails (for - example, because of DB_OUT_OF_FILE_SPACE), - the record will be corrupted. Even if - we did not update any externally - stored columns, our update could cause - the record to grow so that a - non-updated column was selected for - external storage. This non-update - would not have been written to the - undo log, and thus the record cannot - be rolled back. */ - ut_a(err == DB_SUCCESS); - goto stored_big_rec; - } } else { err = row_ins_sec_index_entry_by_modify( mode, &cursor, entry, thr, &mtr); @@ -2157,6 +2120,7 @@ function_exit: mtr_commit(&mtr); if (big_rec) { + rec_t* rec; mtr_start(&mtr); btr_cur_search_to_nth_level(index, 0, entry, PAGE_CUR_LE, @@ -2167,7 +2131,7 @@ function_exit: err = btr_store_big_rec_extern_fields(index, rec, offsets, big_rec, &mtr); -stored_big_rec: + if (modify) { dtuple_big_rec_free(big_rec); } else { diff --git a/storage/innobase/row/row0umod.c b/storage/innobase/row/row0umod.c index 0b00aa2411a..a3333fcc536 100644 --- a/storage/innobase/row/row0umod.c +++ b/storage/innobase/row/row0umod.c @@ -119,7 +119,6 @@ row_undo_mod_clust_low( | BTR_KEEP_SYS_FLAG, btr_cur, &dummy_big_rec, node->update, node->cmpl_info, thr, mtr); - ut_ad(!dummy_big_rec); } return(err); @@ -472,7 +471,6 @@ row_undo_mod_del_unmark_sec_and_undo_update( BTR_KEEP_SYS_FLAG | BTR_NO_LOCKING_FLAG, btr_cur, &dummy_big_rec, update, 0, thr, &mtr); - ut_ad(!dummy_big_rec); } mem_heap_free(heap); diff --git a/storage/innobase/row/row0upd.c b/storage/innobase/row/row0upd.c index 694b00ea265..0790cfe02e2 100644 --- a/storage/innobase/row/row0upd.c +++ b/storage/innobase/row/row0upd.c @@ -1580,48 +1580,32 @@ row_upd_clust_rec( ut_ad(!rec_get_deleted_flag(btr_pcur_get_rec(pcur), dict_table_is_comp(index->table))); - err = btr_cur_pessimistic_update( - BTR_NO_LOCKING_FLAG | BTR_KEEP_POS_FLAG, btr_cur, - &big_rec, node->update, node->cmpl_info, thr, mtr); + err = btr_cur_pessimistic_update(BTR_NO_LOCKING_FLAG, btr_cur, + &big_rec, node->update, + node->cmpl_info, thr, mtr); + mtr_commit(mtr); - if (big_rec) { + if (err == DB_SUCCESS && big_rec) { mem_heap_t* heap = NULL; ulint offsets_[REC_OFFS_NORMAL_SIZE]; rec_t* rec; *offsets_ = (sizeof offsets_) / sizeof *offsets_; - ut_a(err == DB_SUCCESS); - /* Write out the externally stored columns while still - x-latching index->lock and block->lock. We have to - mtr_commit(mtr) first, so that the redo log will be - written in the correct order. Otherwise, we would run - into trouble on crash recovery if mtr freed B-tree - pages on which some of the big_rec fields will be - written. */ - btr_cur_mtr_commit_and_start(btr_cur, mtr); + mtr_start(mtr); + ut_a(btr_pcur_restore_position(BTR_MODIFY_TREE, pcur, mtr)); rec = btr_cur_get_rec(btr_cur); err = btr_store_big_rec_extern_fields( index, rec, rec_get_offsets(rec, index, offsets_, ULINT_UNDEFINED, &heap), - big_rec, mtr); + big_rec, mtr); if (UNIV_LIKELY_NULL(heap)) { mem_heap_free(heap); } - /* If writing big_rec fails (for example, because of - DB_OUT_OF_FILE_SPACE), the record will be corrupted. - Even if we did not update any externally stored - columns, our update could cause the record to grow so - that a non-updated column was selected for external - storage. This non-update would not have been written - to the undo log, and thus the record cannot be rolled - back. */ - ut_a(err == DB_SUCCESS); + mtr_commit(mtr); } - mtr_commit(mtr); - if (big_rec) { dtuple_big_rec_free(big_rec); } diff --git a/storage/innodb_plugin/ChangeLog b/storage/innodb_plugin/ChangeLog index e6724eb08c2..859b9fb627a 100644 --- a/storage/innodb_plugin/ChangeLog +++ b/storage/innodb_plugin/ChangeLog @@ -101,16 +101,6 @@ * page/page0zip.c, rem/rem0rec.c: Fix Bug#61191 question about page_zip_available() -2011-06-16 The InnoDB Team - - * btr/btr0btr.c, btr/btr0cur.c, include/btr0btr.h, include/btr0cur.h, - include/btr0cur.ic, include/buf0buf.h, include/buf0buf.ic, - include/page0cur.ic, include/page0page.h, include/page0page.ic, - include/sync0rw.ic, include/sync0sync.h, page/page0cur.c, - page/page0page.c, row/row0ins.c, row/row0upd.c, - sync/sync0rw.c, sync/sync0sync.c: - Fix Bug#12612184 Race condition after btr_cur_pessimistic_update() - 2011-06-09 The InnoDB Team * btr/btr0cur.c, include/rem0rec.h, include/rem0rec.ic, * row/row0row.c, row/row0vers.c, trx/trx0rec.c: diff --git a/storage/innodb_plugin/btr/btr0btr.c b/storage/innodb_plugin/btr/btr0btr.c index cb94ef08cd6..23729c12c1a 100644 --- a/storage/innodb_plugin/btr/btr0btr.c +++ b/storage/innodb_plugin/btr/btr0btr.c @@ -3001,16 +3001,15 @@ btr_node_ptr_delete( ut_a(err == DB_SUCCESS); if (!compressed) { - btr_cur_compress_if_useful(&cursor, FALSE, mtr); + btr_cur_compress_if_useful(&cursor, mtr); } } /*************************************************************//** If page is the only on its level, this function moves its records to the -father page, thus reducing the tree height. -@return father block */ +father page, thus reducing the tree height. */ static -buf_block_t* +void btr_lift_page_up( /*=============*/ dict_index_t* index, /*!< in: index tree */ @@ -3127,8 +3126,6 @@ btr_lift_page_up( } ut_ad(page_validate(father_page, index)); ut_ad(btr_check_node_ptr(index, father_block, mtr)); - - return(father_block); } /*************************************************************//** @@ -3145,13 +3142,11 @@ UNIV_INTERN ibool btr_compress( /*=========*/ - btr_cur_t* cursor, /*!< in/out: cursor on the page to merge - or lift; the page must not be empty: - when deleting records, use btr_discard_page() - if the page would become empty */ - ibool adjust, /*!< in: TRUE if should adjust the - cursor position even if compression occurs */ - mtr_t* mtr) /*!< in/out: mini-transaction */ + btr_cur_t* cursor, /*!< in: cursor on the page to merge or lift; + the page must not be empty: in record delete + use btr_discard_page if the page would become + empty */ + mtr_t* mtr) /*!< in: mtr */ { dict_index_t* index; ulint space; @@ -3169,14 +3164,12 @@ btr_compress( ulint* offsets; ulint data_size; ulint n_recs; - ulint nth_rec = 0; /* remove bogus warning */ ulint max_ins_size; ulint max_ins_size_reorg; block = btr_cur_get_block(cursor); page = btr_cur_get_page(cursor); index = btr_cur_get_index(cursor); - ut_a((ibool) !!page_is_comp(page) == dict_table_is_comp(index->table)); ut_ad(mtr_memo_contains(mtr, dict_index_get_lock(index), @@ -3197,10 +3190,6 @@ btr_compress( offsets = btr_page_get_father_block(NULL, heap, index, block, mtr, &father_cursor); - if (adjust) { - nth_rec = page_rec_get_n_recs_before(btr_cur_get_rec(cursor)); - } - /* Decide the page to which we try to merge and which will inherit the locks */ @@ -3227,9 +3216,9 @@ btr_compress( } else { /* The page is the only one on the level, lift the records to the father */ - - merge_block = btr_lift_page_up(index, block, mtr); - goto func_exit; + btr_lift_page_up(index, block, mtr); + mem_heap_free(heap); + return(TRUE); } n_recs = page_get_n_recs(page); @@ -3311,10 +3300,6 @@ err_exit: btr_node_ptr_delete(index, block, mtr); lock_update_merge_left(merge_block, orig_pred, block); - - if (adjust) { - nth_rec += page_rec_get_n_recs_before(orig_pred); - } } else { rec_t* orig_succ; #ifdef UNIV_BTR_DEBUG @@ -3379,6 +3364,7 @@ err_exit: } btr_blob_dbg_remove(page, index, "btr_compress"); + mem_heap_free(heap); if (!dict_index_is_clust(index) && page_is_leaf(merge_page)) { /* Update the free bits of the B-tree page in the @@ -3430,16 +3416,6 @@ err_exit: btr_page_free(index, block, mtr); ut_ad(btr_check_node_ptr(index, merge_block, mtr)); -func_exit: - mem_heap_free(heap); - - if (adjust) { - btr_cur_position( - index, - page_rec_get_nth(merge_block->frame, nth_rec), - merge_block, cursor); - } - return(TRUE); } diff --git a/storage/innodb_plugin/btr/btr0cur.c b/storage/innodb_plugin/btr/btr0cur.c index 4c862f061c5..9f4be053a43 100644 --- a/storage/innodb_plugin/btr/btr0cur.c +++ b/storage/innodb_plugin/btr/btr0cur.c @@ -2099,9 +2099,7 @@ btr_cur_pessimistic_update( /*=======================*/ ulint flags, /*!< in: undo logging, locking, and rollback flags */ - btr_cur_t* cursor, /*!< in/out: cursor on the record to update; - cursor may become invalid if *big_rec == NULL - || !(flags & BTR_KEEP_POS_FLAG) */ + btr_cur_t* cursor, /*!< in: cursor on the record to update */ mem_heap_t** heap, /*!< in/out: pointer to memory heap, or NULL */ big_rec_t** big_rec,/*!< out: big rec vector whose fields have to be stored externally by the caller, or NULL */ @@ -2240,7 +2238,7 @@ btr_cur_pessimistic_update( record to be inserted: we have to remember which fields were such */ ut_ad(!page_is_comp(page) || !rec_get_node_ptr_flag(rec)); - ut_ad(rec_offs_validate(rec, index, offsets)); + offsets = rec_get_offsets(rec, index, offsets, ULINT_UNDEFINED, heap); n_ext += btr_push_update_extern_fields(new_entry, update, *heap); if (UNIV_LIKELY_NULL(page_zip)) { @@ -2263,10 +2261,6 @@ make_external: err = DB_TOO_BIG_RECORD; goto return_after_reservations; } - - ut_ad(page_is_leaf(page)); - ut_ad(dict_index_is_clust(index)); - ut_ad(flags & BTR_KEEP_POS_FLAG); } /* Store state of explicit locks on rec on the page infimum record, @@ -2294,8 +2288,6 @@ make_external: rec = btr_cur_insert_if_possible(cursor, new_entry, n_ext, mtr); if (rec) { - page_cursor->rec = rec; - lock_rec_restore_from_page_infimum(btr_cur_get_block(cursor), rec, block); @@ -2309,10 +2301,7 @@ make_external: rec, index, offsets, mtr); } - btr_cur_compress_if_useful( - cursor, - big_rec_vec != NULL && (flags & BTR_KEEP_POS_FLAG), - mtr); + btr_cur_compress_if_useful(cursor, mtr); if (page_zip && !dict_index_is_clust(index) && page_is_leaf(page)) { @@ -2332,21 +2321,6 @@ make_external: } } - if (big_rec_vec) { - ut_ad(page_is_leaf(page)); - ut_ad(dict_index_is_clust(index)); - ut_ad(flags & BTR_KEEP_POS_FLAG); - - /* btr_page_split_and_insert() in - btr_cur_pessimistic_insert() invokes - mtr_memo_release(mtr, index->lock, MTR_MEMO_X_LOCK). - We must keep the index->lock when we created a - big_rec, so that row_upd_clust_rec() can store the - big_rec in the same mini-transaction. */ - - mtr_x_lock(dict_index_get_lock(index), mtr); - } - /* Was the record to be updated positioned as the first user record on its page? */ was_first = page_cur_is_before_first(page_cursor); @@ -2362,7 +2336,6 @@ make_external: ut_a(rec); ut_a(err == DB_SUCCESS); ut_a(dummy_big_rec == NULL); - page_cursor->rec = rec; if (dict_index_is_sec_or_ibuf(index)) { /* Update PAGE_MAX_TRX_ID in the index page header. @@ -2421,39 +2394,6 @@ return_after_reservations: return(err); } -/**************************************************************//** -Commits and restarts a mini-transaction so that it will retain an -x-lock on index->lock and the cursor page. */ -UNIV_INTERN -void -btr_cur_mtr_commit_and_start( -/*=========================*/ - btr_cur_t* cursor, /*!< in: cursor */ - mtr_t* mtr) /*!< in/out: mini-transaction */ -{ - buf_block_t* block; - - block = btr_cur_get_block(cursor); - - ut_ad(mtr_memo_contains(mtr, dict_index_get_lock(cursor->index), - MTR_MEMO_X_LOCK)); - ut_ad(mtr_memo_contains(mtr, block, MTR_MEMO_PAGE_X_FIX)); - /* Keep the locks across the mtr_commit(mtr). */ - rw_lock_x_lock(dict_index_get_lock(cursor->index)); - rw_lock_x_lock(&block->lock); - mutex_enter(&block->mutex); - buf_block_buf_fix_inc(block, __FILE__, __LINE__); - mutex_exit(&block->mutex); - /* Write out the redo log. */ - mtr_commit(mtr); - mtr_start(mtr); - /* Reassociate the locks with the mini-transaction. - They will be released on mtr_commit(mtr). */ - mtr_memo_push(mtr, dict_index_get_lock(cursor->index), - MTR_MEMO_X_LOCK); - mtr_memo_push(mtr, block, MTR_MEMO_PAGE_X_FIX); -} - /*==================== B-TREE DELETE MARK AND UNMARK ===============*/ /****************************************************************//** @@ -2829,12 +2769,10 @@ UNIV_INTERN ibool btr_cur_compress_if_useful( /*=======================*/ - btr_cur_t* cursor, /*!< in/out: cursor on the page to compress; - cursor does not stay valid if !adjust and - compression occurs */ - ibool adjust, /*!< in: TRUE if should adjust the - cursor position even if compression occurs */ - mtr_t* mtr) /*!< in/out: mini-transaction */ + btr_cur_t* cursor, /*!< in: cursor on the page to compress; + cursor does not stay valid if compression + occurs */ + mtr_t* mtr) /*!< in: mtr */ { ut_ad(mtr_memo_contains(mtr, dict_index_get_lock(btr_cur_get_index(cursor)), @@ -2843,7 +2781,7 @@ btr_cur_compress_if_useful( MTR_MEMO_PAGE_X_FIX)); return(btr_cur_compress_recommendation(cursor, mtr) - && btr_compress(cursor, adjust, mtr)); + && btr_compress(cursor, mtr)); } /*******************************************************//** @@ -3085,7 +3023,7 @@ return_after_reservations: mem_heap_free(heap); if (ret == FALSE) { - ret = btr_cur_compress_if_useful(cursor, FALSE, mtr); + ret = btr_cur_compress_if_useful(cursor, mtr); } if (n_extents > 0) { diff --git a/storage/innodb_plugin/include/btr0btr.h b/storage/innodb_plugin/include/btr0btr.h index c0a038dd21d..e32da9e4c86 100644 --- a/storage/innodb_plugin/include/btr0btr.h +++ b/storage/innodb_plugin/include/btr0btr.h @@ -488,14 +488,11 @@ UNIV_INTERN ibool btr_compress( /*=========*/ - btr_cur_t* cursor, /*!< in/out: cursor on the page to merge - or lift; the page must not be empty: - when deleting records, use btr_discard_page() - if the page would become empty */ - ibool adjust, /*!< in: TRUE if should adjust the - cursor position even if compression occurs */ - mtr_t* mtr) /*!< in/out: mini-transaction */ - __attribute__((nonnull)); + btr_cur_t* cursor, /*!< in: cursor on the page to merge or lift; + the page must not be empty: in record delete + use btr_discard_page if the page would become + empty */ + mtr_t* mtr); /*!< in: mtr */ /*************************************************************//** Discards a page from a B-tree. This is used to remove the last record from a B-tree page: the whole page must be removed at the same time. This cannot diff --git a/storage/innodb_plugin/include/btr0cur.h b/storage/innodb_plugin/include/btr0cur.h index 6094a2a6c7a..3669ce28f02 100644 --- a/storage/innodb_plugin/include/btr0cur.h +++ b/storage/innodb_plugin/include/btr0cur.h @@ -36,9 +36,6 @@ Created 10/16/1994 Heikki Tuuri #define BTR_NO_LOCKING_FLAG 2 /* do no record lock checking */ #define BTR_KEEP_SYS_FLAG 4 /* sys fields will be found from the update vector or inserted entry */ -#define BTR_KEEP_POS_FLAG 8 /* btr_cur_pessimistic_update() - must keep cursor position when - moving columns to big_rec */ #ifndef UNIV_HOTBACKUP #include "que0types.h" @@ -312,9 +309,7 @@ btr_cur_pessimistic_update( /*=======================*/ ulint flags, /*!< in: undo logging, locking, and rollback flags */ - btr_cur_t* cursor, /*!< in/out: cursor on the record to update; - cursor may become invalid if *big_rec == NULL - || !(flags & BTR_KEEP_POS_FLAG) */ + btr_cur_t* cursor, /*!< in: cursor on the record to update */ mem_heap_t** heap, /*!< in/out: pointer to memory heap, or NULL */ big_rec_t** big_rec,/*!< out: big rec vector whose fields have to be stored externally by the caller, or NULL */ @@ -326,16 +321,6 @@ btr_cur_pessimistic_update( que_thr_t* thr, /*!< in: query thread */ mtr_t* mtr); /*!< in: mtr; must be committed before latching any further pages */ -/***************************************************************** -Commits and restarts a mini-transaction so that it will retain an -x-lock on index->lock and the cursor page. */ -UNIV_INTERN -void -btr_cur_mtr_commit_and_start( -/*=========================*/ - btr_cur_t* cursor, /*!< in: cursor */ - mtr_t* mtr) /*!< in/out: mini-transaction */ - __attribute__((nonnull)); /***********************************************************//** Marks a clustered index record deleted. Writes an undo log record to undo log on this delete marking. Writes in the trx id field the id @@ -391,13 +376,10 @@ UNIV_INTERN ibool btr_cur_compress_if_useful( /*=======================*/ - btr_cur_t* cursor, /*!< in/out: cursor on the page to compress; + btr_cur_t* cursor, /*!< in: cursor on the page to compress; cursor does not stay valid if compression occurs */ - ibool adjust, /*!< in: TRUE if should adjust the - cursor position even if compression occurs */ - mtr_t* mtr) /*!< in/out: mini-transaction */ - __attribute__((nonnull)); + mtr_t* mtr); /*!< in: mtr */ /*******************************************************//** Removes the record on which the tree cursor is positioned. It is assumed that the mtr has an x-latch on the page where the cursor is positioned, diff --git a/storage/innodb_plugin/include/btr0cur.ic b/storage/innodb_plugin/include/btr0cur.ic index c833b3e8572..cd3a5d895bb 100644 --- a/storage/innodb_plugin/include/btr0cur.ic +++ b/storage/innodb_plugin/include/btr0cur.ic @@ -139,7 +139,7 @@ btr_cur_compress_recommendation( btr_cur_t* cursor, /*!< in: btr cursor */ mtr_t* mtr) /*!< in: mtr */ { - const page_t* page; + page_t* page; ut_ad(mtr_memo_contains(mtr, btr_cur_get_block(cursor), MTR_MEMO_PAGE_X_FIX)); diff --git a/storage/innodb_plugin/include/buf0buf.h b/storage/innodb_plugin/include/buf0buf.h index e7fe3901ad4..489d1bec5b6 100644 --- a/storage/innodb_plugin/include/buf0buf.h +++ b/storage/innodb_plugin/include/buf0buf.h @@ -468,31 +468,6 @@ buf_block_get_modify_clock( #else /* !UNIV_HOTBACKUP */ # define buf_block_modify_clock_inc(block) ((void) 0) #endif /* !UNIV_HOTBACKUP */ -/*******************************************************************//** -Increments the bufferfix count. */ -UNIV_INLINE -void -buf_block_buf_fix_inc_func( -/*=======================*/ -#ifdef UNIV_SYNC_DEBUG - const char* file, /*!< in: file name */ - ulint line, /*!< in: line */ -#endif /* UNIV_SYNC_DEBUG */ - buf_block_t* block) /*!< in/out: block to bufferfix */ - __attribute__((nonnull)); -#ifdef UNIV_SYNC_DEBUG -/** Increments the bufferfix count. -@param b in/out: block to bufferfix -@param f in: file name where requested -@param l in: line number where requested */ -# define buf_block_buf_fix_inc(b,f,l) buf_block_buf_fix_inc_func(f,l,b) -#else /* UNIV_SYNC_DEBUG */ -/** Increments the bufferfix count. -@param b in/out: block to bufferfix -@param f in: file name where requested -@param l in: line number where requested */ -# define buf_block_buf_fix_inc(b,f,l) buf_block_buf_fix_inc_func(b) -#endif /* UNIV_SYNC_DEBUG */ /********************************************************************//** Calculates a page checksum which is stored to the page when it is written to a file. Note that we must be careful to calculate the same value diff --git a/storage/innodb_plugin/include/buf0buf.ic b/storage/innodb_plugin/include/buf0buf.ic index 2f3d65e80bd..0fe1dbc2da5 100644 --- a/storage/innodb_plugin/include/buf0buf.ic +++ b/storage/innodb_plugin/include/buf0buf.ic @@ -916,6 +916,19 @@ buf_block_buf_fix_inc_func( block->page.buf_fix_count++; } +#ifdef UNIV_SYNC_DEBUG +/** Increments the bufferfix count. +@param b in/out: block to bufferfix +@param f in: file name where requested +@param l in: line number where requested */ +# define buf_block_buf_fix_inc(b,f,l) buf_block_buf_fix_inc_func(f,l,b) +#else /* UNIV_SYNC_DEBUG */ +/** Increments the bufferfix count. +@param b in/out: block to bufferfix +@param f in: file name where requested +@param l in: line number where requested */ +# define buf_block_buf_fix_inc(b,f,l) buf_block_buf_fix_inc_func(b) +#endif /* UNIV_SYNC_DEBUG */ /*******************************************************************//** Decrements the bufferfix count. */ diff --git a/storage/innodb_plugin/include/page0page.h b/storage/innodb_plugin/include/page0page.h index caf4cee2c57..12c4fed75d2 100644 --- a/storage/innodb_plugin/include/page0page.h +++ b/storage/innodb_plugin/include/page0page.h @@ -281,42 +281,16 @@ page_get_supremum_offset( const page_t* page); /*!< in: page which must have record(s) */ #define page_get_infimum_rec(page) ((page) + page_get_infimum_offset(page)) #define page_get_supremum_rec(page) ((page) + page_get_supremum_offset(page)) - /************************************************************//** -Returns the nth record of the record list. -This is the inverse function of page_rec_get_n_recs_before(). -@return nth record */ -UNIV_INTERN -const rec_t* -page_rec_get_nth_const( -/*===================*/ - const page_t* page, /*!< in: page */ - ulint nth) /*!< in: nth record */ - __attribute__((nonnull, warn_unused_result)); -/************************************************************//** -Returns the nth record of the record list. -This is the inverse function of page_rec_get_n_recs_before(). -@return nth record */ -UNIV_INLINE -rec_t* -page_rec_get_nth( -/*=============*/ - page_t* page, /*< in: page */ - ulint nth) /*!< in: nth record */ - __attribute__((nonnull, warn_unused_result)); - -#ifndef UNIV_HOTBACKUP -/************************************************************//** -Returns the middle record of the records on the page. If there is an -even number of records in the list, returns the first record of the -upper half-list. +Returns the middle record of record list. If there are an even number +of records in the list, returns the first record of upper half-list. @return middle record */ -UNIV_INLINE +UNIV_INTERN rec_t* page_get_middle_rec( /*================*/ - page_t* page) /*!< in: page */ - __attribute__((nonnull, warn_unused_result)); + page_t* page); /*!< in: page */ +#ifndef UNIV_HOTBACKUP /*************************************************************//** Compares a data tuple to a physical record. Differs from the function cmp_dtuple_rec_with_match in the way that the record must reside on an @@ -371,7 +345,6 @@ page_get_n_recs( /***************************************************************//** Returns the number of records before the given record in chain. The number includes infimum and supremum records. -This is the inverse function of page_rec_get_nth(). @return number of records */ UNIV_INTERN ulint diff --git a/storage/innodb_plugin/include/page0page.ic b/storage/innodb_plugin/include/page0page.ic index 1450b0892b3..b096a5ba321 100644 --- a/storage/innodb_plugin/include/page0page.ic +++ b/storage/innodb_plugin/include/page0page.ic @@ -420,37 +420,7 @@ page_rec_is_infimum( return(page_rec_is_infimum_low(page_offset(rec))); } -/************************************************************//** -Returns the nth record of the record list. -This is the inverse function of page_rec_get_n_recs_before(). -@return nth record */ -UNIV_INLINE -rec_t* -page_rec_get_nth( -/*=============*/ - page_t* page, /*!< in: page */ - ulint nth) /*!< in: nth record */ -{ - return((rec_t*) page_rec_get_nth_const(page, nth)); -} - #ifndef UNIV_HOTBACKUP -/************************************************************//** -Returns the middle record of the records on the page. If there is an -even number of records in the list, returns the first record of the -upper half-list. -@return middle record */ -UNIV_INLINE -rec_t* -page_get_middle_rec( -/*================*/ - page_t* page) /*!< in: page */ -{ - ulint middle = (page_get_n_recs(page) + PAGE_HEAP_NO_USER_LOW) / 2; - - return(page_rec_get_nth(page, middle)); -} - /*************************************************************//** Compares a data tuple to a physical record. Differs from the function cmp_dtuple_rec_with_match in the way that the record must reside on an diff --git a/storage/innodb_plugin/page/page0cur.c b/storage/innodb_plugin/page/page0cur.c index b8c492328e8..ab5aa257338 100644 --- a/storage/innodb_plugin/page/page0cur.c +++ b/storage/innodb_plugin/page/page0cur.c @@ -1180,15 +1180,14 @@ page_cur_insert_rec_zip_reorg( /* Before trying to reorganize the page, store the number of preceding records on the page. */ pos = page_rec_get_n_recs_before(rec); - ut_ad(pos > 0); if (page_zip_reorganize(block, index, mtr)) { /* The page was reorganized: Find rec by seeking to pos, and update *current_rec. */ - if (pos > 1) { - rec = page_rec_get_nth(page, pos - 1); - } else { - rec = page + PAGE_NEW_INFIMUM; + rec = page + PAGE_NEW_INFIMUM; + + while (--pos) { + rec = page + rec_get_next_offs(rec, TRUE); } *current_rec = rec; @@ -1284,12 +1283,6 @@ page_cur_insert_rec_zip( insert_rec = page_cur_insert_rec_zip_reorg( current_rec, block, index, insert_rec, page, page_zip, mtr); -#ifdef UNIV_DEBUG - if (insert_rec) { - rec_offs_make_valid( - insert_rec, index, offsets); - } -#endif /* UNIV_DEBUG */ } return(insert_rec); diff --git a/storage/innodb_plugin/page/page0page.c b/storage/innodb_plugin/page/page0page.c index 1b9470acbbc..93869e997b5 100644 --- a/storage/innodb_plugin/page/page0page.c +++ b/storage/innodb_plugin/page/page0page.c @@ -1475,54 +1475,55 @@ page_dir_balance_slot( } } +#ifndef UNIV_HOTBACKUP /************************************************************//** -Returns the nth record of the record list. -This is the inverse function of page_rec_get_n_recs_before(). -@return nth record */ +Returns the middle record of the record list. If there are an even number +of records in the list, returns the first record of the upper half-list. +@return middle record */ UNIV_INTERN -const rec_t* -page_rec_get_nth_const( -/*===================*/ - const page_t* page, /*!< in: page */ - ulint nth) /*!< in: nth record */ +rec_t* +page_get_middle_rec( +/*================*/ + page_t* page) /*!< in: page */ { - const page_dir_slot_t* slot; + page_dir_slot_t* slot; + ulint middle; ulint i; ulint n_owned; - const rec_t* rec; + ulint count; + rec_t* rec; - ut_ad(nth < UNIV_PAGE_SIZE / (REC_N_NEW_EXTRA_BYTES + 1)); + /* This many records we must leave behind */ + middle = (page_get_n_recs(page) + PAGE_HEAP_NO_USER_LOW) / 2; + + count = 0; for (i = 0;; i++) { slot = page_dir_get_nth_slot(page, i); n_owned = page_dir_slot_get_n_owned(slot); - if (n_owned > nth) { + if (count + n_owned > middle) { break; } else { - nth -= n_owned; + count += n_owned; } } ut_ad(i > 0); slot = page_dir_get_nth_slot(page, i - 1); - rec = page_dir_slot_get_rec(slot); + rec = (rec_t*) page_dir_slot_get_rec(slot); + rec = page_rec_get_next(rec); - if (page_is_comp(page)) { - do { - rec = page_rec_get_next_low(rec, TRUE); - ut_ad(rec); - } while (nth--); - } else { - do { - rec = page_rec_get_next_low(rec, FALSE); - ut_ad(rec); - } while (nth--); + /* There are now count records behind rec */ + + for (i = 0; i < middle - count; i++) { + rec = page_rec_get_next(rec); } return(rec); } +#endif /* !UNIV_HOTBACKUP */ /***************************************************************//** Returns the number of records before the given record in chain. @@ -1584,7 +1585,6 @@ page_rec_get_n_recs_before( n--; ut_ad(n >= 0); - ut_ad(n < UNIV_PAGE_SIZE / (REC_N_NEW_EXTRA_BYTES + 1)); return((ulint) n); } diff --git a/storage/innodb_plugin/row/row0ins.c b/storage/innodb_plugin/row/row0ins.c index f0f6eca627f..82c3c931bef 100644 --- a/storage/innodb_plugin/row/row0ins.c +++ b/storage/innodb_plugin/row/row0ins.c @@ -345,9 +345,9 @@ row_ins_clust_index_entry_by_modify( return(DB_LOCK_TABLE_FULL); } - err = btr_cur_pessimistic_update( - BTR_KEEP_POS_FLAG, cursor, heap, big_rec, update, - 0, thr, mtr); + err = btr_cur_pessimistic_update(0, cursor, + heap, big_rec, update, + 0, thr, mtr); } return(err); @@ -1989,7 +1989,6 @@ row_ins_index_entry_low( ulint modify = 0; /* remove warning */ rec_t* insert_rec; rec_t* rec; - ulint* offsets; ulint err; ulint n_unique; big_rec_t* big_rec = NULL; @@ -2093,42 +2092,6 @@ row_ins_index_entry_low( err = row_ins_clust_index_entry_by_modify( mode, &cursor, &heap, &big_rec, entry, thr, &mtr); - - if (big_rec) { - ut_a(err == DB_SUCCESS); - /* Write out the externally stored - columns while still x-latching - index->lock and block->lock. We have - to mtr_commit(mtr) first, so that the - redo log will be written in the - correct order. Otherwise, we would run - into trouble on crash recovery if mtr - freed B-tree pages on which some of - the big_rec fields will be written. */ - btr_cur_mtr_commit_and_start(&cursor, &mtr); - - rec = btr_cur_get_rec(&cursor); - offsets = rec_get_offsets( - rec, index, NULL, - ULINT_UNDEFINED, &heap); - - err = btr_store_big_rec_extern_fields( - index, btr_cur_get_block(&cursor), - rec, offsets, &mtr, FALSE, big_rec); - /* If writing big_rec fails (for - example, because of DB_OUT_OF_FILE_SPACE), - the record will be corrupted. Even if - we did not update any externally - stored columns, our update could cause - the record to grow so that a - non-updated column was selected for - external storage. This non-update - would not have been written to the - undo log, and thus the record cannot - be rolled back. */ - ut_a(err == DB_SUCCESS); - goto stored_big_rec; - } } else { ut_ad(!n_ext); err = row_ins_sec_index_entry_by_modify( @@ -2157,6 +2120,8 @@ function_exit: mtr_commit(&mtr); if (UNIV_LIKELY_NULL(big_rec)) { + rec_t* rec; + ulint* offsets; mtr_start(&mtr); btr_cur_search_to_nth_level(index, 0, entry, PAGE_CUR_LE, @@ -2170,7 +2135,6 @@ function_exit: index, btr_cur_get_block(&cursor), rec, offsets, &mtr, FALSE, big_rec); -stored_big_rec: if (modify) { dtuple_big_rec_free(big_rec); } else { diff --git a/storage/innodb_plugin/row/row0upd.c b/storage/innodb_plugin/row/row0upd.c index b5952ff0a78..072ca1d7b54 100644 --- a/storage/innodb_plugin/row/row0upd.c +++ b/storage/innodb_plugin/row/row0upd.c @@ -1969,43 +1969,28 @@ row_upd_clust_rec( ut_ad(!rec_get_deleted_flag(btr_pcur_get_rec(pcur), dict_table_is_comp(index->table))); - err = btr_cur_pessimistic_update( - BTR_NO_LOCKING_FLAG | BTR_KEEP_POS_FLAG, btr_cur, - &heap, &big_rec, node->update, node->cmpl_info, thr, mtr); - if (big_rec) { + err = btr_cur_pessimistic_update(BTR_NO_LOCKING_FLAG, btr_cur, + &heap, &big_rec, node->update, + node->cmpl_info, thr, mtr); + mtr_commit(mtr); + + if (err == DB_SUCCESS && big_rec) { ulint offsets_[REC_OFFS_NORMAL_SIZE]; rec_t* rec; rec_offs_init(offsets_); - ut_a(err == DB_SUCCESS); - /* Write out the externally stored columns while still - x-latching index->lock and block->lock. We have to - mtr_commit(mtr) first, so that the redo log will be - written in the correct order. Otherwise, we would run - into trouble on crash recovery if mtr freed B-tree - pages on which some of the big_rec fields will be - written. */ - btr_cur_mtr_commit_and_start(btr_cur, mtr); + mtr_start(mtr); + ut_a(btr_pcur_restore_position(BTR_MODIFY_TREE, pcur, mtr)); rec = btr_cur_get_rec(btr_cur); err = btr_store_big_rec_extern_fields( index, btr_cur_get_block(btr_cur), rec, rec_get_offsets(rec, index, offsets_, ULINT_UNDEFINED, &heap), mtr, TRUE, big_rec); - /* If writing big_rec fails (for example, because of - DB_OUT_OF_FILE_SPACE), the record will be corrupted. - Even if we did not update any externally stored - columns, our update could cause the record to grow so - that a non-updated column was selected for external - storage. This non-update would not have been written - to the undo log, and thus the record cannot be rolled - back. */ - ut_a(err == DB_SUCCESS); + mtr_commit(mtr); } - mtr_commit(mtr); - if (UNIV_LIKELY_NULL(heap)) { mem_heap_free(heap); } From 20ffbd3dd76b01acc967502385cd202f3912b19f Mon Sep 17 00:00:00 2001 From: Karen Langford Date: Fri, 28 Oct 2011 16:40:46 +0200 Subject: [PATCH 18/19] Patch to fix libedit vis.h problem (OSX/FreeBSD) --- cmd-line-utils/libedit/np/unvis.c | 8 ++++---- cmd-line-utils/libedit/np/vis.c | 8 ++++---- cmd-line-utils/libedit/read.c | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cmd-line-utils/libedit/np/unvis.c b/cmd-line-utils/libedit/np/unvis.c index 4c523608c27..9267db71137 100644 --- a/cmd-line-utils/libedit/np/unvis.c +++ b/cmd-line-utils/libedit/np/unvis.c @@ -50,11 +50,11 @@ static char sccsid[] = "@(#)unvis.c 8.1 (Berkeley) 6/4/93"; #include #include #include -#ifdef HAVE_VIS_H -#include -#else +/* + XXXMYSQL : Due to different versions of vis.h available, + use the one bundled with libedit. +*/ #include "np/vis.h" -#endif #ifdef __weak_alias __weak_alias(strnunvisx,_strnunvisx) diff --git a/cmd-line-utils/libedit/np/vis.c b/cmd-line-utils/libedit/np/vis.c index 6c75438c352..d7cc37b4f47 100644 --- a/cmd-line-utils/libedit/np/vis.c +++ b/cmd-line-utils/libedit/np/vis.c @@ -68,11 +68,11 @@ #include #include -#ifdef HAVE_VIS_H -#include -#else +/* + XXXMYSQL : Due to different versions of vis.h available, + use the one bundled with libedit. +*/ #include "np/vis.h" -#endif #include #include diff --git a/cmd-line-utils/libedit/read.c b/cmd-line-utils/libedit/read.c index 48bf4efd59b..a919b888965 100644 --- a/cmd-line-utils/libedit/read.c +++ b/cmd-line-utils/libedit/read.c @@ -333,7 +333,7 @@ memset(&state, 0, sizeof(mbstate_t)); #ifdef WIDECHAR ++cbp; - if (cbp > MB_CUR_MAX) { /* "shouldn't happen" */ + if (cbp > (size_t) MB_CUR_MAX) { /* "shouldn't happen" */ *cp = '\0'; return (-1); } From aac03193362f8e68ccbc46743a52e830ce94f44f Mon Sep 17 00:00:00 2001 From: Karen Langford Date: Sat, 29 Oct 2011 20:08:40 +0200 Subject: [PATCH 19/19] Patch to fix stdint.h missing from pre Solaris 10 versions. --- cmd-line-utils/libedit/histedit.h | 5 +++++ cmd-line-utils/libedit/np/unvis.c | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/cmd-line-utils/libedit/histedit.h b/cmd-line-utils/libedit/histedit.h index 9f2b245f851..523d27b0be2 100644 --- a/cmd-line-utils/libedit/histedit.h +++ b/cmd-line-utils/libedit/histedit.h @@ -43,7 +43,12 @@ #define LIBEDIT_MAJOR 2 #define LIBEDIT_MINOR 11 +/* XXXMYSQL : stdint.h might not be available on older Solaris platforms. */ +#if defined(__sun) || defined(__sun__) +#include +#else #include +#endif #include #include diff --git a/cmd-line-utils/libedit/np/unvis.c b/cmd-line-utils/libedit/np/unvis.c index 9267db71137..812d280b2d8 100644 --- a/cmd-line-utils/libedit/np/unvis.c +++ b/cmd-line-utils/libedit/np/unvis.c @@ -47,7 +47,14 @@ static char sccsid[] = "@(#)unvis.c 8.1 (Berkeley) 6/4/93"; #include #include + +/* XXXMYSQL : stdint.h might not be available on older Solaris platforms. */ +#if defined(__sun) || defined(__sun__) +#include +#else #include +#endif + #include #include /*