From 5c8d6c7473d2b7fad7dd9ded24e686450b7fbb8a Mon Sep 17 00:00:00 2001 From: Mats Kindahl Date: Mon, 1 Mar 2010 23:40:24 +0100 Subject: [PATCH 1/5] Bug #49022: Plugins included into bin release cannot be installed on debug version of server When starting a server using mysqld_safe with the option --mysqld-version=debug, the debug version of the server is started. When attempting to load a plugin that is not debug-built, the load will fail because a non-debug built plugin use, e.g., fastmutex while the debug-built server uses safemutex. To solve this problem, mysqld_safe will set the plugin-dir to use a directory where debug-built plugins are placed. This patch sets the plugin dir to /whatever, when mysqld-version=whatever, and just otherwise. --- scripts/mysqld_safe.sh | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/scripts/mysqld_safe.sh b/scripts/mysqld_safe.sh index f08e870d5e0..a4ce5f9575b 100644 --- a/scripts/mysqld_safe.sh +++ b/scripts/mysqld_safe.sh @@ -54,6 +54,8 @@ Usage: $0 [OPTIONS] --mysqld=FILE Use the specified file as mysqld --mysqld-version=VERSION Use "mysqld-VERSION" as mysqld --nice=NICE Set the scheduling priority of mysqld + --plugin-dir=DIR Plugins are under DIR or DIR/VERSION, if + VERSION is given --skip-kill-mysqld Don't try to kill stray mysqld processes --syslog Log messages to syslog with 'logger' --skip-syslog Log messages to error log (default) @@ -172,6 +174,7 @@ parse_arguments() { --basedir=*) MY_BASEDIR_VERSION="$val" ;; --datadir=*) DATADIR="$val" ;; --pid-file=*) pid_file="$val" ;; + --plugin-dir=*) PLUGIN_DIR="$val" ;; --user=*) user="$val"; SET_USER=1 ;; # these might have been set in a [mysqld_safe] section of my.cnf @@ -189,6 +192,7 @@ parse_arguments() { if test -n "$val" then MYSQLD="mysqld-$val" + PLUGIN_VARIANT="/$val" else MYSQLD="mysqld" fi @@ -695,8 +699,10 @@ fi cmd="`mysqld_ld_preload_text`$NOHUP_NICENESS" +plugin_dir="${PLUGIN_DIR:-@PLUGINDIR@}${PLUGIN_VARIANT}" + for i in "$ledir/$MYSQLD" "$defaults" "--basedir=$MY_BASEDIR_VERSION" \ - "--datadir=$DATADIR" "$USER_OPTION" + "--datadir=$DATADIR" "--plugin-dir=$plugin_dir" "$USER_OPTION" do cmd="$cmd "`shell_quote_string "$i"` done From fcd119066cce94c57de43bd8a9a65b0cb8f5d18b Mon Sep 17 00:00:00 2001 From: Joerg Bruehe Date: Wed, 10 Mar 2010 10:48:43 +0100 Subject: [PATCH 2/5] Part of the fixes for bug#49022 Plugins included into bin release cannot be installed on debug version of server IF the build process was split into separate "debug" and "optimized" builds AND the plugin files of the debug build got copied into "plugin/debug/" (both is done for MySQL release builds starting from 5.5.3), THEN these debug plugin files are to be included in the final binary package. This change deals with the inclusion only, the other parts are done in different changesets. plugin/Makefile.am: The "install" rule must not assume that "debug" files are present, this depends on the preceding steps in the build. If they are present, we cannot copy them by simply using "libtool install" (as we could do during an ordinary build) because they are already kept in "plugin/debug/". When doing the copy to the destination hierarchy, we must ensure that symlinks are not expanded but rather copied as symlinks. "cp -d" is specific to GNU, the portable way would use "cp -P", but that is not fully specified with recursion. So we fall back on "tar c | tar x", which is known to keep symlinks unchanged (and un-expanded). Using "$(TAR)" is just a precaution in case of weird path settings or other portability issues. --- plugin/Makefile.am | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/plugin/Makefile.am b/plugin/Makefile.am index 68f1f939836..bddd0c929fc 100644 --- a/plugin/Makefile.am +++ b/plugin/Makefile.am @@ -1,4 +1,4 @@ -# Copyright (C) 2005-2006 MySQL AB +# Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -24,5 +24,10 @@ EXTRA_DIST = fulltext/configure.in SUBDIRS = @mysql_pg_dirs@ DIST_SUBDIRS = @mysql_pg_distdirs@ +# As of 5.5.3-m3, we want to include the plugin files of a debug build in the package +install-exec-hook: + $(mkinstalldirs) $(DESTDIR)$(pkglibdir) $(DESTDIR)$(pkglibdir)/plugin + test ! -d debug || $(TAR) cf - debug | ( cd $(DESTDIR)$(pkglibdir) && $(TAR) xvf - ) + # Don't update the files from bitkeeper %::SCCS/s.% From 3139b904948e72f9d7a6ed5849c8aeadc0d8c7fa Mon Sep 17 00:00:00 2001 From: Joerg Bruehe Date: Wed, 10 Mar 2010 14:36:27 +0100 Subject: [PATCH 3/5] Part of the fixes for bug#49022 Plugins included into bin release cannot be installed on debug version of server Ensure that the plugin files of the debug build get into the optimized tree, so that they find their way into the final RPMs. support-files/mysql.spec.sh: Use "make install" in the debug build tree to get the debug plugin files to a temporary location, then move them to "plugin/debug/" in the optimized build tree so that the install hook in "plugin/Makefile.am" forwards them into the final installation. This fixes bug#49022 for the generic RPMs. Unrelated: The plugin objects were listed twice in the file list, do a cleanup. --- support-files/mysql.spec.sh | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/support-files/mysql.spec.sh b/support-files/mysql.spec.sh index 7c7f841bd37..f02773e73b9 100644 --- a/support-files/mysql.spec.sh +++ b/support-files/mysql.spec.sh @@ -561,6 +561,14 @@ install -d $RBR%{_libdir} install -d $RBR%{_mandir} install -d $RBR%{_sbindir} +# Get the plugin files from the debug build +mkdir $RBR/tmp-debug-plugin $MBD/plugin/debug +( cd $RPM_BUILD_DIR/mysql-%{mysql_version}/mysql-debug-%{mysql_version}/plugin + make install DESTDIR=$RBR/tmp-debug-plugin + mv $RBR/tmp-debug-plugin/usr/local/mysql/lib/mysql/plugin/* $MBD/plugin/debug/ + # From here, the install hook in "plugin/Makefile.am" will do the rest. +) +rmdir -p $RBR/tmp-debug-plugin/usr/local/mysql/lib/mysql/plugin # Install all binaries (cd $MBD && make install DESTDIR=$RBR testroot=%{_datadir}) @@ -859,10 +867,6 @@ fi %attr(755, root, root) %{_bindir}/resolve_stack_dump %attr(755, root, root) %{_bindir}/resolveip -%attr(755, root, root) %{_libdir}/mysql/plugin/ha_example.so* -%attr(755, root, root) %{_libdir}/mysql/plugin/semisync_master.so* -%attr(755, root, root) %{_libdir}/mysql/plugin/semisync_slave.so* - %if %{WITH_TCMALLOC} %attr(755, root, root) %{_libdir}/mysql/%{malloc_lib_target} %endif @@ -873,6 +877,9 @@ fi %attr(755, root, root) %{_libdir}/mysql/plugin/ha_example.so* %attr(755, root, root) %{_libdir}/mysql/plugin/semisync_master.so* %attr(755, root, root) %{_libdir}/mysql/plugin/semisync_slave.so* +%attr(755, root, root) %{_libdir}/mysql/plugin/debug/ha_example.so* +%attr(755, root, root) %{_libdir}/mysql/plugin/debug/semisync_master.so* +%attr(755, root, root) %{_libdir}/mysql/plugin/debug/semisync_slave.so* %if %{WITH_TCMALLOC} %attr(755, root, root) %{_libdir}/mysql/%{malloc_lib_target} @@ -1007,6 +1014,12 @@ fi %{_libdir}/mysql/plugin/semisync_master.la %{_libdir}/mysql/plugin/semisync_slave.a %{_libdir}/mysql/plugin/semisync_slave.la +%{_libdir}/mysql/plugin/debug/ha_example.a +%{_libdir}/mysql/plugin/debug/ha_example.la +%{_libdir}/mysql/plugin/debug/semisync_master.a +%{_libdir}/mysql/plugin/debug/semisync_master.la +%{_libdir}/mysql/plugin/debug/semisync_slave.a +%{_libdir}/mysql/plugin/debug/semisync_slave.la %files shared %defattr(-, root, root, 0755) @@ -1042,6 +1055,12 @@ fi # merging BK trees) ############################################################################## %changelog +* Wed Mar 10 2010 Joerg Bruehe + +- Take the result of the debug plugin build and put it into the optimized tree, + so that it becomes part of the final installation; + include the files in the packlist. Part of the fixes for bug#49022. + * Mon Mar 01 2010 Joerg Bruehe - Set "Oracle and/or its affiliates" as the vendor and copyright owner, From b11740894eb4aceeafde2bbd848adb24ef7cc67e Mon Sep 17 00:00:00 2001 From: Davi Arnaut Date: Wed, 10 Mar 2010 10:36:40 -0300 Subject: [PATCH 4/5] Bug#33669: Transactional temporary tables do not work under --read-only The problem was that in read only mode (read_only enabled), the server would mistakenly deny data modification attempts for temporary tables which belong to a transactional storage engine (eg. InnoDB). The solution is to allow transactional temporary tables to be modified under read only mode. As a whole, the read only mode does not apply to any kind of temporary table. mysql-test/r/read_only_innodb.result: Add test case result for Bug#33669 mysql-test/t/read_only_innodb.test: Add test case for Bug#33669 sql/lock.cc: Rename mysql_lock_tables_check to lock_tables_check and make it static. Move locking related checks from get_lock_data to lock_tables_check. Allow write locks to temporary tables even under read-only. --- mysql-test/r/read_only_innodb.result | 171 +++++++++++++++++++++++++++ mysql-test/t/read_only_innodb.test | 146 +++++++++++++++++++++++ sql/lock.cc | 105 +++++++--------- 3 files changed, 363 insertions(+), 59 deletions(-) diff --git a/mysql-test/r/read_only_innodb.result b/mysql-test/r/read_only_innodb.result index 690de085bf9..13e5980f900 100644 --- a/mysql-test/r/read_only_innodb.result +++ b/mysql-test/r/read_only_innodb.result @@ -46,3 +46,174 @@ UNLOCK TABLES; DROP TABLE t1; DROP USER test@localhost; echo End of 5.1 tests +# +# Bug#33669: Transactional temporary tables do not work under --read-only +# +DROP DATABASE IF EXISTS db1; +# Setup user and tables +CREATE USER bug33669@localhost; +CREATE DATABASE db1; +CREATE TABLE db1.t1 (a INT) ENGINE=INNODB; +CREATE TABLE db1.t2 (a INT) ENGINE=INNODB; +INSERT INTO db1.t1 VALUES (1); +INSERT INTO db1.t2 VALUES (2); +GRANT CREATE TEMPORARY TABLES, DROP, INSERT, DELETE, UPDATE, +SELECT, LOCK TABLES ON db1.* TO bug33669@localhost; +SET GLOBAL READ_ONLY = ON; +# Connection con1 (user bug33669): + +# Create, insert and drop temporary table: + +CREATE TEMPORARY TABLE temp (a INT) ENGINE=INNODB; +INSERT INTO temp VALUES (1); +DROP TABLE temp; + +# Lock base tables and use temporary table: + +CREATE TEMPORARY TABLE temp (a INT) ENGINE=INNODB; +LOCK TABLES t1 READ, t2 READ; +SELECT * FROM t1; +a +1 +INSERT INTO temp values (1); +SELECT * FROM t2; +a +2 +UNLOCK TABLES; +DROP TABLE temp; + +# Transaction + +BEGIN; +SELECT * FROM t1; +a +1 +CREATE TEMPORARY TABLE temp (a INT) ENGINE=INNODB; +INSERT INTO t1 VALUES (1); +ERROR HY000: The MySQL server is running with the --read-only option so it cannot execute this statement +INSERT INTO temp VALUES (1); +SELECT * FROM t2; +a +2 +ROLLBACK; +SELECT * FROM temp; +a +DROP TABLE temp; + +# Lock base table as READ and temporary table as WRITE: + +CREATE TEMPORARY TABLE temp (a INT) ENGINE=INNODB; +LOCK TABLES t1 READ, temp WRITE; +SELECT * FROM t1; +a +1 +SELECT * FROM temp; +a +INSERT INTO t1 VALUES (1); +ERROR HY000: The MySQL server is running with the --read-only option so it cannot execute this statement +INSERT INTO temp VALUES (1); +DROP TABLE temp; +UNLOCK TABLES; + +# Lock temporary table that shadows a base table: + +CREATE TEMPORARY TABLE t1 (a INT) ENGINE=INNODB; +LOCK TABLES t1 WRITE; +DROP TABLE t1; +SELECT * FROM t1; +ERROR HY000: Table 't1' was not locked with LOCK TABLES + +# INSERT SELECT from base table into temporary table: + +CREATE TEMPORARY TABLE temp1 (a INT) ENGINE=INNODB; +CREATE TEMPORARY TABLE temp2 LIKE temp1; +BEGIN; +INSERT INTO temp1 VALUES (10); +INSERT INTO temp2 VALUES (10); +INSERT INTO temp1 SELECT * FROM t1; +INSERT INTO temp2 SELECT * FROM t2; +SELECT * FROM temp1 ORDER BY a; +a +1 +10 +SELECT * FROM temp2 ORDER BY a; +a +2 +10 +ROLLBACK; +SELECT * FROM temp1,temp2; +a a +LOCK TABLES t1 READ, t2 READ; +INSERT INTO temp1 VALUES (10); +INSERT INTO temp2 VALUES (10); +INSERT INTO temp1 SELECT * FROM t1; +INSERT INTO temp2 SELECT * FROM t2; +SELECT * FROM temp1 ORDER BY a; +a +1 +10 +SELECT * FROM temp2 ORDER BY a; +a +2 +10 +UNLOCK TABLES; +DELETE temp1, temp2 FROM temp1, temp2; +INSERT INTO temp1 VALUES (10); +INSERT INTO temp2 VALUES (10); +INSERT INTO temp1 SELECT * FROM t1; +INSERT INTO temp2 SELECT * FROM t2; +SELECT * FROM temp1 ORDER BY a; +a +1 +10 +SELECT * FROM temp2 ORDER BY a; +a +2 +10 +DROP TABLE temp1, temp2; + +# INSERT and INSERT SELECT that uses subqueries: +CREATE TEMPORARY TABLE temp1 (a INT) ENGINE=INNODB; +CREATE TEMPORARY TABLE temp2 LIKE temp1; +INSERT INTO temp1 (a) VALUES ((SELECT MAX(a) FROM t1)); +LOCK TABLES t2 READ; +INSERT INTO temp2 (a) VALUES ((SELECT MAX(a) FROM t2)); +UNLOCK TABLES; +LOCK TABLES t1 READ, t2 READ; +INSERT INTO temp1 SELECT * FROM t1 WHERE a < (SELECT MAX(a) FROM t2); +INSERT INTO temp2 SELECT * FROM t2 WHERE a > (SELECT MAX(a) FROM t1); +UNLOCK TABLES; +INSERT INTO temp1 SELECT * FROM t1 WHERE a < (SELECT MAX(a) FROM t2); +INSERT INTO temp2 SELECT * FROM t2 WHERE a > (SELECT MAX(a) FROM t1); +SELECT * FROM temp1 ORDER BY a; +a +1 +1 +1 +SELECT * FROM temp2 ORDER BY a; +a +2 +2 +2 +DROP TABLE temp1, temp2; + +# Multiple table update: + +CREATE TEMPORARY TABLE temp1 (a INT) ENGINE=INNODB; +CREATE TEMPORARY TABLE temp2 LIKE temp1; +INSERT INTO temp1 VALUES (1),(2); +INSERT INTO temp2 VALUES (3),(4); +UPDATE temp1,temp2 SET temp1.a = 5, temp2.a = 10; +SELECT * FROM temp1, temp2; +a a +5 10 +5 10 +5 10 +5 10 +DROP TABLE temp1, temp2; + +# Disconnect and cleanup + +SET GLOBAL READ_ONLY = OFF; +DROP USER bug33669@localhost; +DROP DATABASE db1; diff --git a/mysql-test/t/read_only_innodb.test b/mysql-test/t/read_only_innodb.test index 9e001f2b997..3bb626f2ca7 100644 --- a/mysql-test/t/read_only_innodb.test +++ b/mysql-test/t/read_only_innodb.test @@ -83,3 +83,149 @@ DROP USER test@localhost; disconnect con1; --echo echo End of 5.1 tests + +--echo # +--echo # Bug#33669: Transactional temporary tables do not work under --read-only +--echo # + +--disable_warnings +DROP DATABASE IF EXISTS db1; +--enable_warnings + +--echo # Setup user and tables +CREATE USER bug33669@localhost; +CREATE DATABASE db1; +CREATE TABLE db1.t1 (a INT) ENGINE=INNODB; +CREATE TABLE db1.t2 (a INT) ENGINE=INNODB; +INSERT INTO db1.t1 VALUES (1); +INSERT INTO db1.t2 VALUES (2); +GRANT CREATE TEMPORARY TABLES, DROP, INSERT, DELETE, UPDATE, + SELECT, LOCK TABLES ON db1.* TO bug33669@localhost; +SET GLOBAL READ_ONLY = ON; +connect(con1,localhost,bug33669,,db1); +--echo # Connection con1 (user bug33669): + +--echo +--echo # Create, insert and drop temporary table: +--echo +CREATE TEMPORARY TABLE temp (a INT) ENGINE=INNODB; +INSERT INTO temp VALUES (1); +DROP TABLE temp; + +--echo +--echo # Lock base tables and use temporary table: +--echo +CREATE TEMPORARY TABLE temp (a INT) ENGINE=INNODB; +LOCK TABLES t1 READ, t2 READ; +SELECT * FROM t1; +INSERT INTO temp values (1); +SELECT * FROM t2; +UNLOCK TABLES; +DROP TABLE temp; + +--echo +--echo # Transaction +--echo +BEGIN; +SELECT * FROM t1; +CREATE TEMPORARY TABLE temp (a INT) ENGINE=INNODB; +--error ER_OPTION_PREVENTS_STATEMENT +INSERT INTO t1 VALUES (1); +INSERT INTO temp VALUES (1); +SELECT * FROM t2; +ROLLBACK; +SELECT * FROM temp; +DROP TABLE temp; + +--echo +--echo # Lock base table as READ and temporary table as WRITE: +--echo +CREATE TEMPORARY TABLE temp (a INT) ENGINE=INNODB; +LOCK TABLES t1 READ, temp WRITE; +SELECT * FROM t1; +SELECT * FROM temp; +--error ER_OPTION_PREVENTS_STATEMENT +INSERT INTO t1 VALUES (1); +INSERT INTO temp VALUES (1); +DROP TABLE temp; +UNLOCK TABLES; + +--echo +--echo # Lock temporary table that shadows a base table: +--echo +CREATE TEMPORARY TABLE t1 (a INT) ENGINE=INNODB; +LOCK TABLES t1 WRITE; +DROP TABLE t1; +--error ER_TABLE_NOT_LOCKED +SELECT * FROM t1; + +--echo +--echo # INSERT SELECT from base table into temporary table: +--echo + +CREATE TEMPORARY TABLE temp1 (a INT) ENGINE=INNODB; +CREATE TEMPORARY TABLE temp2 LIKE temp1; +BEGIN; +INSERT INTO temp1 VALUES (10); +INSERT INTO temp2 VALUES (10); +INSERT INTO temp1 SELECT * FROM t1; +INSERT INTO temp2 SELECT * FROM t2; +SELECT * FROM temp1 ORDER BY a; +SELECT * FROM temp2 ORDER BY a; +ROLLBACK; +SELECT * FROM temp1,temp2; +LOCK TABLES t1 READ, t2 READ; +INSERT INTO temp1 VALUES (10); +INSERT INTO temp2 VALUES (10); +INSERT INTO temp1 SELECT * FROM t1; +INSERT INTO temp2 SELECT * FROM t2; +SELECT * FROM temp1 ORDER BY a; +SELECT * FROM temp2 ORDER BY a; +UNLOCK TABLES; +DELETE temp1, temp2 FROM temp1, temp2; +INSERT INTO temp1 VALUES (10); +INSERT INTO temp2 VALUES (10); +INSERT INTO temp1 SELECT * FROM t1; +INSERT INTO temp2 SELECT * FROM t2; +SELECT * FROM temp1 ORDER BY a; +SELECT * FROM temp2 ORDER BY a; +DROP TABLE temp1, temp2; + +--echo +--echo # INSERT and INSERT SELECT that uses subqueries: +CREATE TEMPORARY TABLE temp1 (a INT) ENGINE=INNODB; +CREATE TEMPORARY TABLE temp2 LIKE temp1; +INSERT INTO temp1 (a) VALUES ((SELECT MAX(a) FROM t1)); +LOCK TABLES t2 READ; +INSERT INTO temp2 (a) VALUES ((SELECT MAX(a) FROM t2)); +UNLOCK TABLES; +LOCK TABLES t1 READ, t2 READ; +INSERT INTO temp1 SELECT * FROM t1 WHERE a < (SELECT MAX(a) FROM t2); +INSERT INTO temp2 SELECT * FROM t2 WHERE a > (SELECT MAX(a) FROM t1); +UNLOCK TABLES; +INSERT INTO temp1 SELECT * FROM t1 WHERE a < (SELECT MAX(a) FROM t2); +INSERT INTO temp2 SELECT * FROM t2 WHERE a > (SELECT MAX(a) FROM t1); +SELECT * FROM temp1 ORDER BY a; +SELECT * FROM temp2 ORDER BY a; +DROP TABLE temp1, temp2; + +--echo +--echo # Multiple table update: +--echo + +CREATE TEMPORARY TABLE temp1 (a INT) ENGINE=INNODB; +CREATE TEMPORARY TABLE temp2 LIKE temp1; +INSERT INTO temp1 VALUES (1),(2); +INSERT INTO temp2 VALUES (3),(4); +UPDATE temp1,temp2 SET temp1.a = 5, temp2.a = 10; +SELECT * FROM temp1, temp2; +DROP TABLE temp1, temp2; + +--echo +--echo # Disconnect and cleanup +--echo +disconnect con1; +connection default; +SET GLOBAL READ_ONLY = OFF; +DROP USER bug33669@localhost; +DROP DATABASE db1; diff --git a/sql/lock.cc b/sql/lock.cc index 3ff131bb828..78a16cea18a 100644 --- a/sql/lock.cc +++ b/sql/lock.cc @@ -89,8 +89,8 @@ extern HASH open_cache; #define GET_LOCK_UNLOCK 1 #define GET_LOCK_STORE_LOCKS 2 -static MYSQL_LOCK *get_lock_data(THD *thd, TABLE **table,uint count, - uint flags, TABLE **write_locked); +static MYSQL_LOCK *get_lock_data(THD *thd, TABLE **table_ptr, uint count, + uint flags); static int lock_external(THD *thd, TABLE **table,uint count); static int unlock_external(THD *thd, TABLE **table,uint count); static void print_lock_error(int error, const char *); @@ -107,15 +107,18 @@ static int thr_lock_errno_to_mysql[]= @param flags Lock flags @return 0 if all the check passed, non zero if a check failed. */ -int mysql_lock_tables_check(THD *thd, TABLE **tables, uint count, uint flags) +static int +lock_tables_check(THD *thd, TABLE **tables, uint count, + bool *write_lock_used, uint flags) { - bool log_table_write_query; - uint system_count; - uint i; + uint system_count, i; + bool is_superuser, log_table_write_query; - DBUG_ENTER("mysql_lock_tables_check"); + DBUG_ENTER("lock_tables_check"); system_count= 0; + *write_lock_used= FALSE; + is_superuser= thd->security_ctx->master_access & SUPER_ACL; log_table_write_query= (is_log_table_write_query(thd->lex->sql_command) || ((flags & MYSQL_LOCK_PERF_SCHEMA) != 0)); @@ -148,10 +151,18 @@ int mysql_lock_tables_check(THD *thd, TABLE **tables, uint count, uint flags) } } - if ((t->s->table_category == TABLE_CATEGORY_SYSTEM) && - (t->reginfo.lock_type >= TL_WRITE_ALLOW_WRITE)) + if (t->reginfo.lock_type >= TL_WRITE_ALLOW_WRITE) { - system_count++; + *write_lock_used= TRUE; + + if (t->s->table_category == TABLE_CATEGORY_SYSTEM) + system_count++; + + if (t->db_stat & HA_READ_ONLY) + { + my_error(ER_OPEN_AS_READONLY, MYF(0), t->alias); + DBUG_RETURN(1); + } } /* @@ -172,6 +183,20 @@ int mysql_lock_tables_check(THD *thd, TABLE **tables, uint count, uint flags) thd->mdl_context.is_lock_owner(MDL_key::TABLE, t->s->db.str, t->s->table_name.str, MDL_SHARED))); + + /* + Prevent modifications to base tables if READ_ONLY is activated. + In any case, read only does not apply to temporary tables. + */ + if (!(flags & MYSQL_LOCK_IGNORE_GLOBAL_READ_ONLY) && !t->s->tmp_table) + { + if (t->reginfo.lock_type >= TL_WRITE_ALLOW_WRITE && + !is_superuser && opt_readonly && !thd->slave_thread) + { + my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--read-only"); + DBUG_RETURN(1); + } + } } /* @@ -267,15 +292,15 @@ static void reset_lock_data_and_free(MYSQL_LOCK **mysql_lock) MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **tables, uint count, uint flags, bool *need_reopen) { - MYSQL_LOCK *sql_lock; - TABLE *write_lock_used; int rc; + MYSQL_LOCK *sql_lock; + bool write_lock_used; DBUG_ENTER("mysql_lock_tables"); *need_reopen= FALSE; - if (mysql_lock_tables_check(thd, tables, count, flags)) + if (lock_tables_check(thd, tables, count, &write_lock_used, flags)) DBUG_RETURN (NULL); ulong timeout= (flags & MYSQL_LOCK_IGNORE_TIMEOUT) ? @@ -283,8 +308,7 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **tables, uint count, for (;;) { - if (! (sql_lock= get_lock_data(thd, tables, count, GET_LOCK_STORE_LOCKS, - &write_lock_used))) + if (! (sql_lock= get_lock_data(thd, tables, count, GET_LOCK_STORE_LOCKS))) break; if (global_read_lock && write_lock_used && @@ -308,21 +332,6 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **tables, uint count, } } - if (!(flags & MYSQL_LOCK_IGNORE_GLOBAL_READ_ONLY) && - write_lock_used && - opt_readonly && - !(thd->security_ctx->master_access & SUPER_ACL) && - !thd->slave_thread) - { - /* - Someone has issued SET GLOBAL READ_ONLY=1 and we want a write lock. - We do not wait for READ_ONLY=0, and fail. - */ - reset_lock_data_and_free(&sql_lock); - my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--read-only"); - break; - } - thd_proc_info(thd, "System lock"); DBUG_PRINT("info", ("thd->proc_info %s", thd->proc_info)); if (sql_lock->table_count && lock_external(thd, sql_lock->table, @@ -459,9 +468,7 @@ void mysql_unlock_tables(THD *thd, MYSQL_LOCK *sql_lock) void mysql_unlock_some_tables(THD *thd, TABLE **table,uint count) { MYSQL_LOCK *sql_lock; - TABLE *write_lock_used; - if ((sql_lock= get_lock_data(thd, table, count, GET_LOCK_UNLOCK, - &write_lock_used))) + if ((sql_lock= get_lock_data(thd, table, count, GET_LOCK_UNLOCK))) mysql_unlock_tables(thd, sql_lock); } @@ -603,9 +610,7 @@ void mysql_lock_downgrade_write(THD *thd, TABLE *table, thr_lock_type new_lock_type) { MYSQL_LOCK *locked; - TABLE *write_lock_used; - if ((locked = get_lock_data(thd, &table, 1, GET_LOCK_UNLOCK, - &write_lock_used))) + if ((locked = get_lock_data(thd, &table, 1, GET_LOCK_UNLOCK))) { for (uint i=0; i < locked->lock_count; i++) thr_downgrade_write_lock(locked->locks[i], new_lock_type); @@ -619,11 +624,9 @@ void mysql_lock_downgrade_write(THD *thd, TABLE *table, void mysql_lock_abort(THD *thd, TABLE *table, bool upgrade_lock) { MYSQL_LOCK *locked; - TABLE *write_lock_used; DBUG_ENTER("mysql_lock_abort"); - if ((locked= get_lock_data(thd, &table, 1, GET_LOCK_UNLOCK, - &write_lock_used))) + if ((locked= get_lock_data(thd, &table, 1, GET_LOCK_UNLOCK))) { for (uint i=0; i < locked->lock_count; i++) thr_abort_locks(locked->locks[i]->lock, upgrade_lock); @@ -648,12 +651,10 @@ void mysql_lock_abort(THD *thd, TABLE *table, bool upgrade_lock) bool mysql_lock_abort_for_thread(THD *thd, TABLE *table) { MYSQL_LOCK *locked; - TABLE *write_lock_used; bool result= FALSE; DBUG_ENTER("mysql_lock_abort_for_thread"); - if ((locked= get_lock_data(thd, &table, 1, GET_LOCK_UNLOCK, - &write_lock_used))) + if ((locked= get_lock_data(thd, &table, 1, GET_LOCK_UNLOCK))) { for (uint i=0; i < locked->lock_count; i++) { @@ -848,11 +849,10 @@ static int unlock_external(THD *thd, TABLE **table,uint count) @param flags One of: - GET_LOCK_UNLOCK : If we should send TL_IGNORE to store lock - GET_LOCK_STORE_LOCKS : Store lock info in TABLE - @param write_lock_used Store pointer to last table with WRITE_ALLOW_WRITE */ static MYSQL_LOCK *get_lock_data(THD *thd, TABLE **table_ptr, uint count, - uint flags, TABLE **write_lock_used) + uint flags) { uint i,tables,lock_count; MYSQL_LOCK *sql_lock; @@ -861,9 +861,8 @@ static MYSQL_LOCK *get_lock_data(THD *thd, TABLE **table_ptr, uint count, DBUG_ENTER("get_lock_data"); DBUG_ASSERT((flags == GET_LOCK_UNLOCK) || (flags == GET_LOCK_STORE_LOCKS)); - DBUG_PRINT("info", ("count %d", count)); - *write_lock_used=0; + for (i=tables=lock_count=0 ; i < count ; i++) { TABLE *t= table_ptr[i]; @@ -895,24 +894,12 @@ static MYSQL_LOCK *get_lock_data(THD *thd, TABLE **table_ptr, uint count, { TABLE *table; enum thr_lock_type lock_type; + THR_LOCK_DATA **org_locks = locks; if ((table=table_ptr[i])->s->tmp_table == NON_TRANSACTIONAL_TMP_TABLE) continue; lock_type= table->reginfo.lock_type; DBUG_ASSERT(lock_type != TL_WRITE_DEFAULT && lock_type != TL_READ_DEFAULT); - if (lock_type >= TL_WRITE_ALLOW_WRITE) - { - *write_lock_used=table; - if (table->db_stat & HA_READ_ONLY) - { - my_error(ER_OPEN_AS_READONLY,MYF(0),table->alias); - /* Clear the lock type of the lock data that are stored already. */ - sql_lock->lock_count= (uint) (locks - sql_lock->locks); - reset_lock_data_and_free(&sql_lock); - DBUG_RETURN(0); - } - } - THR_LOCK_DATA **org_locks = locks; locks_start= locks; locks= table->file->store_lock(thd, locks, (flags & GET_LOCK_UNLOCK) ? TL_IGNORE : From 7fe455a6bcbb2ba528bf77fc0f718b0c240895d0 Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Wed, 10 Mar 2010 17:35:25 +0300 Subject: [PATCH 5/5] A fix and a test case for Bug#51710 FLUSH TABLES WITH READ LOCK kills the server. Prohibit FLUSH TABLES WITH READ LOCK application to views or temporary tables. Fix a subtle bug in the implementation when we actually did not remove table share objects from the table cache after acquiring exclusive locks. mysql-test/r/flush.result: Update results (Bug#51710) mysql-test/t/flush.test: Add a test case for Bug#51710. sql/sql_parse.cc: Fix Bug#51710 "FLUSH TABLES WITH READ LOCK killes the server. Ensure we don't open views and temporary tables. Fix a yet another bug in the implementation which did not actually remove the tables from cache after acquiring exclusive locks. --- mysql-test/r/flush.result | 27 +++++++++++++++++++++++++++ mysql-test/t/flush.test | 31 +++++++++++++++++++++++++++++++ sql/sql_parse.cc | 23 +++++++++++++++++++++++ 3 files changed, 81 insertions(+) diff --git a/mysql-test/r/flush.result b/mysql-test/r/flush.result index fd23bfa0562..3702443d04a 100644 --- a/mysql-test/r/flush.result +++ b/mysql-test/r/flush.result @@ -207,3 +207,30 @@ insert into t2 (a) values (3); unlock tables; # --> connection con1 drop table t1, t2, t3; +# +# Bug#51710 FLUSH TABLES WITH READ LOCK kills the server +# +drop view if exists v1, v2, v3; +drop table if exists t1, v1; +create table t1 (a int); +create view v1 as select 1; +create view v2 as select * from t1; +create view v3 as select * from v2; +flush table v1, v2, v3 with read lock; +ERROR HY000: 'test.v1' is not BASE TABLE +flush table v1 with read lock; +ERROR HY000: 'test.v1' is not BASE TABLE +flush table v2 with read lock; +ERROR HY000: 'test.v2' is not BASE TABLE +flush table v3 with read lock; +ERROR HY000: 'test.v3' is not BASE TABLE +create temporary table v1 (a int); +flush table v1 with read lock; +ERROR HY000: 'test.v1' is not BASE TABLE +drop view v1; +create table v1 (a int); +flush table v1 with read lock; +drop temporary table v1; +unlock tables; +drop view v2, v3; +drop table t1, v1; diff --git a/mysql-test/t/flush.test b/mysql-test/t/flush.test index 582d2562fc6..0d406338394 100644 --- a/mysql-test/t/flush.test +++ b/mysql-test/t/flush.test @@ -324,3 +324,34 @@ disconnect con1; --source include/wait_until_disconnected.inc connection default; drop table t1, t2, t3; + +--echo # +--echo # Bug#51710 FLUSH TABLES WITH READ LOCK kills the server +--echo # +--disable_warnings +drop view if exists v1, v2, v3; +drop table if exists t1, v1; +--enable_warnings +create table t1 (a int); +create view v1 as select 1; +create view v2 as select * from t1; +create view v3 as select * from v2; + +--error ER_WRONG_OBJECT +flush table v1, v2, v3 with read lock; +--error ER_WRONG_OBJECT +flush table v1 with read lock; +--error ER_WRONG_OBJECT +flush table v2 with read lock; +--error ER_WRONG_OBJECT +flush table v3 with read lock; +create temporary table v1 (a int); +--error ER_WRONG_OBJECT +flush table v1 with read lock; +drop view v1; +create table v1 (a int); +flush table v1 with read lock; +drop temporary table v1; +unlock tables; +drop view v2, v3; +drop table t1, v1; diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 6403ad99282..23e0d8c0d70 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -1631,6 +1631,14 @@ int prepare_schema_table(THD *thd, LEX *lex, Table_ident *table_ident, - you can't flush WITH READ LOCK a non-existent table - you can't flush WITH READ LOCK under LOCK TABLES - currently incompatible with the GRL (@todo: fix) + + Effect on views and temporary tables. + ------------------------------------ + You can only apply this command to existing base tables. + If a view with such name exists, ER_WRONG_OBJECT is returned. + If a temporary table with such name exists, it's ignored: + if there is a base table, it's used, otherwise ER_NO_SUCH_TABLE + is returned. */ static bool flush_tables_with_read_lock(THD *thd, TABLE_LIST *all_tables) @@ -1665,6 +1673,21 @@ static bool flush_tables_with_read_lock(THD *thd, TABLE_LIST *all_tables) if (lock_table_names(thd, all_tables)) goto error; + for (table_list= all_tables; table_list; + table_list= table_list->next_global) + { + /* Remove the table from cache. */ + mysql_mutex_lock(&LOCK_open); + tdc_remove_table(thd, TDC_RT_REMOVE_ALL, + table_list->db, + table_list->table_name); + mysql_mutex_unlock(&LOCK_open); + + /* Skip views and temporary tables. */ + table_list->required_type= FRMTYPE_TABLE; /* Don't try to flush views. */ + table_list->open_type= OT_BASE_ONLY; /* Ignore temporary tables. */ + } + if (open_and_lock_tables(thd, all_tables, FALSE, MYSQL_OPEN_HAS_MDL_LOCK, &lock_tables_prelocking_strategy) ||