events.cc 33.5 KB
Newer Older
1
/* Copyright (C) 2004-2006 MySQL AB
unknown's avatar
unknown committed
2 3 4

   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
unknown's avatar
unknown committed
5
   the Free Software Foundation; version 2 of the License.
unknown's avatar
unknown committed
6 7 8 9 10 11 12 13 14 15

   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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */

16 17
#include "mysql_priv.h"
#include "events.h"
18
#include "event_data_objects.h"
unknown's avatar
unknown committed
19
#include "event_db_repository.h"
20
#include "event_queue.h"
21
#include "event_scheduler.h"
unknown's avatar
unknown committed
22
#include "sp_head.h" // for Stored_program_creation_ctx
23

24 25 26 27 28
/**
  @addtogroup Event_Scheduler
  @{
*/

29 30
/*
 TODO list :
unknown's avatar
unknown committed
31 32
 - CREATE EVENT should not go into binary log! Does it now? The SQL statements
   issued by the EVENT are replicated.
33 34 35 36 37 38 39
   I have an idea how to solve the problem at failover. So the status field
   will be ENUM('DISABLED', 'ENABLED', 'SLAVESIDE_DISABLED').
   In this case when CREATE EVENT is replicated it should go into the binary
   as SLAVESIDE_DISABLED if it is ENABLED, when it's created as DISABLEd it
   should be replicated as disabled. If an event is ALTERed as DISABLED the
   query should go untouched into the binary log, when ALTERed as enable then
   it should go as SLAVESIDE_DISABLED. This is regarding the SQL interface.
40 41
   TT routines however modify mysql.event internally and this does not go the
   log so in this case queries has to be injected into the log...somehow... or
42 43 44
   maybe a solution is RBR for this case, because the event may go only from
   ENABLED to DISABLED status change and this is safe for replicating. As well
   an event may be deleted which is also safe for RBR.
unknown's avatar
unknown committed
45 46 47

 - Add logging to file

unknown's avatar
unknown committed
48
*/
unknown's avatar
unknown committed
49 50


51 52 53 54 55 56
/*
  If the user (un)intentionally removes an event directly from mysql.event
  the following sequence has to be used to be able to remove the in-memory
  counterpart.
  1. CREATE EVENT the_name ON SCHEDULE EVERY 1 SECOND DISABLE DO SELECT 1;
  2. DROP EVENT the_name
57

58 59 60 61 62 63 64 65
  In other words, the first one will create a row in mysql.event . In the
  second step because there will be a line, disk based drop will pass and
  the scheduler will remove the memory counterpart. The reason is that
  in-memory queue does not check whether the event we try to drop from memory
  is disabled. Disabled events are not kept in-memory because they are not
  eligible for execution.
*/

66 67 68 69 70 71 72
/*
  Keep the order of the first to as in var_typelib
  sys_var_event_scheduler::value_ptr() references this array. Keep in
  mind!
*/
static const char *opt_event_scheduler_state_names[]=
    { "OFF", "ON", "0", "1", "DISABLED", NullS };
73

74
const TYPELIB Events::opt_typelib=
75
{
76 77 78 79 80 81 82 83 84 85 86 87 88 89
  array_elements(opt_event_scheduler_state_names)-1,
  "",
  opt_event_scheduler_state_names,
  NULL
};


/*
  The order should not be changed. We consider OFF to be equivalent of INT 0
  And ON of 1. If OFF & ON are interchanged the logic in
  sys_var_event_scheduler::update() will be broken!
*/
static const char *var_event_scheduler_state_names[]= { "OFF", "ON", NullS };

90
const TYPELIB Events::var_typelib=
91 92
{
  array_elements(var_event_scheduler_state_names)-1,
93
  "",
94
  var_event_scheduler_state_names,
95 96 97
  NULL
};

98 99 100 101 102 103 104
Event_queue *Events::event_queue;
Event_scheduler *Events::scheduler;
Event_db_repository *Events::db_repository;
enum Events::enum_opt_event_scheduler
Events::opt_event_scheduler= Events::EVENTS_OFF;
pthread_mutex_t Events::LOCK_event_metadata;
bool Events::check_system_tables_error= FALSE;
105

106 107 108

/*
  Compares 2 LEX strings regarding case.
unknown's avatar
unknown committed
109

110
  SYNOPSIS
111
    sortcmp_lex_string()
112 113 114
      s   First LEX_STRING
      t   Second LEX_STRING
      cs  Charset
115

116
  RETURN VALUE
117 118 119
   -1   s < t
    0   s == t
    1   s > t
120
*/
121

unknown's avatar
unknown committed
122 123
int sortcmp_lex_string(LEX_STRING s, LEX_STRING t, CHARSET_INFO *cs)
{
unknown's avatar
unknown committed
124 125
 return cs->coll->strnncollsp(cs, (uchar *) s.str,s.length,
                                  (uchar *) t.str,t.length, 0);
unknown's avatar
unknown committed
126 127
}

128

129 130
/**
  @brief Initialize the start up option of the Events scheduler.
unknown's avatar
unknown committed
131

132 133 134 135
  Do not initialize the scheduler subsystem yet - the initialization
  is split into steps as it has to fit into the common MySQL
  initialization framework.
  No locking as this is called only at start up.
unknown's avatar
unknown committed
136

137 138 139 140 141 142
  @param[in,out]  argument  The value of the argument. If this value
                            is found in the typelib, the argument is
                            updated.

  @retval TRUE  unknown option value
  @retval FALSE success
unknown's avatar
unknown committed
143
*/
unknown's avatar
unknown committed
144

