Commit db905454 authored by ingo@mysql.com's avatar ingo@mysql.com

Merge mysql.com:/home/mydev/mysql-5.1

into  mysql.com:/home/mydev/mysql-5.1-bug8841
parents b068e845 e9af8c1d
...@@ -147,10 +147,10 @@ static struct my_option my_long_options[] = ...@@ -147,10 +147,10 @@ static struct my_option my_long_options[] =
REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
#include <sslopt-longopts.h> #include <sslopt-longopts.h>
{"use-threads", OPT_USE_THREADS, {"use-threads", OPT_USE_THREADS,
"Parrelize the loading of files. Requires an arguement for the number \ "Load files in parallel. The argument is the number "
threads to use for loading of data.", "of threads to use for loading data.",
(gptr*) &opt_use_threads, (gptr*) &opt_use_threads, 0, (gptr*) &opt_use_threads, (gptr*) &opt_use_threads, 0,
GET_UINT, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, GET_UINT, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
#ifndef DONT_ALLOW_USER_CHANGE #ifndef DONT_ALLOW_USER_CHANGE
{"user", 'u', "User for login if not current user.", (gptr*) &current_user, {"user", 'u', "User for login if not current user.", (gptr*) &current_user,
(gptr*) &current_user, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, (gptr*) &current_user, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
......
...@@ -55,7 +55,7 @@ ...@@ -55,7 +55,7 @@
load statements, and then run all the queries in the query file load statements, and then run all the queries in the query file
with five clients (five times each): with five clients (five times each):
mysqlslap --drop-schema --concurrency=5 \ mysqlslap --concurrency=5 \
--iterations=5 --query=query.sql --create=create.sql \ --iterations=5 --query=query.sql --create=create.sql \
--delimiter=";" --delimiter=";"
...@@ -425,12 +425,12 @@ static struct my_option my_long_options[] = ...@@ -425,12 +425,12 @@ static struct my_option my_long_options[] =
(gptr*) &lock_directory, (gptr*) &lock_directory, 0, GET_STR, (gptr*) &lock_directory, (gptr*) &lock_directory, 0, GET_STR,
REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
{"number-char-cols", 'x', {"number-char-cols", 'x',
"Number of INT columns to create table with if specifying --sql-generate-sql.", "Number of INT columns to create table with if specifying --auto-generate-sql.",
(gptr*) &num_char_cols, (gptr*) &num_char_cols, 0, GET_UINT, REQUIRED_ARG, (gptr*) &num_char_cols, (gptr*) &num_char_cols, 0, GET_UINT, REQUIRED_ARG,
1, 0, 0, 0, 0, 0}, 1, 0, 0, 0, 0, 0},
{"number-int-cols", 'y', {"number-int-cols", 'y',
"Number of VARCHAR columns to create table with if specifying " "Number of VARCHAR columns to create table with if specifying "
"--sql-generate-sql.", (gptr*) &num_int_cols, (gptr*) &num_int_cols, 0, "--auto-generate-sql.", (gptr*) &num_int_cols, (gptr*) &num_int_cols, 0,
GET_UINT, REQUIRED_ARG, 1, 0, 0, 0, 0, 0}, GET_UINT, REQUIRED_ARG, 1, 0, 0, 0, 0, 0},
{"number-of-queries", OPT_MYSQL_NUMBER_OF_QUERY, {"number-of-queries", OPT_MYSQL_NUMBER_OF_QUERY,
"Limit each client to this number of queries (this is not exact).", "Limit each client to this number of queries (this is not exact).",
......
This diff is collapsed.
...@@ -33,6 +33,138 @@ select count(*) from t_event3; ...@@ -33,6 +33,138 @@ select count(*) from t_event3;
drop event event3; drop event event3;
drop table t_event3; drop table t_event3;
set names utf8;
#
# SHOW CREATE EVENT test begin
#
CREATE EVENT root6 ON SCHEDULE EVERY '10:20' MINUTE_SECOND ON COMPLETION PRESERVE ENABLE COMMENT 'some comment' DO select 1;
SHOW CREATE EVENT root6;
create event root7 on schedule every 2 year do select 1;
SHOW CREATE EVENT root7;
create event root8 on schedule every '2:5' year_month do select 1;
SHOW CREATE EVENT root8;
create event root8_1 on schedule every '2:15' year_month do select 1;
SHOW CREATE EVENT root8_1;
create event root9 on schedule every 2 week ON COMPLETION PRESERVE DISABLE COMMENT 'коментар на кирилица' do select 1;
SHOW CREATE EVENT root9;
create event root10 on schedule every '20:5' day_hour do select 1;
SHOW CREATE EVENT root10;
create event root11 on schedule every '20:25' day_hour do select 1;
SHOW CREATE EVENT root11;
create event root12 on schedule every '20:25' hour_minute do select 1;
SHOW CREATE EVENT root12;
create event root13 on schedule every '25:25' hour_minute do select 1;
SHOW CREATE EVENT root13;
create event root13_1 on schedule every '11:65' hour_minute do select 1;
SHOW CREATE EVENT root13_1;
create event root14 on schedule every '35:35' minute_second do select 1;
SHOW CREATE EVENT root14;
create event root15 on schedule every '35:66' minute_second do select 1;
SHOW CREATE EVENT root15;
create event root16 on schedule every '35:56' day_minute do select 1;
SHOW CREATE EVENT root16;
create event root17 on schedule every '35:12:45' day_minute do select 1;
SHOW CREATE EVENT root17;
create event root17_1 on schedule every '35:25:65' day_minute do select 1;
SHOW CREATE EVENT root17_1;
create event root18 on schedule every '35:12:45' hour_second do select 1;
SHOW CREATE EVENT root18;
create event root19 on schedule every '15:59:85' hour_second do select 1;
SHOW CREATE EVENT root19;
create event root20 on schedule every '50:20:12:45' day_second do select 1;
SHOW CREATE EVENT root20;
set names cp1251;
create event ðóóò21 on schedule every '50:23:59:95' day_second COMMENT 'òîâà å 1251 êîìåíòàð' do select 1;
SHOW CREATE EVENT ðóóò21;
insert into mysql.event (db, name, body, definer, interval_value, interval_field) values (database(), "root22", "select 1", user(), 100, "SECOND_MICROSECOND");
--error 1235
show create event root22;
--error 1235
SHOW EVENTS;
drop event root22;
drop event root6;
drop event root7;
drop event root8;
drop event root8_1;
drop event root9;
drop event root10;
drop event root11;
drop event root12;
drop event root13;
drop event root13_1;
drop event root14;
drop event root15;
drop event root16;
drop event root17;
drop event root17_1;
drop event root18;
drop event root19;
drop event root20;
drop event ðóóò21;
set names latin1;
#
# SHOW CREATE EVENT test end
#
#
# mysql.event intact checking start
#
# There should be at least 1 second between the ALTERs or we can't catch the change of create_time!!
#
CREATE EVENT intact_check ON SCHEDULE EVERY 10 HOUR DO SELECT "nothing";
--replace_column 8 # 9 #
SHOW EVENTS;
ALTER TABLE mysql.event ADD dummy INT FIRST;
--error 1525
SHOW EVENTS;
ALTER TABLE mysql.event DROP dummy, ADD dummy2 VARCHAR(64) FIRST;
--error 1525
SHOW EVENTS;
ALTER TABLE mysql.event DROP dummy2;
--replace_column 8 # 9 #
SHOW EVENTS;
CREATE TABLE event_like LIKE mysql.event;
INSERT INTO event_like SELECT * FROM mysql.event;
#sleep a bit or we won't catch the change of time
--sleep 1
ALTER TABLE mysql.event MODIFY db char(20) character set utf8 collate utf8_bin default '';
#wait a bit or we won't see the difference because of seconds resolution
--sleep 1
SHOW CREATE TABLE mysql.event;
--error 1526
SELECT event_name FROM INFORMATION_SCHEMA.EVENTS;
--sleep 1
ALTER TABLE mysql.event MODIFY db char(64) character set utf8 collate utf8_bin default '';
--sleep 1
--echo "This should work"
--replace_column 8 # 9 #
SHOW EVENTS;
--sleep 1
ALTER TABLE mysql.event MODIFY db char(64) character set cp1251 default '';
--error 1526
SELECT event_name FROM INFORMATION_SCHEMA.EVENTS;
--sleep 1
ALTER TABLE mysql.event MODIFY db varchar(64) character set utf8 collate utf8_bin default '';
--error 1526
SELECT event_name FROM INFORMATION_SCHEMA.EVENTS;
--sleep 1
ALTER TABLE mysql.event DROP comment, DROP starts;
--sleep 1
--error 1525
SELECT event_name FROM INFORMATION_SCHEMA.EVENTS;
DROP TABLE mysql.event;
CREATE TABLE mysql.event like event_like;
INSERT INTO mysql.event SELECT * FROM event_like;
DROP TABLE event_like;
--replace_column 8 # 9 #
SHOW EVENTS;
DROP EVENT intact_check;
#
# mysql.event intact checking end
#
# #
#INFORMATION_SCHEMA.EVENTS test begin #INFORMATION_SCHEMA.EVENTS test begin
# #
......
This diff is collapsed.
...@@ -109,6 +109,7 @@ public: ...@@ -109,6 +109,7 @@ public:
enum enum_event_on_completion on_completion; enum enum_event_on_completion on_completion;
enum enum_event_status status; enum enum_event_status status;
sp_head *sphead; sp_head *sphead;
ulong sql_mode;
const uchar *body_begin; const uchar *body_begin;
...@@ -119,8 +120,9 @@ public: ...@@ -119,8 +120,9 @@ public:
event_timed():running(0), status_changed(false), last_executed_changed(false), event_timed():running(0), status_changed(false), last_executed_changed(false),
expression(0), created(0), modified(0), expression(0), created(0), modified(0),
on_completion(MYSQL_EVENT_ON_COMPLETION_DROP), on_completion(MYSQL_EVENT_ON_COMPLETION_DROP),
status(MYSQL_EVENT_ENABLED), sphead(0), dropped(false), status(MYSQL_EVENT_ENABLED), sphead(0), sql_mode(0),
free_sphead_on_delete(true), flags(0) body_begin(0), dropped(false), free_sphead_on_delete(true),
flags(0)
{ {
pthread_mutex_init(&this->LOCK_running, MY_MUTEX_INIT_FAST); pthread_mutex_init(&this->LOCK_running, MY_MUTEX_INIT_FAST);
...@@ -177,8 +179,8 @@ public: ...@@ -177,8 +179,8 @@ public:
bool bool
update_fields(THD *thd); update_fields(THD *thd);
char * int
get_show_create_event(THD *thd, uint32 *length); get_create_event(THD *thd, String *buf);
int int
execute(THD *thd, MEM_ROOT *mem_root= NULL); execute(THD *thd, MEM_ROOT *mem_root= NULL);
...@@ -221,8 +223,16 @@ evex_drop_event(THD *thd, event_timed *et, bool drop_if_exists, ...@@ -221,8 +223,16 @@ evex_drop_event(THD *thd, event_timed *et, bool drop_if_exists,
int int
evex_open_event_table(THD *thd, enum thr_lock_type lock_type, TABLE **table); evex_open_event_table(THD *thd, enum thr_lock_type lock_type, TABLE **table);
int
evex_show_create_event(THD *thd, sp_name *spn, LEX_STRING definer);
int sortcmp_lex_string(LEX_STRING s, LEX_STRING t, CHARSET_INFO *cs); int sortcmp_lex_string(LEX_STRING s, LEX_STRING t, CHARSET_INFO *cs);
int
event_reconstruct_interval_expression(String *buf,
interval_type interval,
longlong expression);
int int
init_events(); init_events();
......
...@@ -33,9 +33,9 @@ extern ulong thread_created; ...@@ -33,9 +33,9 @@ extern ulong thread_created;
extern const char *my_localhost; extern const char *my_localhost;
extern pthread_attr_t connection_attrib; extern pthread_attr_t connection_attrib;
pthread_mutex_t LOCK_event_arrays, pthread_mutex_t LOCK_event_arrays, // mutex for when working with the queue
LOCK_workers_count, LOCK_workers_count, // mutex for when inc/dec uint workers_count
LOCK_evex_running; LOCK_evex_running; // mutes for managing bool evex_is_running
bool evex_is_running= false; bool evex_is_running= false;
...@@ -62,6 +62,19 @@ event_executor_worker(void *arg); ...@@ -62,6 +62,19 @@ event_executor_worker(void *arg);
pthread_handler_t pthread_handler_t
event_executor_main(void *arg); event_executor_main(void *arg);
/*
Returns the seconds difference of 2 TIME structs
SYNOPSIS
evex_time_diff()
a - TIME struct 1
b - TIME struct 2
Returns:
the seconds difference
*/
static int static int
evex_time_diff(TIME *a, TIME *b) evex_time_diff(TIME *a, TIME *b)
{ {
...@@ -69,6 +82,19 @@ evex_time_diff(TIME *a, TIME *b) ...@@ -69,6 +82,19 @@ evex_time_diff(TIME *a, TIME *b)
} }
/*
Inits the mutexes used by the scheduler module
SYNOPSIS
evex_init_mutexes()
NOTES
The mutexes are :
LOCK_event_arrays
LOCK_workers_count
LOCK_evex_running
*/
static void static void
evex_init_mutexes() evex_init_mutexes()
{ {
...@@ -84,6 +110,75 @@ evex_init_mutexes() ...@@ -84,6 +110,75 @@ evex_init_mutexes()
} }
/*
Opens mysql.db and mysql.user and checks whether
1. mysql.db has column Event_priv at column 20 (0 based);
2. mysql.user has column Event_priv at column 29 (0 based);
Synopsis
evex_check_system_tables()
*/
void
evex_check_system_tables()
{
THD *thd= current_thd;
TABLE_LIST tables;
bool not_used;
Open_tables_state backup;
// thd is 0x0 during boot of the server. Later it's !=0x0
if (!thd)
return;
thd->reset_n_backup_open_tables_state(&backup);
bzero((char*) &tables, sizeof(tables));
tables.db= (char*) "mysql";
tables.table_name= tables.alias= (char*) "db";
tables.lock_type= TL_READ;
if (simple_open_n_lock_tables(thd, &tables))
sql_print_error("Cannot open mysql.db");
else
{
table_check_intact(tables.table, MYSQL_DB_FIELD_COUNT, mysql_db_table_fields,
&mysql_db_table_last_check,ER_EVENT_CANNOT_LOAD_FROM_TABLE);
close_thread_tables(thd);
}
bzero((char*) &tables, sizeof(tables));
tables.db= (char*) "mysql";
tables.table_name= tables.alias= (char*) "user";
tables.lock_type= TL_READ;
if (simple_open_n_lock_tables(thd, &tables))
sql_print_error("Cannot open mysql.db");
else
{
if (tables.table->s->fields < 29 ||
strncmp(tables.table->field[29]->field_name,
STRING_WITH_LEN("Event_priv")))
sql_print_error("mysql.user has no `Event_priv` column at position 29");
close_thread_tables(thd);
}
thd->restore_backup_open_tables_state(&backup);
}
/*
Inits the scheduler. Called on server start and every time the scheduler
is started with switching the event_scheduler global variable to TRUE
SYNOPSIS
init_events()
NOTES
Inits the mutexes used by the scheduler. Done at server start.
*/
int int
init_events() init_events()
{ {
...@@ -92,6 +187,8 @@ init_events() ...@@ -92,6 +187,8 @@ init_events()
DBUG_ENTER("init_events"); DBUG_ENTER("init_events");
DBUG_PRINT("info",("Starting events main thread")); DBUG_PRINT("info",("Starting events main thread"));
evex_check_system_tables();
evex_init_mutexes(); evex_init_mutexes();
...@@ -114,6 +211,16 @@ init_events() ...@@ -114,6 +211,16 @@ init_events()
} }
/*
Cleans up scheduler memory. Called on server shutdown.
SYNOPSIS
shutdown_events()
NOTES
Destroys the mutexes.
*/
void void
shutdown_events() shutdown_events()
{ {
...@@ -130,6 +237,22 @@ shutdown_events() ...@@ -130,6 +237,22 @@ shutdown_events()
} }
/*
Inits an scheduler thread handler, both the main and a worker
SYNOPSIS
init_event_thread()
thd - the THD of the thread. Has to be allocated by the caller.
NOTES
1. The host of the thead is my_localhost
2. thd->net is initted with NULL - no communication.
Returns
0 - OK
-1 - Error
*/
static int static int
init_event_thread(THD* thd) init_event_thread(THD* thd)
{ {
...@@ -166,6 +289,22 @@ init_event_thread(THD* thd) ...@@ -166,6 +289,22 @@ init_event_thread(THD* thd)
DBUG_RETURN(0); DBUG_RETURN(0);
} }
/*
The main scheduler thread. Inits the priority queue on start and
destroys it on thread shutdown. Forks child threads for every event
execution. Sleeps between thread forking and does not do a busy wait.
SYNOPSIS
event_executor_main()
arg - unused
NOTES
1. The host of the thead is my_localhost
2. thd->net is initted with NULL - no communication.
*/
pthread_handler_t pthread_handler_t
event_executor_main(void *arg) event_executor_main(void *arg)
{ {
...@@ -437,6 +576,15 @@ err_no_thd: ...@@ -437,6 +576,15 @@ err_no_thd:
} }
/*
Function that executes an event in a child thread. Setups the
environment for the event execution and cleans after that.
SYNOPSIS
event_executor_worker()
arg - the event_timed object to be processed
*/
pthread_handler_t pthread_handler_t
event_executor_worker(void *event_void) event_executor_worker(void *event_void)
{ {
...@@ -562,6 +710,24 @@ err_no_thd: ...@@ -562,6 +710,24 @@ err_no_thd:
} }
/*
Loads all ENABLED events from mysql.event into the prioritized
queue. Called during scheduler main thread initialization. Compiles
the events. Creates event_timed instances for every ENABLED event
from mysql.event.
SYNOPSIS
evex_load_events_from_db()
thd - Thread context. Used for memory allocation in some cases.
RETURNS
0 - OK
-1 - Error
NOTES
Reports the error to the console
*/
static int static int
evex_load_events_from_db(THD *thd) evex_load_events_from_db(THD *thd)
{ {
...@@ -648,7 +814,22 @@ end: ...@@ -648,7 +814,22 @@ end:
} }
bool sys_var_event_executor::update(THD *thd, set_var *var) /*
The update method of the global variable event_scheduler.
If event_scheduler is switched from 0 to 1 then the scheduler main
thread is started.
SYNOPSIS
event_executor_worker()
thd - Thread context (unused)
car - the new value
Returns
0 - OK (always)
*/
bool
sys_var_event_executor::update(THD *thd, set_var *var)
{ {
// here start the thread if not running. // here start the thread if not running.
DBUG_ENTER("sys_var_event_executor::update"); DBUG_ENTER("sys_var_event_executor::update");
......
...@@ -116,7 +116,10 @@ event_timed::init_body(THD *thd) ...@@ -116,7 +116,10 @@ event_timed::init_body(THD *thd)
while (body.length && body_begin[body.length-1] == '\0') while (body.length && body_begin[body.length-1] == '\0')
body.length--; body.length--;
body.str= strmake_root(root, (char *)body_begin, body.length); //the first is always space which I cannot skip in the parser
DBUG_ASSERT(*body_begin == ' ');
body.length--;
body.str= strmake_root(root, (char *)body_begin + 1, body.length);
DBUG_VOID_RETURN; DBUG_VOID_RETURN;
} }
...@@ -847,6 +850,15 @@ err: ...@@ -847,6 +850,15 @@ err:
} }
/*
Set the internal last_executed TIME struct to now. NOW is the
time according to thd->query_start(), so the THD's clock.
Synopsis
event_timed::drop()
thd - thread context
*/
void void
event_timed::mark_last_executed(THD *thd) event_timed::mark_last_executed(THD *thd)
{ {
...@@ -864,7 +876,13 @@ event_timed::mark_last_executed(THD *thd) ...@@ -864,7 +876,13 @@ event_timed::mark_last_executed(THD *thd)
/* /*
Returns : Drops the event
Synopsis
event_timed::drop()
thd - thread context
RETURNS :
0 - OK 0 - OK
-1 - Cannot open mysql.event -1 - Cannot open mysql.event
-2 - Cannot find the event in mysql.event (already deleted?) -2 - Cannot find the event in mysql.event (already deleted?)
...@@ -884,6 +902,22 @@ event_timed::drop(THD *thd) ...@@ -884,6 +902,22 @@ event_timed::drop(THD *thd)
} }
/*
Saves status and last_executed_at to the disk if changed.
Synopsis
event_timed::drop()
thd - thread context
Returns :
0 - OK
SP_OPEN_TABLE_FAILED - Error while opening mysql.event for writing
EVEX_WRITE_ROW_FAILED - On error to write to disk
others - return code from SE in case deletion of the event row
failed.
*/
bool bool
event_timed::update_fields(THD *thd) event_timed::update_fields(THD *thd)
{ {
...@@ -941,40 +975,75 @@ done: ...@@ -941,40 +975,75 @@ done:
} }
char * /*
event_timed::get_show_create_event(THD *thd, uint32 *length) Get SHOW CREATE EVENT as string
thd - Thread
buf - String*, should be already allocated. CREATE EVENT goes inside.
Returns:
0 - OK
1 - Error (for now if mysql.event has been tampered and MICROSECONDS
interval or derivative has been put there.
*/
int
event_timed::get_create_event(THD *thd, String *buf)
{ {
char *dst, *ret; int multipl= 0;
uint len, tmp_len; char tmp_buff[128];
DBUG_ENTER("get_show_create_event"); String expr_buf(tmp_buff, sizeof(tmp_buff), system_charset_info);
expr_buf.length(0);
DBUG_ENTER("get_create_event");
DBUG_PRINT("ret_info",("body_len=[%d]body=[%s]", body.length, body.str)); DBUG_PRINT("ret_info",("body_len=[%d]body=[%s]", body.length, body.str));
len = strlen("CREATE EVENT `") + dbname.length + strlen("`.`") + name.length + if (expression &&
strlen("` ON SCHEDULE EVERY 5 MINUTE DO ") + body.length;// + strlen(";"); event_reconstruct_interval_expression(&expr_buf, interval, expression))
DBUG_RETURN(1);
ret= dst= (char*) alloc_root(thd->mem_root, len + 1);
memcpy(dst, "CREATE EVENT `", tmp_len= strlen("CREATE EVENT `")); buf->append(STRING_WITH_LEN("CREATE EVENT "));
dst+= tmp_len; append_identifier(thd, buf, dbname.str, dbname.length);
memcpy(dst, dbname.str, tmp_len=dbname.length); buf->append(STRING_WITH_LEN("."));
dst+= tmp_len; append_identifier(thd, buf, name.str, name.length);
memcpy(dst, "`.`", tmp_len= strlen("`.`"));
dst+= tmp_len; buf->append(STRING_WITH_LEN(" ON SCHEDULE "));
memcpy(dst, name.str, tmp_len= name.length); if (expression)
dst+= tmp_len; {
memcpy(dst, "` ON SCHEDULE EVERY 5 MINUTE DO ", buf->append(STRING_WITH_LEN("EVERY "));
tmp_len= strlen("` ON SCHEDULE EVERY 5 MINUTE DO ")); buf->append(expr_buf);
dst+= tmp_len; }
else
memcpy(dst, body.str, tmp_len= body.length); {
dst+= tmp_len; char dtime_buff[20*2+32];// +32 to make my_snprintf_{8bit|ucs2} happy
// memcpy(dst, ";", 1); buf->append(STRING_WITH_LEN("AT '"));
// ++dst; /*
*dst= '\0'; pass the buffer and the second param tells fills the buffer and returns
the number of chars to copy
*length= len; */
buf->append(dtime_buff, my_datetime_to_str(&execute_at, dtime_buff));
DBUG_PRINT("ret_info",("len=%d",*length)); buf->append(STRING_WITH_LEN("'"));
DBUG_RETURN(ret); }
if (on_completion == MYSQL_EVENT_ON_COMPLETION_DROP)
buf->append(STRING_WITH_LEN(" ON COMPLETION NOT PRESERVE "));
else
buf->append(STRING_WITH_LEN(" ON COMPLETION PRESERVE "));
if (status == MYSQL_EVENT_ENABLED)
buf->append(STRING_WITH_LEN("ENABLE"));
else
buf->append(STRING_WITH_LEN("DISABLE"));
if (comment.length)
{
buf->append(STRING_WITH_LEN(" COMMENT "));
append_unescaped(buf, comment.str, comment.length);
}
buf->append(STRING_WITH_LEN(" DO "));
buf->append(body.str, body.length);
DBUG_RETURN(0);
} }
...@@ -1035,13 +1104,21 @@ done: ...@@ -1035,13 +1104,21 @@ done:
/* /*
Compiles an event before it's execution. Compiles the anonymous
sp_head object held by the event
Synopsis
event_timed::compile()
thd - thread context, used for memory allocation mostly
mem_root - if != NULL then this memory root is used for allocs
instead of thd->mem_root
Returns Returns
0 - Success 0 - Success
EVEX_COMPILE_ERROR - Error during compilation EVEX_COMPILE_ERROR - Error during compilation
*/ */
int int
event_timed::compile(THD *thd, MEM_ROOT *mem_root) event_timed::compile(THD *thd, MEM_ROOT *mem_root)
{ {
...@@ -1054,9 +1131,13 @@ event_timed::compile(THD *thd, MEM_ROOT *mem_root) ...@@ -1054,9 +1131,13 @@ event_timed::compile(THD *thd, MEM_ROOT *mem_root)
char *old_query; char *old_query;
uint old_query_len; uint old_query_len;
st_sp_chistics *p; st_sp_chistics *p;
CHARSET_INFO *old_character_set_client, *old_collation_connection, char create_buf[2048];
String show_create(create_buf, sizeof(create_buf), system_charset_info);
CHARSET_INFO *old_character_set_client,
*old_collation_connection,
*old_character_set_results; *old_character_set_results;
show_create.length(0);
old_character_set_client= thd->variables.character_set_client; old_character_set_client= thd->variables.character_set_client;
old_character_set_results= thd->variables.character_set_results; old_character_set_results= thd->variables.character_set_results;
old_collation_connection= thd->variables.collation_connection; old_collation_connection= thd->variables.collation_connection;
...@@ -1079,7 +1160,11 @@ event_timed::compile(THD *thd, MEM_ROOT *mem_root) ...@@ -1079,7 +1160,11 @@ event_timed::compile(THD *thd, MEM_ROOT *mem_root)
old_query= thd->query; old_query= thd->query;
old_db= thd->db; old_db= thd->db;
thd->db= dbname.str; thd->db= dbname.str;
thd->query= get_show_create_event(thd, &thd->query_length);
get_create_event(thd, &show_create);
thd->query= show_create.c_ptr();
thd->query_length= show_create.length();
DBUG_PRINT("event_timed::compile", ("query:%s",thd->query)); DBUG_PRINT("event_timed::compile", ("query:%s",thd->query));
thd->lex= &lex; thd->lex= &lex;
......
...@@ -5779,10 +5779,10 @@ ER_EVENT_OPEN_TABLE_FAILED ...@@ -5779,10 +5779,10 @@ ER_EVENT_OPEN_TABLE_FAILED
eng "Failed to open mysql.event" eng "Failed to open mysql.event"
ER_EVENT_NEITHER_M_EXPR_NOR_M_AT ER_EVENT_NEITHER_M_EXPR_NOR_M_AT
eng "No datetime expression provided" eng "No datetime expression provided"
ER_EVENT_COL_COUNT_DOESNT_MATCH ER_COL_COUNT_DOESNT_MATCH_CORRUPTED
eng "Column count of %s.%s is wrong. Table probably corrupted" eng "Column count of mysql.%s is wrong. Expected %d, found %d. Table probably corrupted"
ER_EVENT_CANNOT_LOAD_FROM_TABLE ER_EVENT_CANNOT_LOAD_FROM_TABLE
eng "Cannot load from mysql.event. Table probably corrupted" eng "Cannot load from mysql.%s. Table probably corrupted. See error log."
ER_EVENT_CANNOT_DELETE ER_EVENT_CANNOT_DELETE
eng "Failed to delete the event from mysql.event" eng "Failed to delete the event from mysql.event"
ER_EVENT_COMPILE_ERROR ER_EVENT_COMPILE_ERROR
...@@ -5802,3 +5802,5 @@ ER_SP_WRONG_NAME 42000 ...@@ -5802,3 +5802,5 @@ ER_SP_WRONG_NAME 42000
eng "Incorrect routine name '%-.64s'" eng "Incorrect routine name '%-.64s'"
ER_FOREIGN_DUPLICATE_KEY 23000 S1009 ER_FOREIGN_DUPLICATE_KEY 23000 S1009
eng "Upholding foreign key constraints for table '%.64s', entry '%-.64s', key %d would lead to a duplicate entry" eng "Upholding foreign key constraints for table '%.64s', entry '%-.64s', key %d would lead to a duplicate entry"
ER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE
eng "Column count of mysql.%s is wrong. Expected %d, found %d. Created with MySQL %d, now running %d. Please use scripts/mysql_fix_privilege_tables"
...@@ -36,6 +36,122 @@ ...@@ -36,6 +36,122 @@
#define FIRST_NON_YN_FIELD 26 #define FIRST_NON_YN_FIELD 26
time_t mysql_db_table_last_check= 0L;
TABLE_FIELD_W_TYPE mysql_db_table_fields[MYSQL_DB_FIELD_COUNT] = {
{
{(char *) STRING_WITH_LEN("Host")},
{(char *) STRING_WITH_LEN("char(60)")},
{NULL, 0}
},
{
{(char *) STRING_WITH_LEN("Db")},
{(char *) STRING_WITH_LEN("char(64)")},
{NULL, 0}
},
{
{(char *) STRING_WITH_LEN("User")},
{(char *) STRING_WITH_LEN("char(16)")},
{NULL, 0}
},
{
{(char *) STRING_WITH_LEN("Select_priv")},
{(char *) STRING_WITH_LEN("enum('N','Y')")},
{(char *) STRING_WITH_LEN("utf8")}
},
{
{(char *) STRING_WITH_LEN("Insert_priv")},
{(char *) STRING_WITH_LEN("enum('N','Y')")},
{(char *) STRING_WITH_LEN("utf8")}
},
{
{(char *) STRING_WITH_LEN("Update_priv")},
{(char *) STRING_WITH_LEN("enum('N','Y')")},
{(char *) STRING_WITH_LEN("utf8")}
},
{
{(char *) STRING_WITH_LEN("Delete_priv")},
{(char *) STRING_WITH_LEN("enum('N','Y')")},
{(char *) STRING_WITH_LEN("utf8")}
},
{
{(char *) STRING_WITH_LEN("Create_priv")},
{(char *) STRING_WITH_LEN("enum('N','Y')")},
{(char *) STRING_WITH_LEN("utf8")}
},
{
{(char *) STRING_WITH_LEN("Drop_priv")},
{(char *) STRING_WITH_LEN("enum('N','Y')")},
{(char *) STRING_WITH_LEN("utf8")}
},
{
{(char *) STRING_WITH_LEN("Grant_priv")},
{(char *) STRING_WITH_LEN("enum('N','Y')")},
{(char *) STRING_WITH_LEN("utf8")}
},
{
{(char *) STRING_WITH_LEN("References_priv")},
{(char *) STRING_WITH_LEN("enum('N','Y')")},
{(char *) STRING_WITH_LEN("utf8")}
},
{
{(char *) STRING_WITH_LEN("Index_priv")},
{(char *) STRING_WITH_LEN("enum('N','Y')")},
{(char *) STRING_WITH_LEN("utf8")}
},
{
{(char *) STRING_WITH_LEN("Alter_priv")},
{(char *) STRING_WITH_LEN("enum('N','Y')")},
{(char *) STRING_WITH_LEN("utf8")}
},
{
{(char *) STRING_WITH_LEN("Create_tmp_table_priv")},
{(char *) STRING_WITH_LEN("enum('N','Y')")},
{(char *) STRING_WITH_LEN("utf8")}
},
{
{(char *) STRING_WITH_LEN("Lock_tables_priv")},
{(char *) STRING_WITH_LEN("enum('N','Y')")},
{(char *) STRING_WITH_LEN("utf8")}
},
{
{(char *) STRING_WITH_LEN("Create_view_priv")},
{(char *) STRING_WITH_LEN("enum('N','Y')")},
{(char *) STRING_WITH_LEN("utf8")}
},
{
{(char *) STRING_WITH_LEN("Show_view_priv")},
{(char *) STRING_WITH_LEN("enum('N','Y')")},
{(char *) STRING_WITH_LEN("utf8")}
},
{
{(char *) STRING_WITH_LEN("Create_routine_priv")},
{(char *) STRING_WITH_LEN("enum('N','Y')")},
{(char *) STRING_WITH_LEN("utf8")}
},
{
{(char *) STRING_WITH_LEN("Alter_routine_priv")},
{(char *) STRING_WITH_LEN("enum('N','Y')")},
{(char *) STRING_WITH_LEN("utf8")}
},
{
{(char *) STRING_WITH_LEN("Execute_priv")},
{(char *) STRING_WITH_LEN("enum('N','Y')")},
{(char *) STRING_WITH_LEN("utf8")}
},
{
{(char *) STRING_WITH_LEN("Event_priv")},
{(char *) STRING_WITH_LEN("enum('N','Y')")},
{(char *) STRING_WITH_LEN("utf8")}
},
{
{(char *) STRING_WITH_LEN("Trigger_priv")},
{(char *) STRING_WITH_LEN("enum('N','Y')")},
{(char *) STRING_WITH_LEN("utf8")}
}
};
class acl_entry :public hash_filo_element class acl_entry :public hash_filo_element
{ {
public: public:
...@@ -441,14 +557,14 @@ static my_bool acl_load(THD *thd, TABLE_LIST *tables) ...@@ -441,14 +557,14 @@ static my_bool acl_load(THD *thd, TABLE_LIST *tables)
while (!(read_record_info.read_record(&read_record_info))) while (!(read_record_info.read_record(&read_record_info)))
{ {
ACL_DB db; ACL_DB db;
update_hostname(&db.host,get_field(&mem, table->field[0])); update_hostname(&db.host,get_field(&mem, table->field[MYSQL_DB_FIELD_HOST]));
db.db=get_field(&mem, table->field[1]); db.db=get_field(&mem, table->field[MYSQL_DB_FIELD_DB]);
if (!db.db) if (!db.db)
{ {
sql_print_warning("Found an entry in the 'db' table with empty database name; Skipped"); sql_print_warning("Found an entry in the 'db' table with empty database name; Skipped");
continue; continue;
} }
db.user=get_field(&mem, table->field[2]); db.user=get_field(&mem, table->field[MYSQL_DB_FIELD_USER]);
if (check_no_resolve && hostname_requires_resolving(db.host.hostname)) if (check_no_resolve && hostname_requires_resolving(db.host.hostname))
{ {
sql_print_warning("'db' entry '%s %s@%s' " sql_print_warning("'db' entry '%s %s@%s' "
......
...@@ -134,6 +134,36 @@ ...@@ -134,6 +134,36 @@
(((A) & ALTER_PROC_ACL) >> 23) | \ (((A) & ALTER_PROC_ACL) >> 23) | \
(((A) & GRANT_ACL) >> 8)) (((A) & GRANT_ACL) >> 8))
enum mysql_db_table_field
{
MYSQL_DB_FIELD_HOST = 0,
MYSQL_DB_FIELD_DB,
MYSQL_DB_FIELD_USER,
MYSQL_DB_FIELD_SELECT_PRIV,
MYSQL_DB_FIELD_INSERT_PRIV,
MYSQL_DB_FIELD_UPDATE_PRIV,
MYSQL_DB_FIELD_DELETE_PRIV,
MYSQL_DB_FIELD_CREATE_PRIV,
MYSQL_DB_FIELD_DROP_PRIV,
MYSQL_DB_FIELD_GRANT_PRIV,
MYSQL_DB_FIELD_REFERENCES_PRIV,
MYSQL_DB_FIELD_INDEX_PRIV,
MYSQL_DB_FIELD_ALTER_PRIV,
MYSQL_DB_FIELD_CREATE_TMP_TABLE_PRIV,
MYSQL_DB_FIELD_LOCK_TABLES_PRIV,
MYSQL_DB_FIELD_CREATE_VIEW_PRIV,
MYSQL_DB_FIELD_SHOW_VIEW_PRIV,
MYSQL_DB_FIELD_CREATE_ROUTINE_PRIV,
MYSQL_DB_FIELD_ALTER_ROUTINE_PRIV,
MYSQL_DB_FIELD_EXECUTE_PRIV,
MYSQL_DB_FIELD_EVENT_PRIV,
MYSQL_DB_FIELD_TRIGGER_PRIV,
MYSQL_DB_FIELD_COUNT
};
extern TABLE_FIELD_W_TYPE mysql_db_table_fields[];
extern time_t mysql_db_table_last_check;
/* Classes */ /* Classes */
struct acl_host_and_ip struct acl_host_and_ip
......
...@@ -3799,6 +3799,14 @@ end_with_restore_list: ...@@ -3799,6 +3799,14 @@ end_with_restore_list:
} }
case SQLCOM_SHOW_CREATE_EVENT: case SQLCOM_SHOW_CREATE_EVENT:
{ {
DBUG_ASSERT(lex->spname);
DBUG_ASSERT(lex->et);
if (! lex->spname->m_db.str)
{
my_message(ER_NO_DB_ERROR, ER(ER_NO_DB_ERROR), MYF(0));
res= true;
break;
}
if (check_access(thd, EVENT_ACL, lex->spname->m_db.str, 0, 0, 0, if (check_access(thd, EVENT_ACL, lex->spname->m_db.str, 0, 0, 0,
is_schema_db(lex->spname->m_db.str))) is_schema_db(lex->spname->m_db.str)))
break; break;
...@@ -3808,8 +3816,7 @@ end_with_restore_list: ...@@ -3808,8 +3816,7 @@ end_with_restore_list:
my_error(ER_TOO_LONG_IDENT, MYF(0), lex->spname->m_name.str); my_error(ER_TOO_LONG_IDENT, MYF(0), lex->spname->m_name.str);
goto error; goto error;
} }
/* TODO : Implement it */ res= evex_show_create_event(thd, lex->spname, lex->et->definer);
send_ok(thd, 1);
break; break;
} }
case SQLCOM_CREATE_FUNCTION: // UDF function case SQLCOM_CREATE_FUNCTION: // UDF function
......
...@@ -3766,30 +3766,6 @@ static int get_schema_partitions_record(THD *thd, struct st_table_list *tables, ...@@ -3766,30 +3766,6 @@ static int get_schema_partitions_record(THD *thd, struct st_table_list *tables,
} }
static LEX_STRING interval_type_to_name[] = {
{(char *) STRING_WITH_LEN("INTERVAL_YEAR")},
{(char *) STRING_WITH_LEN("INTERVAL_QUARTER")},
{(char *) STRING_WITH_LEN("INTERVAL_MONTH")},
{(char *) STRING_WITH_LEN("INTERVAL_DAY")},
{(char *) STRING_WITH_LEN("INTERVAL_HOUR")},
{(char *) STRING_WITH_LEN("INTERVAL_MINUTE")},
{(char *) STRING_WITH_LEN("INTERVAL_WEEK")},
{(char *) STRING_WITH_LEN("INTERVAL_SECOND")},
{(char *) STRING_WITH_LEN("INTERVAL_MICROSECOND ")},
{(char *) STRING_WITH_LEN("INTERVAL_YEAR_MONTH")},
{(char *) STRING_WITH_LEN("INTERVAL_DAY_HOUR")},
{(char *) STRING_WITH_LEN("INTERVAL_DAY_MINUTE")},
{(char *) STRING_WITH_LEN("INTERVAL_DAY_SECOND")},
{(char *) STRING_WITH_LEN("INTERVAL_HOUR_MINUTE")},
{(char *) STRING_WITH_LEN("INTERVAL_HOUR_SECOND")},
{(char *) STRING_WITH_LEN("INTERVAL_MINUTE_SECOND")},
{(char *) STRING_WITH_LEN("INTERVAL_DAY_MICROSECOND")},
{(char *) STRING_WITH_LEN("INTERVAL_HOUR_MICROSECOND")},
{(char *) STRING_WITH_LEN("INTERVAL_MINUTE_MICROSECOND")},
{(char *) STRING_WITH_LEN("INTERVAL_SECOND_MICROSECOND")}
};
static interval_type get_real_interval_type(interval_type i_type) static interval_type get_real_interval_type(interval_type i_type)
{ {
switch (i_type) { switch (i_type) {
...@@ -3863,6 +3839,7 @@ fill_events_copy_to_schema_table(THD *thd, TABLE *sch_table, TABLE *event_table) ...@@ -3863,6 +3839,7 @@ fill_events_copy_to_schema_table(THD *thd, TABLE *sch_table, TABLE *event_table)
sch_table->field[9]->set_null(); sch_table->field[9]->set_null();
if (et.expression) if (et.expression)
{ {
String show_str;
//type //type
sch_table->field[5]->store(STRING_WITH_LEN("RECURRING"), scs); sch_table->field[5]->store(STRING_WITH_LEN("RECURRING"), scs);
//execute_at //execute_at
...@@ -3871,9 +3848,11 @@ fill_events_copy_to_schema_table(THD *thd, TABLE *sch_table, TABLE *event_table) ...@@ -3871,9 +3848,11 @@ fill_events_copy_to_schema_table(THD *thd, TABLE *sch_table, TABLE *event_table)
sch_table->field[7]->set_notnull(); sch_table->field[7]->set_notnull();
sch_table->field[7]->store((longlong) et.expression); sch_table->field[7]->store((longlong) et.expression);
//interval_type //interval_type
LEX_STRING *ival=&interval_type_to_name[get_real_interval_type(et.interval)]; if (event_reconstruct_interval_expression(&show_str, et.interval,
et.expression))
DBUG_RETURN(1);
sch_table->field[8]->set_notnull(); sch_table->field[8]->set_notnull();
sch_table->field[8]->store(ival->str, ival->length, scs); sch_table->field[8]->store(show_str.c_ptr(), show_str.length(), scs);
//starts & ends //starts & ends
sch_table->field[10]->set_notnull(); sch_table->field[10]->set_notnull();
sch_table->field[10]->store_time(&et.starts, MYSQL_TIMESTAMP_DATETIME); sch_table->field[10]->store_time(&et.starts, MYSQL_TIMESTAMP_DATETIME);
......
...@@ -8431,6 +8431,10 @@ show_param: ...@@ -8431,6 +8431,10 @@ show_param:
{ {
Lex->sql_command = SQLCOM_SHOW_CREATE_EVENT; Lex->sql_command = SQLCOM_SHOW_CREATE_EVENT;
Lex->spname= $3; Lex->spname= $3;
Lex->et= new event_timed();
if (!Lex->et)
YYABORT;
Lex->et->init_definer(YYTHD);
} }
; ;
......
...@@ -343,9 +343,7 @@ int open_table_def(THD *thd, TABLE_SHARE *share, uint db_flags) ...@@ -343,9 +343,7 @@ int open_table_def(THD *thd, TABLE_SHARE *share, uint db_flags)
allow to lock such tables for writing with any other tables (even with allow to lock such tables for writing with any other tables (even with
other system tables) and some privilege tables need this. other system tables) and some privilege tables need this.
*/ */
if (!my_strcasecmp(system_charset_info, share->table_name.str, "proc") if (!my_strcasecmp(system_charset_info, share->table_name.str, "proc"))
|| !my_strcasecmp(system_charset_info, share->table_name.str,
"event"))
share->system_table= 1; share->system_table= 1;
else else
{ {
...@@ -2291,6 +2289,144 @@ bool check_column_name(const char *name) ...@@ -2291,6 +2289,144 @@ bool check_column_name(const char *name)
return last_char_is_space || (uint) (name - start) > NAME_LEN; return last_char_is_space || (uint) (name - start) > NAME_LEN;
} }
/*
Checks whether a table is intact. Should be done *just* after the table has
been opened.
Synopsis
table_check_intact()
table - the table to check
table_f_count - expected number of columns in the table
table_def - expected structure of the table (column name and type)
last_create_time- the table->file->create_time of the table in memory
we have checked last time
error_num - ER_XXXX from the error messages file. When 0 no error
is sent to the client in case types does not match.
If different col number either
ER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE or
ER_COL_COUNT_DOESNT_MATCH_CORRUPTED is used
RETURNS
0 - OK
1 - There was an error
*/
my_bool
table_check_intact(TABLE *table, uint table_f_count,
TABLE_FIELD_W_TYPE *table_def, time_t *last_create_time,
int error_num)
{
uint i;
my_bool error= FALSE;
my_bool fields_diff_count;
DBUG_ENTER("table_check_intact");
DBUG_PRINT("info",("table=%s expected_count=%d",table->alias, table_f_count));
DBUG_PRINT("info",("last_create_time=%d", *last_create_time));
if ((fields_diff_count= (table->s->fields != table_f_count)) ||
(*last_create_time != table->file->create_time))
{
DBUG_PRINT("info", ("I am suspecting, checking table"));
if (fields_diff_count)
{
// previous MySQL version
error= TRUE;
if (MYSQL_VERSION_ID > table->s->mysql_version)
my_error(ER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE, MYF(0), table->alias,
table_f_count, table->s->fields, table->s->mysql_version,
MYSQL_VERSION_ID);
else if (MYSQL_VERSION_ID == table->s->mysql_version)
my_error(ER_COL_COUNT_DOESNT_MATCH_CORRUPTED,MYF(0), table->alias,
table_f_count, table->s->fields);
else
/*
moving from newer mysql to older one -> let's say not an error but
will check the definition afterwards. If a column was added at the end
then we don't care much since it's not in the middle.
*/
error= FALSE;
}
//definitely something has changed
char buffer[255];
for (i=0 ;i < table_f_count; ++i, ++table_def)
{
Field *field= table->field[i];
String sql_type(buffer, sizeof(buffer), system_charset_info);
sql_type.length(0);
/*
name changes are not fatal, we use sequence numbers => no prob for us
but this can show tampered table or broken table.
*/
if (!fields_diff_count || i < table->s->fields)
{
if (strncmp(field->field_name, table_def->name.str,
table_def->name.length))
{
sql_print_error("(%s) Expected field %s at position %d, found %s",
table->alias, table_def->name.str, i,
field->field_name);
}
/*
IF the type does not match than something is really wrong
Check up to length - 1. Why?
1. datetime -> datetim -> the same
2. int(11) -> int(11 -> the same
3. set('one','two') -> set('one','two'
so for sets if the same prefix is there it's ok if more are
added as part of the set. The same is valid for enum. So a new
table running on a old server will be valid.
*/
field->sql_type(sql_type);
if (strncmp(sql_type.c_ptr(), table_def->type.str,
table_def->type.length - 1))
{
sql_print_error("(%s) Expected field %s at position %d to have type "
"%s, found %s", table->alias, table_def->name.str,
i, table_def->type.str, sql_type.c_ptr());
error= TRUE;
}
else if (table_def->cset.str && !field->has_charset())
{
sql_print_error("(%s) Expected field %s at position %d to have "
"character set '%s' but found no such", table->alias,
table_def->name.str, i, table_def->cset.str);
error= TRUE;
}
else if (table_def->cset.str &&
strcmp(field->charset()->csname, table_def->cset.str))
{
sql_print_error("(%s) Expected field %s at position %d to have "
"character set '%s' but found '%s'", table->alias,
table_def->name.str, i, table_def->cset.str,
field->charset()->csname);
error= TRUE;
}
}
else
{
sql_print_error("(%s) Expected field %s at position %d to have type %s "
" but no field found.", table_def->name.str,
table_def->name.str, i, table_def->type.str);
error= TRUE;
}
}
if (!error)
*last_create_time= table->file->create_time;
else if (!fields_diff_count && error_num)
my_error(error_num,MYF(0), table->alias, table_f_count, table->s->fields);
}
else
{
DBUG_PRINT("info", ("Table seems ok without thorough checking."));
*last_create_time= table->file->create_time;
}
DBUG_RETURN(error);
}
/* /*
Create Item_field for each column in the table. Create Item_field for each column in the table.
......
...@@ -866,4 +866,15 @@ typedef struct st_open_table_list{ ...@@ -866,4 +866,15 @@ typedef struct st_open_table_list{
uint32 in_use,locked; uint32 in_use,locked;
} OPEN_TABLE_LIST; } OPEN_TABLE_LIST;
typedef struct st_table_field_w_type
{
LEX_STRING name;
LEX_STRING type;
LEX_STRING cset;
} TABLE_FIELD_W_TYPE;
my_bool
table_check_intact(TABLE *table, uint table_f_count,
TABLE_FIELD_W_TYPE *table_def, time_t *last_create_time,
int error_num);
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