1
0
mirror of https://github.com/MariaDB/server.git synced 2025-07-29 05:21:33 +03:00

Merge 10.2 into 10.3

This commit is contained in:
Marko Mäkelä
2019-09-27 15:56:15 +03:00
16 changed files with 67 additions and 136 deletions

View File

@ -1,9 +1,10 @@
call mtr.add_suppression("Plugin 'file_key_management'"); call mtr.add_suppression("Plugin 'file_key_management'");
call mtr.add_suppression("Plugin 'InnoDB' init function returned error."); call mtr.add_suppression("Plugin 'InnoDB' init function returned error.");
call mtr.add_suppression("InnoDB: The page \\[page id: space=[1-9][0-9]*, page number=[0-9]+\\] in file .*test/t[1-4]\\.ibd cannot be decrypted"); call mtr.add_suppression("InnoDB: The page \\[page id: space=[1-9][0-9]*, page number=[0-9]+\\] in file '.*test/t[1-4]\\.ibd' cannot be decrypted");
call mtr.add_suppression("failed to read or decrypt \\[page id: space=[1-9][0-9]*, page number=[1-9][0-9]*\\]"); call mtr.add_suppression("failed to read or decrypt \\[page id: space=[1-9][0-9]*, page number=[1-9][0-9]*\\]");
call mtr.add_suppression("InnoDB: Unable to decompress .*.test.t1\\.ibd\\[page id: space=[1-9][0-9]*, page number=[0-9]+\\]"); call mtr.add_suppression("InnoDB: Unable to decompress .*.test.t[12]\\.ibd\\[page id: space=[1-9][0-9]*, page number=[0-9]+\\]");
call mtr.add_suppression("InnoDB: Database page corruption on disk or a failed file read of tablespace test/t1 page \\[page id: space=[1-9][0-9]*, page number=[0-9]*\\]"); call mtr.add_suppression("InnoDB: Database page corruption on disk or a failed file read of tablespace test/t[12] page \\[page id: space=[1-9][0-9]*, page number=[0-9]*\\]");
call mtr.add_suppression("InnoDB: Failed to read file '.*' at offset .*");
call mtr.add_suppression("InnoDB: Plugin initialization aborted"); call mtr.add_suppression("InnoDB: Plugin initialization aborted");
call mtr.add_suppression("Plugin 'InnoDB' registration as a STORAGE ENGINE failed"); call mtr.add_suppression("Plugin 'InnoDB' registration as a STORAGE ENGINE failed");
# Restart mysqld --file-key-management-filename=keys2.txt # Restart mysqld --file-key-management-filename=keys2.txt

View File

@ -5,10 +5,11 @@
call mtr.add_suppression("Plugin 'file_key_management'"); call mtr.add_suppression("Plugin 'file_key_management'");
call mtr.add_suppression("Plugin 'InnoDB' init function returned error."); call mtr.add_suppression("Plugin 'InnoDB' init function returned error.");
call mtr.add_suppression("InnoDB: The page \\[page id: space=[1-9][0-9]*, page number=[0-9]+\\] in file .*test/t[1-4]\\.ibd cannot be decrypted"); call mtr.add_suppression("InnoDB: The page \\[page id: space=[1-9][0-9]*, page number=[0-9]+\\] in file '.*test/t[1-4]\\.ibd' cannot be decrypted");
call mtr.add_suppression("failed to read or decrypt \\[page id: space=[1-9][0-9]*, page number=[1-9][0-9]*\\]"); call mtr.add_suppression("failed to read or decrypt \\[page id: space=[1-9][0-9]*, page number=[1-9][0-9]*\\]");
call mtr.add_suppression("InnoDB: Unable to decompress .*.test.t1\\.ibd\\[page id: space=[1-9][0-9]*, page number=[0-9]+\\]"); call mtr.add_suppression("InnoDB: Unable to decompress .*.test.t[12]\\.ibd\\[page id: space=[1-9][0-9]*, page number=[0-9]+\\]");
call mtr.add_suppression("InnoDB: Database page corruption on disk or a failed file read of tablespace test/t1 page \\[page id: space=[1-9][0-9]*, page number=[0-9]*\\]"); call mtr.add_suppression("InnoDB: Database page corruption on disk or a failed file read of tablespace test/t[12] page \\[page id: space=[1-9][0-9]*, page number=[0-9]*\\]");
call mtr.add_suppression("InnoDB: Failed to read file '.*' at offset .*");
call mtr.add_suppression("InnoDB: Plugin initialization aborted"); call mtr.add_suppression("InnoDB: Plugin initialization aborted");
call mtr.add_suppression("Plugin 'InnoDB' registration as a STORAGE ENGINE failed"); call mtr.add_suppression("Plugin 'InnoDB' registration as a STORAGE ENGINE failed");