145 146
bool
Events::set_opt_event_scheduler(char *argument)
unknown's avatar
unknown committed
147
{
148
  if (argument == NULL)
149
    opt_event_scheduler= Events::EVENTS_ON;
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
  else
  {
    int type;
    /*
      type=   1   2      3   4      5
           (OFF | ON) - (0 | 1) (DISABLE )
    */
    const static enum enum_opt_event_scheduler type2state[]=
    { EVENTS_OFF, EVENTS_ON, EVENTS_OFF, EVENTS_ON, EVENTS_DISABLED };

    type= find_type(argument, &opt_typelib, 1);

    DBUG_ASSERT(type >= 0 && type <= 5); /* guaranteed by find_type */

    if (type == 0)
    {
      fprintf(stderr, "Unknown option to event-scheduler: %s\n", argument);
      return TRUE;
    }
    opt_event_scheduler= type2state[type-1];
  }
  return FALSE;
unknown's avatar
unknown committed
172 173
}

unknown's avatar
unknown committed
174

175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
/**
  Return a string representation of the current scheduler mode.
*/

const char *
Events::get_opt_event_scheduler_str()
{
  const char *str;

  pthread_mutex_lock(&LOCK_event_metadata);
  str= opt_typelib.type_names[(int) opt_event_scheduler];
  pthread_mutex_unlock(&LOCK_event_metadata);

  return str;
}


/**
  Push an error into the error stack if the system tables are
  not up to date.
*/

bool Events::check_if_system_tables_error()
{
  DBUG_ENTER("Events::check_if_system_tables_error");

  if (check_system_tables_error)
  {
    my_error(ER_EVENTS_DB_ERROR, MYF(0));
    DBUG_RETURN(TRUE);
  }

  DBUG_RETURN(FALSE);
}


/**
212 213 214 215 216
  Reconstructs interval expression from interval type and expression
  value that is in form of a value of the smalles entity:
  For
    YEAR_MONTH - expression is in months
    DAY_MINUTE - expression is in minutes
unknown's avatar
unknown committed
217

218 219
  SYNOPSIS
    Events::reconstruct_interval_expression()
220 221 222
      buf         Preallocated String buffer to add the value to
      interval    The interval type (for instance YEAR_MONTH)
      expression  The value in the lowest entity
unknown's avatar
unknown committed
223

unknown's avatar
unknown committed
224
  RETURN VALUE
225 226
    0  OK
    1  Error
227 228 229
*/

int
unknown's avatar
unknown committed
230 231
Events::reconstruct_interval_expression(String *buf, interval_type interval,
                                        longlong expression)
232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249
{
  ulonglong expr= expression;
  char tmp_buff[128], *end;
  bool close_quote= TRUE;
  int multipl= 0;
  char separator=':';

  switch (interval) {
  case INTERVAL_YEAR_MONTH:
    multipl= 12;
    separator= '-';
    goto common_1_lev_code;
  case INTERVAL_DAY_HOUR:
    multipl= 24;
    separator= ' ';
    goto common_1_lev_code;
  case INTERVAL_HOUR_MINUTE:
  case INTERVAL_MINUTE_SECOND:
unknown's avatar
unknown committed
250
    multipl= 60;
251 252 253 254 255 256 257 258
common_1_lev_code:
    buf->append('\'');
    end= longlong10_to_str(expression/multipl, tmp_buff, 10);
    buf->append(tmp_buff, (uint) (end- tmp_buff));
    expr= expr - (expr/multipl)*multipl;
    break;
  case INTERVAL_DAY_MINUTE:
  {
259
    ulonglong tmp_expr= expr;
260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276

    tmp_expr/=(24*60);
    buf->append('\'');
    end= longlong10_to_str(tmp_expr, tmp_buff, 10);
    buf->append(tmp_buff, (uint) (end- tmp_buff));// days
    buf->append(' ');

    tmp_expr= expr - tmp_expr*(24*60);//minutes left
    end= longlong10_to_str(tmp_expr/60, tmp_buff, 10);
    buf->append(tmp_buff, (uint) (end- tmp_buff));// hours

    expr= tmp_expr - (tmp_expr/60)*60;
    /* the code after the switch will finish */
  }
    break;
  case INTERVAL_HOUR_SECOND:
  {
277
    ulonglong tmp_expr= expr;
278 279 280 281 282 283 284 285 286 287 288 289 290

    buf->append('\'');
    end= longlong10_to_str(tmp_expr/3600, tmp_buff, 10);
    buf->append(tmp_buff, (uint) (end- tmp_buff));// hours
    buf->append(':');

    tmp_expr= tmp_expr - (tmp_expr/3600)*3600;
    end= longlong10_to_str(tmp_expr/60, tmp_buff, 10);
    buf->append(tmp_buff, (uint) (end- tmp_buff));// minutes

    expr= tmp_expr - (tmp_expr/60)*60;
    /* the code after the switch will finish */
  }
unknown's avatar
unknown committed
291
    break;
292 293
  case INTERVAL_DAY_SECOND:
  {
294
    ulonglong tmp_expr= expr;
295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313

    tmp_expr/=(24*3600);
    buf->append('\'');
    end= longlong10_to_str(tmp_expr, tmp_buff, 10);
    buf->append(tmp_buff, (uint) (end- tmp_buff));// days
    buf->append(' ');

    tmp_expr= expr - tmp_expr*(24*3600);//seconds left
    end= longlong10_to_str(tmp_expr/3600, tmp_buff, 10);
    buf->append(tmp_buff, (uint) (end- tmp_buff));// hours
    buf->append(':');

    tmp_expr= tmp_expr - (tmp_expr/3600)*3600;
    end= longlong10_to_str(tmp_expr/60, tmp_buff, 10);
    buf->append(tmp_buff, (uint) (end- tmp_buff));// minutes

    expr= tmp_expr - (tmp_expr/60)*60;
    /* the code after the switch will finish */
  }
314
    break;
315 316 317 318
  case INTERVAL_DAY_MICROSECOND:
  case INTERVAL_HOUR_MICROSECOND:
  case INTERVAL_MINUTE_MICROSECOND:
  case INTERVAL_SECOND_MICROSECOND:
319
  case INTERVAL_MICROSECOND:
unknown's avatar
unknown committed
320
    my_error(ER_NOT_SUPPORTED_YET, MYF(0), "MICROSECOND");
321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338
    return 1;
    break;
  case INTERVAL_QUARTER:
    expr/= 3;
    close_quote= FALSE;
    break;
  case INTERVAL_WEEK:
    expr/= 7;
  default:
    close_quote= FALSE;
    break;
  }
  if (close_quote)
    buf->append(separator);
  end= longlong10_to_str(expr, tmp_buff, 10);
  buf->append(tmp_buff, (uint) (end- tmp_buff));
  if (close_quote)
    buf->append('\'');
unknown's avatar
unknown committed
339

340 341 342
  return 0;
}

