Commit 4f18083b authored by Davi Arnaut's avatar Davi Arnaut

Bug#42643: InnoDB does not support replication of TRUNCATE TABLE

The problem was that TRUNCATE TABLE didn't take a exclusive
lock on a table if it resorted to truncating via delete of
all rows in the table. Specifically for InnoDB tables, this
could break proper isolation as InnoDB ends up aborting some
granted locks when truncating a table.

The solution is to take a exclusive metadata lock before
TRUNCATE TABLE can proceed. This guarantees that no other
transaction is using the table.

Incompatible change: Truncate via delete no longer fails
if sql_safe_updates is activated (this was a undocumented
side effect).

libmysqld/CMakeLists.txt:
  Add new files to the build list.
libmysqld/Makefile.am:
  Add new files to the build list.
mysql-test/extra/binlog_tests/binlog_truncate.test:
  Add test case for Bug#42643
mysql-test/include/mix1.inc:
  Update test case as TRUNCATE TABLE now grabs a exclusive lock.
  Ensure that TRUNCATE waits for granted locks on the table.
mysql-test/suite/binlog/t/binlog_truncate_innodb.test:
  As with other data modifying statements, TRUNCATE is still not
  possible in a transaction with isolation level READ COMMITTED
  or READ UNCOMMITED. It would be possible to implement so, but
  it is not worth the effort.
mysql-test/suite/binlog/t/binlog_truncate_myisam.test:
  Test under different binlog formats.
mysql-test/suite/binlog/t/disabled.def:
  Re-enable test case.
mysql-test/t/innodb_bug38231.test:
  Truncate no longer works with row-level locks.
mysql-test/t/mdl_sync.test:
  Ensure that a acquired lock is not given up due to a conflict.
mysql-test/t/partition_innodb_semi_consistent.test:
  End transaction as to release metadata locks.
mysql-test/t/truncate.test:
  A metadata lock is now taken before the object is verified.
sql/CMakeLists.txt:
  Add new files to the build list.
sql/Makefile.am:
  Add new files to the build list.
sql/datadict.cc:
  Introduce a new file specific for data dictionary operations.
sql/datadict.h:
  Add header file.
sql/sql_base.cc:
  Rename data dictionary function.
sql/sql_bitmap.h:
  Include dependency.
sql/sql_delete.cc:
  Move away from relying on mysql_delete() to delete all rows of
  a table. Thus, move any bits related to truncate to sql_truncate.cc
sql/sql_delete.h:
  Remove parameter.
sql/sql_parse.cc:
  Add protection against the global read lock -- a intention
  exclusive lock can be acquired in the truncate path.
sql/sql_show.cc:
  Add sync point for testing scenarios where a pending flush
  is ignored.
sql/sql_truncate.cc:
  Acquire a shared metadata lock before accessing table metadata.
  Upgrade the lock to a exclusive one if the table can be re-created.
  Rework binlog rules to better reflect the requirements.
sql/sql_yacc.yy:
  Set appropriate lock types for table to be truncated.
sql/table.h:
  Move to data dictionary header.
