WL#3337 (Events new architecture)
Cut number 6. Move code from sql_show.cc to event_db_repository.cc that more belongs to the latter. sql/event_db_repository.cc: move code that works with mysql.event from sql_show.cc to event_db_repository.cc . Route through Event_db_repository's interface which is proxied by class Events. The code relies on a function from sql_show.cc which does the actual storage in the schema table. I think it's better to leave the function there because the structure of I_S.EVENTS is defined in sql_show.cc sql/event_db_repository.h: I_S / SHOW EVENTS handling hooks sql/event_scheduler.cc: use the pointer to db_repository which Event_scheduler already has sql/events.cc: Put a comment to get_instance sql/events.h: callback for I_S (sql_show.cc) sql/sql_show.cc: move code that belongs more to Event_db_repository than to here. Use a callback of class Events. Only 1 function is left here, because it copies data into the actual rows of I_S.EVENTS and belongs to this file. sql/sql_show.h: export this function will be called from event_db_repository.cc
This commit is contained in:
parent
acefb78bc3
commit
8ca78787a5
@ -20,6 +20,7 @@
|
||||
#include "sp_head.h"
|
||||
#include "sp.h"
|
||||
#include "events.h"
|
||||
#include "sql_show.h"
|
||||
|
||||
#define EVEX_DB_FIELD_LEN 64
|
||||
#define EVEX_NAME_FIELD_LEN 64
|
||||
@ -188,8 +189,7 @@ evex_fill_row(THD *thd, TABLE *table, Event_timed *et, my_bool is_update)
|
||||
if (et->expression)
|
||||
{
|
||||
table->field[ET_FIELD_INTERVAL_EXPR]->set_notnull();
|
||||
table->field[ET_FIELD_INTERVAL_EXPR]->
|
||||
store((longlong)et->expression, true);
|
||||
table->field[ET_FIELD_INTERVAL_EXPR]->store((longlong)et->expression, true);
|
||||
|
||||
table->field[ET_FIELD_TRANSIENT_INTERVAL]->set_notnull();
|
||||
/*
|
||||
@ -276,16 +276,181 @@ evex_db_find_event_by_name(THD *thd, const LEX_STRING dbname,
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Performs an index scan of event_table (mysql.event) and fills schema_table.
|
||||
|
||||
Synopsis
|
||||
Event_db_repository::index_read_for_db_for_i_s()
|
||||
thd Thread
|
||||
schema_table The I_S.EVENTS table
|
||||
event_table The event table to use for loading (mysql.event)
|
||||
|
||||
Returns
|
||||
0 OK
|
||||
1 Error
|
||||
*/
|
||||
|
||||
int
|
||||
Event_db_repository::index_read_for_db_for_i_s(THD *thd, TABLE *schema_table,
|
||||
TABLE *event_table, char *db)
|
||||
{
|
||||
int ret=0;
|
||||
CHARSET_INFO *scs= system_charset_info;
|
||||
KEY *key_info;
|
||||
uint key_len;
|
||||
byte *key_buf= NULL;
|
||||
LINT_INIT(key_buf);
|
||||
|
||||
DBUG_ENTER("Event_db_repository::index_read_for_db_for_i_s");
|
||||
|
||||
DBUG_PRINT("info", ("Using prefix scanning on PK"));
|
||||
event_table->file->ha_index_init(0, 1);
|
||||
event_table->field[ET_FIELD_DB]->store(db, strlen(db), scs);
|
||||
key_info= event_table->key_info;
|
||||
key_len= key_info->key_part[0].store_length;
|
||||
|
||||
if (!(key_buf= (byte *)alloc_root(thd->mem_root, key_len)))
|
||||
{
|
||||
ret= 1;
|
||||
/* don't send error, it would be done by sql_alloc_error_handler() */
|
||||
}
|
||||
else
|
||||
{
|
||||
key_copy(key_buf, event_table->record[0], key_info, key_len);
|
||||
if (!(ret= event_table->file->index_read(event_table->record[0], key_buf,
|
||||
key_len, HA_READ_PREFIX)))
|
||||
{
|
||||
DBUG_PRINT("info",("Found rows. Let's retrieve them. ret=%d", ret));
|
||||
do
|
||||
{
|
||||
ret= copy_event_to_schema_table(thd, schema_table, event_table);
|
||||
if (ret == 0)
|
||||
ret= event_table->file->index_next_same(event_table->record[0],
|
||||
key_buf, key_len);
|
||||
} while (ret == 0);
|
||||
}
|
||||
DBUG_PRINT("info", ("Scan finished. ret=%d", ret));
|
||||
}
|
||||
event_table->file->ha_index_end();
|
||||
/* ret is guaranteed to be != 0 */
|
||||
if (ret == HA_ERR_END_OF_FILE || ret == HA_ERR_KEY_NOT_FOUND)
|
||||
DBUG_RETURN(0);
|
||||
DBUG_RETURN(1);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Performs a table scan of event_table (mysql.event) and fills schema_table.
|
||||
|
||||
Synopsis
|
||||
Events_db_repository::table_scan_all_for_i_s()
|
||||
thd Thread
|
||||
schema_table The I_S.EVENTS in memory table
|
||||
event_table The event table to use for loading.
|
||||
|
||||
Returns
|
||||
0 OK
|
||||
1 Error
|
||||
*/
|
||||
|
||||
int
|
||||
Event_db_repository::table_scan_all_for_i_s(THD *thd, TABLE *schema_table,
|
||||
TABLE *event_table)
|
||||
{
|
||||
int ret;
|
||||
READ_RECORD read_record_info;
|
||||
|
||||
DBUG_ENTER("Event_db_repository::table_scan_all_for_i_s");
|
||||
init_read_record(&read_record_info, thd, event_table, NULL, 1, 0);
|
||||
|
||||
/*
|
||||
rr_sequential, in read_record(), returns 137==HA_ERR_END_OF_FILE,
|
||||
but rr_handle_error returns -1 for that reason. Thus, read_record()
|
||||
returns -1 eventually.
|
||||
*/
|
||||
do
|
||||
{
|
||||
ret= read_record_info.read_record(&read_record_info);
|
||||
if (ret == 0)
|
||||
ret= copy_event_to_schema_table(thd, schema_table, event_table);
|
||||
}
|
||||
while (ret == 0);
|
||||
|
||||
DBUG_PRINT("info", ("Scan finished. ret=%d", ret));
|
||||
end_read_record(&read_record_info);
|
||||
|
||||
/* ret is guaranteed to be != 0 */
|
||||
DBUG_RETURN(ret == -1? 0:1);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Fills I_S.EVENTS with data loaded from mysql.event. Also used by
|
||||
SHOW EVENTS
|
||||
|
||||
Synopsis
|
||||
Event_db_repository::fill_schema_events()
|
||||
thd Thread
|
||||
tables The schema table
|
||||
db If not NULL then get events only from this schema
|
||||
|
||||
Returns
|
||||
0 OK
|
||||
1 Error
|
||||
*/
|
||||
|
||||
int
|
||||
Event_db_repository::fill_schema_events(THD *thd, TABLE_LIST *tables, char *db)
|
||||
{
|
||||
TABLE *schema_table= tables->table;
|
||||
TABLE *event_table= NULL;
|
||||
Open_tables_state backup;
|
||||
int ret= 0;
|
||||
|
||||
DBUG_ENTER("Event_db_repository::fill_schema_events");
|
||||
DBUG_PRINT("info",("db=%s", db? db:"(null)"));
|
||||
|
||||
thd->reset_n_backup_open_tables_state(&backup);
|
||||
if (open_event_table(thd, TL_READ, &event_table))
|
||||
{
|
||||
sql_print_error("Table mysql.event is damaged.");
|
||||
thd->restore_backup_open_tables_state(&backup);
|
||||
DBUG_RETURN(1);
|
||||
}
|
||||
|
||||
/*
|
||||
1. SELECT I_S => use table scan. I_S.EVENTS does not guarantee order
|
||||
thus we won't order it. OTOH, SHOW EVENTS will be
|
||||
ordered.
|
||||
2. SHOW EVENTS => PRIMARY KEY with prefix scanning on (db)
|
||||
Reasoning: Events are per schema, therefore a scan over an index
|
||||
will save use from doing a table scan and comparing
|
||||
every single row's `db` with the schema which we show.
|
||||
*/
|
||||
if (db)
|
||||
ret= index_read_for_db_for_i_s(thd, schema_table, event_table, db);
|
||||
else
|
||||
ret= table_scan_all_for_i_s(thd, schema_table, event_table);
|
||||
|
||||
close_thread_tables(thd);
|
||||
thd->restore_backup_open_tables_state(&backup);
|
||||
|
||||
DBUG_PRINT("info", ("Return code=%d", ret));
|
||||
DBUG_RETURN(ret);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Looks for a named event in mysql.event and in case of success returns
|
||||
an object will data loaded from the table.
|
||||
|
||||
SYNOPSIS
|
||||
db_find_event()
|
||||
Event_db_repository::find_event()
|
||||
thd THD
|
||||
name the name of the event to find
|
||||
ett event's data if event is found
|
||||
tbl TABLE object to use when not NULL
|
||||
root On which root to load the event
|
||||
|
||||
NOTES
|
||||
1) Use sp_name for look up, return in **ett if found
|
||||
@ -308,7 +473,7 @@ Event_db_repository::find_event(THD *thd, sp_name *name, Event_timed **ett,
|
||||
|
||||
if (tbl)
|
||||
table= tbl;
|
||||
else if (Events::get_instance()->db_repository.open_event_table(thd, TL_READ, &table))
|
||||
else if (open_event_table(thd, TL_READ, &table))
|
||||
{
|
||||
my_error(ER_EVENT_OPEN_TABLE_FAILED, MYF(0));
|
||||
ret= EVEX_GENERAL_ERROR;
|
||||
|
@ -43,6 +43,17 @@ evex_db_find_event_by_name(THD *thd, const LEX_STRING dbname,
|
||||
const LEX_STRING ev_name,
|
||||
TABLE *table);
|
||||
|
||||
int
|
||||
events_table_index_read_for_db(THD *thd, TABLE *schema_table,
|
||||
TABLE *event_table);
|
||||
|
||||
int
|
||||
events_table_scan_all(THD *thd, TABLE *schema_table, TABLE *event_table);
|
||||
|
||||
int
|
||||
fill_schema_events(THD *thd, TABLE_LIST *tables, COND * /* cond */);
|
||||
|
||||
|
||||
class Event_queue_element;
|
||||
|
||||
class Event_db_repository
|
||||
@ -57,9 +68,6 @@ public:
|
||||
void
|
||||
deinit_repository();
|
||||
|
||||
int
|
||||
open_event_table(THD *thd, enum thr_lock_type lock_type, TABLE **table);
|
||||
|
||||
int
|
||||
create_event(THD *thd, Event_timed *et, my_bool create_if_not,
|
||||
uint *rows_affected);
|
||||
@ -86,11 +94,23 @@ public:
|
||||
|
||||
int
|
||||
find_event_by_name(THD *thd, LEX_STRING db, LEX_STRING name, TABLE *table);
|
||||
private:
|
||||
|
||||
int
|
||||
open_event_table(THD *thd, enum thr_lock_type lock_type, TABLE **table);
|
||||
|
||||
int
|
||||
fill_schema_events(THD *thd, TABLE_LIST *tables, char *db);
|
||||
|
||||
private:
|
||||
int
|
||||
drop_events_by_field(THD *thd, enum enum_events_table_field field,
|
||||
LEX_STRING field_value);
|
||||
int
|
||||
index_read_for_db_for_i_s(THD *thd, TABLE *schema_table, TABLE *event_table,
|
||||
char *db);
|
||||
|
||||
int
|
||||
table_scan_all_for_i_s(THD *thd, TABLE *schema_table, TABLE *event_table);
|
||||
|
||||
MEM_ROOT repo_root;
|
||||
|
||||
|
@ -2159,7 +2159,7 @@ Event_scheduler::load_events_from_db(THD *thd)
|
||||
DBUG_RETURN(EVEX_GENERAL_ERROR);
|
||||
}
|
||||
|
||||
if ((ret= Events::get_instance()->open_event_table(thd, TL_READ, &table)))
|
||||
if ((ret= db_repository->open_event_table(thd, TL_READ, &table)))
|
||||
{
|
||||
sql_print_error("SCHEDULER: Table mysql.event is damaged. Can not open.");
|
||||
DBUG_RETURN(EVEX_OPEN_TABLE_FAILED);
|
||||
|
@ -88,6 +88,15 @@ int sortcmp_lex_string(LEX_STRING s, LEX_STRING t, CHARSET_INFO *cs)
|
||||
(unsigned char *) t.str,t.length, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
Accessor for the singleton instance.
|
||||
|
||||
SYNOPSIS
|
||||
Events::get_instance()
|
||||
|
||||
RETURN VALUE
|
||||
address
|
||||
*/
|
||||
|
||||
Events *
|
||||
Events::get_instance()
|
||||
@ -110,7 +119,7 @@ Events::get_instance()
|
||||
interval - the interval type (for instance YEAR_MONTH)
|
||||
expression - the value in the lowest entity
|
||||
|
||||
RETURNS
|
||||
RETURN VALUE
|
||||
0 - OK
|
||||
1 - Error
|
||||
*/
|
||||
@ -562,6 +571,42 @@ Events::dump_internal_status(THD *thd)
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Proxy for Event_db_repository::fill_schema_events.
|
||||
Callback for I_S from sql_show.cc
|
||||
|
||||
SYNOPSIS
|
||||
Events::fill_schema_events()
|
||||
thd Thread
|
||||
tables The schema table
|
||||
cond Unused
|
||||
|
||||
RETURN VALUE
|
||||
0 OK
|
||||
!0 Error
|
||||
*/
|
||||
|
||||
int
|
||||
Events::fill_schema_events(THD *thd, TABLE_LIST *tables, COND * /* cond */)
|
||||
{
|
||||
char *db= NULL;
|
||||
DBUG_ENTER("Events::fill_schema_events");
|
||||
/*
|
||||
If it's SHOW EVENTS then thd->lex->select_lex.db is guaranteed not to
|
||||
be NULL. Let's do an assert anyway.
|
||||
*/
|
||||
if (thd->lex->orig_sql_command == SQLCOM_SHOW_EVENTS)
|
||||
{
|
||||
DBUG_ASSERT(thd->lex->select_lex.db);
|
||||
if (check_access(thd, EVENT_ACL, thd->lex->select_lex.db, 0, 0, 0,
|
||||
is_schema_db(thd->lex->select_lex.db)))
|
||||
DBUG_RETURN(1);
|
||||
db= thd->lex->select_lex.db;
|
||||
}
|
||||
DBUG_RETURN(get_instance()->db_repository.fill_schema_events(thd, tables, db));
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Inits Events mutexes
|
||||
|
||||
|
@ -90,6 +90,9 @@ public:
|
||||
|
||||
int
|
||||
drop_schema_events(THD *thd, char *db);
|
||||
|
||||
static int
|
||||
fill_schema_events(THD *thd, TABLE_LIST *tables, COND * /* cond */);
|
||||
|
||||
int
|
||||
dump_internal_status(THD *thd);
|
||||
|
181
sql/sql_show.cc
181
sql/sql_show.cc
@ -4160,7 +4160,7 @@ extern LEX_STRING interval_type_to_name[];
|
||||
1 Error
|
||||
*/
|
||||
|
||||
static int
|
||||
int
|
||||
copy_event_to_schema_table(THD *thd, TABLE *sch_table, TABLE *event_table)
|
||||
{
|
||||
const char *wild= thd->lex->wild ? thd->lex->wild->ptr() : NullS;
|
||||
@ -4295,183 +4295,6 @@ copy_event_to_schema_table(THD *thd, TABLE *sch_table, TABLE *event_table)
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Performs an index scan of event_table (mysql.event) and fills schema_table.
|
||||
|
||||
Synopsis
|
||||
events_table_index_read_for_db()
|
||||
thd Thread
|
||||
schema_table The I_S.EVENTS table
|
||||
event_table The event table to use for loading (mysql.event)
|
||||
|
||||
Returns
|
||||
0 OK
|
||||
1 Error
|
||||
*/
|
||||
|
||||
static
|
||||
int events_table_index_read_for_db(THD *thd, TABLE *schema_table,
|
||||
TABLE *event_table)
|
||||
{
|
||||
int ret=0;
|
||||
CHARSET_INFO *scs= system_charset_info;
|
||||
KEY *key_info;
|
||||
uint key_len;
|
||||
byte *key_buf= NULL;
|
||||
LINT_INIT(key_buf);
|
||||
|
||||
DBUG_ENTER("schema_events_do_index_scan");
|
||||
|
||||
DBUG_PRINT("info", ("Using prefix scanning on PK"));
|
||||
event_table->file->ha_index_init(0, 1);
|
||||
event_table->field[ET_FIELD_DB]->
|
||||
store(thd->lex->select_lex.db, strlen(thd->lex->select_lex.db), scs);
|
||||
key_info= event_table->key_info;
|
||||
key_len= key_info->key_part[0].store_length;
|
||||
|
||||
if (!(key_buf= (byte *)alloc_root(thd->mem_root, key_len)))
|
||||
{
|
||||
ret= 1;
|
||||
/* don't send error, it would be done by sql_alloc_error_handler() */
|
||||
}
|
||||
else
|
||||
{
|
||||
key_copy(key_buf, event_table->record[0], key_info, key_len);
|
||||
if (!(ret= event_table->file->index_read(event_table->record[0], key_buf,
|
||||
key_len, HA_READ_PREFIX)))
|
||||
{
|
||||
DBUG_PRINT("info",("Found rows. Let's retrieve them. ret=%d", ret));
|
||||
do
|
||||
{
|
||||
ret= copy_event_to_schema_table(thd, schema_table, event_table);
|
||||
if (ret == 0)
|
||||
ret= event_table->file->index_next_same(event_table->record[0],
|
||||
key_buf, key_len);
|
||||
} while (ret == 0);
|
||||
}
|
||||
DBUG_PRINT("info", ("Scan finished. ret=%d", ret));
|
||||
}
|
||||
event_table->file->ha_index_end();
|
||||
/* ret is guaranteed to be != 0 */
|
||||
if (ret == HA_ERR_END_OF_FILE || ret == HA_ERR_KEY_NOT_FOUND)
|
||||
DBUG_RETURN(0);
|
||||
DBUG_RETURN(1);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Performs a table scan of event_table (mysql.event) and fills schema_table.
|
||||
|
||||
Synopsis
|
||||
events_table_scan_all()
|
||||
thd Thread
|
||||
schema_table The I_S.EVENTS in memory table
|
||||
event_table The event table to use for loading.
|
||||
|
||||
Returns
|
||||
0 OK
|
||||
1 Error
|
||||
*/
|
||||
|
||||
static
|
||||
int events_table_scan_all(THD *thd, TABLE *schema_table,
|
||||
TABLE *event_table)
|
||||
{
|
||||
int ret;
|
||||
READ_RECORD read_record_info;
|
||||
|
||||
DBUG_ENTER("schema_events_do_table_scan");
|
||||
init_read_record(&read_record_info, thd, event_table, NULL, 1, 0);
|
||||
|
||||
/*
|
||||
rr_sequential, in read_record(), returns 137==HA_ERR_END_OF_FILE,
|
||||
but rr_handle_error returns -1 for that reason. Thus, read_record()
|
||||
returns -1 eventually.
|
||||
*/
|
||||
do
|
||||
{
|
||||
ret= read_record_info.read_record(&read_record_info);
|
||||
if (ret == 0)
|
||||
ret= copy_event_to_schema_table(thd, schema_table, event_table);
|
||||
}
|
||||
while (ret == 0);
|
||||
|
||||
DBUG_PRINT("info", ("Scan finished. ret=%d", ret));
|
||||
end_read_record(&read_record_info);
|
||||
|
||||
/* ret is guaranteed to be != 0 */
|
||||
DBUG_RETURN(ret == -1? 0:1);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Fills I_S.EVENTS with data loaded from mysql.event. Also used by
|
||||
SHOW EVENTS
|
||||
|
||||
Synopsis
|
||||
fill_schema_events()
|
||||
thd Thread
|
||||
tables The schema table
|
||||
cond Unused
|
||||
|
||||
Returns
|
||||
0 OK
|
||||
1 Error
|
||||
*/
|
||||
|
||||
int fill_schema_events(THD *thd, TABLE_LIST *tables, COND * /* cond */)
|
||||
{
|
||||
TABLE *schema_table= tables->table;
|
||||
TABLE *event_table= NULL;
|
||||
Open_tables_state backup;
|
||||
int ret= 0;
|
||||
|
||||
DBUG_ENTER("fill_schema_events");
|
||||
/*
|
||||
If it's SHOW EVENTS then thd->lex->select_lex.db is guaranteed not to
|
||||
be NULL. Let's do an assert anyway.
|
||||
*/
|
||||
if (thd->lex->orig_sql_command == SQLCOM_SHOW_EVENTS)
|
||||
{
|
||||
DBUG_ASSERT(thd->lex->select_lex.db);
|
||||
if (check_access(thd, EVENT_ACL, thd->lex->select_lex.db, 0, 0, 0,
|
||||
is_schema_db(thd->lex->select_lex.db)))
|
||||
DBUG_RETURN(1);
|
||||
}
|
||||
|
||||
DBUG_PRINT("info",("db=%s", thd->lex->select_lex.db?
|
||||
thd->lex->select_lex.db:"(null)"));
|
||||
|
||||
thd->reset_n_backup_open_tables_state(&backup);
|
||||
if (Events::get_instance()->open_event_table(thd, TL_READ, &event_table))
|
||||
{
|
||||
sql_print_error("Table mysql.event is damaged.");
|
||||
thd->restore_backup_open_tables_state(&backup);
|
||||
DBUG_RETURN(1);
|
||||
}
|
||||
|
||||
/*
|
||||
1. SELECT I_S => use table scan. I_S.EVENTS does not guarantee order
|
||||
thus we won't order it. OTOH, SHOW EVENTS will be
|
||||
ordered.
|
||||
2. SHOW EVENTS => PRIMARY KEY with prefix scanning on (db)
|
||||
Reasoning: Events are per schema, therefore a scan over an index
|
||||
will save use from doing a table scan and comparing
|
||||
every single row's `db` with the schema which we show.
|
||||
*/
|
||||
if (thd->lex->orig_sql_command == SQLCOM_SHOW_EVENTS)
|
||||
ret= events_table_index_read_for_db(thd, schema_table, event_table);
|
||||
else
|
||||
ret= events_table_scan_all(thd, schema_table, event_table);
|
||||
|
||||
close_thread_tables(thd);
|
||||
thd->restore_backup_open_tables_state(&backup);
|
||||
|
||||
DBUG_PRINT("info", ("Return code=%d", ret));
|
||||
DBUG_RETURN(ret);
|
||||
}
|
||||
|
||||
|
||||
int fill_open_tables(THD *thd, TABLE_LIST *tables, COND *cond)
|
||||
{
|
||||
DBUG_ENTER("fill_open_tables");
|
||||
@ -5568,7 +5391,7 @@ ST_SCHEMA_TABLE schema_tables[]=
|
||||
{"ENGINES", engines_fields_info, create_schema_table,
|
||||
fill_schema_engines, make_old_format, 0, -1, -1, 0},
|
||||
{"EVENTS", events_fields_info, create_schema_table,
|
||||
fill_schema_events, make_old_format, 0, -1, -1, 0},
|
||||
Events::fill_schema_events, make_old_format, 0, -1, -1, 0},
|
||||
{"FILES", files_fields_info, create_schema_table,
|
||||
fill_schema_files, 0, 0, -1, -1, 0},
|
||||
{"KEY_COLUMN_USAGE", key_column_usage_fields_info, create_schema_table,
|
||||
|
@ -14,4 +14,6 @@ int store_create_info(THD *thd, TABLE_LIST *table_list, String *packet,
|
||||
HA_CREATE_INFO *create_info_arg);
|
||||
int view_store_create_info(THD *thd, TABLE_LIST *table, String *buff);
|
||||
|
||||
int copy_event_to_schema_table(THD *thd, TABLE *sch_table, TABLE *event_table);
|
||||
|
||||
#endif /* SQL_SHOW_H */
|
||||
|
Loading…
x
Reference in New Issue
Block a user