diff options
author | Davi Arnaut <Davi.Arnaut@Sun.COM> | 2010-03-10 10:36:40 -0300 |
---|---|---|
committer | Davi Arnaut <Davi.Arnaut@Sun.COM> | 2010-03-10 10:36:40 -0300 |
commit | b11740894eb4aceeafde2bbd848adb24ef7cc67e (patch) | |
tree | 63a59799aa68a830ed65ed4c3589abbcd1580e75 | |
parent | e58f200a3df514dede23874d6874e8ad295a5eea (diff) | |
download | mariadb-git-b11740894eb4aceeafde2bbd848adb24ef7cc67e.tar.gz |
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.
-rw-r--r-- | mysql-test/r/read_only_innodb.result | 171 | ||||
-rw-r--r-- | mysql-test/t/read_only_innodb.test | 146 | ||||
-rw-r--r-- | 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 : |