343

344 345 346 347 348 349 350 351 352 353 354 355 356 357
/**
  Create a new event.

  @param[in,out]  thd            THD
  @param[in]      parse_data     Event's data from parsing stage
  @param[in]      if_not_exists  Whether IF NOT EXISTS was
                                 specified
  In case there is an event with the same name (db) and
  IF NOT EXISTS is specified, an warning is put into the stack.
  @sa Events::drop_event for the notes about locking, pre-locking
  and Events DDL.

  @retval  FALSE  OK
  @retval  TRUE   Error (reported)
358 359
*/

360 361 362
bool
Events::create_event(THD *thd, Event_parse_data *parse_data,
                     bool if_not_exists)
363
{
364 365
  int ret;
  DBUG_ENTER("Events::create_event");
366

367 368 369 370 371 372 373 374
  /*
    Let's commit the transaction first - MySQL manual specifies
    that a DDL issues an implicit commit, and it doesn't say "successful
    DDL", so that an implicit commit is a property of any successfully
    parsed DDL statement.
  */
  if (end_active_trans(thd))
    DBUG_RETURN(TRUE);
375

376 377
  if (check_if_system_tables_error())
    DBUG_RETURN(TRUE);
378

379 380 381 382 383 384 385
  /*
    Perform semantic checks outside of Event_db_repository:
    once CREATE EVENT is supported in prepared statements, the
    checks will be moved to PREPARE phase.
  */
  if (parse_data->check_parse_data(thd))
    DBUG_RETURN(TRUE);
386

387 388
  /* At create, one of them must be set */
  DBUG_ASSERT(parse_data->expression || parse_data->execute_at);
389

390 391 392
  if (check_access(thd, EVENT_ACL, parse_data->dbname.str, 0, 0, 0,
                   is_schema_db(parse_data->dbname.str)))
    DBUG_RETURN(TRUE);
393

394
  if (check_db_dir_existence(parse_data->dbname.str))
395
  {
396
    my_error(ER_BAD_DB_ERROR, MYF(0), parse_data->dbname.str);
397 398
    DBUG_RETURN(TRUE);
  }
399

400 401
  if (parse_data->do_not_create)
    DBUG_RETURN(FALSE);
402 403 404 405 406 407 408
  /* 
    Turn off row binlogging of this statement and use statement-based 
    so that all supporting tables are updated for CREATE EVENT command.
  */
  if (thd->current_stmt_binlog_row_based)
    thd->clear_current_stmt_binlog_row_based();

409
  pthread_mutex_lock(&LOCK_event_metadata);
410

411
  /* On error conditions my_error() is called so no need to handle here */
412
  if (!(ret= db_repository->create_event(thd, parse_data, if_not_exists)))
413
  {
414 415 416 417 418 419 420
    Event_queue_element *new_element;

    if (!(new_element= new Event_queue_element()))
      ret= TRUE;                                // OOM
    else if ((ret= db_repository->load_named_event(thd, parse_data->dbname,
                                                   parse_data->name,
                                                   new_element)))
421
    {
422 423
      db_repository->drop_event(thd, parse_data->dbname, parse_data->name,
                                TRUE);
424
      delete new_element;
425
    }
426
    else
427
    {
428 429
      /* TODO: do not ignore the out parameter and a possible OOM error! */
      bool created;
430 431 432
      if (event_queue)
        event_queue->create_event(thd, new_element, &created);
      /* Binlog the create event. */
433
      DBUG_ASSERT(thd->query && thd->query_length);
434
      write_bin_log(thd, TRUE, thd->query, thd->query_length);
435
    }
436
  }
437
  pthread_mutex_unlock(&LOCK_event_metadata);
unknown's avatar
unknown committed
438

439 440 441 442
  DBUG_RETURN(ret);
}


