Commit 4e7ee166 authored by Marko Mäkelä's avatar Marko Mäkelä

MDEV-18295 IMPORT TABLESPACE fails with instant-altered tables

When importing a tablespace, we must initialize dummy DEFAULT NULL
values for any instantly added columns in order to avoid a debug
assertion failure when PageConverter::update_records() invokes
rec_get_offsets(). Finally, when the operation completes, we must
evict and reload the table definition, so that the correct
default values for instantly added columns will be loaded.

ha_innobase::discard_or_import_tablespace(): On successful
IMPORT TABLESPACE, evict and reload the table definition,
so that btr_cur_instant_init() will load the correct metadata.

PageConverter::update_index_page(): Fill in dummy DEFAULT NULL values
for instantly added columns. These will be replaced upon the
completion of the operation by evicting and reloading the metadata.

row_discard_tablespace(): Invoke dict_table_t::remove_instant().
After DISCARD TABLESPACE, the table is no longer in "instant ALTER"
format, because there is no data file attached.
parent 4eae9eec
set default_storage_engine=innodb;
#
# MDEV-18295 IMPORT TABLESPACE fails with instant-altered tables
#
create table t2 (x int, z int default 41);
alter table t2 discard tablespace;
create table t1 (x int);
insert into t1 values (1);
alter table t1 add z int default 42, algorithm instant;
select * from t1;
x z
1 42
flush tables t1 for export;
unlock tables;
# The metadata has to be updated to instant ADD COLUMN.
alter table t2 import tablespace;
select * from t2;
x z
1 42
insert into t2 set x=2;
select * from t2;
x z
1 42
2 41
alter table t1 discard tablespace;
flush tables t2 for export;
unlock tables;
# Both the metadata and the data file used instant ADD COLUMN.
alter table t1 import tablespace;
select * from t1;
x z
1 42
2 41
drop table t2;
create table t2 select * from t1;
alter table t1 discard tablespace;
flush tables t2 for export;
unlock tables;
# The instant ADD COLUMN has to be removed from the metadata.
alter table t1 import tablespace;
select * from t1;
x z
1 42
2 41
drop table t2;
drop table t1;
--source include/have_innodb.inc
set default_storage_engine=innodb;
--echo #
--echo # MDEV-18295 IMPORT TABLESPACE fails with instant-altered tables
--echo #
create table t2 (x int, z int default 41);
alter table t2 discard tablespace;
create table t1 (x int);
insert into t1 values (1);
alter table t1 add z int default 42, algorithm instant;
select * from t1;
flush tables t1 for export;
--let $MYSQLD_DATADIR= `select @@datadir`
--move_file $MYSQLD_DATADIR/test/t1.cfg $MYSQLD_DATADIR/test/t2.cfg
--copy_file $MYSQLD_DATADIR/test/t1.ibd $MYSQLD_DATADIR/test/t2.ibd
unlock tables;
--echo # The metadata has to be updated to instant ADD COLUMN.
alter table t2 import tablespace;
select * from t2;
insert into t2 set x=2;
select * from t2;
alter table t1 discard tablespace;
flush tables t2 for export;
--move_file $MYSQLD_DATADIR/test/t2.cfg $MYSQLD_DATADIR/test/t1.cfg
--copy_file $MYSQLD_DATADIR/test/t2.ibd $MYSQLD_DATADIR/test/t1.ibd
unlock tables;
--echo # Both the metadata and the data file used instant ADD COLUMN.
alter table t1 import tablespace;
select * from t1;
drop table t2;
create table t2 select * from t1;
alter table t1 discard tablespace;
flush tables t2 for export;
--move_file $MYSQLD_DATADIR/test/t2.cfg $MYSQLD_DATADIR/test/t1.cfg
--copy_file $MYSQLD_DATADIR/test/t2.ibd $MYSQLD_DATADIR/test/t1.ibd
unlock tables;
--echo # The instant ADD COLUMN has to be removed from the metadata.
alter table t1 import tablespace;
select * from t1;
drop table t2;
drop table t1;
...@@ -4,7 +4,7 @@ Copyright (c) 2000, 2018, Oracle and/or its affiliates. All Rights Reserved. ...@@ -4,7 +4,7 @@ Copyright (c) 2000, 2018, Oracle and/or its affiliates. All Rights Reserved.
Copyright (c) 2008, 2009 Google Inc. Copyright (c) 2008, 2009 Google Inc.
Copyright (c) 2009, Percona Inc. Copyright (c) 2009, Percona Inc.
Copyright (c) 2012, Facebook Inc. Copyright (c) 2012, Facebook Inc.
Copyright (c) 2013, 2018, MariaDB Corporation. Copyright (c) 2013, 2019, MariaDB Corporation.
Portions of this file contain modifications contributed and copyrighted by Portions of this file contain modifications contributed and copyrighted by
Google, Inc. Those modifications are gratefully acknowledged and are described Google, Inc. Those modifications are gratefully acknowledged and are described
...@@ -12682,10 +12682,7 @@ ha_innobase::discard_or_import_tablespace( ...@@ -12682,10 +12682,7 @@ ha_innobase::discard_or_import_tablespace(
DBUG_RETURN(HA_ERR_TABLE_READONLY); DBUG_RETURN(HA_ERR_TABLE_READONLY);
} }
dict_table_t* dict_table = m_prebuilt->table; if (m_prebuilt->table->is_temporary()) {
if (dict_table->is_temporary()) {
ib_senderrf( ib_senderrf(
m_prebuilt->trx->mysql_thd, IB_LOG_LEVEL_ERROR, m_prebuilt->trx->mysql_thd, IB_LOG_LEVEL_ERROR,
ER_CANNOT_DISCARD_TEMPORARY_TABLE); ER_CANNOT_DISCARD_TEMPORARY_TABLE);
...@@ -12693,11 +12690,11 @@ ha_innobase::discard_or_import_tablespace( ...@@ -12693,11 +12690,11 @@ ha_innobase::discard_or_import_tablespace(
DBUG_RETURN(HA_ERR_TABLE_NEEDS_UPGRADE); DBUG_RETURN(HA_ERR_TABLE_NEEDS_UPGRADE);
} }
if (dict_table->space == fil_system.sys_space) { if (m_prebuilt->table->space == fil_system.sys_space) {
ib_senderrf( ib_senderrf(
m_prebuilt->trx->mysql_thd, IB_LOG_LEVEL_ERROR, m_prebuilt->trx->mysql_thd, IB_LOG_LEVEL_ERROR,
ER_TABLE_IN_SYSTEM_TABLESPACE, ER_TABLE_IN_SYSTEM_TABLESPACE,
dict_table->name.m_name); m_prebuilt->table->name.m_name);
DBUG_RETURN(HA_ERR_TABLE_NEEDS_UPGRADE); DBUG_RETURN(HA_ERR_TABLE_NEEDS_UPGRADE);
} }
...@@ -12706,7 +12703,7 @@ ha_innobase::discard_or_import_tablespace( ...@@ -12706,7 +12703,7 @@ ha_innobase::discard_or_import_tablespace(
/* Obtain an exclusive lock on the table. */ /* Obtain an exclusive lock on the table. */
dberr_t err = row_mysql_lock_table( dberr_t err = row_mysql_lock_table(
m_prebuilt->trx, dict_table, LOCK_X, m_prebuilt->trx, m_prebuilt->table, LOCK_X,
discard ? "setting table lock for DISCARD TABLESPACE" discard ? "setting table lock for DISCARD TABLESPACE"
: "setting table lock for IMPORT TABLESPACE"); : "setting table lock for IMPORT TABLESPACE");
...@@ -12719,32 +12716,32 @@ ha_innobase::discard_or_import_tablespace( ...@@ -12719,32 +12716,32 @@ ha_innobase::discard_or_import_tablespace(
user may want to set the DISCARD flag in order to IMPORT user may want to set the DISCARD flag in order to IMPORT
a new tablespace. */ a new tablespace. */
if (!dict_table->is_readable()) { if (!m_prebuilt->table->is_readable()) {
ib_senderrf( ib_senderrf(
m_prebuilt->trx->mysql_thd, m_prebuilt->trx->mysql_thd,
IB_LOG_LEVEL_WARN, ER_TABLESPACE_MISSING, IB_LOG_LEVEL_WARN, ER_TABLESPACE_MISSING,
dict_table->name.m_name); m_prebuilt->table->name.m_name);
} }
err = row_discard_tablespace_for_mysql( err = row_discard_tablespace_for_mysql(
dict_table->name.m_name, m_prebuilt->trx); m_prebuilt->table->name.m_name, m_prebuilt->trx);
} else if (dict_table->is_readable()) { } else if (m_prebuilt->table->is_readable()) {
/* Commit the transaction in order to /* Commit the transaction in order to
release the table lock. */ release the table lock. */
trx_commit_for_mysql(m_prebuilt->trx); trx_commit_for_mysql(m_prebuilt->trx);
ib::error() << "Unable to import tablespace " ib::error() << "Unable to import tablespace "
<< dict_table->name << " because it already" << m_prebuilt->table->name << " because it already"
" exists. Please DISCARD the tablespace" " exists. Please DISCARD the tablespace"
" before IMPORT."; " before IMPORT.";
ib_senderrf( ib_senderrf(
m_prebuilt->trx->mysql_thd, IB_LOG_LEVEL_ERROR, m_prebuilt->trx->mysql_thd, IB_LOG_LEVEL_ERROR,
ER_TABLESPACE_EXISTS, dict_table->name.m_name); ER_TABLESPACE_EXISTS, m_prebuilt->table->name.m_name);
DBUG_RETURN(HA_ERR_TABLE_EXIST); DBUG_RETURN(HA_ERR_TABLE_EXIST);
} else { } else {
err = row_import_for_mysql(dict_table, m_prebuilt); err = row_import_for_mysql(m_prebuilt->table, m_prebuilt);
if (err == DB_SUCCESS) { if (err == DB_SUCCESS) {
...@@ -12760,12 +12757,35 @@ ha_innobase::discard_or_import_tablespace( ...@@ -12760,12 +12757,35 @@ ha_innobase::discard_or_import_tablespace(
/* Commit the transaction in order to release the table lock. */ /* Commit the transaction in order to release the table lock. */
trx_commit_for_mysql(m_prebuilt->trx); trx_commit_for_mysql(m_prebuilt->trx);
if (err == DB_SUCCESS && !discard if (discard || err != DB_SUCCESS) {
&& dict_stats_is_persistent_enabled(dict_table)) { DBUG_RETURN(convert_error_code_to_mysql(
err, m_prebuilt->table->flags, NULL));
}
/* Evict and reload the table definition in order to invoke
btr_cur_instant_init(). */
table_id_t id = m_prebuilt->table->id;
ut_ad(id);
mutex_enter(&dict_sys->mutex);
dict_table_close(m_prebuilt->table, TRUE, FALSE);
dict_table_remove_from_cache(m_prebuilt->table);
m_prebuilt->table = dict_table_open_on_id(id, TRUE,
DICT_TABLE_OP_NORMAL);
mutex_exit(&dict_sys->mutex);
if (!m_prebuilt->table) {
err = DB_TABLE_NOT_FOUND;
} else {
if (const Field* ai = table->found_next_number_field) {
initialize_auto_increment(m_prebuilt->table, ai);
}
dict_stats_init(m_prebuilt->table);
}
if (dict_stats_is_persistent_enabled(m_prebuilt->table)) {
dberr_t ret; dberr_t ret;
/* Adjust the persistent statistics. */ /* Adjust the persistent statistics. */
ret = dict_stats_update(dict_table, ret = dict_stats_update(m_prebuilt->table,
DICT_STATS_RECALC_PERSISTENT); DICT_STATS_RECALC_PERSISTENT);
if (ret != DB_SUCCESS) { if (ret != DB_SUCCESS) {
...@@ -12775,11 +12795,12 @@ ha_innobase::discard_or_import_tablespace( ...@@ -12775,11 +12795,12 @@ ha_innobase::discard_or_import_tablespace(
ER_ALTER_INFO, ER_ALTER_INFO,
"Error updating stats for table '%s'" "Error updating stats for table '%s'"
" after table rebuild: %s", " after table rebuild: %s",
dict_table->name.m_name, ut_strerr(ret)); m_prebuilt->table->name.m_name,
ut_strerr(ret));
} }
} }
DBUG_RETURN(convert_error_code_to_mysql(err, dict_table->flags, NULL)); DBUG_RETURN(0);
} }
/** /**
......
/***************************************************************************** /*****************************************************************************
Copyright (c) 2012, 2016, Oracle and/or its affiliates. All Rights Reserved. Copyright (c) 2012, 2016, Oracle and/or its affiliates. All Rights Reserved.
Copyright (c) 2015, 2018, MariaDB Corporation. Copyright (c) 2015, 2019, MariaDB Corporation.
This program is free software; you can redistribute it and/or modify it under 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 the terms of the GNU General Public License as published by the Free Software
...@@ -1820,14 +1820,23 @@ PageConverter::update_index_page( ...@@ -1820,14 +1820,23 @@ PageConverter::update_index_page(
if (dict_index_is_clust(m_index->m_srv_index)) { if (dict_index_is_clust(m_index->m_srv_index)) {
if (page_is_root(page)) { if (page_is_root(page)) {
dict_index_t* index = const_cast<dict_index_t*>(
m_index->m_srv_index);
/* Preserve the PAGE_ROOT_AUTO_INC. */ /* Preserve the PAGE_ROOT_AUTO_INC. */
if (m_index->m_srv_index->table->supports_instant() if (index->table->supports_instant()) {
&& btr_cur_instant_root_init( if (btr_cur_instant_root_init(index, page)) {
const_cast<dict_index_t*>(
m_index->m_srv_index),
page)) {
return(DB_CORRUPTION); return(DB_CORRUPTION);
} }
/* Provisionally set all instantly
added columns to be DEFAULT NULL. */
for (unsigned i = index->n_core_fields;
i < index->n_fields; i++) {
dict_col_t* col = index->fields[i].col;
col->def_val.len = UNIV_SQL_NULL;
col->def_val.data = NULL;
}
}
} else { } else {
/* Clear PAGE_MAX_TRX_ID so that it can be /* Clear PAGE_MAX_TRX_ID so that it can be
used for other purposes in the future. IMPORT used for other purposes in the future. IMPORT
......
...@@ -3074,13 +3074,14 @@ row_discard_tablespace( ...@@ -3074,13 +3074,14 @@ row_discard_tablespace(
table->flags2 |= DICT_TF2_DISCARDED; table->flags2 |= DICT_TF2_DISCARDED;
dict_table_change_id_in_cache(table, new_id); dict_table_change_id_in_cache(table, new_id);
/* Reset the root page numbers. */ dict_index_t* index = UT_LIST_GET_FIRST(table->indexes);
if (index) index->remove_instant();
for (dict_index_t* index = UT_LIST_GET_FIRST(table->indexes); /* Reset the root page numbers. */
index != 0; for (; index; index = UT_LIST_GET_NEXT(indexes, index)) {
index = UT_LIST_GET_NEXT(indexes, index)) {
index->page = FIL_NULL; index->page = FIL_NULL;
} }
/* If the tablespace did not already exist or we couldn't /* If the tablespace did not already exist or we couldn't
write to it, we treat that as a successful DISCARD. It is write to it, we treat that as a successful DISCARD. It is
unusable anyway. */ unusable anyway. */
......
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