View File

@ -1,19 +0,0 @@
#
# Bug Bug #27304661 MYSQL CRASH DOING SYNC INDEX ]
# [FATAL] INNODB: SEMAPHORE WAIT HAS LASTED > 600
#
CREATE TABLE t1 (
id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
f1 TEXT(500),
FULLTEXT idx (f1)
) ENGINE=InnoDB;
insert into t1 (f1) values ('fjdhfsjhf'),('dhjfhjshfj'),('dhjafjhfj');
set @save_table_definition_cache=@@global.table_definition_cache;
set @save_table_open_cache=@@global.table_open_cache;
set global table_definition_cache=400;
set global table_open_cache= 1024;
SET @save_dbug = @@GLOBAL.debug_dbug;
SET GLOBAL DEBUG_DBUG="+d,crash_if_fts_table_is_evicted";
set @@global.table_definition_cache=@save_table_definition_cache;
set @@global.table_open_cache=@save_table_open_cache;
drop table t1;

View File

@ -1,49 +0,0 @@
--source include/have_innodb.inc
--source include/have_debug.inc
--source include/big_test.inc
--source include/have_64bit.inc
--echo #
--echo # Bug Bug #27304661 MYSQL CRASH DOING SYNC INDEX ]
--echo # [FATAL] INNODB: SEMAPHORE WAIT HAS LASTED > 600
--echo #
CREATE TABLE t1 (
id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
f1 TEXT(500),
FULLTEXT idx (f1)
) ENGINE=InnoDB;
insert into t1 (f1) values ('fjdhfsjhf'),('dhjfhjshfj'),('dhjafjhfj');
--source include/restart_mysqld.inc
set @save_table_definition_cache=@@global.table_definition_cache;
set @save_table_open_cache=@@global.table_open_cache;
set global table_definition_cache=400;
set global table_open_cache= 1024;
SET @save_dbug = @@GLOBAL.debug_dbug;
SET GLOBAL DEBUG_DBUG="+d,crash_if_fts_table_is_evicted";
#Create 1000 tables, try the best to evict t1 .
--disable_query_log
let $loop=1000;
while($loop)
{
eval create table t_$loop(id int, name text(100), fulltext idxt_$loop(name) )engine=innodb;
dec $loop;
}
let $loop=1000;
while($loop)
{
eval drop table t_$loop;
dec $loop;
}
SET GLOBAL DEBUG_DBUG = @save_dbug;
--enable_query_log
set @@global.table_definition_cache=@save_table_definition_cache;
set @@global.table_open_cache=@save_table_open_cache;
drop table t1;

View File

@ -585,7 +585,6 @@ innodb.xa_recovery : MDEV-15279 - mysqld got exception
#----------------------------------------------------------------------- #-----------------------------------------------------------------------
innodb_fts.fulltext2 : Modified in 10.3.17 innodb_fts.fulltext2 : Modified in 10.3.17
innodb_fts.fulltext_table_evict : Modified in 10.3.18
innodb_fts.innodb_fts_misc : Modified in 10.3.18 innodb_fts.innodb_fts_misc : Modified in 10.3.18
innodb_fts.innodb_fts_misc_debug : MDEV-14156 - Unexpected warning innodb_fts.innodb_fts_misc_debug : MDEV-14156 - Unexpected warning
innodb_fts.innodb_fts_plugin : MDEV-13888 - Errors in server log innodb_fts.innodb_fts_plugin : MDEV-13888 - Errors in server log

View File