443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459
/**
  Alter an event.

  @param[in,out] thd         THD
  @param[in]     parse_data  Event's data from parsing stage
  @param[in]     new_dbname  A new schema name for the event. Set in the case of
                             ALTER EVENT RENAME, otherwise is NULL.
  @param[in]     new_name    A new name for the event. Set in the case of
                             ALTER EVENT RENAME

  Parameter 'et' contains data about dbname and event name.
  Parameter 'new_name' is the new name of the event, if not null
  this means that RENAME TO was specified in the query
  @sa Events::drop_event for the locking notes.

  @retval  FALSE  OK
  @retval  TRUE   error (reported)
460 461
*/

462
bool
463 464
Events::update_event(THD *thd, Event_parse_data *parse_data,
                     LEX_STRING *new_dbname, LEX_STRING *new_name)
465
{
466
  int ret;
467
  Event_queue_element *new_element;
468

469
  DBUG_ENTER("Events::update_event");
470 471 472 473 474 475 476 477 478

  /*
    For consistency, implicit COMMIT should be the first thing in the
    execution chain.
  */
  if (end_active_trans(thd))
    DBUG_RETURN(TRUE);

  if (check_if_system_tables_error())
479
    DBUG_RETURN(TRUE);
480 481 482 483 484 485

  if (parse_data->check_parse_data(thd) || parse_data->do_not_create)
    DBUG_RETURN(TRUE);

  if (check_access(thd, EVENT_ACL, parse_data->dbname.str, 0, 0, 0,
                   is_schema_db(parse_data->dbname.str)))
486
    DBUG_RETURN(TRUE);
487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512

  if (new_dbname)                               /* It's a rename */
  {
    /* Check that the new and the old names differ. */
    if ( !sortcmp_lex_string(parse_data->dbname, *new_dbname,
                             system_charset_info) &&
         !sortcmp_lex_string(parse_data->name, *new_name,
                             system_charset_info))
    {
      my_error(ER_EVENT_SAME_NAME, MYF(0), parse_data->name.str);
      DBUG_RETURN(TRUE);
    }

    /*
      And the user has sufficient privileges to use the target database.
      Do it before checking whether the database exists: we don't want
      to tell the user that a database doesn't exist if they can not
      access it.
    */
    if (check_access(thd, EVENT_ACL, new_dbname->str, 0, 0, 0,
                     is_schema_db(new_dbname->str)))
      DBUG_RETURN(TRUE);

    /* Check that the target database exists */
    if (check_db_dir_existence(new_dbname->str))
    {
513
      my_error(ER_BAD_DB_ERROR, MYF(0), new_dbname->str);
514 515
      DBUG_RETURN(TRUE);
    }
516
  }
517

518 519 520 521 522 523 524
  /* 
    Turn off row binlogging of this statement and use statement-based 
    so that all supporting tables are updated for UPDATE EVENT command.
  */
  if (thd->current_stmt_binlog_row_based)
    thd->clear_current_stmt_binlog_row_based();

525
  pthread_mutex_lock(&LOCK_event_metadata);
526

527
  /* On error conditions my_error() is called so no need to handle here */
528 529
  if (!(ret= db_repository->update_event(thd, parse_data,
                                         new_dbname, new_name)))
530
  {
531 532 533 534 535 536 537
    LEX_STRING dbname= new_dbname ? *new_dbname : parse_data->dbname;
    LEX_STRING name= new_name ? *new_name : parse_data->name;

    if (!(new_element= new Event_queue_element()))
      ret= TRUE;                                // OOM
    else if ((ret= db_repository->load_named_event(thd, dbname, name,
                                                   new_element)))
538 539
    {
      DBUG_ASSERT(ret == OP_LOAD_ERROR);
540
      delete new_element;
541
    }
542
    else
543
    {
544 545 546 547 548 549
      /*
        TODO: check if an update actually has inserted an entry
        into the queue.
        If not, and the element is ON COMPLETION NOT PRESERVE, delete
        it right away.
      */
550 551 552 553
      if (event_queue)
        event_queue->update_event(thd, parse_data->dbname, parse_data->name,
                                  new_element);
      /* Binlog the alter event. */
554
      DBUG_ASSERT(thd->query && thd->query_length);
555
      write_bin_log(thd, TRUE, thd->query, thd->query_length);
556
    }
557
  }
558 559
  pthread_mutex_unlock(&LOCK_event_metadata);

560 561 562 563
  DBUG_RETURN(ret);
}


564
/**
565 566
  Drops an event

567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585
  @param[in,out]  thd        THD
  @param[in]      dbname     Event's schema
  @param[in]      name       Event's name
  @param[in]      if_exists  When this is set and the event does not exist
                             a warning is pushed into the warning stack.
                             Otherwise the operation produces an error.

  @note Similarly to DROP PROCEDURE, we do not allow DROP EVENT
  under LOCK TABLES mode, unless table mysql.event is locked.  To
  ensure that, we do not reset & backup the open tables state in
  this function - if in LOCK TABLES or pre-locking mode, this will
  lead to an error 'Table mysql.event is not locked with LOCK
  TABLES' unless it _is_ locked. In pre-locked mode there is
  another barrier - DROP EVENT commits the current transaction,
  and COMMIT/ROLLBACK is not allowed in stored functions and
  triggers.

  @retval  FALSE  OK
  @retval  TRUE   Error (reported)
586 587
*/

