Commit e0d348a5 authored by Marko Mäkelä's avatar Marko Mäkelä

Restore the 'default row' on database startup

btr_cur_instant_init_low(): Read and validate the 'default row'

btr_cur_instant_root_init(): Avoid tripping a debug assertion

innobase_add_instant_try(): Use field_ref_zero for empty values.
Tolerate the hidden FTS_DOC_ID column.
parent 569489b9
call mtr.add_suppression("InnoDB: Table `test`\\.`t[15]` (has an unreadable root page|is corrupted)");
call mtr.add_suppression("InnoDB: The page \\[page id: space=[1-9][0-9]*, page number=[1-9][0-9]*\\] in file '.*test.t[15]\\.ibd' cannot be decrypted\\.");
call mtr.add_suppression("Couldn't load plugins from 'file_key_management");
create table t5 (
......@@ -18,8 +19,8 @@ CREATE TABLE `t1` (
insert into t1 values (1,2,'maria','db','encryption');
alter table t1 encrypted='yes' `encryption_key_id`=1;
select * from t1;
ERROR HY000: Got error 192 'Table encrypted but decryption failed. This could be because correct encryption management plugin is not loaded, used encryption key is not available or encryption method does not match.' from InnoDB
ERROR 42S02: Table 'test.t1' doesn't exist in engine
select * from t5;
ERROR HY000: Got error 192 'Table encrypted but decryption failed. This could be because correct encryption management plugin is not loaded, used encryption key is not available or encryption method does not match.' from InnoDB
ERROR 42S02: Table 'test.t5' doesn't exist in engine
drop table t1;
drop table t5;
call mtr.add_suppression("InnoDB: Table `test`\\.`t[13]` (has an unreadable root page|is corrupted)");
call mtr.add_suppression("InnoDB: The page \\[page id: space=[1-9][0-9]*, page number=[1-9][0-9]*\\] in file '.*test.t[123]\\.ibd' cannot be decrypted\\.");
SET GLOBAL innodb_file_per_table = ON;
set global innodb_compression_algorithm = 1;
......@@ -14,10 +15,10 @@ COMMIT;
# Backup tables before corrupting
# Corrupt tables
SELECT * FROM t1;
ERROR HY000: Got error 192 'Table encrypted but decryption failed. This could be because correct encryption management plugin is not loaded, used encryption key is not available or encryption method does not match.' from InnoDB
ERROR 42S02: Table 'test.t1' doesn't exist in engine
SELECT * FROM t2;
ERROR HY000: Got error 192 'Table encrypted but decryption failed. This could be because correct encryption management plugin is not loaded, used encryption key is not available or encryption method does not match.' from InnoDB
SELECT * FROM t3;
ERROR HY000: Got error 192 'Table encrypted but decryption failed. This could be because correct encryption management plugin is not loaded, used encryption key is not available or encryption method does not match.' from InnoDB
ERROR 42S02: Table 'test.t3' doesn't exist in engine
# Restore the original tables
DROP TABLE t1,t2,t3;
call mtr.add_suppression("InnoDB: Table `test`\\.`t1` (has an unreadable root page|is corrupted)");
call mtr.add_suppression("InnoDB: The page \\[page id: space=[1-9][0-9]*, page number=[1-9][0-9]*\\] in file '.*test.t[123]\\.ibd' cannot be decrypted\\.");
# Start server with keys2.txt
CREATE TABLE t1(a int not null primary key auto_increment, b varchar(128)) engine=innodb ENCRYPTED=YES ENCRYPTION_KEY_ID=19;
......@@ -32,11 +33,11 @@ SELECT COUNT(1) FROM t2;
COUNT(1)
2048
SELECT COUNT(1) FROM t2,t1 where t2.a = t1.a;
ERROR HY000: Got error 192 'Table encrypted but decryption failed. This could be because correct encryption management plugin is not loaded, used encryption key is not available or encryption method does not match.' from InnoDB
ERROR 42S02: Table 'test.t1' doesn't exist in engine
SELECT COUNT(1) FROM t1 where b = 'ab';
ERROR HY000: Got error 192 'Table encrypted but decryption failed. This could be because correct encryption management plugin is not loaded, used encryption key is not available or encryption method does not match.' from InnoDB
ERROR 42S02: Table 'test.t1' doesn't exist in engine
SELECT COUNT(1) FROM t1;
ERROR HY000: Got error 192 'Table encrypted but decryption failed. This could be because correct encryption management plugin is not loaded, used encryption key is not available or encryption method does not match.' from InnoDB
ERROR 42S02: Table 'test.t1' doesn't exist in engine
# Start server with keys2.txt
SELECT COUNT(1) FROM t1;
......
......@@ -7,6 +7,7 @@
# MDEV-9559: Server without encryption configs crashes if selecting from an implicitly encrypted table
#
call mtr.add_suppression("InnoDB: Table `test`\\.`t[15]` (has an unreadable root page|is corrupted)");
call mtr.add_suppression("InnoDB: The page \\[page id: space=[1-9][0-9]*, page number=[1-9][0-9]*\\] in file '.*test.t[15]\\.ibd' cannot be decrypted\\.");
# Suppression for builds where file_key_management plugin is linked statically
......@@ -39,9 +40,9 @@ alter table t1 encrypted='yes' `encryption_key_id`=1;
--let $restart_parameters=--innodb-encrypt-tables=OFF
--source include/restart_mysqld.inc
--error ER_GET_ERRMSG
--error ER_NO_SUCH_TABLE_IN_ENGINE
select * from t1;
--error ER_GET_ERRMSG
--error ER_NO_SUCH_TABLE_IN_ENGINE
select * from t5;
--let $restart_parameters=--innodb-encrypt-tables=ON --plugin-load-add=file_key_management.so --file-key-management --file-key-management-filename=$MYSQL_TEST_DIR/std_data/keys2.txt
......
......@@ -7,6 +7,7 @@
# Don't test under embedded
-- source include/not_embedded.inc
call mtr.add_suppression("InnoDB: Table `test`\\.`t[13]` (has an unreadable root page|is corrupted)");
call mtr.add_suppression("InnoDB: The page \\[page id: space=[1-9][0-9]*, page number=[1-9][0-9]*\\] in file '.*test.t[123]\\.ibd' cannot be decrypted\\.");
SET GLOBAL innodb_file_per_table = ON;
......@@ -65,11 +66,11 @@ EOF
--source include/start_mysqld.inc
--error ER_GET_ERRMSG
--error ER_NO_SUCH_TABLE_IN_ENGINE
SELECT * FROM t1;
--error ER_GET_ERRMSG
SELECT * FROM t2;
--error ER_GET_ERRMSG
--error ER_NO_SUCH_TABLE_IN_ENGINE
SELECT * FROM t3;
--source include/shutdown_mysqld.inc
......
......@@ -7,6 +7,7 @@
# MDEV-11004: Unable to start (Segfault or os error 2) when encryption key missing
#
call mtr.add_suppression("InnoDB: Table `test`\\.`t1` (has an unreadable root page|is corrupted)");
call mtr.add_suppression("InnoDB: The page \\[page id: space=[1-9][0-9]*, page number=[1-9][0-9]*\\] in file '.*test.t[123]\\.ibd' cannot be decrypted\\.");
--echo # Start server with keys2.txt
......@@ -42,11 +43,11 @@ CREATE TABLE t4(a int not null primary key auto_increment, b varchar(128)) engin
SELECT SLEEP(5);
SELECT COUNT(1) FROM t3;
SELECT COUNT(1) FROM t2;
--error 1296
--error ER_NO_SUCH_TABLE_IN_ENGINE
SELECT COUNT(1) FROM t2,t1 where t2.a = t1.a;
--error 1296
--error ER_NO_SUCH_TABLE_IN_ENGINE
SELECT COUNT(1) FROM t1 where b = 'ab';
--error 1296
--error ER_NO_SUCH_TABLE_IN_ENGINE
SELECT COUNT(1) FROM t1;
--echo
......
......@@ -409,43 +409,120 @@ btr_cur_instant_init_low(dict_index_t* index, mtr_t* mtr)
ut_ad(index->table->supports_instant());
ut_ad(index->table->is_readable());
if (page_t* root = btr_root_get(index, mtr)) {
if (btr_cur_instant_root_init(index, root)) {
page_t* root = btr_root_get(index, mtr);
if (!root || btr_cur_instant_root_init(index, root)) {
ib::error() << "Table " << index->table->name
<< " has an unreadable root page";
index->table->corrupted = true;
return DB_CORRUPTION;
}
ut_ad(index->n_core_null_bytes != dict_index_t::NO_CORE_NULL_BYTES);
if (!index->is_instant()) {
return DB_SUCCESS;
}
btr_cur_t cur;
dberr_t err = btr_cur_open_at_index_side(true, index, BTR_SEARCH_LEAF,
&cur, 0, mtr);
if (err != DB_SUCCESS) {
index->table->corrupted = true;
return err;
}
ut_ad(page_cur_is_before_first(&cur.page_cur));
ut_ad(page_is_leaf(cur.page_cur.block->frame));
page_cur_move_to_next(&cur.page_cur);
const rec_t* rec = cur.page_cur.rec;
if (page_rec_is_supremum(rec) || !rec_is_default_row(rec, index)) {
ib::error() << "Table " << index->table->name
<< " is missing instant ALTER metadata";
index->table->corrupted = true;
return DB_CORRUPTION;
}
if (dict_table_is_comp(index->table)) {
if (rec_get_info_bits(rec, true) != REC_INFO_MIN_REC_FLAG
&& rec_get_status(rec) != REC_STATUS_COLUMNS_ADDED) {
incompatible:
ib::error() << "Table " << index->table->name
<< " has an unreadable root page";
<< " contains unrecognizable "
"instant ALTER metadata";
index->table->corrupted = true;
return DB_CORRUPTION;
}
ut_ad(index->n_core_null_bytes
!= dict_index_t::NO_CORE_NULL_BYTES);
if (!index->is_instant()) {
return DB_SUCCESS;
}
btr_cur_t cur;
dberr_t err = btr_cur_open_at_index_side(
true, index, BTR_SEARCH_LEAF, &cur, 0, mtr);
if (err != DB_SUCCESS) {
index->table->corrupted = true;
return err;
}
} else if (rec_get_info_bits(rec, false) != REC_INFO_MIN_REC_FLAG) {
goto incompatible;
}
page_cur_set_before_first(cur.page_cur.block, &cur.page_cur);
page_cur_move_to_next(&cur.page_cur);
const rec_t* rec = cur.page_cur.rec;
if (page_rec_is_supremum(rec)
|| rec_get_info_bits(rec, page_rec_is_comp(rec))
!= REC_INFO_MIN_REC_FLAG) {
ib::error() << "Table " << index->table->name
<< " is missing instant ALTER metadata";
index->table->corrupted = true;
return DB_CORRUPTION;
/* Read the 'default row'. We can get here on server restart
or when the table was evicted from the data dictionary cache
and is now being accessed again.
Here, READ COMMITTED and REPEATABLE READ should be equivalent.
Committing the ADD COLUMN operation would acquire
MDL_EXCLUSIVE and LOCK_X|LOCK_TABLE, which would prevent any
concurrent operations on the table, including table eviction
from the cache. */
mem_heap_t* heap = NULL;
ulint* offsets = rec_get_offsets(rec, index, NULL, true,
ULINT_UNDEFINED, &heap);
if (rec_offs_any_default(offsets)) {
inconsistent:
mem_heap_free(heap);
goto incompatible;
}
/* In fact, because we only ever append fields to the 'default
value' record, it is also OK to perform READ UNCOMMITTED and
then ignore any extra fields, provided that
trx_rw_is_active(DB_TRX_ID). */
if (rec_offs_n_fields(offsets) > index->n_fields
&& !trx_rw_is_active(row_get_rec_trx_id(rec, index, offsets),
NULL, false)) {
goto inconsistent;
}
for (unsigned i = index->n_core_fields; i < index->n_fields; i++) {
ulint len;
const byte* data = rec_get_nth_field(rec, offsets, i, &len);
dict_col_t* col = index->fields[i].col;
ut_ad(!col->is_instant());
ut_ad(!col->def_val.data);
col->def_val.len = len;
switch (len) {
case UNIV_SQL_NULL:
continue;
case 0:
col->def_val.data = field_ref_zero;
continue;
}
ut_ad(len != UNIV_SQL_DEFAULT);
if (!rec_offs_nth_extern(offsets, i)) {
col->def_val.data = mem_heap_dup(
index->table->heap, data, len);
} else if (len < BTR_EXTERN_FIELD_REF_SIZE
|| !memcmp(data + len - BTR_EXTERN_FIELD_REF_SIZE,
field_ref_zero,
BTR_EXTERN_FIELD_REF_SIZE)) {
col->def_val.len = UNIV_SQL_DEFAULT;
goto inconsistent;
} else {
/* TODO: read the default values, using
READ COMMITTED isolation level */
col->def_val.data = btr_copy_externally_stored_field(
&col->def_val.len, data,
dict_table_page_size(index->table),
len, index->table->heap);
}
}
return(DB_SUCCESS);
mem_heap_free(heap);
return DB_SUCCESS;
}
/** Load the instant ALTER TABLE metadata from the clustered index
......@@ -500,7 +577,7 @@ btr_cur_instant_root_init(dict_index_t* index, const page_t* page)
}
uint16_t n = page_get_instant(page);
if (n < index->n_uniq + 2 || n > index->n_fields) {
if (n < index->n_uniq + DATA_ROLL_PTR || n > index->n_fields) {
/* The PRIMARY KEY (or hidden DB_ROW_ID) and
DB_TRX_ID,DB_ROLL_PTR columns must always be present
as 'core' fields. All fields, including those for
......@@ -509,9 +586,12 @@ btr_cur_instant_root_init(dict_index_t* index, const page_t* page)
return true;
}
index->n_core_fields = n;
ut_ad(!index->is_dummy);
ut_d(index->is_dummy = true);
index->n_core_null_bytes = n == index->n_fields
? UT_BITS_IN_BYTES(index->n_nullable)
: UT_BITS_IN_BYTES(index->get_n_nullable(n));
ut_d(index->is_dummy = false);
return false;
}
......
......@@ -4268,7 +4268,8 @@ innobase_add_instant_try(
case MYSQL_TYPE_LONG_BLOB:
/* Store the empty string for 'core'
variable-length NOT NULL columns. */
dfield_set_data(dfield, "", 0);
dfield_set_data(dfield,
field_ref_zero, 0);
break;
default:
/* For fixed-length NOT NULL
......@@ -4327,7 +4328,7 @@ innobase_add_instant_try(
if (!new_field->field) {
col->def_val.data = dfield_get_len(dfield)
? dfield_get_data(dfield) : "";
? dfield_get_data(dfield) : field_ref_zero;
col->def_val.len = dfield_get_len(dfield);
pars_info_t* info = pars_info_create();
......@@ -4360,112 +4361,112 @@ innobase_add_instant_try(
}
DBUG_ASSERT(af == &altered_table->field[altered_table->s->fields]);
const ulint n_stored = new_table->n_cols - DATA_N_SYS_COLS;
/* FIXME: account for a hidden FTS_DOC_ID column */
DBUG_ASSERT(i == n_stored);
DBUG_ASSERT(new_table->n_cols == new_table->n_def);
/* There may exist a hidden FTS_DOC_ID column for FULLTEXT INDEX. */
DBUG_ASSERT(DATA_N_SYS_COLS + i == new_table->n_cols
|| (1 + DATA_N_SYS_COLS + i == new_table->n_cols
&& !strcmp(dict_table_get_col_name(new_table, i),
FTS_DOC_ID_COL_NAME)));
i = new_table->n_cols - DATA_N_SYS_COLS;
byte trx_id[DATA_TRX_ID_LEN], roll_ptr[DATA_ROLL_PTR_LEN];
dfield_set_data(dtuple_get_nth_field(row, i++), field_ref_zero,
DATA_ROW_ID_LEN);
dfield_set_data(dtuple_get_nth_field(row, i++), trx_id, sizeof trx_id);
dfield_set_data(dtuple_get_nth_field(row, i),roll_ptr,sizeof roll_ptr);
DBUG_ASSERT(i + 1 == new_table->n_cols);
trx_write_trx_id(trx_id, trx->id);
/* The DB_ROLL_PTR will be assigned later, when allocating undo log.
Silence a Valgrind warning in dtuple_validate() when
row_ins_clust_index_entry_low() searches for the insert position. */
memset(roll_ptr, 0, sizeof roll_ptr);
const ulint n_cols = dict_table_encode_n_col(
n_stored, user_table->n_v_cols)
+ ((user_table->flags & DICT_TF_COMPACT) << 31);
pars_info_t* info = pars_info_create();
pars_info_add_int4_literal(info, "n_cols", n_cols);
pars_info_add_int4_literal(info, "id", user_table->id);
if (innobase_update_n_virtual(user_table,
dict_table_encode_n_col(
new_table->n_cols
- DATA_N_SYS_COLS,
new_table->n_v_cols)
| (user_table->flags & DICT_TF_COMPACT)
<< 31,
trx) != DB_SUCCESS) {
return true;
}
dtuple_t* entry = row_build_index_entry(row, NULL, index, ctx->heap);
entry->info_bits = REC_INFO_DEFAULT_ROW;
dberr_t err = que_eval_sql(
info,
"PROCEDURE N_COLS () IS\n"
"BEGIN\n"
"UPDATE SYS_TABLES SET N_COLS = :n_cols WHERE ID = :id;\n"
"END;\n", FALSE, trx);
if (err == DB_SUCCESS) {
mtr_t mtr;
mtr.start();
mtr.set_named_space(index->space);
btr_cur_t cursor;
btr_cur_open_at_index_side(true, index, BTR_MODIFY_TREE,
&cursor, 0, &mtr);
buf_block_t* block = btr_cur_get_block(&cursor);
rec_t* rec = btr_cur_get_rec(&cursor);
ut_ad(page_rec_is_infimum(rec));
rec = page_rec_get_next(rec);
ut_ad(page_is_leaf(block->frame));
ut_ad(!buf_block_get_page_zip(block));
if (rec_is_default_row(rec, index)) {
ut_ad(page_rec_is_user_rec(rec));
ut_ad(user_table->is_instant());
if (page_rec_is_last(rec, block->frame)) {
goto empty_table;
}
/* FIXME: calculate and apply an update vector
with the instantly added columns (no changes
to key columns!) */
mtr.commit();
return false;
} else if (page_rec_is_supremum(rec)) {
ut_ad(!user_table->is_instant());
mtr_t mtr;
mtr.start();
mtr.set_named_space(index->space);
btr_cur_t cursor;
btr_cur_open_at_index_side(true, index, BTR_MODIFY_TREE, &cursor, 0,
&mtr);
buf_block_t* block = btr_cur_get_block(&cursor);
rec_t* rec = btr_cur_get_rec(&cursor);
ut_ad(page_rec_is_infimum(rec));
rec = page_rec_get_next(rec);
ut_ad(page_is_leaf(block->frame));
ut_ad(!buf_block_get_page_zip(block));
if (rec_is_default_row(rec, index)) {
ut_ad(page_rec_is_user_rec(rec));
ut_ad(user_table->is_instant());
if (page_rec_is_last(rec, block->frame)) {
goto empty_table;
}
/* FIXME: calculate and apply an update vector with
the instantly added columns (no changes to key columns!) */
mtr.commit();
return false;
} else if (page_rec_is_supremum(rec)) {
ut_ad(!user_table->is_instant());
empty_table:
/* The table is empty. */
ut_ad(page_is_root(block->frame));
btr_page_empty(block, NULL, index, 0, &mtr);
index->remove_instant();
/* The table is empty. */
ut_ad(page_is_root(block->frame));
btr_page_empty(block, NULL, index, 0, &mtr);
index->remove_instant();
mtr.commit();
return false;
}
/* Convert the table to the instant ADD COLUMN format. */
ut_ad(!user_table->is_instant());
mtr.commit();
mtr.start();
mtr.set_named_space(index->space);
dberr_t err;
if (page_t* root = btr_root_get(index, &mtr)) {
switch (fil_page_get_type(root)) {
case FIL_PAGE_TYPE_INSTANT:
DBUG_ASSERT(page_get_instant(root)
== index->n_core_fields);
break;
case FIL_PAGE_INDEX:
DBUG_ASSERT(!page_is_comp(root)
|| !page_get_instant(root));
break;
default:
DBUG_ASSERT(!"wrong page type");
mtr.commit();
return false;
return true;
}
/* Convert the table to the instant ADD COLUMN format. */
ut_ad(!user_table->is_instant());
mlog_write_ulint(root + FIL_PAGE_TYPE,
FIL_PAGE_TYPE_INSTANT, MLOG_2BYTES,
&mtr);
page_set_instant(root, index->n_core_fields, &mtr);
mtr.commit();
mtr.start();
mtr.set_named_space(index->space);
if (page_t* root = btr_root_get(index, &mtr)) {
switch (fil_page_get_type(root)) {
case FIL_PAGE_TYPE_INSTANT:
DBUG_ASSERT(page_get_instant(root)
== index->n_core_fields);
break;
case FIL_PAGE_INDEX:
DBUG_ASSERT(!page_is_comp(root)
|| !page_get_instant(root));
break;
default:
DBUG_ASSERT(!"wrong page type");
mtr.commit();
return true;
}
mlog_write_ulint(root + FIL_PAGE_TYPE,
FIL_PAGE_TYPE_INSTANT, MLOG_2BYTES,
&mtr);
page_set_instant(root, index->n_core_fields, &mtr);
mtr.commit();
mtr.start();
mtr.set_named_space(index->space);
err = row_ins_clust_index_entry_low(
BTR_NO_LOCKING_FLAG, BTR_MODIFY_TREE, index,
index->n_uniq, entry, 0, ctx->thr, false);
} else {
err = DB_CORRUPTION;
}
mtr.commit();
err = row_ins_clust_index_entry_low(
BTR_NO_LOCKING_FLAG, BTR_MODIFY_TREE, index,
index->n_uniq, entry, 0, ctx->thr, false);
} else {
err = DB_CORRUPTION;
}
return(err != DB_SUCCESS);
mtr.commit();
return err != DB_SUCCESS;
}
/** Update INNODB SYS_COLUMNS on new virtual column's position
......
......@@ -618,7 +618,7 @@ struct dict_col_t{
/** original default value of instantly added column */
const void* data;
/** len of data, or UNIV_SQL_DEFAULT if unavailable */
unsigned len;
ulint len;
} def_val;
/** Retrieve the column name.
......@@ -1047,7 +1047,7 @@ struct dict_index_t{
unsigned n = n_nullable;
for (; n_prefix < n_fields; n_prefix++) {
const dict_col_t* col = fields[n_prefix].col;
DBUG_ASSERT(col->is_instant());
DBUG_ASSERT(is_dummy || col->is_instant());
n -= col->is_nullable();
}
DBUG_ASSERT(n < n_def);
......
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