@ -345,11 +345,9 @@ static bool convert_const_to_int(THD *thd, Item_field *field_item,
TABLE *table= field->table; TABLE *table= field->table;
sql_mode_t orig_sql_mode= thd->variables.sql_mode; sql_mode_t orig_sql_mode= thd->variables.sql_mode;
enum_check_fields orig_count_cuted_fields= thd->count_cuted_fields; enum_check_fields orig_count_cuted_fields= thd->count_cuted_fields;
my_bitmap_map *old_maps[2]; my_bitmap_map *old_maps[2] = { NULL, NULL };
ulonglong UNINIT_VAR(orig_field_val); /* original field value if valid */ ulonglong UNINIT_VAR(orig_field_val); /* original field value if valid */
LINT_INIT_STRUCT(old_maps);
/* table->read_set may not be set if we come here from a CREATE TABLE */ /* table->read_set may not be set if we come here from a CREATE TABLE */
if (table && table->read_set) if (table && table->read_set)
dbug_tmp_use_all_columns(table, old_maps, dbug_tmp_use_all_columns(table, old_maps,

View File

@ -1,5 +1,5 @@
/* /*
Copyright (c) 2010, 2015, MariaDB Copyright (c) 2010, 2019, MariaDB
This program is free software; you can redistribute it and/or modify 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 it under the terms of the GNU General Public License as published by
@ -92,7 +92,7 @@ class Loose_scan_opt
public: public:
Loose_scan_opt(): Loose_scan_opt():
try_loosescan(FALSE), try_loosescan(false),
bound_sj_equalities(0), bound_sj_equalities(0),
quick_uses_applicable_index(0), quick_uses_applicable_index(0),
quick_max_loose_keypart(0), quick_max_loose_keypart(0),
@ -100,10 +100,11 @@ public:
best_loose_scan_cost(0), best_loose_scan_cost(0),
best_loose_scan_records(0), best_loose_scan_records(0),
best_loose_scan_start_key(NULL), best_loose_scan_start_key(NULL),
best_max_loose_keypart(0) best_max_loose_keypart(0),
best_ref_depend_map(0)
{ {
} }
void init(JOIN *join, JOIN_TAB *s, table_map remaining_tables) void init(JOIN *join, JOIN_TAB *s, table_map remaining_tables)
{ {
/* /*

View File

@ -1156,7 +1156,7 @@ dict_table_open_on_name(
table = dict_table_check_if_in_cache_low(table_name); table = dict_table_check_if_in_cache_low(table_name);
if (table == NULL) { if (table == NULL) {
table = dict_load_table(table_name, true, ignore_err); table = dict_load_table(table_name, ignore_err);
} }
ut_ad(!table || table->cached); ut_ad(!table || table->cached);
@ -1418,14 +1418,7 @@ dict_make_room_in_cache(
prev_table = UT_LIST_GET_PREV(table_LRU, table); prev_table = UT_LIST_GET_PREV(table_LRU, table);
if (dict_table_can_be_evicted(table)) { if (dict_table_can_be_evicted(table)) {
ut_ad(!table->fts);
DBUG_EXECUTE_IF("crash_if_fts_table_is_evicted",
{
if (table->fts &&
dict_table_has_fts_index(table)) {
ut_ad(0);
}
};);
dict_table_remove_from_cache_low(table, TRUE); dict_table_remove_from_cache_low(table, TRUE);
++n_evicted; ++n_evicted;

View File

@ -69,7 +69,6 @@ NULL. These tables must be subsequently loaded so that all the foreign
key constraints are loaded into memory. key constraints are loaded into memory.
@param[in] name Table name in the db/tablename format @param[in] name Table name in the db/tablename format
@param[in] cached true=add to cache, false=do not
@param[in] ignore_err Error to be ignored when loading table @param[in] ignore_err Error to be ignored when loading table
and its index definition and its index definition
@param[out] fk_tables Related table names that must also be @param[out] fk_tables Related table names that must also be
@ -82,7 +81,6 @@ static
dict_table_t* dict_table_t*
dict_load_table_one( dict_load_table_one(
const table_name_t& name, const table_name_t& name,
bool cached,
dict_err_ignore_t ignore_err, dict_err_ignore_t ignore_err,
dict_names_t& fk_tables); dict_names_t& fk_tables);
@ -2731,17 +2729,12 @@ the cluster definition if the table is a member in a cluster. Also loads
all foreign key constraints where the foreign key is in the table or where all foreign key constraints where the foreign key is in the table or where
a foreign key references columns in this table. a foreign key references columns in this table.
@param[in] name Table name in the dbname/tablename format @param[in] name Table name in the dbname/tablename format
@param[in] cached true=add to cache, false=do not
@param[in] ignore_err Error to be ignored when loading @param[in] ignore_err Error to be ignored when loading
table and its index definition table and its index definition
@return table, NULL if does not exist; if the table is stored in an @return table, NULL if does not exist; if the table is stored in an
.ibd file, but the file does not exist, then we set the file_unreadable .ibd file, but the file does not exist, then we set the file_unreadable
flag in the table object we return. */ flag in the table object we return. */
dict_table_t* dict_table_t* dict_load_table(const char* name, dict_err_ignore_t ignore_err)
dict_load_table(
const char* name,
bool cached,
dict_err_ignore_t ignore_err)
{ {
dict_names_t fk_list; dict_names_t fk_list;
dict_table_t* result; dict_table_t* result;
@ -2756,12 +2749,12 @@ dict_load_table(
if (!result) { if (!result) {
result = dict_load_table_one(const_cast<char*>(name), result = dict_load_table_one(const_cast<char*>(name),
cached, ignore_err, fk_list); ignore_err, fk_list);
while (!fk_list.empty()) { while (!fk_list.empty()) {
if (!dict_table_check_if_in_cache_low(fk_list.front())) if (!dict_table_check_if_in_cache_low(fk_list.front()))
dict_load_table_one( dict_load_table_one(
const_cast<char*>(fk_list.front()), const_cast<char*>(fk_list.front()),
cached, ignore_err, fk_list); ignore_err, fk_list);
fk_list.pop_front(); fk_list.pop_front();
} }
} }
@ -2854,7 +2847,6 @@ NULL. These tables must be subsequently loaded so that all the foreign
key constraints are loaded into memory. key constraints are loaded into memory.
@param[in] name Table name in the db/tablename format @param[in] name Table name in the db/tablename format
@param[in] cached true=add to cache, false=do not
@param[in] ignore_err Error to be ignored when loading table @param[in] ignore_err Error to be ignored when loading table
and its index definition and its index definition
@param[out] fk_tables Related table names that must also be @param[out] fk_tables Related table names that must also be
@ -2867,7 +2859,6 @@ static
dict_table_t* dict_table_t*
dict_load_table_one( dict_load_table_one(
const table_name_t& name, const table_name_t& name,
bool cached,
dict_err_ignore_t ignore_err, dict_err_ignore_t ignore_err,
dict_names_t& fk_tables) dict_names_t& fk_tables)
{ {
@ -2956,10 +2947,8 @@ err_exit:
dict_table_add_system_columns(table, heap); dict_table_add_system_columns(table, heap);
if (cached) { table->can_be_evicted = true;
table->can_be_evicted = true; table->add_to_cache();
table->add_to_cache();
}
mem_heap_empty(heap); mem_heap_empty(heap);
@ -2997,7 +2986,7 @@ err_exit:
} }
} }
if (err == DB_SUCCESS && cached && table->is_readable()) { if (err == DB_SUCCESS && table->is_readable()) {
if (table->space && !fil_space_get_size(table->space_id)) { if (table->space && !fil_space_get_size(table->space_id)) {
corrupted: corrupted:
table->corrupted = true; table->corrupted = true;
@ -3042,7 +3031,7 @@ corrupted:
of the error condition, since the user may want to dump data from the of the error condition, since the user may want to dump data from the
clustered index. However we load the foreign key information only if clustered index. However we load the foreign key information only if
all indexes were loaded. */ all indexes were loaded. */
if (!cached || !table->is_readable()) { if (!table->is_readable()) {
/* Don't attempt to load the indexes from disk. */ /* Don't attempt to load the indexes from disk. */
} else if (err == DB_SUCCESS) { } else if (err == DB_SUCCESS) {
err = dict_load_foreigns(table->name.m_name, NULL, err = dict_load_foreigns(table->name.m_name, NULL,
@ -3194,7 +3183,7 @@ check_rec:
/* Load the table definition to memory */ /* Load the table definition to memory */
char* table_name = mem_heap_strdupl( char* table_name = mem_heap_strdupl(
heap, (char*) field, len); heap, (char*) field, len);
table = dict_load_table(table_name, true, ignore_err); table = dict_load_table(table_name, ignore_err);
} }
} }
} }

View File

@ -12449,7 +12449,7 @@ int create_table_info_t::create_table(bool create_fk)
DICT_ERR_IGNORE_NONE, DICT_ERR_IGNORE_NONE,
fk_tables); fk_tables);
while (err == DB_SUCCESS && !fk_tables.empty()) { while (err == DB_SUCCESS && !fk_tables.empty()) {
dict_load_table(fk_tables.front(), true, dict_load_table(fk_tables.front(),
DICT_ERR_IGNORE_NONE); DICT_ERR_IGNORE_NONE);
fk_tables.pop_front(); fk_tables.pop_front();
} }

View File

@ -8212,7 +8212,7 @@ innobase_update_foreign_cache(
also be loaded. */ also be loaded. */
while (err == DB_SUCCESS && !fk_tables.empty()) { while (err == DB_SUCCESS && !fk_tables.empty()) {
dict_table_t* table = dict_load_table( dict_table_t* table = dict_load_table(
fk_tables.front(), true, DICT_ERR_IGNORE_NONE); fk_tables.front(), DICT_ERR_IGNORE_NONE);
if (table == NULL) { if (table == NULL) {
err = DB_TABLE_NOT_FOUND; err = DB_TABLE_NOT_FOUND;

View File

@ -101,17 +101,12 @@ the cluster definition if the table is a member in a cluster. Also loads
all foreign key constraints where the foreign key is in the table or where all foreign key constraints where the foreign key is in the table or where
a foreign key references columns in this table. a foreign key references columns in this table.
@param[in] name Table name in the dbname/tablename format @param[in] name Table name in the dbname/tablename format
@param[in] cached true=add to cache, false=do not
@param[in] ignore_err Error to be ignored when loading @param[in] ignore_err Error to be ignored when loading
table and its index definition table and its index definition
@return table, NULL if does not exist; if the table is stored in an @return table, NULL if does not exist; if the table is stored in an
.ibd file, but the file does not exist, then we set the file_unreadable .ibd file, but the file does not exist, then we set the file_unreadable
flag in the table object we return. */ flag in the table object we return. */
dict_table_t* dict_table_t* dict_load_table(const char* name, dict_err_ignore_t ignore_err);
dict_load_table(
const char* name,
bool cached,
dict_err_ignore_t ignore_err);
/***********************************************************************//** /***********************************************************************//**
Loads a table object based on the table id. Loads a table object based on the table id.

View File

@ -55,7 +55,7 @@ dict_table_get_low(
} }
if (table == NULL) { if (table == NULL) {
table = dict_load_table(table_name, true, DICT_ERR_IGNORE_NONE); table = dict_load_table(table_name, DICT_ERR_IGNORE_NONE);
} }
ut_ad(!table || table->cached); ut_ad(!table || table->cached);

View File

@ -2182,18 +2182,20 @@ void recv_recover_corrupt_page(page_id_t page_id)
mutex_enter(&recv_sys->mutex); mutex_enter(&recv_sys->mutex);
if (!recv_sys->apply_log_recs) { if (!recv_sys->apply_log_recs) {
mutex_exit(&recv_sys->mutex); } else if (recv_addr_t* recv_addr = recv_get_fil_addr_struct(
return; page_id.space(), page_id.page_no())) {
} switch (recv_addr->state) {
case RECV_WILL_NOT_READ:
recv_addr_t* recv_addr = recv_get_fil_addr_struct( ut_ad(!"wrong state");
page_id.space(), page_id.page_no()); break;
case RECV_BEING_PROCESSED:
ut_ad(recv_addr->state != RECV_WILL_NOT_READ); case RECV_PROCESSED:
break;
if (recv_addr->state != RECV_BEING_PROCESSED default:
&& recv_addr->state != RECV_PROCESSED) { recv_addr->state = RECV_PROCESSED;
recv_sys->n_addrs--; ut_ad(recv_sys->n_addrs);
recv_sys->n_addrs--;
}
} }
mutex_exit(&recv_sys->mutex); mutex_exit(&recv_sys->mutex);

View File

@ -2777,7 +2777,7 @@ row_mysql_drop_garbage_tables()
btr_pcur_store_position(&pcur, &mtr); btr_pcur_store_position(&pcur, &mtr);
btr_pcur_commit_specify_mtr(&pcur, &mtr); btr_pcur_commit_specify_mtr(&pcur, &mtr);
if (dict_load_table(table_name, true, if (dict_load_table(table_name,
DICT_ERR_IGNORE_DROP)) { DICT_ERR_IGNORE_DROP)) {
row_drop_table_for_mysql(table_name, trx, row_drop_table_for_mysql(table_name, trx,
SQLCOM_DROP_TABLE); SQLCOM_DROP_TABLE);
@ -3278,7 +3278,7 @@ row_drop_table_from_cache(
dict_table_remove_from_cache(table); dict_table_remove_from_cache(table);
if (dict_load_table(tablename, true, DICT_ERR_IGNORE_FK_NOKEY)) { if (dict_load_table(tablename, DICT_ERR_IGNORE_FK_NOKEY)) {
ib::error() << "Not able to remove table " ib::error() << "Not able to remove table "
<< ut_get_name(trx, tablename) << ut_get_name(trx, tablename)
<< " from the dictionary cache!"; << " from the dictionary cache!";
@ -4599,7 +4599,7 @@ end:
dict_mem_table_fill_foreign_vcol_set(table); dict_mem_table_fill_foreign_vcol_set(table);
while (!fk_tables.empty()) { while (!fk_tables.empty()) {
dict_load_table(fk_tables.front(), true, dict_load_table(fk_tables.front(),
DICT_ERR_IGNORE_NONE); DICT_ERR_IGNORE_NONE);
fk_tables.pop_front(); fk_tables.pop_front();
} }

View File

@ -994,13 +994,22 @@ rw_lock_own(
ut_ad(lock); ut_ad(lock);
ut_ad(rw_lock_validate(lock)); ut_ad(rw_lock_validate(lock));
const os_thread_id_t thread_id = os_thread_get_curr_id();
if (!os_thread_eq(lock->writer_thread, thread_id)) {
} else if (lock_type == RW_LOCK_X && rw_lock_get_x_lock_count(lock)) {
return TRUE;
} else if (lock_type == RW_LOCK_SX && rw_lock_get_sx_lock_count(lock)) {
return TRUE;
}
rw_lock_debug_mutex_enter(); rw_lock_debug_mutex_enter();
for (const rw_lock_debug_t* info = UT_LIST_GET_FIRST(lock->debug_list); for (const rw_lock_debug_t* info = UT_LIST_GET_FIRST(lock->debug_list);
info != NULL; info != NULL;
info = UT_LIST_GET_NEXT(list, info)) { info = UT_LIST_GET_NEXT(list, info)) {
if (os_thread_eq(info->thread_id, os_thread_get_curr_id()) if (os_thread_eq(info->thread_id, thread_id)
&& info->pass == 0 && info->pass == 0
&& info->lock_type == lock_type) { && info->lock_type == lock_type) {
@ -1025,12 +1034,23 @@ bool rw_lock_own_flagged(const rw_lock_t* lock, rw_lock_flags_t flags)
{ {
ut_ad(rw_lock_validate(lock)); ut_ad(rw_lock_validate(lock));
const os_thread_id_t thread_id = os_thread_get_curr_id();
if (!os_thread_eq(lock->writer_thread, thread_id)) {
} else if ((flags & RW_LOCK_FLAG_X)
&& rw_lock_get_x_lock_count(lock)) {
return true;
} else if ((flags & RW_LOCK_FLAG_SX)
&& rw_lock_get_sx_lock_count(lock)) {
return true;
}
rw_lock_debug_mutex_enter(); rw_lock_debug_mutex_enter();
for (rw_lock_debug_t* info = UT_LIST_GET_FIRST(lock->debug_list); for (rw_lock_debug_t* info = UT_LIST_GET_FIRST(lock->debug_list);
info != NULL; info != NULL;
info = UT_LIST_GET_NEXT(list, info)) { info = UT_LIST_GET_NEXT(list, info)) {
if (!os_thread_eq(info->thread_id, os_thread_get_curr_id()) if (!os_thread_eq(info->thread_id, thread_id)
|| info->pass) { || info->pass) {
continue; continue;
} }