588
bool
589
Events::drop_event(THD *thd, LEX_STRING dbname, LEX_STRING name, bool if_exists)
590
{
591 592
  int ret;
  DBUG_ENTER("Events::drop_event");
593 594 595 596 597 598 599 600 601 602 603 604 605

  /*
    In MySQL, DDL must always commit: since mysql.* tables are
    non-transactional, we must modify them outside a transaction
    to not break atomicity.
    But the second and more important reason to commit here
    regardless whether we're actually changing mysql.event table
    or not is replication: end_active_trans syncs the binary log,
    and unless we run DDL in it's own transaction it may simply
    never appear on the slave in case the outside transaction
    rolls back.
  */
  if (end_active_trans(thd))
606
    DBUG_RETURN(TRUE);
unknown's avatar
unknown committed
607

608 609 610 611 612
  if (check_if_system_tables_error())
    DBUG_RETURN(TRUE);

  if (check_access(thd, EVENT_ACL, dbname.str, 0, 0, 0,
                   is_schema_db(dbname.str)))
613
    DBUG_RETURN(TRUE);
unknown's avatar
unknown committed
614

615 616
  /*
    Turn off row binlogging of this statement and use statement-based so
617 618 619 620 621
    that all supporting tables are updated for DROP EVENT command.
  */
  if (thd->current_stmt_binlog_row_based)
    thd->clear_current_stmt_binlog_row_based();

622 623
  pthread_mutex_lock(&LOCK_event_metadata);
  /* On error conditions my_error() is called so no need to handle here */
624
  if (!(ret= db_repository->drop_event(thd, dbname, name, if_exists)))
625
  {
626 627
    if (event_queue)
      event_queue->drop_event(thd, dbname, name);
628
    /* Binlog the drop event. */
629
    DBUG_ASSERT(thd->query && thd->query_length);
630
    write_bin_log(thd, TRUE, thd->query, thd->query_length);
631
  }
632 633 634 635 636
  pthread_mutex_unlock(&LOCK_event_metadata);
  DBUG_RETURN(ret);
}


637
/**
638 639
  Drops all events from a schema

640 641 642 643 644 645
  @note We allow to drop all events in a schema even if the
  scheduler is disabled. This is to not produce any warnings
  in case of DROP DATABASE and a disabled scheduler.

  @param[in,out]  thd  Thread
  @param[in]      db   ASCIIZ schema name
646 647
*/

648
void
649 650
Events::drop_schema_events(THD *thd, char *db)
{
651
  LEX_STRING const db_lex= { db, strlen(db) };
652 653

  DBUG_ENTER("Events::drop_schema_events");
654
  DBUG_PRINT("enter", ("dropping events from %s", db));
655 656 657 658 659

  /*
    sic: no check if the scheduler is disabled or system tables
    are damaged, as intended.
  */
660 661

  pthread_mutex_lock(&LOCK_event_metadata);
662 663
  if (event_queue)
    event_queue->drop_schema_events(thd, db_lex);
664
  db_repository->drop_schema_events(thd, db_lex);
665 666
  pthread_mutex_unlock(&LOCK_event_metadata);

667
  DBUG_VOID_RETURN;
668 669
}

670

671 672 673
/**
  A helper function to generate SHOW CREATE EVENT output from
  a named event
674 675
*/

676 677
static bool
send_show_create_event(THD *thd, Event_timed *et, Protocol *protocol)
678
{
679 680 681 682 683
  char show_str_buf[10 * STRING_BUFFER_USUAL_SIZE];
  String show_str(show_str_buf, sizeof(show_str_buf), system_charset_info);
  List<Item> field_list;
  LEX_STRING sql_mode;
  const String *tz_name;
684

685 686 687 688
  DBUG_ENTER("send_show_create_event");

  show_str.length(0);
  if (et->get_create_event(thd, &show_str))
689
    DBUG_RETURN(TRUE);
690

691
  field_list.push_back(new Item_empty_string("Event", NAME_CHAR_LEN));
692

693 694 695
  if (sys_var_thd_sql_mode::symbolic_mode_representation(thd, et->sql_mode,
                                                         &sql_mode))
    DBUG_RETURN(TRUE);
unknown's avatar
unknown committed
696

697
  field_list.push_back(new Item_empty_string("sql_mode", sql_mode.length));
698

699
  tz_name= et->time_zone->get_name();
700

701 702
  field_list.push_back(new Item_empty_string("time_zone",
                                             tz_name->length()));
703

704 705
  field_list.push_back(new Item_empty_string("Create Event",
                                             show_str.length()));
706

unknown's avatar
unknown committed
707 708 709 710 711 712 713 714 715
  field_list.push_back(
    new Item_empty_string("character_set_client", MY_CS_NAME_SIZE));

  field_list.push_back(
    new Item_empty_string("collation_connection", MY_CS_NAME_SIZE));

  field_list.push_back(
    new Item_empty_string("Database Collation", MY_CS_NAME_SIZE));

716 717 718
  if (protocol->send_fields(&field_list,
                            Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
    DBUG_RETURN(TRUE);
719

720
  protocol->prepare_for_resend();
721

722 723 724
  protocol->store(et->name.str, et->name.length, system_charset_info);
  protocol->store(sql_mode.str, sql_mode.length, system_charset_info);
  protocol->store(tz_name->ptr(), tz_name->length(), system_charset_info);
725 726
  protocol->store(show_str.c_ptr(), show_str.length(),
                  et->creation_ctx->get_client_cs());
unknown's avatar
unknown committed
727 728 729 730 731 732 733 734 735
  protocol->store(et->creation_ctx->get_client_cs()->csname,
                  strlen(et->creation_ctx->get_client_cs()->csname),
                  system_charset_info);
  protocol->store(et->creation_ctx->get_connection_cl()->name,
                  strlen(et->creation_ctx->get_connection_cl()->name),
                  system_charset_info);
  protocol->store(et->creation_ctx->get_db_cl()->name,
                  strlen(et->creation_ctx->get_db_cl()->name),
                  system_charset_info);
736

737 738
  if (protocol->write())
    DBUG_RETURN(TRUE);
739

740
  send_eof(thd);
741

742 743
  DBUG_RETURN(FALSE);
}
744

745

746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789
/**
  Implement SHOW CREATE EVENT statement

      thd   Thread context
      spn   The name of the event (db, name)

  @retval  FALSE  OK
  @retval  TRUE   error (reported)
*/

bool
Events::show_create_event(THD *thd, LEX_STRING dbname, LEX_STRING name)
{
  Open_tables_state open_tables_backup;
  Event_timed et;
  bool ret;

  DBUG_ENTER("Events::show_create_event");
  DBUG_PRINT("enter", ("name: %s@%s", dbname.str, name.str));

  if (check_if_system_tables_error())
    DBUG_RETURN(TRUE);

  if (check_access(thd, EVENT_ACL, dbname.str, 0, 0, 0,
                   is_schema_db(dbname.str)))
    DBUG_RETURN(TRUE);

  /*
    We would like to allow SHOW CREATE EVENT under LOCK TABLES and
    in pre-locked mode. mysql.event table is marked as a system table.
    This flag reduces the set of its participation scenarios in LOCK TABLES
    operation, and therefore an out-of-bound open of this table
    for reading like the one below (sic, only for reading) is
    more or less deadlock-free. For additional information about when a
    deadlock can occur please refer to the description of 'system table'
    flag.
  */
  thd->reset_n_backup_open_tables_state(&open_tables_backup);
  ret= db_repository->load_named_event(thd, dbname, name, &et);
  thd->restore_backup_open_tables_state(&open_tables_backup);

  if (!ret)
    ret= send_show_create_event(thd, &et, thd->protocol);

790 791
  DBUG_RETURN(ret);
}
792

793

794 795
/**
  Check access rights and fill INFORMATION_SCHEMA.events table.
unknown's avatar
unknown committed
796

797
  @param[in,out]  thd     Thread context
unknown's avatar
unknown committed
798
  @param[in]      tables  The temporary table to fill.
799

800 801 802 803 804 805 806 807
  In MySQL INFORMATION_SCHEMA tables are temporary tables that are
  created and filled on demand. In this function, we fill
  INFORMATION_SCHEMA.events. It is a callback for I_S module, invoked from
  sql_show.cc

  @return Has to be integer, as such is the requirement of the I_S API
  @retval  0  success
  @retval  1  an error, pushed into the error stack
808 809 810
*/

int
811
Events::fill_schema_events(THD *thd, TABLE_LIST *tables, COND * /* cond */)
812
{
813
  char *db= NULL;
814 815
  int ret;
  Open_tables_state open_tables_backup;
816
  DBUG_ENTER("Events::fill_schema_events");
817 818 819

  if (check_if_system_tables_error())
    DBUG_RETURN(1);
820

821 822 823 824 825 826 827
  /*
    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->sql_command == SQLCOM_SHOW_EVENTS)
  {
    DBUG_ASSERT(thd->lex->select_lex.db);
828 829
    if (!is_schema_db(thd->lex->select_lex.db) &&  // There is no events in I_S
        check_access(thd, EVENT_ACL, thd->lex->select_lex.db, 0, 0, 0, 0))
830 831 832
      DBUG_RETURN(1);
    db= thd->lex->select_lex.db;
  }
833 834 835 836 837 838 839 840 841 842 843
  /*
    Reset and backup of the currently open tables in this thread
    is a way to allow SELECTs from INFORMATION_SCHEMA.events under
    LOCK TABLES and in pre-locked mode. See also
    Events::show_create_event for additional comments.
  */
  thd->reset_n_backup_open_tables_state(&open_tables_backup);
  ret= db_repository->fill_schema_events(thd, tables, db);
  thd->restore_backup_open_tables_state(&open_tables_backup);

  DBUG_RETURN(ret);
844
}
845 846


847 848 849 850 851 852 853 854 855 856
/*
  Inits the scheduler's structures.

  SYNOPSIS
    Events::init()

  NOTES
    This function is not synchronized.

  RETURN VALUE
857 858
    FALSE  OK
    TRUE   Error in case the scheduler can't start
859 860
*/

unknown's avatar
unknown committed
861
bool
862
Events::init(my_bool opt_noacl)
863
{
864 865
  THD *thd;
  bool res= FALSE;
866

867
  DBUG_ENTER("Events::init");
868

869 870 871 872
  /* Disable the scheduler if running with --skip-grant-tables */
  if (opt_noacl)
    opt_event_scheduler= EVENTS_DISABLED;

873

874 875
  /* We need a temporary THD during boot */
  if (!(thd= new THD()))
unknown's avatar
unknown committed
876
  {
877 878 879 880 881 882 883 884 885 886
    res= TRUE;
    goto end;
  }
  /*
    The thread stack does not start from this function but we cannot
    guess the real value. So better some value that doesn't assert than
    no value.
  */
  thd->thread_stack= (char*) &thd;
  thd->store_globals();
887
  lex_start(thd);
888

889 890 891 892 893
  /*
    We will need Event_db_repository anyway, even if the scheduler is
    disabled - to perform events DDL.
  */
  if (!(db_repository= new Event_db_repository))
894
  {
895
    res= TRUE; /* fatal error: request unireg_abort */
896 897 898
    goto end;
  }

899 900 901 902 903
  /*
    Since we allow event DDL even if the scheduler is disabled,
    check the system tables, as we might need them.
  */
  if (Event_db_repository::check_system_tables(thd))
904
  {
905 906 907 908 909 910 911 912
    sql_print_error("Event Scheduler: An error occurred when initializing "
                    "system tables.%s",
                    opt_event_scheduler == EVENTS_DISABLED ?
                    "" : " Disabling the Event Scheduler.");

    /* Disable the scheduler since the system tables are not up to date */
    opt_event_scheduler= EVENTS_DISABLED;
    check_system_tables_error= TRUE;
913
    goto end;
unknown's avatar
unknown committed
914
  }
915

916 917 918 919 920 921 922
  /*
    Was disabled explicitly from the command line, or because we're running
    with --skip-grant-tables, or because we have no system tables.
  */
  if (opt_event_scheduler == Events::EVENTS_DISABLED)
    goto end;

923

924 925
  DBUG_ASSERT(opt_event_scheduler == Events::EVENTS_ON ||
              opt_event_scheduler == Events::EVENTS_OFF);
926

927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942
  if (!(event_queue= new Event_queue) ||
      !(scheduler= new Event_scheduler(event_queue)))
  {
    res= TRUE; /* fatal error: request unireg_abort */
    goto end;
  }

  if (event_queue->init_queue(thd) || load_events_from_db(thd) ||
      opt_event_scheduler == EVENTS_ON && scheduler->start())
  {
    sql_print_error("Event Scheduler: Error while loading from disk.");
    res= TRUE; /* fatal error: request unireg_abort */
    goto end;
  }
  Event_worker_thread::init(db_repository);

943
end:
944 945 946 947 948 949
  if (res)
  {
    delete db_repository;
    delete event_queue;
    delete scheduler;
  }
950 951 952 953 954
  delete thd;
  /* Remember that we don't have a THD */
  my_pthread_setspecific_ptr(THR_THD,  NULL);

  DBUG_RETURN(res);
955 956 957 958 959 960 961
}


/*
  Cleans up scheduler's resources. Called at server shutdown.

  SYNOPSIS
unknown's avatar
unknown committed
962
    Events::deinit()
963 964 965 966 967 968

  NOTES
    This function is not synchronized.
*/

void
unknown's avatar
unknown committed
969
Events::deinit()
970
{
unknown's avatar
unknown committed
971
  DBUG_ENTER("Events::deinit");
972

973 974 975 976 977 978
  if (opt_event_scheduler != EVENTS_DISABLED)
  {
    delete scheduler;
    scheduler= NULL;                            /* safety */
    delete event_queue;
    event_queue= NULL;                          /* safety */
979
  }
980

981 982 983
  delete db_repository;
  db_repository= NULL;                          /* safety */

984 985 986 987
  DBUG_VOID_RETURN;
}


988
/**
989 990 991 992 993 994 995 996 997 998
  Inits Events mutexes

  SYNOPSIS
    Events::init_mutexes()
      thd  Thread
*/

void
Events::init_mutexes()
{
999
  pthread_mutex_init(&LOCK_event_metadata, MY_MUTEX_INIT_FAST);
1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012
}


/*
  Destroys Events mutexes

  SYNOPSIS
    Events::destroy_mutexes()
*/

void
Events::destroy_mutexes()
{
1013
  pthread_mutex_destroy(&LOCK_event_metadata);
unknown's avatar
unknown committed
1014 1015 1016
}


1017
/*
1018 1019 1020 1021
  Dumps the internal status of the scheduler and the memory cache
  into a table with two columns - Name & Value. Different properties
  which could be useful for debugging for instance deadlocks are
  returned.
1022 1023 1024 1025 1026

  SYNOPSIS
    Events::dump_internal_status()
*/

1027 1028
void
Events::dump_internal_status()
1029
{
1030
  DBUG_ENTER("Events::dump_internal_status");
1031 1032 1033
  puts("\n\n\nEvents status:");
  puts("LLA = Last Locked At  LUA = Last Unlocked At");
  puts("WOC = Waiting On Condition  DL = Data Locked");
1034

1035 1036 1037 1038 1039 1040 1041 1042
  pthread_mutex_lock(&LOCK_event_metadata);
  if (opt_event_scheduler == EVENTS_DISABLED)
    puts("The Event Scheduler is disabled");
  else
  {
    scheduler->dump_internal_status();
    event_queue->dump_internal_status();
  }
1043

1044
  pthread_mutex_unlock(&LOCK_event_metadata);
1045
  DBUG_VOID_RETURN;
1046 1047 1048
}


1049
/**
1050
  Starts or stops the event scheduler thread.
unknown's avatar
unknown committed
1051

1052 1053
  @retval FALSE success
  @retval TRUE  error
unknown's avatar
unknown committed
1054 1055
*/

1056
bool
1057
Events::switch_event_scheduler_state(enum_opt_event_scheduler new_state)
1058
{
1059
  bool ret= FALSE;
1060

1061
  DBUG_ENTER("Events::switch_event_scheduler_state");
1062

1063 1064
  DBUG_ASSERT(new_state == Events::EVENTS_ON ||
              new_state == Events::EVENTS_OFF);
1065

1066 1067 1068 1069 1070 1071
  /*
    If the scheduler was disabled because there are no/bad
    system tables, produce a more meaningful error message
    than ER_OPTION_PREVENTS_STATEMENT
  */
  if (check_if_system_tables_error())
1072 1073
    DBUG_RETURN(TRUE);

1074
  pthread_mutex_lock(&LOCK_event_metadata);
1075

1076
  if (opt_event_scheduler == EVENTS_DISABLED)
1077
  {
1078 1079
    my_error(ER_OPTION_PREVENTS_STATEMENT,
             MYF(0), "--event-scheduler=DISABLED or --skip-grant-tables");
1080
    ret= TRUE;
1081
    goto end;
1082 1083
  }

1084
  if (new_state == EVENTS_ON)
1085
    ret= scheduler->start();
1086
  else
1087 1088 1089
    ret= scheduler->stop();

  if (ret)
1090
  {
1091 1092
    my_error(ER_EVENT_SET_VAR_ERROR, MYF(0));
    goto end;
1093 1094
  }

1095
  opt_event_scheduler= new_state;
1096

1097 1098
end:
  pthread_mutex_unlock(&LOCK_event_metadata);
1099 1100
  DBUG_RETURN(ret);
}
1101 1102


1103 1104 1105
/**
  Loads all ENABLED events from mysql.event into a prioritized
  queue.
1106

1107 1108 1109 1110 1111
  This function is called during the server start up. It reads
  every event, computes the next execution time, and if the event
  needs execution, adds it to a prioritized queue. Otherwise, if
  ON COMPLETION DROP is specified, the event is automatically
  removed from the table.
1112

1113
  @param[in,out] thd Thread context. Used for memory allocation in some cases.
1114

1115 1116 1117 1118
  @retval  FALSE  success
  @retval  TRUE   error, the load is aborted

  @note Reports the error to the console
1119 1120
*/

1121
bool
1122 1123 1124 1125
Events::load_events_from_db(THD *thd)
{
  TABLE *table;
  READ_RECORD read_record_info;
1126
  bool ret= TRUE;
1127
  uint count= 0;
1128
  ulong saved_master_access;
1129 1130

  DBUG_ENTER("Events::load_events_from_db");
unknown's avatar
unknown committed
1131
  DBUG_PRINT("enter", ("thd: 0x%lx", (long) thd));
1132

1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146
  /*
    NOTE: even if we run in read-only mode, we should be able to lock the
    mysql.event table for writing. In order to achieve this, we should call
    mysql_lock_tables() under the super user.
  */

  saved_master_access= thd->security_ctx->master_access;
  thd->security_ctx->master_access |= SUPER_ACL;

  ret= db_repository->open_event_table(thd, TL_WRITE, &table);

  thd->security_ctx->master_access= saved_master_access;

  if (ret)
1147
  {
1148 1149
    sql_print_error("Event Scheduler: Failed to open table mysql.event");
    DBUG_RETURN(TRUE);
1150 1151
  }

1152
  init_read_record(&read_record_info, thd, table, NULL, 0, 1);
1153 1154 1155
  while (!(read_record_info.read_record(&read_record_info)))
  {
    Event_queue_element *et;
1156 1157 1158
    bool created;
    bool drop_on_completion;

1159
    if (!(et= new Event_queue_element))
1160 1161
      goto end;

1162 1163
    DBUG_PRINT("info", ("Loading event from row."));

1164
    if (et->load_from_row(thd, table))
1165
    {
1166
      sql_print_error("Event Scheduler: "
1167 1168
                      "Error while loading events from mysql.event. "
                      "The table probably contains bad data or is corrupted");
1169
      delete et;
1170
      goto end;
1171
    }
1172 1173
    drop_on_completion= (et->on_completion ==
                         Event_queue_element::ON_COMPLETION_DROP);
1174 1175


1176
    if (event_queue->create_event(thd, et, &created))
1177
    {
1178 1179 1180
      /* Out of memory */
      delete et;
      goto end;
1181
    }
1182 1183 1184
    if (created)
      count++;
    else if (drop_on_completion)
1185 1186
    {
      /*
1187
        If not created, a stale event - drop if immediately if
1188 1189 1190 1191 1192 1193
        ON COMPLETION NOT PRESERVE.
        XXX: This won't be replicated, thus the drop won't appear in
             in the slave. When the slave is restarted it will drop events.
             However, as the slave will be "out of sync", it might happen that
             an event created on the master, after master restart, won't be
             replicated to the slave correctly, as the create will fail there.
1194
      */
1195 1196 1197 1198 1199
      int rc= table->file->ha_delete_row(table->record[0]);
      if (rc)
      {
        table->file->print_error(rc, MYF(0));
        goto end;
1200 1201 1202
      }
    }
  }
1203 1204 1205 1206
  sql_print_information("Event Scheduler: Loaded %d event%s",
                        count, (count == 1) ? "" : "s");
  ret= FALSE;

1207 1208 1209 1210 1211 1212 1213
end:
  end_read_record(&read_record_info);

  close_thread_tables(thd);

  DBUG_RETURN(ret);
}
1214 1215 1216 1217

/**
  @} (End of group Event_Scheduler)
*/