/*
Copyright ( c ) 2006 , 2011 , Oracle and / or its affiliates .
This program is free software ; you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation ; version 2 of the License .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program ; if not , write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */
#include "mariadb.h"
#include "sql_priv.h"
#include "unireg.h"
#include "sql_base.h" // close_thread_tables
#include "sql_parse.h"
#include "event_db_repository.h"
#include "key.h" // key_copy
#include "sql_db.h" // get_default_db_collation
#include "sql_time.h" // interval_type_to_name
#include "tztime.h" // struct Time_zone
#include "records.h" // init_read_record, end_read_record
#include "sp_head.h"
#include "event_data_objects.h"
#include "events.h"
#include "sql_show.h"
#include "lock.h" // MYSQL_LOCK_IGNORE_TIMEOUT
#include "transaction.h"
/**
@ addtogroup Event_Scheduler
@ {
*/
static
const TABLE_FIELD_TYPE event_table_fields[ET_FIELD_COUNT] =
{
{
{ STRING_WITH_LEN("db" ) },
{ STRING_WITH_LEN("char(64)" ) },
{ STRING_WITH_LEN("utf8mb" ) }
},
{
{ STRING_WITH_LEN("name" ) },
{ STRING_WITH_LEN("char(64)" ) },
{ STRING_WITH_LEN("utf8mb" ) }
},
{
{ STRING_WITH_LEN("body" ) },
{ STRING_WITH_LEN("longblob" ) },
{NULL, 0 }
},
{
{ STRING_WITH_LEN("definer" ) },
{ STRING_WITH_LEN("varchar(" ) },
{ STRING_WITH_LEN("utf8mb" ) }
},
{
{ STRING_WITH_LEN("execute_at" ) },
{ STRING_WITH_LEN("datetime" ) + CAN_BE_NULL },
{NULL, 0 }
},
{
{ STRING_WITH_LEN("interval_value" ) },
{ STRING_WITH_LEN("int(11)" ) + CAN_BE_NULL },
{NULL, 0 }
},
{
{ STRING_WITH_LEN("interval_field" ) },
{ STRING_WITH_LEN("enum('YEAR','QUARTER','MONTH','DAY',"
"'HOUR','MINUTE','WEEK','SECOND','MICROSECOND','YEAR_MONTH','DAY_HOUR',"
"'DAY_MINUTE','DAY_SECOND','HOUR_MINUTE','HOUR_SECOND','MINUTE_SECOND',"
"'DAY_MICROSECOND','HOUR_MICROSECOND','MINUTE_MICROSECOND',"
"'SECOND_MICROSECOND')" ) + CAN_BE_NULL },
{NULL, 0 }
},
{
{ STRING_WITH_LEN("created" ) },
{ STRING_WITH_LEN("timestamp" ) },
{NULL, 0 }
},
{
{ STRING_WITH_LEN("modified" ) },
{ STRING_WITH_LEN("timestamp" ) },
{NULL, 0 }
},
{
{ STRING_WITH_LEN("last_executed" ) },
{ STRING_WITH_LEN("datetime" ) + CAN_BE_NULL },
{NULL, 0 }
},
{
{ STRING_WITH_LEN("starts" ) },
{ STRING_WITH_LEN("datetime" ) + CAN_BE_NULL },
{NULL, 0 }
},
{
{ STRING_WITH_LEN("ends" ) },
{ STRING_WITH_LEN("datetime" ) + CAN_BE_NULL },
{NULL, 0 }
},
{
{ STRING_WITH_LEN("status" ) },
{ STRING_WITH_LEN("enum('ENABLED','DISABLED','SLAVESIDE_DISABLED')" ) },
{NULL, 0 }
},
{
{ STRING_WITH_LEN("on_completion" ) },
{ STRING_WITH_LEN("enum('DROP','PRESERVE')" ) },
{NULL, 0 }
},
{
{ STRING_WITH_LEN("sql_mode" ) },
{ STRING_WITH_LEN("set('REAL_AS_FLOAT','PIPES_AS_CONCAT','ANSI_QUOTES',"
"'IGNORE_SPACE','IGNORE_BAD_TABLE_OPTIONS','ONLY_FULL_GROUP_BY',"
"'NO_UNSIGNED_SUBTRACTION',"
"'NO_DIR_IN_CREATE','POSTGRESQL','ORACLE','MSSQL','DB2','MAXDB',"
"'NO_KEY_OPTIONS','NO_TABLE_OPTIONS','NO_FIELD_OPTIONS','MYSQL323','MYSQL40',"
"'ANSI','NO_AUTO_VALUE_ON_ZERO','NO_BACKSLASH_ESCAPES','STRICT_TRANS_TABLES',"
"'STRICT_ALL_TABLES','NO_ZERO_IN_DATE','NO_ZERO_DATE','INVALID_DATES',"
"'ERROR_FOR_DIVISION_BY_ZERO','TRADITIONAL','NO_AUTO_CREATE_USER',"
"'HIGH_NOT_PRECEDENCE','NO_ENGINE_SUBSTITUTION','PAD_CHAR_TO_FULL_LENGTH',"
"'EMPTY_STRING_IS_NULL','SIMULTANEOUS_ASSIGNMENT')" ) },
{NULL, 0 }
},
{
{ STRING_WITH_LEN("comment" ) },
{ STRING_WITH_LEN("char(64)" ) },
{ STRING_WITH_LEN("utf8mb" ) }
},
{
{ STRING_WITH_LEN("originator" ) },
{ STRING_WITH_LEN("int(10)" ) },
{NULL, 0 }
},
{
{ STRING_WITH_LEN("time_zone" ) },
{ STRING_WITH_LEN("char(64)" ) },
{ STRING_WITH_LEN("latin1" ) }
},
{
{ STRING_WITH_LEN("character_set_client" ) },
{ STRING_WITH_LEN("char(32)" ) + CAN_BE_NULL },
{ STRING_WITH_LEN("utf8mb" ) }
},
{
{ STRING_WITH_LEN("collation_connection" ) },
{ STRING_WITH_LEN("char(" ) + CAN_BE_NULL },
{ STRING_WITH_LEN("utf8mb" ) }
},
{
{ STRING_WITH_LEN("db_collation" ) },
{ STRING_WITH_LEN("char(" ) + CAN_BE_NULL },
{ STRING_WITH_LEN("utf8mb" ) }
},
{
{ STRING_WITH_LEN("body_utf8" ) },
{ STRING_WITH_LEN("longblob" ) + CAN_BE_NULL },
{ NULL, 0 }
}
};
static LEX_CSTRING MYSQL_EVENT_NAME= { STRING_WITH_LEN("event" ) };
static const TABLE_FIELD_DEF
event_table_def= {ET_FIELD_COUNT, event_table_fields, 0 , (uint*) 0 };
/** In case of an error, a message is printed to the error log. */
static Table_check_intact_log_error table_intact;
/**
Puts some data common to CREATE and ALTER EVENT into a row .
Used both when an event is created and when it is altered .
@ param thd THD
@ param table The row to fill out
@ param et Event ' s data
@ param sp Event stored routine
@ param is_update CREATE EVENT or ALTER EVENT
@ retval FALSE success
@ retval TRUE error
*/
static bool
mysql_event_fill_row(THD *thd,
TABLE *table,
Event_parse_data *et,
sp_head *sp,
sql_mode_t sql_mode,
my_bool is_update)
{
CHARSET_INFO *scs= system_charset_info;
enum enum_events_table_field f_num;
Field **fields= table->field;
int rs= FALSE ;
DBUG_ENTER("mysql_event_fill_row" );
DBUG_PRINT("info" , ("dbname=[%s]" , et->dbname.str));
DBUG_PRINT("info" , ("name =[%s]" , et->name.str));
DBUG_ASSERT(et->on_completion != Event_parse_data::ON_COMPLETION_DEFAULT);
if (table->s->fields < ET_FIELD_COUNT)
{
/*
Safety : this can only happen if someone started the server
and then altered mysql . event .
*/
my_error(ER_COL_COUNT_DOESNT_MATCH_CORRUPTED_V2, MYF(0 ),
table->s->db.str, table->alias.c_ptr(),
(int ) ET_FIELD_COUNT, table->s->fields);
DBUG_RETURN(TRUE );
}
if (fields[f_num= ET_FIELD_DEFINER]->
store(et->definer.str, et->definer.length, scs))
goto err_truncate;
if (fields[f_num= ET_FIELD_DB]->store(et->dbname.str, et->dbname.length, scs))
goto err_truncate;
if (fields[f_num= ET_FIELD_NAME]->store(et->name.str, et->name.length, scs))
goto err_truncate;
/* ON_COMPLETION field is NOT NULL thus not calling set_notnull()*/
rs|= fields[ET_FIELD_ON_COMPLETION]->store((longlong)et->on_completion, TRUE );
/*
Set STATUS value unconditionally in case of CREATE EVENT .
For ALTER EVENT set it only if value of this field was changed .
Since STATUS field is NOT NULL call to set_notnull ( ) is not needed .
*/
if (!is_update || et->status_changed)
rs|= fields[ET_FIELD_STATUS]->store((longlong)et->status, TRUE );
rs|= fields[ET_FIELD_ORIGINATOR]->store((longlong)et->originator, TRUE );
if (!is_update)
rs|= fields[ET_FIELD_CREATED]->set_time();
/*
Change the SQL_MODE only if body was present in an ALTER EVENT and of course
always during CREATE EVENT .
*/
if (et->body_changed)
{
DBUG_ASSERT(sp->m_body.str);
rs|= fields[ET_FIELD_SQL_MODE]->store((longlong)sql_mode, TRUE );
if (fields[f_num= ET_FIELD_BODY]->store(sp->m_body.str,
sp->m_body.length,
scs))
{
goto err_truncate;
}
}
if (et->expression)
{
const String *tz_name= thd->variables.time_zone->get_name();
if (!is_update || !et->starts_null)
{
fields[ET_FIELD_TIME_ZONE]->set_notnull();
rs|= fields[ET_FIELD_TIME_ZONE]->store(tz_name->ptr(), tz_name->length(),
tz_name->charset());
}
fields[ET_FIELD_INTERVAL_EXPR]->set_notnull();
rs|= fields[ET_FIELD_INTERVAL_EXPR]->store((longlong)et->expression, TRUE );
fields[ET_FIELD_TRANSIENT_INTERVAL]->set_notnull();
rs|= fields[ET_FIELD_TRANSIENT_INTERVAL]->
store(interval_type_to_name[et->interval].str,
interval_type_to_name[et->interval].length,
scs);
fields[ET_FIELD_EXECUTE_AT]->set_null();
if (!et->starts_null)
{
MYSQL_TIME time;
my_tz_OFFSET0->gmt_sec_to_TIME(&time, et->starts);
fields[ET_FIELD_STARTS]->set_notnull();
fields[ET_FIELD_STARTS]->store_time(&time);
}
if (!et->ends_null)
{
MYSQL_TIME time;
my_tz_OFFSET0->gmt_sec_to_TIME(&time, et->ends);
fields[ET_FIELD_ENDS]->set_notnull();
fields[ET_FIELD_ENDS]->store_time(&time);
}
}
else if (et->execute_at)
{
const String *tz_name= thd->variables.time_zone->get_name();
fields[ET_FIELD_TIME_ZONE]->set_notnull();
rs|= fields[ET_FIELD_TIME_ZONE]->store(tz_name->ptr(), tz_name->length(),
tz_name->charset());
fields[ET_FIELD_INTERVAL_EXPR]->set_null();
fields[ET_FIELD_TRANSIENT_INTERVAL]->set_null();
fields[ET_FIELD_STARTS]->set_null();
fields[ET_FIELD_ENDS]->set_null();
MYSQL_TIME time;
my_tz_OFFSET0->gmt_sec_to_TIME(&time, et->execute_at);
fields[ET_FIELD_EXECUTE_AT]->set_notnull();
fields[ET_FIELD_EXECUTE_AT]->store_time(&time);
}
else
{
DBUG_ASSERT(is_update);
/*
it is normal to be here when the action is update
this is an error if the action is create . something is borked
*/
}
rs|= fields[ET_FIELD_MODIFIED]->set_time();
if (et->comment.str)
{
if (fields[f_num= ET_FIELD_COMMENT]->
store(et->comment.str, et->comment.length, scs))
goto err_truncate;
}
fields[ET_FIELD_CHARACTER_SET_CLIENT]->set_notnull();
rs|= fields[ET_FIELD_CHARACTER_SET_CLIENT]->
store(&thd->variables.character_set_client->cs_name,
system_charset_info);
fields[ET_FIELD_COLLATION_CONNECTION]->set_notnull();
rs|= fields[ET_FIELD_COLLATION_CONNECTION]->
store(&thd->variables.collation_connection->coll_name,
system_charset_info);
{
CHARSET_INFO *db_cl= get_default_db_collation(thd, et->dbname.str);
fields[ET_FIELD_DB_COLLATION]->set_notnull();
rs|= fields[ET_FIELD_DB_COLLATION]->store(&db_cl->coll_name,
system_charset_info);
}
if (et->body_changed)
{
fields[ET_FIELD_BODY_UTF8]->set_notnull();
rs|= fields[ET_FIELD_BODY_UTF8]->store(&sp->m_body_utf8,
system_charset_info);
}
if (rs)
{
my_error(ER_EVENT_STORE_FAILED, MYF(0 ), fields[f_num]->field_name.str, rs);
DBUG_RETURN(TRUE );
}
DBUG_RETURN(FALSE );
err_truncate:
my_error(ER_EVENT_DATA_TOO_LONG, MYF(0 ), fields[f_num]->field_name.str);
DBUG_RETURN(TRUE );
}
/*
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 )
db For which schema to do an index scan .
RETURN VALUE
0 OK
1 Error
*/
bool
Event_db_repository::index_read_for_db_for_i_s(THD *thd, TABLE *schema_table,
TABLE *event_table,
const char *db)
{
CHARSET_INFO *scs= system_charset_info;
KEY *key_info;
uint key_len;
uchar *key_buf;
DBUG_ENTER("Event_db_repository::index_read_for_db_for_i_s" );
DBUG_PRINT("info" , ("Using prefix scanning on PK" ));
int ret= event_table->file->ha_index_init(0 , 1 );
if (ret)
{
event_table->file->print_error(ret, MYF(0 ));
DBUG_RETURN(true );
}
key_info= event_table->key_info;
if (key_info->user_defined_key_parts == 0 ||
key_info->key_part[0 ].field != event_table->field[ET_FIELD_DB])
{
/* Corrupted table: no index or index on a wrong column */
my_error(ER_CANNOT_LOAD_FROM_TABLE_V2, MYF(0 ), "mysql" , "event" );
ret= 1 ;
goto end;
}
event_table->field[ET_FIELD_DB]->store(db, strlen(db), scs);
key_len= key_info->key_part[0 ].store_length;
if (!(key_buf= (uchar *)alloc_root(thd->mem_root, key_len)))
{
/* Don't send error, it would be done by sql_alloc_error_handler() */
ret= 1 ;
goto end;
}
key_copy(key_buf, event_table->record[0 ], key_info, key_len);
if (!(ret= event_table->file->ha_index_read_map(event_table->record[0 ],
key_buf,
(key_part_map)1 ,
HA_READ_KEY_EXACT)))
{
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->ha_index_next_same(event_table->record[0 ],
key_buf, key_len);
} while (ret == 0 );
}
DBUG_PRINT("info" , ("Scan finished. ret=%d" , ret));
/* ret is guaranteed to be != 0 */
if (ret == HA_ERR_END_OF_FILE || ret == HA_ERR_KEY_NOT_FOUND)
ret= 0 ;
else
event_table->file->print_error(ret, MYF(0 ));
end:
event_table->file->ha_index_end();
DBUG_RETURN(MY_TEST(ret));
}
/*
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 .
RETURN VALUE
FALSE OK
TRUE Error
*/
bool
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" );
if (init_read_record(&read_record_info, thd, event_table, NULL, NULL, 1 , 0 ,
FALSE ))
DBUG_RETURN(TRUE );
/*
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();
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 ? FALSE :TRUE );
}
/**
Fills I_S . EVENTS with data loaded from mysql . event . Also used by
SHOW EVENTS
The reason we reset and backup open tables here is that this
function may be called from any query that accesses
INFORMATION_SCHEMA - including a query that is issued from
a pre - locked statement , one that already has open and locked
tables .
@ retval FALSE success
@ retval TRUE error
*/
bool
Event_db_repository::fill_schema_events(THD *thd, TABLE_LIST *i_s_table,
const char *db)
{
TABLE *schema_table= i_s_table->table;
TABLE_LIST event_table;
int ret= 0 ;
DBUG_ENTER("Event_db_repository::fill_schema_events" );
DBUG_PRINT("info" ,("db=%s" , db? db:"(null)" ));
start_new_trans new_trans(thd);
event_table.init_one_table(&MYSQL_SCHEMA_NAME, &MYSQL_EVENT_NAME, 0 , TL_READ);
if (open_system_tables_for_read(thd, &event_table))
{
new_trans.restore_old_transaction();
DBUG_RETURN(TRUE );
}
if (table_intact.check(event_table.table, &event_table_def))
{
my_error(ER_EVENT_OPEN_TABLE_FAILED, MYF(0 ));
ret= 1 ;
goto err;
}
/*
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.table, db);
else
ret= table_scan_all_for_i_s(thd, schema_table, event_table.table);
err:
thd->commit_whole_transaction_and_close_tables();
new_trans.restore_old_transaction();
DBUG_PRINT("info" , ("Return code=%d" , ret));
DBUG_RETURN(ret);
}
/**
Open mysql . event table for read .
It ' s assumed that the caller knows what they are doing :
- whether it was necessary to reset - and - backup the open tables state
- whether the requested lock does not lead to a deadlock
- whether this open mode would work under LOCK TABLES , or inside a
stored function or trigger .
Note that if the table can ' t be locked successfully this operation will
close it . Therefore it provides guarantee that it either opens and locks
table or fails without leaving any tables open .
@ param [ in ] thd Thread context
@ param [ in ] lock_type How to lock the table
@ param [ out ] table We will store the open table here
@ retval TRUE open and lock failed - an error message is pushed into the
stack
@ retval FALSE success
*/
bool
Event_db_repository::open_event_table(THD *thd, enum thr_lock_type lock_type,
TABLE **table)
{
TABLE_LIST tables;
DBUG_ENTER("Event_db_repository::open_event_table" );
tables.init_one_table(&MYSQL_SCHEMA_NAME, &MYSQL_EVENT_NAME, 0 , lock_type);
if (open_and_lock_tables(thd, &tables, FALSE , MYSQL_LOCK_IGNORE_TIMEOUT))
DBUG_RETURN(TRUE );
*table= tables.table;
tables.table->use_all_columns();
/* NOTE: &tables pointer will be invalid after return */
tables.table->pos_in_table_list= NULL;
if (table_intact.check(*table, &event_table_def))
{
thd->commit_whole_transaction_and_close_tables();
*table= 0 ; // Table is now closed
my_error(ER_EVENT_OPEN_TABLE_FAILED, MYF(0 ));
DBUG_RETURN(TRUE );
}
DBUG_RETURN(FALSE );
}
/**
Creates an event record in mysql . event table .
Creates an event . Relies on mysql_event_fill_row which is shared with
: : update_event .
@ pre All semantic checks must be performed outside . This function
only creates a record on disk .
@ pre The thread handle has no open tables .
@ param [ in , out ] thd THD
@ param [ in ] parse_data Parsed event definition
@ param [ in ] create_if_not TRUE if IF NOT EXISTS clause was provided
to CREATE EVENT statement
@ param [ out ] event_already_exists When method is completed successfully
set to true if event already exists else
set to false
@ retval FALSE success
@ retval TRUE error
*/
bool
Event_db_repository::create_event(THD *thd, Event_parse_data *parse_data,
bool *event_already_exists)
{
int ret= 1 ;
TABLE *table= NULL;
sp_head *sp= thd->lex->sphead;
sql_mode_t saved_mode= thd->variables.sql_mode;
/*
Take a savepoint to release only the lock on mysql . event
table at the end but keep the global read lock and
possible other locks taken by the caller .
*/
MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint();
DBUG_ENTER("Event_db_repository::create_event" );
DBUG_PRINT("info" , ("open mysql.event for update" ));
DBUG_ASSERT(sp);
/* Reset sql_mode during data dictionary operations. */
thd->variables.sql_mode= 0 ;
if (open_event_table(thd, TL_WRITE, &table))
goto end;
DBUG_PRINT("info" , ("name: %.*s" , (int ) parse_data->name.length,
parse_data->name.str));
DBUG_PRINT("info" , ("check existence of an event with the same name" ));
if (!find_named_event(&parse_data->dbname, &parse_data->name, table))
{
if (thd->lex->create_info.or_replace())
{
*event_already_exists= false ; // Force the caller to update event_queue
if ((ret= table->file->ha_delete_row(table->record[0 ])))
{
table->file->print_error(ret, MYF(0 ));
goto end;
}
}
else if (thd->lex->create_info.if_not_exists())
{
*event_already_exists= true ;
push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
ER_EVENT_ALREADY_EXISTS,
ER_THD(thd, ER_EVENT_ALREADY_EXISTS),
parse_data->name.str);
ret= 0 ;
goto end;
}
else
{
my_error(ER_EVENT_ALREADY_EXISTS, MYF(0 ), parse_data->name.str);
goto end;
}
} else
*event_already_exists= false ;
DBUG_PRINT("info" , ("non-existent, go forward" ));
restore_record(table, s->default_values); // Get default values for fields
if (check_string_char_length(&parse_data->dbname, 0 ,
table->field[ET_FIELD_DB]->char_length(),
system_charset_info, 1 ))
{
my_error(ER_TOO_LONG_IDENT, MYF(0 ), parse_data->dbname.str);
goto end;
}
if (check_string_char_length(&parse_data->name, 0 ,
table->field[ET_FIELD_NAME]->char_length(),
system_charset_info, 1 ))
{
my_error(ER_TOO_LONG_IDENT, MYF(0 ), parse_data->name.str);
goto end;
}
if (sp->m_body.length > table->field[ET_FIELD_BODY]->field_length)
{
my_error(ER_TOO_LONG_BODY, MYF(0 ), parse_data->name.str);
goto end;
}
/*
mysql_event_fill_row ( ) calls my_error ( ) in case of error so no need to
handle it here
*/
if (mysql_event_fill_row(thd, table, parse_data, sp, saved_mode, FALSE ))
goto end;
if ((ret= table->file->ha_write_row(table->record[0 ])))
{
table->file->print_error(ret, MYF(0 ));
goto end;
}
ret= 0 ;
end:
if (table)
thd->commit_whole_transaction_and_close_tables();
thd->mdl_context.rollback_to_savepoint(mdl_savepoint);
thd->variables.sql_mode= saved_mode;
DBUG_RETURN(MY_TEST(ret));
}
/**
Used to execute ALTER EVENT . Pendant to Events : : update_event ( ) .
@ param [ in , out ] thd thread handle
@ param [ in ] parse_data parsed event definition
@ param [ in ] new_dbname not NULL if ALTER EVENT RENAME
points at a new database name
@ param [ in ] new_name not NULL if ALTER EVENT RENAME
points at a new event name
@ pre All semantic checks are performed outside this function ,
it only updates the event definition on disk .
@ pre We don ' t have any tables open in the given thread .
@ retval FALSE success
@ retval TRUE error ( reported )
*/
bool
Event_db_repository::update_event(THD *thd, Event_parse_data *parse_data,
LEX_CSTRING *new_dbname,
LEX_CSTRING *new_name)
{
CHARSET_INFO *scs= system_charset_info;
TABLE *table= NULL;
sp_head *sp= thd->lex->sphead;
sql_mode_t saved_mode= thd->variables.sql_mode;
/*
Take a savepoint to release only the lock on mysql . event
table at the end but keep the global read lock and
possible other locks taken by the caller .
*/
MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint();
int ret= 1 ;
DBUG_ENTER("Event_db_repository::update_event" );
/* None or both must be set */
DBUG_ASSERT((new_dbname && new_name) || new_dbname == new_name);
/* Reset sql_mode during data dictionary operations. */
thd->variables.sql_mode= 0 ;
if (open_event_table(thd, TL_WRITE, &table))
goto end;
DBUG_PRINT("info" , ("dbname: %s" , parse_data->dbname.str));
DBUG_PRINT("info" , ("name: %s" , parse_data->name.str));
DBUG_PRINT("info" , ("user: %s" , parse_data->definer.str));
/* first look whether we overwrite */
if (new_name)
{
DBUG_PRINT("info" , ("rename to: %s@%s" , new_dbname->str, new_name->str));
if (!find_named_event(new_dbname, new_name, table))
{
my_error(ER_EVENT_ALREADY_EXISTS, MYF(0 ), new_name->str);
goto end;
}
}
/*
. . . and then if there is such an event . Don ' t exchange the blocks
because you will get error 120 from table handler because new_name will
overwrite the key and SE will tell us that it cannot find the already found
row ( copied into record [ 1 ] later
*/
if (find_named_event(&parse_data->dbname, &parse_data->name, table))
{
my_error(ER_EVENT_DOES_NOT_EXIST, MYF(0 ), parse_data->name.str);
goto end;
}
store_record(table,record[1 ]);
/*
We check whether ALTER EVENT was given dates that are in the past .
However to know how to react , we need the ON COMPLETION type . The
check is deferred to this point because by now we have the previous
setting ( from the event - table ) to fall back on if nothing was specified
in the ALTER EVENT - statement .
*/
if (parse_data->check_dates(thd,
(int ) table->field[ET_FIELD_ON_COMPLETION]->val_int()))
goto end;
/*
mysql_event_fill_row ( ) calls my_error ( ) in case of error so no need to
handle it here
*/
if (mysql_event_fill_row(thd, table, parse_data, sp, saved_mode, TRUE ))
goto end;
if (new_dbname)
{
table->field[ET_FIELD_DB]->store(new_dbname->str, new_dbname->length, scs);
table->field[ET_FIELD_NAME]->store(new_name->str, new_name->length, scs);
}
if ((ret= table->file->ha_update_row(table->record[1 ], table->record[0 ])))
{
table->file->print_error(ret, MYF(0 ));
goto end;
}
ret= 0 ;
end:
if (table)
thd->commit_whole_transaction_and_close_tables();
thd->mdl_context.rollback_to_savepoint(mdl_savepoint);
thd->variables.sql_mode= saved_mode;
DBUG_RETURN(MY_TEST(ret));
}
/**
Delete event record from mysql . event table .
@ param [ in , out ] thd thread handle
@ param [ in ] db Database name
@ param [ in ] name Event name
@ param [ in ] drop_if_exists DROP IF EXISTS clause was specified .
If set , and the event does not exist ,
the error is downgraded to a warning .
@ retval FALSE success
@ retval TRUE error ( reported )
*/
bool
Event_db_repository::drop_event(THD *thd, const LEX_CSTRING *db,
const LEX_CSTRING *name,
bool drop_if_exists)
{
TABLE *table= NULL;
/*
Take a savepoint to release only the lock on mysql . event
table at the end but keep the global read lock and
possible other locks taken by the caller .
*/
MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint();
int ret= 1 ;
DBUG_ENTER("Event_db_repository::drop_event" );
DBUG_PRINT("enter" , ("%s@%s" , db->str, name->str));
if (open_event_table(thd, TL_WRITE, &table))
goto end;
if (!find_named_event(db, name, table))
{
if ((ret= table->file->ha_delete_row(table->record[0 ])))
table->file->print_error(ret, MYF(0 ));
goto end;
}
/* Event not found */
if (!drop_if_exists)
{
my_error(ER_EVENT_DOES_NOT_EXIST, MYF(0 ), name->str);
goto end;
}
push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
ER_SP_DOES_NOT_EXIST, ER_THD(thd, ER_SP_DOES_NOT_EXIST),
"Event" , name->str);
ret= 0 ;
end:
if (table)
thd->commit_whole_transaction_and_close_tables();
thd->mdl_context.rollback_to_savepoint(mdl_savepoint);
DBUG_RETURN(MY_TEST(ret));
}
/**
Positions the internal pointer of ` table ` to the place where ( db , name )
is stored .
In case search succeeded , the table cursor points at the found row .
@ param [ in ] db database name
@ param [ in ] name event name
@ param [ in , out ] table mysql . event table
@ retval FALSE an event with such db / name key exists
@ retval TRUE no record found or an error occurred .
*/
bool
Event_db_repository::find_named_event(const LEX_CSTRING *db,
const LEX_CSTRING *name,
TABLE *table)
{
uchar key[MAX_KEY_LENGTH];
DBUG_ENTER("Event_db_repository::find_named_event" );
DBUG_PRINT("enter" , ("name: %.*s" , (int ) name->length, name->str));
/*
Create key to find row . We have to use field - > store ( ) to be able to
handle VARCHAR and CHAR fields .
Assumption here is that the two first fields in the table are
' db ' and ' name ' and the first key is the primary key over the
same fields .
*/
if (db->length > table->field[ET_FIELD_DB]->field_length ||
name->length > table->field[ET_FIELD_NAME]->field_length ||
table->s->keys == 0 ||
table->key_info[0 ].user_defined_key_parts != 2 ||
table->key_info[0 ].key_part[0 ].fieldnr != ET_FIELD_DB+1 ||
table->key_info[0 ].key_part[1 ].fieldnr != ET_FIELD_NAME+1 )
DBUG_RETURN(TRUE );
table->field[ET_FIELD_DB]->store(db->str, db->length, &my_charset_bin);
table->field[ET_FIELD_NAME]->store(name->str, name->length, &my_charset_bin);
key_copy(key, table->record[0 ], table->key_info, table->key_info->key_length);
if (table->file->ha_index_read_idx_map(table->record[0 ], 0 , key,
HA_WHOLE_KEY,
HA_READ_KEY_EXACT))
{
DBUG_PRINT("info" , ("Row not found" ));
DBUG_RETURN(TRUE );
}
DBUG_PRINT("info" , ("Row found!" ));
DBUG_RETURN(FALSE );
}
/*
Drops all events in the selected database , from mysql . event .
SYNOPSIS
Event_db_repository : : drop_schema_events ( )
thd Thread
schema The database to clean from events
*/
void
Event_db_repository::drop_schema_events(THD *thd, const LEX_CSTRING *schema)
{
int ret= 0 ;
TABLE *table= NULL;
READ_RECORD read_record_info;
enum enum_events_table_field field= ET_FIELD_DB;
DBUG_ENTER("Event_db_repository::drop_schema_events" );
DBUG_PRINT("enter" , ("field: %d schema: %s" , field, schema->str));
start_new_trans new_trans(thd);
if (open_event_table(thd, TL_WRITE, &table))
{
new_trans.restore_old_transaction();
DBUG_VOID_RETURN;
}
/* only enabled events are in memory, so we go now and delete the rest */
if (init_read_record(&read_record_info, thd, table, NULL, NULL, 1 , 0 , FALSE ))
goto end;
while (!ret && !(read_record_info.read_record()))
{
char *et_field= get_field(thd->mem_root, table->field[field]);
/* et_field may be NULL if the table is corrupted or out of memory */
if (et_field)
{
LEX_CSTRING et_field_lex= { et_field, strlen(et_field) };
DBUG_PRINT("info" , ("Current event %s name=%s" , et_field,
get_field(thd->mem_root,
table->field[ET_FIELD_NAME])));
if (!sortcmp_lex_string(&et_field_lex, schema, system_charset_info))
{
DBUG_PRINT("info" , ("Dropping" ));
if ((ret= table->file->ha_delete_row(table->record[0 ])))
table->file->print_error(ret, MYF(0 ));
}
}
}
end_read_record(&read_record_info);
end:
thd->commit_whole_transaction_and_close_tables();
new_trans.restore_old_transaction();
DBUG_VOID_RETURN;
}
/**
Looks for a named event in mysql . event and then loads it from
the table .
@ pre The given thread does not have open tables .
@ retval FALSE success
@ retval TRUE error
*/
bool
Event_db_repository::load_named_event(THD *thd, const LEX_CSTRING *dbname,
const LEX_CSTRING *name,
Event_basic *etn)
{
bool ret;
TABLE_LIST event_table;
DBUG_ENTER("Event_db_repository::load_named_event" );
DBUG_PRINT("enter" ,("thd: %p name: %*s" , thd,
(int ) name->length, name->str));
start_new_trans new_trans(thd);
/* Reset sql_mode during data dictionary operations. */
Sql_mode_instant_set sms(thd, 0 );
event_table.init_one_table(&MYSQL_SCHEMA_NAME, &MYSQL_EVENT_NAME, 0 , TL_READ);
/*
We don ' t use open_event_table ( ) here to make sure that SHOW
CREATE EVENT works properly in transactional context , and
does not release transactional metadata locks when the
event table is closed .
*/
if (!(ret= open_system_tables_for_read(thd, &event_table)))
{
if (table_intact.check(event_table.table, &event_table_def))
{
thd->commit_whole_transaction_and_close_tables();
new_trans.restore_old_transaction();
my_error(ER_EVENT_OPEN_TABLE_FAILED, MYF(0 ));
DBUG_RETURN(TRUE );
}
if ((ret= find_named_event(dbname, name, event_table.table)))
my_error(ER_EVENT_DOES_NOT_EXIST, MYF(0 ), name->str);
else if ((ret= etn->load_from_row(thd, event_table.table)))
my_error(ER_CANNOT_LOAD_FROM_TABLE_V2, MYF(0 ), "mysql" , "event" );
thd->commit_whole_transaction_and_close_tables();
}
new_trans.restore_old_transaction();
DBUG_RETURN(ret);
}
/**
Update the event record in mysql . event table with a changed status
and / or last execution time .
@ pre The thread handle does not have open tables .
*/
bool
Event_db_repository::
update_timing_fields_for_event(THD *thd,
const LEX_CSTRING *event_db_name,
const LEX_CSTRING *event_name,
my_time_t last_executed,
ulonglong status)
{
TABLE *table= NULL;
Field **fields;
int ret= 1 ;
MYSQL_TIME time;
DBUG_ENTER("Event_db_repository::update_timing_fields_for_event" );
DBUG_ASSERT(thd->security_ctx->master_access & PRIV_IGNORE_READ_ONLY);
/*
Take a savepoint to release only the lock on mysql . event
table at the end but keep the global read lock and
possible other locks taken by the caller .
*/
MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint();
if (open_event_table(thd, TL_WRITE, &table))
DBUG_RETURN(1 );
fields= table->field;
/*
Turn off row binlogging of event timing updates . These are not used
for RBR of events replicated to the slave .
*/
table->file->row_logging= 0 ;
if (find_named_event(event_db_name, event_name, table))
goto end;
store_record(table, record[1 ]);
my_tz_OFFSET0->gmt_sec_to_TIME(&time, last_executed);
fields[ET_FIELD_LAST_EXECUTED]->set_notnull();
fields[ET_FIELD_LAST_EXECUTED]->store_time(&time);
fields[ET_FIELD_STATUS]->set_notnull();
fields[ET_FIELD_STATUS]->store(status, TRUE );
if ((ret= table->file->ha_update_row(table->record[1 ], table->record[0 ])))
{
table->file->print_error(ret, MYF(0 ));
goto end;
}
ret= 0 ;
end:
if (thd->commit_whole_transaction_and_close_tables())
ret= 1 ;
thd->mdl_context.rollback_to_savepoint(mdl_savepoint);
DBUG_RETURN(MY_TEST(ret));
}
/**
Open mysql . db , mysql . user and mysql . event and check whether :
- mysql . db exists and is up to date ( or from a newer version of MySQL ) ,
- mysql . user has column Event_priv at an expected position ,
- mysql . event exists and is up to date ( or from a newer version of
MySQL )
This function is called only when the server is started .
@ pre The passed in thread handle has no open tables .
@ retval FALSE OK
@ retval TRUE Error , an error message is output to the error log .
*/
bool
Event_db_repository::check_system_tables(THD *thd)
{
TABLE_LIST tables;
int ret= FALSE ;
DBUG_ENTER("Event_db_repository::check_system_tables" );
DBUG_PRINT("enter" , ("thd: %p" , thd));
/* Check mysql.event */
tables.init_one_table(&MYSQL_SCHEMA_NAME, &MYSQL_EVENT_NAME, 0 , TL_READ);
if (open_and_lock_tables(thd, &tables, FALSE , MYSQL_LOCK_IGNORE_TIMEOUT))
{
ret= 1 ;
sql_print_error("Cannot open mysql.event" );
}
else
{
if (table_intact.check(tables.table, &event_table_def))
ret= 1 ;
close_mysql_tables(thd);
}
DBUG_RETURN(MY_TEST(ret));
}
/**
@ } ( End of group Event_Scheduler )
*/
Messung V0.5 in Prozent C=96 H=67 G=82
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.11Bemerkung:
(vorverarbeitet am 2026-10-08)
¤
*Bot Zugriff