parent bee0f214
......@@ -996,6 +996,8 @@ libmysqld/.deps/sql_crypt.Po
libmysqld/.deps/sql_cursor.Po
libmysqld/.deps/sql_db.Po
libmysqld/.deps/sql_delete.Po
libmysqld/.deps/sql_truncate.Po
libmysqld/.deps/datadict.Po
libmysqld/.deps/sql_derived.Po
libmysqld/.deps/sql_do.Po
libmysqld/.deps/sql_error.Po
......@@ -1172,6 +1174,8 @@ libmysqld/sql_cursor.cc
libmysqld/sql_cursor.h
libmysqld/sql_db.cc
libmysqld/sql_delete.cc
libmysqld/sql_truncate.cc
libmysqld/datadict.cc
libmysqld/sql_derived.cc
libmysqld/sql_do.cc
libmysqld/sql_error.cc
......@@ -2062,6 +2066,8 @@ sql/.deps/sql_crypt.Po
sql/.deps/sql_cursor.Po
sql/.deps/sql_db.Po
sql/.deps/sql_delete.Po
sql/.deps/sql_truncate.Po
sql/.deps/datadict.Po
sql/.deps/sql_derived.Po
sql/.deps/sql_do.Po
sql/.deps/sql_error.Po
......
......@@ -63,7 +63,8 @@ SET(SQL_EMBEDDED_SOURCES emb_qcache.cc libmysqld.c lib_sql.cc
../sql/sql_class.cc ../sql/sql_crypt.cc ../sql/sql_cursor.cc
../sql/sql_db.cc ../sql/sql_delete.cc ../sql/sql_derived.cc
../sql/sql_do.cc ../sql/sql_error.cc ../sql/sql_handler.cc
../sql/sql_help.cc ../sql/sql_insert.cc
../sql/sql_help.cc ../sql/sql_insert.cc ../sql/datadict.cc
../sql/sql_truncate.cc
../sql/sql_lex.cc ../sql/keycaches.cc
../sql/sql_list.cc ../sql/sql_load.cc ../sql/sql_locale.cc
../sql/sql_binlog.cc ../sql/sql_manager.cc ../sql/sql_map.cc
......
......@@ -63,7 +63,7 @@ sqlsources = derror.cc field.cc field_conv.cc strfunc.cc filesort.cc \
protocol.cc net_serv.cc opt_range.cc \
opt_sum.cc procedure.cc records.cc sql_acl.cc \
sql_load.cc discover.cc sql_locale.cc \
sql_profile.cc \
sql_profile.cc sql_truncate.cc datadict.cc \
sql_analyse.cc sql_base.cc sql_cache.cc sql_class.cc \
sql_crypt.cc sql_db.cc sql_delete.cc sql_error.cc sql_insert.cc \
sql_lex.cc sql_list.cc sql_manager.cc sql_map.cc \
......
......@@ -25,3 +25,44 @@ TRUNCATE TABLE t2;
source include/show_binlog_events.inc;
DROP TABLE t1,t2;
--echo #
--echo # Bug#42643: InnoDB does not support replication of TRUNCATE TABLE
--echo #
eval CREATE TABLE t1 (a INT) ENGINE=$engine;
eval CREATE TABLE t2 (a INT) ENGINE=$engine;
INSERT INTO t1 VALUES (1),(2);
let $binlog_start = query_get_value("SHOW MASTER STATUS", Position, 1);
if (`select length('$before_truncate') > 0`) {
eval $before_truncate;
}
--echo # Connection: default
BEGIN;
INSERT INTO t2 SELECT * FROM t1;
connect (truncate,localhost,root,,);
--echo # Connection: truncate
send TRUNCATE TABLE t1;
connection default;
--echo # Connection: default
INSERT INTO t2 SELECT * FROM t1;
SELECT COUNT(*) FROM t2;
COMMIT;
connection truncate;
--echo # Connection: truncate
--echo # Reaping TRUNCATE TABLE
--reap
SELECT COUNT(*) FROM t1;
SELECT COUNT(*) FROM t2;
connection default;
--echo # Connection: default
source include/show_binlog_events.inc;
disconnect truncate;
DROP TABLE t1,t2;
......@@ -1351,6 +1351,13 @@ connection con1;
SELECT * FROM t1;
ROLLBACK;
--echo # Switch to connection con2
connection con2;
ROLLBACK;
--echo # Switch to connection con1
connection con1;
--echo # 2. test for serialized update:
CREATE TABLE t2 (a INT);
......@@ -1435,6 +1442,7 @@ connection con2;
--reap
SELECT * FROM t1;
--enable_abort_on_error
connection default;
disconnect con1;
disconnect con2;
......@@ -1556,3 +1564,36 @@ SELECT 1 FROM (SELECT COUNT(DISTINCT c1)
DROP TABLE t1;
--echo End of 5.1 tests
--echo #
--echo # Bug#42643: InnoDB does not support replication of TRUNCATE TABLE
--echo #
--echo # Check that a TRUNCATE TABLE statement, needing an exclusive meta
--echo # data lock, waits for a shared metadata lock owned by a concurrent
--echo # transaction.
--echo #
eval CREATE TABLE t1 (a INT) ENGINE=$engine_type;
INSERT INTO t1 VALUES (1),(2),(3);
BEGIN;
SELECT * FROM t1 ORDER BY a;
--echo # Connection con1
connect (con1, localhost, root,,);
--send TRUNCATE TABLE t1;
--echo # Connection default
connection default;
let $wait_condition= SELECT COUNT(*)=1 FROM information_schema.processlist
WHERE state='Waiting for table' AND info='TRUNCATE TABLE t1';
--source include/wait_condition.inc
SELECT * FROM t1 ORDER BY a;
ROLLBACK;
--echo # Connection con1
connection con1;
--echo # Reaping TRUNCATE TABLE
--reap
SELECT * FROM t1;
--echo # Disconnect con1
disconnect con1;
--echo # Connection default
connection default;
DROP TABLE t1;
SET storage_engine=InnoDB;
INSERT INTO bug38231 VALUES (1), (10), (300);
SET autocommit=0;
SELECT * FROM bug38231 FOR UPDATE;
a
1
10
300
TRUNCATE TABLE bug38231;
COMMIT;
DROP TABLE bug38231;
......@@ -1590,6 +1590,9 @@ SELECT * FROM t1;
a b
1 12
ROLLBACK;
# Switch to connection con2
ROLLBACK;
# Switch to connection con1
# 2. test for serialized update:
CREATE TABLE t2 (a INT);
TRUNCATE t1;
......@@ -1764,6 +1767,37 @@ id select_type table type possible_keys key key_len ref rows Extra
2 DERIVED t1 index c3,c2 c2 14 NULL 5
DROP TABLE t1;
End of 5.1 tests
#
# Bug#42643: InnoDB does not support replication of TRUNCATE TABLE
#
# Check that a TRUNCATE TABLE statement, needing an exclusive meta
# data lock, waits for a shared metadata lock owned by a concurrent
# transaction.
#
CREATE TABLE t1 (a INT) ENGINE=InnoDB;
INSERT INTO t1 VALUES (1),(2),(3);
BEGIN;
SELECT * FROM t1 ORDER BY a;
a
1
2
3
# Connection con1
TRUNCATE TABLE t1;;
# Connection default
SELECT * FROM t1 ORDER BY a;
a
1
2
3
ROLLBACK;
# Connection con1
# Reaping TRUNCATE TABLE
SELECT * FROM t1;
a
# Disconnect con1
# Connection default
DROP TABLE t1;
drop table if exists t1, t2, t3;
create table t1(a int);
insert into t1 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
......
......@@ -2381,3 +2381,42 @@ commit;
# Reap ALTER TABLE.
set debug_sync= 'RESET';
drop table t1;
#
# Bug#42643: InnoDB does not support replication of TRUNCATE TABLE
#
# Ensure that a acquired lock is not given up due to a conflict.
#
DROP TABLE IF EXISTS t1;
CREATE TABLE t1 (a INT) ENGINE=InnoDB;
INSERT INTO t1 VALUES (1),(2),(3);
# Connection: con1
SET debug_sync='lock_table_for_truncate SIGNAL parked_truncate WAIT_FOR go_truncate';
TRUNCATE TABLE t1;
# Connection: default
SET debug_sync='now WAIT_FOR parked_truncate';
# Connection: con2
SET debug_sync='after_open_table_ignore_flush SIGNAL parked_show WAIT_FOR go_show';
SHOW FIELDS FROM t1;
# Connection: default
SET debug_sync='now WAIT_FOR parked_show';
# Connection: con3
SET debug_sync='after_flush_unlock SIGNAL parked_flush WAIT_FOR go_flush';
FLUSH TABLES t1;
# Connection: default
SET debug_sync='now WAIT_FOR parked_flush';
SET debug_sync='now SIGNAL go_truncate';
# Connection: con1
# Reaping...
# Connection: default
SET debug_sync= 'now SIGNAL go_show';
# Connection: con2 (SHOW FIELDS FROM t1)
# Reaping...
Field Type Null Key Default Extra
a int(11) YES NULL
# Connection: default
SET debug_sync= 'now SIGNAL go_flush';
# Connection: con3 (FLUSH TABLES t1)
# Reaping...
# Connection: default
SET debug_sync= 'RESET';
DROP TABLE t1;
......@@ -64,6 +64,7 @@ a b
# Switch to connection con2
UPDATE t1 SET b = 21 WHERE a = 1;
ERROR HY000: Lock wait timeout exceeded; try restarting transaction
ROLLBACK;
# Switch to connection con1
SELECT * FROM t1;
a b
......@@ -99,6 +100,7 @@ a b
SELECT * FROM t1;
a b
1 init+con1+con2
COMMIT;
# Switch to connection con1
# 3. test for updated key column:
TRUNCATE t1;
......
......@@ -99,7 +99,7 @@ LOCK TABLE t1 WRITE;
SELECT * FROM v1;
ERROR HY000: Table 'v1' was not locked with LOCK TABLES
TRUNCATE v1;
ERROR 42S02: Table 'test.v1' doesn't exist
ERROR HY000: Table 'v1' was not locked with LOCK TABLES
SELECT * FROM v1;
ERROR HY000: Table 'v1' was not locked with LOCK TABLES
UNLOCK TABLES;
......@@ -107,7 +107,7 @@ LOCK TABLE t1 WRITE, t2 WRITE;
SELECT * FROM v1;
ERROR HY000: Table 'v1' was not locked with LOCK TABLES
TRUNCATE v1;
ERROR 42S02: Table 'test.v1' doesn't exist
ERROR HY000: Table 'v1' was not locked with LOCK TABLES
SELECT * FROM v1;
ERROR HY000: Table 'v1' was not locked with LOCK TABLES
UNLOCK TABLES;
......@@ -117,7 +117,7 @@ c1
1
3
TRUNCATE v1;
ERROR 42S02: Table 'test.v1' doesn't exist
ERROR HY000: Table 'v1' was not locked with LOCK TABLES
SELECT * FROM v1;
c1
1
......@@ -129,7 +129,7 @@ c1
1
3
TRUNCATE v1;
ERROR 42S02: Table 'test.v1' doesn't exist
ERROR HY000: Table 'v1' was not locked with LOCK TABLES
SELECT * FROM v1;
c1
1
......
SET @old_binlog_format=@@binlog_format;
SET BINLOG_FORMAT=ROW;
RESET MASTER;
CREATE TABLE t1 (a INT) ENGINE=MyISAM;
CREATE TABLE t2 (a INT) ENGINE=MyISAM;
......@@ -10,3 +12,91 @@ Log_name Pos Event_type Server_id End_log_pos Info
master-bin.000001 # Query # # use `test`; TRUNCATE TABLE t1
master-bin.000001 # Query # # use `test`; TRUNCATE TABLE t2
DROP TABLE t1,t2;
#
# Bug#42643: InnoDB does not support replication of TRUNCATE TABLE
#
CREATE TABLE t1 (a INT) ENGINE=MyISAM;
CREATE TABLE t2 (a INT) ENGINE=MyISAM;
INSERT INTO t1 VALUES (1),(2);
# Connection: default
BEGIN;
INSERT INTO t2 SELECT * FROM t1;
# Connection: truncate
TRUNCATE TABLE t1;
# Connection: default
INSERT INTO t2 SELECT * FROM t1;
SELECT COUNT(*) FROM t2;
COUNT(*)
4
COMMIT;
# Connection: truncate
# Reaping TRUNCATE TABLE
SELECT COUNT(*) FROM t1;
COUNT(*)
0
SELECT COUNT(*) FROM t2;
COUNT(*)
4
# Connection: default
show binlog events from <binlog_start>;
Log_name Pos Event_type Server_id End_log_pos Info
master-bin.000001 # Query # # BEGIN
master-bin.000001 # Table_map # # table_id: # (test.t2)
master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
master-bin.000001 # Query # # COMMIT
master-bin.000001 # Query # # BEGIN
master-bin.000001 # Table_map # # table_id: # (test.t2)
master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
master-bin.000001 # Query # # COMMIT
master-bin.000001 # Query # # use `test`; TRUNCATE TABLE t1
DROP TABLE t1,t2;
SET BINLOG_FORMAT=STATEMENT;
RESET MASTER;
CREATE TABLE t1 (a INT) ENGINE=MyISAM;
CREATE TABLE t2 (a INT) ENGINE=MyISAM;
INSERT INTO t2 VALUES (1),(2),(3);
**** Truncate of empty table shall be logged
TRUNCATE TABLE t1;
TRUNCATE TABLE t2;
show binlog events from <binlog_start>;
Log_name Pos Event_type Server_id End_log_pos Info
master-bin.000001 # Query # # use `test`; TRUNCATE TABLE t1
master-bin.000001 # Query # # use `test`; TRUNCATE TABLE t2
DROP TABLE t1,t2;
#
# Bug#42643: InnoDB does not support replication of TRUNCATE TABLE
#
CREATE TABLE t1 (a INT) ENGINE=MyISAM;
CREATE TABLE t2 (a INT) ENGINE=MyISAM;
INSERT INTO t1 VALUES (1),(2);
# Connection: default
BEGIN;
INSERT INTO t2 SELECT * FROM t1;
# Connection: truncate
TRUNCATE TABLE t1;
# Connection: default
INSERT INTO t2 SELECT * FROM t1;
SELECT COUNT(*) FROM t2;
COUNT(*)
4
COMMIT;
# Connection: truncate
# Reaping TRUNCATE TABLE
SELECT COUNT(*) FROM t1;
COUNT(*)
0
SELECT COUNT(*) FROM t2;
COUNT(*)
4
# Connection: default
show binlog events from <binlog_start>;
Log_name Pos Event_type Server_id End_log_pos Info
master-bin.000001 # Query # # BEGIN
master-bin.000001 # Query # # use `test`; INSERT INTO t2 SELECT * FROM t1
master-bin.000001 # Query # # COMMIT
master-bin.000001 # Query # # BEGIN
master-bin.000001 # Query # # use `test`; INSERT INTO t2 SELECT * FROM t1
master-bin.000001 # Query # # COMMIT
master-bin.000001 # Query # # use `test`; TRUNCATE TABLE t1
DROP TABLE t1,t2;
SET BINLOG_FORMAT=@old_binlog_format;
source include/have_log_bin.inc;
source include/have_innodb.inc;
# It is necessary to reset the master since otherwise the binlog test
# might show the wrong binary log. The default for SHOW BINLOG EVENTS
# is to show the first binary log, not the current one (which is
# actually a better idea).
let $engine = InnoDB;
SET @old_binlog_format=@@binlog_format;
SET BINLOG_FORMAT=ROW;
RESET MASTER;
let $engine = InnoDB;
source extra/binlog_tests/binlog_truncate.test;
# Under transaction isolation level READ UNCOMMITTED and READ
# COMMITTED, InnoDB does not permit statement-based replication of
# row-deleting statement. In these cases, TRUNCATE TABLE should still
# be replicated as a statement.
--echo # Even though the isolation level might be permissive, truncate
--echo # table follows a stricter isolation as its locking is based on
--echo # (exclusive) metadata locks.
let $before_truncate = SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
source extra/binlog_tests/binlog_truncate.test;
......@@ -27,3 +25,20 @@ source extra/binlog_tests/binlog_truncate.test;
let $before_truncate = SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
source extra/binlog_tests/binlog_truncate.test;
SET BINLOG_FORMAT=STATEMENT;
RESET MASTER;
source extra/binlog_tests/binlog_truncate.test;
--echo # Truncate is not supported for SBR if the isolation level is
--echo # READ UNCOMMITTED or READ COMMITTED. These specific isolation
--echo # levels are tested elsewhere.
let $before_truncate = SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
source extra/binlog_tests/binlog_truncate.test;
let $before_truncate = SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
source extra/binlog_tests/binlog_truncate.test;
SET BINLOG_FORMAT=@old_binlog_format;
source include/have_log_bin.inc;
# It is necessary to reset the master since otherwise the binlog test
# might show the wrong binary log. The default for SHOW BINLOG EVENTS
# is to show the first binary log, not the current one (which is
# actually a better idea).
SET @old_binlog_format=@@binlog_format;
let $engine = MyISAM;
SET BINLOG_FORMAT=ROW;
RESET MASTER;
source extra/binlog_tests/binlog_truncate.test;
SET BINLOG_FORMAT=STATEMENT;
RESET MASTER;
let $engine = MyISAM;
source extra/binlog_tests/binlog_truncate.test;
SET BINLOG_FORMAT=@old_binlog_format;
......@@ -9,6 +9,5 @@
# Do not use any TAB characters for whitespace.
#
##############################################################################
binlog_truncate_innodb : BUG#42643 2009-02-06 mats Changes to InnoDB requires to complete fix for BUG#36763
binlog_unsafe : BUG#50312 2010-01-13 lsoares Warnings for unsafe sub-statement not returned to client
......@@ -49,27 +49,9 @@ UNLOCK TABLES;
-- disconnect con1
-- disconnect con2
# test that TRUNCATE works with with row-level locks
-- enable_query_log
-- enable_result_log
INSERT INTO bug38231 VALUES (1), (10), (300);
-- connect (con4,localhost,root,,)
-- connection con4
SET autocommit=0;
SELECT * FROM bug38231 FOR UPDATE;
-- connection default
TRUNCATE TABLE bug38231;
-- connection con4
COMMIT;
-- connection default
-- disconnect con4
DROP TABLE bug38231;
......@@ -3468,6 +3468,84 @@ connection default;
set debug_sync= 'RESET';
drop table t1;
--echo #
--echo # Bug#42643: InnoDB does not support replication of TRUNCATE TABLE
--echo #
--echo # Ensure that a acquired lock is not given up due to a conflict.
--echo #
connect (con1,localhost,root,,test,,);
connect (con2,localhost,root,,test,,);
connect (con3,localhost,root,,test,,);
connection default;
--disable_warnings
DROP TABLE IF EXISTS t1;
--enable_warnings
CREATE TABLE t1 (a INT) ENGINE=InnoDB;
INSERT INTO t1 VALUES (1),(2),(3);
--echo # Connection: con1
connection con1;
SET debug_sync='lock_table_for_truncate SIGNAL parked_truncate WAIT_FOR go_truncate';
send TRUNCATE TABLE t1;
connection default;
--echo # Connection: default
SET debug_sync='now WAIT_FOR parked_truncate';
connection con2;
--echo # Connection: con2
SET debug_sync='after_open_table_ignore_flush SIGNAL parked_show WAIT_FOR go_show';
send SHOW FIELDS FROM t1;
connection default;
--echo # Connection: default
SET debug_sync='now WAIT_FOR parked_show';
connection con3;
--echo # Connection: con3
SET debug_sync='after_flush_unlock SIGNAL parked_flush WAIT_FOR go_flush';
send FLUSH TABLES t1;
connection default;
--echo # Connection: default
SET debug_sync='now WAIT_FOR parked_flush';
SET debug_sync='now SIGNAL go_truncate';
connection con1;
--echo # Connection: con1
--echo # Reaping...
reap;
connection default;
--echo # Connection: default
SET debug_sync= 'now SIGNAL go_show';
connection con2;
--echo # Connection: con2 (SHOW FIELDS FROM t1)
--echo # Reaping...
reap;
connection default;
--echo # Connection: default
SET debug_sync= 'now SIGNAL go_flush';
connection con3;
--echo # Connection: con3 (FLUSH TABLES t1)
--echo # Reaping...
reap;
disconnect con1;
disconnect con2;
disconnect con3;
connection default;
--echo # Connection: default
SET debug_sync= 'RESET';
DROP TABLE t1;
# Check that all connections opened by test cases in this file are really
# gone so execution of other tests won't be affected by their presence.
......
......@@ -101,6 +101,7 @@ connection con2;
--error ER_LOCK_WAIT_TIMEOUT
UPDATE t1 SET b = 21 WHERE a = 1;
--disable_info
ROLLBACK;
--echo # Switch to connection con1
connection con1;
......@@ -150,6 +151,7 @@ SELECT * FROM t1;
connection con2;
--reap
SELECT * FROM t1;
COMMIT;
--echo # Switch to connection con1
connection con1;
......
......@@ -102,7 +102,7 @@ SELECT * FROM v1;
LOCK TABLE t1 WRITE;
--error ER_TABLE_NOT_LOCKED
SELECT * FROM v1;
--error ER_NO_SUCH_TABLE
--error ER_TABLE_NOT_LOCKED
TRUNCATE v1;
--error ER_TABLE_NOT_LOCKED
SELECT * FROM v1;
......@@ -111,7 +111,7 @@ UNLOCK TABLES;
LOCK TABLE t1 WRITE, t2 WRITE;
--error ER_TABLE_NOT_LOCKED
SELECT * FROM v1;
--error ER_NO_SUCH_TABLE
--error ER_TABLE_NOT_LOCKED
TRUNCATE v1;
--error ER_TABLE_NOT_LOCKED
SELECT * FROM v1;
......@@ -119,14 +119,14 @@ UNLOCK TABLES;
#
LOCK TABLE v1 WRITE;
SELECT * FROM v1;
--error ER_NO_SUCH_TABLE
--error ER_TABLE_NOT_LOCKED
TRUNCATE v1;
SELECT * FROM v1;
UNLOCK TABLES;
#
LOCK TABLE t1 WRITE, t2 WRITE, v1 WRITE;
SELECT * FROM v1;
--error ER_NO_SUCH_TABLE
--error ER_TABLE_NOT_LOCKED
TRUNCATE v1;
SELECT * FROM v1;
UNLOCK TABLES;
......
......@@ -75,7 +75,7 @@ SET (SQL_SOURCE
sql_connect.cc scheduler.cc
sql_profile.cc event_parse_data.cc
sql_signal.cc rpl_handler.cc mdl.cc
transaction.cc sys_vars.cc
transaction.cc sys_vars.cc sql_truncate.cc datadict.cc
${GEN_SOURCES}
${MYSYS_LIBWRAP_SOURCE})
......
......@@ -39,7 +39,9 @@ DTRACEFILES = filesort.o \
sql_connect.o \
sql_cursor.o \
sql_delete.o \
sql_truncate.o \
sql_insert.o \
datadict.o \
sql_parse.o \
sql_prepare.o \
sql_select.o \
......@@ -56,7 +58,9 @@ DTRACEFILES_DEPEND = filesort.o \
sql_connect.o \
sql_cursor.o \
sql_delete.o \
sql_truncate.o \
sql_insert.o \
datadict.o \
sql_parse.o \
sql_prepare.o \
sql_select.o \
......@@ -121,7 +125,8 @@ noinst_HEADERS = item.h item_func.h item_sum.h item_cmpfunc.h \
sql_audit.h \
contributors.h sql_servers.h sql_signal.h records.h \
sql_prepare.h rpl_handler.h replication.h mdl.h \
sql_plist.h transaction.h sys_vars.h
sql_plist.h transaction.h sys_vars.h sql_truncate.h \
datadict.h
mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.cc \
item.cc item_sum.cc item_buff.cc item_func.cc \
......@@ -136,10 +141,10 @@ mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.cc \
sql_connect.cc scheduler.cc sql_parse.cc \
keycaches.cc set_var.cc sql_yacc.yy sys_vars.cc \
sql_base.cc table.cc sql_select.cc sql_insert.cc \
sql_profile.cc \
datadict.cc sql_profile.cc \
sql_prepare.cc sql_error.cc sql_locale.cc \
sql_update.cc sql_delete.cc uniques.cc sql_do.cc \
procedure.cc sql_test.cc \
procedure.cc sql_test.cc sql_truncate.cc \
log.cc init.cc derror.cc sql_acl.cc \
unireg.cc des_key_file.cc \
log_event.cc rpl_record.cc \
......
/* Copyright (c) 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
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
#include "datadict.h"
#include "sql_priv.h"
#include "sql_class.h"
#include "sql_table.h"
/**
Check type of .frm if we are not going to parse it.
@param path path to FRM file
@retval FRMTYPE_ERROR error
@retval FRMTYPE_TABLE table
@retval FRMTYPE_VIEW view
*/
frm_type_enum dd_frm_type(THD *thd, char *path, enum legacy_db_type *dbt)
{
File file;
uchar header[10]; //"TYPE=VIEW\n" it is 10 characters
size_t error;
DBUG_ENTER("dd_frm_type");
*dbt= DB_TYPE_UNKNOWN;
if ((file= mysql_file_open(key_file_frm, path, O_RDONLY | O_SHARE, MYF(0))) < 0)
DBUG_RETURN(FRMTYPE_ERROR);
error= mysql_file_read(file, (uchar*) header, sizeof(header), MYF(MY_NABP));
mysql_file_close(file, MYF(MY_WME));
if (error)
DBUG_RETURN(FRMTYPE_ERROR);
if (!strncmp((char*) header, "TYPE=VIEW\n", sizeof(header)))
DBUG_RETURN(FRMTYPE_VIEW);
/*
This is just a check for DB_TYPE. We'll return default unknown type
if the following test is true (arg #3). This should not have effect
on return value from this function (default FRMTYPE_TABLE)
*/
if (header[0] != (uchar) 254 || header[1] != 1 ||
(header[2] != FRM_VER && header[2] != FRM_VER+1 &&
(header[2] < FRM_VER+3 || header[2] > FRM_VER+4)))
DBUG_RETURN(FRMTYPE_TABLE);
*dbt= (enum legacy_db_type) (uint) *(header + 3);
/* Probably a table. */
DBUG_RETURN(FRMTYPE_TABLE);
}
/**
Given a table name, check if the storage engine for the
table referred by this name supports an option 'flag'.
Return an error if the table does not exist or is not a
base table.
@pre Any metadata lock on the table.
@param[in] thd The current session.
@param[in] db Table schema.
@param[in] table_name Table database.
@param[in] flag The option to check.
@param[out] yes_no The result. Undefined if error.
*/
bool dd_check_storage_engine_flag(THD *thd,
const char *db, const char *table_name,
uint32 flag, bool *yes_no)
{
char path[FN_REFLEN + 1];
enum legacy_db_type db_type;
handlerton *table_type;
LEX_STRING db_name = {(char *) db, strlen(db)};
if (check_db_name(&db_name))
{
my_error(ER_WRONG_DB_NAME, MYF(0), db_name.str);
return TRUE;
}
if (check_table_name(table_name, strlen(table_name)))
{
my_error(ER_WRONG_TABLE_NAME, MYF(0), table_name);
return TRUE;
}
/* There should be at least some lock on the table. */
DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_key::TABLE, db,
table_name, MDL_SHARED));
(void) build_table_filename(path, sizeof(path) - 1, db,
table_name, reg_ext, 0);
dd_frm_type(thd, path, &db_type);
/* Type is unknown if the object is not found or is not a table. */
if (db_type == DB_TYPE_UNKNOWN)
{
my_error(ER_NO_SUCH_TABLE, MYF(0), db, table_name);
return TRUE;
}
table_type= ha_resolve_by_legacy_type(thd, db_type);
*yes_no= ha_check_storage_engine_flag(table_type, flag);
return FALSE;
}
/*
Regenerate a metadata locked table.
@param thd Thread context.
@param db Name of the database to which the table belongs to.
@param name Table name.
@retval FALSE Success.
@retval TRUE Error.
*/
bool dd_recreate_table(THD *thd, const char *db, const char *table_name)
{
bool error= TRUE;
HA_CREATE_INFO create_info;
char path[FN_REFLEN + 1];
DBUG_ENTER("dd_recreate_table");
/* There should be a exclusive metadata lock on the table. */
DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_key::TABLE, db, table_name,
MDL_EXCLUSIVE));
memset(&create_info, 0, sizeof(create_info));
/* Create a path to the table, but without a extension. */
build_table_filename(path, sizeof(path) - 1, db, table_name, "", 0);
/* Attempt to reconstruct the table. */
mysql_mutex_lock(&LOCK_open);
error= ha_create_table(thd, path, db, table_name, &create_info, TRUE);
mysql_mutex_unlock(&LOCK_open);
DBUG_RETURN(error);
}
#ifndef DATADICT_INCLUDED
#define DATADICT_INCLUDED
/* Copyright (c) 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
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
#include "handler.h"
/*
Data dictionary API.
*/
enum frm_type_enum
{
FRMTYPE_ERROR= 0,
FRMTYPE_TABLE,
FRMTYPE_VIEW
};
frm_type_enum dd_frm_type(THD *thd, char *path, enum legacy_db_type *dbt);
bool dd_check_storage_engine_flag(THD *thd,
const char *db, const char *table_name,
uint32 flag,
bool *yes_no);
bool dd_recreate_table(THD *thd, const char *db, const char *table_name);
#endif // DATADICT_INCLUDED
......@@ -27,7 +27,7 @@
#include "sql_show.h" // append_identifier
#include "strfunc.h" // find_type
#include "parse_file.h" // sql_parse_prepare, File_parser
#include "sql_view.h" // mysql_frm_type, mysql_make_view, VIEW_ANY_ACL
#include "sql_view.h" // mysql_make_view, VIEW_ANY_ACL
#include "sql_parse.h" // check_table_access
#include "sql_insert.h" // kill_delayed_threads
#include "sql_acl.h" // *_ACL, check_grant_all_columns,
......@@ -52,6 +52,7 @@
#include <hash.h>
#include "rpl_filter.h"
#include "sql_table.h" // build_table_filename
#include "datadict.h" // dd_frm_type()
#ifdef __WIN__
#include <io.h>
#endif
......@@ -2678,7 +2679,7 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
during prelocking process (in this case in theory we still
should hold shared metadata lock on it).
*/
if (mysql_frm_type(thd, path, &not_used) == FRMTYPE_VIEW)
if (dd_frm_type(thd, path, &not_used) == FRMTYPE_VIEW)
{
if (!tdc_open_view(thd, table_list, alias, key, key_length,
mem_root, 0))
......
......@@ -22,6 +22,7 @@
#ifndef SQL_BITMAP_INCLUDED
#define SQL_BITMAP_INCLUDED
#include <my_sys.h>
#include <my_bitmap.h>
template <uint default_width> class Bitmap
......
This diff is collapsed.
......@@ -27,8 +27,6 @@ typedef struct st_sql_list SQL_LIST;
int mysql_prepare_delete(THD *thd, TABLE_LIST *table_list, Item **conds);
bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds,
SQL_LIST *order, ha_rows rows, ulonglong options,
bool reset_auto_increment);
bool mysql_truncate(THD *thd, TABLE_LIST *table_list, bool dont_send_ok);
SQL_LIST *order, ha_rows rows, ulonglong options);
#endif /* SQL_DELETE_INCLUDED */
......@@ -49,6 +49,7 @@
// mysql_recreate_table,
// mysql_backup_table,
// mysql_restore_table
#include "sql_truncate.h" // mysql_truncate_table
#include "sql_connect.h" // check_user,
// decrease_user_connections,
// thd_init_client_charset, check_mqh,
......@@ -253,7 +254,7 @@ void init_update_queries(void)
sql_command_flags[SQLCOM_ALTER_TABLE]= CF_CHANGES_DATA | CF_WRITE_LOGS_COMMAND |
CF_AUTO_COMMIT_TRANS | CF_PROTECT_AGAINST_GRL;
sql_command_flags[SQLCOM_TRUNCATE]= CF_CHANGES_DATA | CF_WRITE_LOGS_COMMAND |
CF_AUTO_COMMIT_TRANS;
CF_AUTO_COMMIT_TRANS | CF_PROTECT_AGAINST_GRL;
sql_command_flags[SQLCOM_DROP_TABLE]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_LOAD]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE |
CF_PROTECT_AGAINST_GRL;
......@@ -3280,9 +3281,8 @@ case SQLCOM_PREPARE:
ER(ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0));
goto error;
}
if (thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, TRUE))
goto error;
res= mysql_truncate(thd, first_table, 0);
if (! (res= mysql_truncate_table(thd, first_table)))
my_ok(thd);
break;
case SQLCOM_DELETE:
{
......@@ -3295,8 +3295,7 @@ case SQLCOM_PREPARE:
MYSQL_DELETE_START(thd->query());
res = mysql_delete(thd, all_tables, select_lex->where,
&select_lex->order_list,
unit->select_limit_cnt, select_lex->options,
FALSE);
unit->select_limit_cnt, select_lex->options);
MYSQL_DELETE_DONE(res, (ulong) thd->get_row_count_func());
break;
}
......
......@@ -129,11 +129,6 @@ bool check_simple_select();
Item *negate_expression(THD *thd, Item *expr);
bool check_stack_overrun(THD *thd, long margin, uchar *dummy);
bool begin_trans(THD *thd);
bool end_active_trans(THD *thd);
int end_trans(THD *thd, enum enum_mysql_completiontype completion);
/* Variables */
extern const char* any_db;
......
......@@ -29,6 +29,7 @@
// start_waiting_global_read_lock
#include "sql_base.h" // tdc_remove_table
#include "sql_handler.h" // mysql_ha_rm_tables
#include "datadict.h"
static TABLE_LIST *rename_tables(THD *thd, TABLE_LIST *table_list,
bool skip_error);
......@@ -283,7 +284,7 @@ do_rename(THD *thd, TABLE_LIST *ren_table, char *new_db, char *new_table_name,
build_table_filename(name, sizeof(name) - 1,
ren_table->db, old_alias, reg_ext, 0);
frm_type= mysql_frm_type(thd, name, &table_type);
frm_type= dd_frm_type(thd, name, &table_type);
switch (frm_type)
{
case FRMTYPE_TABLE:
......
......@@ -27,7 +27,6 @@
// primary_key_name,
// build_table_filename
#include "repl_failsafe.h"
#include "sql_view.h" // mysql_frm_type
#include "sql_parse.h" // check_access, check_table_access
#include "sql_partition.h" // partition_element
#include "sql_db.h" // check_db_dir_existence, load_db_opt_by_name
......@@ -50,6 +49,8 @@
#endif
#include <my_dir.h>
#include "lock.h" // MYSQL_LOCK_IGNORE_FLUSH
#include "debug_sync.h"
#include "datadict.h" // dd_frm_type()
#define STR_OR_NIL(S) ((S) ? (S) : "<nil>")
......@@ -2959,6 +2960,9 @@ fill_schema_show_cols_or_idxs(THD *thd, TABLE_LIST *tables,
(can_deadlock ?
MYSQL_OPEN_FAIL_ON_MDL_CONFLICT : 0)));
lex->sql_command= save_sql_command;
DEBUG_SYNC(thd, "after_open_table_ignore_flush");
/*
get_all_tables() returns 1 on failure and 0 on success thus
return only these and not the result code of ::process_table()
......@@ -3018,7 +3022,7 @@ static int fill_schema_table_names(THD *thd, TABLE *table,
char path[FN_REFLEN + 1];
(void) build_table_filename(path, sizeof(path) - 1, db_name->str,
table_name->str, reg_ext, 0);
switch (mysql_frm_type(thd, path, &not_used)) {
switch (dd_frm_type(thd, path, &not_used)) {
case FRMTYPE_ERROR:
table->field[3]->store(STRING_WITH_LEN("ERROR"),
system_charset_info);
......
......@@ -27,8 +27,8 @@
// start_waiting_global_read_lock,
// unlock_table_names, mysql_unlock_tables
#include "strfunc.h" // find_type2, find_set
#include "sql_view.h" // mysql_frm_type, view_checksum, mysql_frm_type
#include "sql_delete.h" // mysql_truncate
#include "sql_view.h" // view_checksum
#include "sql_truncate.h" // regenerate_locked_table
#include "sql_partition.h" // mem_alloc_error,
// generate_partition_syntax,
// partition_info
......@@ -52,6 +52,7 @@
#include "sql_show.h"
#include "transaction.h"
#include "keycaches.h"
#include "datadict.h" // dd_frm_type()
#ifdef __WIN__
#include <io.h>
......@@ -2096,7 +2097,7 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists,
((access(path, F_OK) &&
ha_create_table_from_engine(thd, db, alias)) ||
(!drop_view &&
mysql_frm_type(thd, path, &frm_db_type) != FRMTYPE_TABLE)))
dd_frm_type(thd, path, &frm_db_type) != FRMTYPE_TABLE)))
{
// Table was not found on disk and table can't be created from engine
if (if_exists)
......@@ -2116,7 +2117,7 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists,
*/
if (frm_db_type == DB_TYPE_UNKNOWN)
{
mysql_frm_type(thd, path, &frm_db_type);
dd_frm_type(thd, path, &frm_db_type);
DBUG_PRINT("info", ("frm_db_type %d from %s", frm_db_type, path));
}
table_type= ha_resolve_by_legacy_type(thd, frm_db_type);
......@@ -4557,12 +4558,18 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list,
"Failed renaming data file");
goto end;
}
if (mysql_truncate(thd, table_list, 1))
if (dd_recreate_table(thd, table_list->db, table_list->table_name))
{
error= send_check_errmsg(thd, table_list, "repair",
"Failed generating table from .frm file");
goto end;
}
/*
'FALSE' for 'using_transactions' means don't postpone
invalidation till the end of a transaction, but do it
immediately.
*/
query_cache_invalidate3(thd, table_list, FALSE);
if (mysql_file_rename(key_file_misc, tmp, from, MYF(MY_WME)))
{
error= send_check_errmsg(thd, table_list, "repair",
......@@ -6544,7 +6551,7 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
into the main table list, like open_tables does).
This code is wrong and will be removed, please do not copy.
*/
frm_type= mysql_frm_type(thd, new_name_buff, &table_type);
frm_type= dd_frm_type(thd, new_name_buff, &table_type);
/* Rename a view */
/* Sic: there is a race here */
if (frm_type == FRMTYPE_VIEW && !(alter_info->flags & ~ALTER_RENAME))
......
This diff is collapsed.
#ifndef SQL_TRUNCATE_INCLUDED
#define SQL_TRUNCATE_INCLUDED
/* Copyright (c) 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
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
class THD;
class TABLE_LIST;
bool mysql_truncate_table(THD *thd, TABLE_LIST *table_ref);
#endif
......@@ -32,6 +32,7 @@
#include "sp.h"
#include "sp_head.h"
#include "sp_cache.h"
#include "datadict.h" // dd_frm_type()
#define MD5_BUFF_LENGTH 33
......@@ -1663,7 +1664,7 @@ bool mysql_drop_view(THD *thd, TABLE_LIST *views, enum_drop_mode drop_mode)
view->db, view->table_name, reg_ext, 0);
if (access(path, F_OK) ||
FRMTYPE_VIEW != (type= mysql_frm_type(thd, path, &not_used)))
FRMTYPE_VIEW != (type= dd_frm_type(thd, path, &not_used)))
{
char name[FN_REFLEN];
my_snprintf(name, sizeof(name), "%s.%s", view->db, view->table_name);
......@@ -1741,54 +1742,6 @@ bool mysql_drop_view(THD *thd, TABLE_LIST *views, enum_drop_mode drop_mode)
}
/*
Check type of .frm if we are not going to parse it
SYNOPSIS
mysql_frm_type()
path path to file
RETURN
FRMTYPE_ERROR error
FRMTYPE_TABLE table
FRMTYPE_VIEW view
*/
frm_type_enum mysql_frm_type(THD *thd, char *path, enum legacy_db_type *dbt)
{
File file;
uchar header[10]; //"TYPE=VIEW\n" it is 10 characters
size_t error;
DBUG_ENTER("mysql_frm_type");
*dbt= DB_TYPE_UNKNOWN;
if ((file= mysql_file_open(key_file_frm,
path, O_RDONLY | O_SHARE, MYF(0))) < 0)
DBUG_RETURN(FRMTYPE_ERROR);
error= mysql_file_read(file, (uchar*) header, sizeof(header), MYF(MY_NABP));
mysql_file_close(file, MYF(MY_WME));
if (error)
DBUG_RETURN(FRMTYPE_ERROR);
if (!strncmp((char*) header, "TYPE=VIEW\n", sizeof(header)))
DBUG_RETURN(FRMTYPE_VIEW);
/*
This is just a check for DB_TYPE. We'll return default unknown type
if the following test is true (arg #3). This should not have effect
on return value from this function (default FRMTYPE_TABLE)
*/
if (header[0] != (uchar) 254 || header[1] != 1 ||
(header[2] != FRM_VER && header[2] != FRM_VER+1 &&
(header[2] < FRM_VER+3 || header[2] > FRM_VER+4)))
DBUG_RETURN(FRMTYPE_TABLE);
*dbt= (enum legacy_db_type) (uint) *(header + 3);
DBUG_RETURN(FRMTYPE_TABLE); // Is probably a .frm table
}
/*
check of key (primary or unique) presence in updatable view
......
......@@ -43,8 +43,6 @@ bool check_key_in_view(THD *thd, TABLE_LIST * view);
bool insert_view_fields(THD *thd, List<Item> *list, TABLE_LIST *view);
frm_type_enum mysql_frm_type(THD *thd, char *path, enum legacy_db_type *dbt);
int view_checksum(THD *thd, TABLE_LIST *view);
extern TYPELIB updatable_views_with_limit_typelib;
......
......@@ -10685,7 +10685,7 @@ opt_delete_option:
;
truncate:
TRUNCATE_SYM opt_table_sym table_name
TRUNCATE_SYM opt_table_sym
{
LEX* lex= Lex;
lex->sql_command= SQLCOM_TRUNCATE;
......@@ -10693,7 +10693,11 @@ truncate:
lex->select_lex.options= 0;
lex->select_lex.sql_cache= SELECT_LEX::SQL_CACHE_UNSPECIFIED;
lex->select_lex.init_order();
YYPS->m_lock_type= TL_WRITE;
YYPS->m_mdl_type= MDL_SHARED_WRITE;
}
table_name
{}
;
opt_table_sym:
......
......@@ -20,6 +20,7 @@
#include "sql_plist.h"
#include "sql_list.h" /* Sql_alloc */
#include "mdl.h"
#include "datadict.h"
#ifndef MYSQL_CLIENT
......@@ -305,14 +306,6 @@ enum tmp_table_type
NO_TMP_TABLE, NON_TRANSACTIONAL_TMP_TABLE, TRANSACTIONAL_TMP_TABLE,
INTERNAL_TMP_TABLE, SYSTEM_TMP_TABLE
};
enum frm_type_enum
{
FRMTYPE_ERROR= 0,
FRMTYPE_TABLE,
FRMTYPE_VIEW
};
enum release_type { RELEASE_NORMAL, RELEASE_WAIT_FOR_DROP };
typedef struct st_filesort_info
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment