event_data_objects.cc 43.3 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
#define MYSQL_LEX 1
17 18
#include "mysql_priv.h"
#include "events.h"
19
#include "event_data_objects.h"
unknown's avatar
unknown committed
20
#include "event_db_repository.h"
21 22
#include "sp_head.h"

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

unknown's avatar
unknown committed
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
/*************************************************************************/

/**
  Event_creation_ctx -- creation context of events.
*/

class Event_creation_ctx :public Stored_program_creation_ctx,
                          public Sql_alloc
{
public:
  static bool load_from_db(THD *thd,
                           MEM_ROOT *event_mem_root,
                           const char *db_name,
                           const char *event_name,
                           TABLE *event_tbl,
                           Stored_program_creation_ctx **ctx);

public:
  virtual Stored_program_creation_ctx *clone(MEM_ROOT *mem_root)
  {
    return new (mem_root)
               Event_creation_ctx(m_client_cs, m_connection_cl, m_db_cl);
  }

protected:
  virtual Object_creation_ctx *create_backup_ctx(THD *thd) const
  {
    /*
      We can avoid usual backup/restore employed in stored programs since we
      know that this is a top level statement and the worker thread is
      allocated exclusively to execute this event.
    */

    return NULL;
  }

private:
  Event_creation_ctx(CHARSET_INFO *client_cs,
                     CHARSET_INFO *connection_cl,
                     CHARSET_INFO *db_cl)
    : Stored_program_creation_ctx(client_cs, connection_cl, db_cl)
  { }
};

/**************************************************************************
  Event_creation_ctx implementation.
**************************************************************************/

bool
Event_creation_ctx::load_from_db(THD *thd,
                                 MEM_ROOT *event_mem_root,
                                 const char *db_name,
                                 const char *event_name,
                                 TABLE *event_tbl,
                                 Stored_program_creation_ctx **ctx)
{
  /* Load character set/collation attributes. */

  CHARSET_INFO *client_cs;
  CHARSET_INFO *connection_cl;
  CHARSET_INFO *db_cl;

  bool invalid_creation_ctx= FALSE;

  if (load_charset(event_mem_root,
                   event_tbl->field[ET_FIELD_CHARACTER_SET_CLIENT],
                   thd->variables.character_set_client,
                   &client_cs))
  {
    sql_print_warning("Event '%s'.'%s': invalid value "
                      "in column mysql.event.character_set_client.",
                      (const char *) db_name,
                      (const char *) event_name);

    invalid_creation_ctx= TRUE;
  }

  if (load_collation(event_mem_root,
                     event_tbl->field[ET_FIELD_COLLATION_CONNECTION],
                     thd->variables.collation_connection,
                     &connection_cl))
  {
    sql_print_warning("Event '%s'.'%s': invalid value "
                      "in column mysql.event.collation_connection.",
                      (const char *) db_name,
                      (const char *) event_name);

    invalid_creation_ctx= TRUE;
  }

  if (load_collation(event_mem_root,
                     event_tbl->field[ET_FIELD_DB_COLLATION],
                     NULL,
                     &db_cl))
  {
    sql_print_warning("Event '%s'.'%s': invalid value "
                      "in column mysql.event.db_collation.",
                      (const char *) db_name,
                      (const char *) event_name);

    invalid_creation_ctx= TRUE;
  }

  /*
    If we failed to resolve the database collation, load the default one
    from the disk.
  */

  if (!db_cl)
    db_cl= get_default_db_collation(thd, db_name);

  /* Create the context. */

  *ctx= new Event_creation_ctx(client_cs, connection_cl, db_cl);

  return invalid_creation_ctx;
}

/*************************************************************************/

148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
/*
  Initiliazes dbname and name of an Event_queue_element_for_exec
  object

  SYNOPSIS
    Event_queue_element_for_exec::init()

  RETURN VALUE
    FALSE  OK
    TRUE   Error (OOM)
*/

bool
Event_queue_element_for_exec::init(LEX_STRING db, LEX_STRING n)
{
  if (!(dbname.str= my_strndup(db.str, dbname.length= db.length, MYF(MY_WME))))
    return TRUE;
  if (!(name.str= my_strndup(n.str, name.length= n.length, MYF(MY_WME))))
  {
167
    my_free((uchar*) dbname.str, MYF(0));
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
    return TRUE;
  }
  return FALSE;
}


/*
  Destructor

  SYNOPSIS
    Event_queue_element_for_exec::~Event_queue_element_for_exec()
*/

Event_queue_element_for_exec::~Event_queue_element_for_exec()
{
183 184
  my_free((uchar*) dbname.str, MYF(0));
  my_free((uchar*) name.str, MYF(0));
185 186 187
}


188 189 190 191 192 193 194 195
/*
  Constructor

  SYNOPSIS
    Event_basic::Event_basic()
*/

Event_basic::Event_basic()
unknown's avatar
unknown committed
196
{
197 198 199 200 201
  DBUG_ENTER("Event_basic::Event_basic");
  /* init memory root */
  init_alloc_root(&mem_root, 256, 512);
  dbname.str= name.str= NULL;
  dbname.length= name.length= 0;
202
  time_zone= NULL;
203 204
  DBUG_VOID_RETURN;
}
unknown's avatar
unknown committed
205

206 207 208

/*
  Destructor
unknown's avatar
unknown committed
209

210 211 212
  SYNOPSIS
    Event_basic::Event_basic()
*/
unknown's avatar
unknown committed
213

214 215 216 217
Event_basic::~Event_basic()
{
  DBUG_ENTER("Event_basic::~Event_basic");
  free_root(&mem_root, MYF(0));
unknown's avatar
unknown committed
218 219 220 221 222
  DBUG_VOID_RETURN;
}


/*
223
  Short function to load a char column into a LEX_STRING
unknown's avatar
unknown committed
224

225
  SYNOPSIS
226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
    Event_basic::load_string_field()
      field_name  The field( enum_events_table_field is not actually used
                  because it's unknown in event_data_objects.h)
      fields      The Field array
      field_value The value
*/

bool
Event_basic::load_string_fields(Field **fields, ...)
{
  bool ret= FALSE;
  va_list args;
  enum enum_events_table_field field_name;
  LEX_STRING *field_value;

  DBUG_ENTER("Event_basic::load_string_fields");
unknown's avatar
unknown committed
242

243 244
  va_start(args, fields);
  field_name= (enum enum_events_table_field) va_arg(args, int);
245
  while (field_name < ET_FIELD_COUNT)
246 247 248 249 250 251 252
  {
    field_value= va_arg(args, LEX_STRING *);
    if ((field_value->str= get_field(&mem_root, fields[field_name])) == NullS)
    {
      ret= TRUE;
      break;
    }
253
    field_value->length= strlen(field_value->str);
254 255 256 257

    field_name= (enum enum_events_table_field) va_arg(args, int);
  }
  va_end(args);
unknown's avatar
unknown committed
258

259 260 261 262
  DBUG_RETURN(ret);
}


263 264 265 266 267 268 269 270 271 272
bool
Event_basic::load_time_zone(THD *thd, const LEX_STRING tz_name)
{
  String str(tz_name.str, &my_charset_latin1);
  time_zone= my_tz_find(thd, &str);

  return (time_zone == NULL);
}


273 274 275 276 277
/*
  Constructor

  SYNOPSIS
    Event_queue_element::Event_queue_element()
unknown's avatar
unknown committed
278 279
*/

280 281
Event_queue_element::Event_queue_element():
  status_changed(FALSE), last_executed_changed(FALSE),
282 283 284
  on_completion(Event_parse_data::ON_COMPLETION_DROP),
  status(Event_parse_data::ENABLED), expression(0), dropped(FALSE),
  execution_count(0)
unknown's avatar
unknown committed
285
{
286
  DBUG_ENTER("Event_queue_element::Event_queue_element");
unknown's avatar
unknown committed
287

288
  starts= ends= execute_at= last_executed= 0;
289
  starts_null= ends_null= execute_at_null= TRUE;
unknown's avatar
unknown committed
290

291 292
  DBUG_VOID_RETURN;
}
293

294

295 296
/*
  Destructor
297

298 299 300 301 302
  SYNOPSIS
    Event_queue_element::Event_queue_element()
*/
Event_queue_element::~Event_queue_element()
{
unknown's avatar
unknown committed
303 304 305 306
}


/*
unknown's avatar
unknown committed
307
  Constructor
unknown's avatar
unknown committed
308

309
  SYNOPSIS
unknown's avatar
unknown committed
310
    Event_timed::Event_timed()
unknown's avatar
unknown committed
311 312
*/

313 314
Event_timed::Event_timed():
  created(0), modified(0), sql_mode(0)
unknown's avatar
unknown committed
315
{
316
  DBUG_ENTER("Event_timed::Event_timed");
unknown's avatar
unknown committed
317
  init();
318
  DBUG_VOID_RETURN;
unknown's avatar
unknown committed
319 320 321 322
}


/*
unknown's avatar
unknown committed
323
  Destructor
324 325

  SYNOPSIS
unknown's avatar
unknown committed
326 327 328 329
    Event_timed::~Event_timed()
*/

Event_timed::~Event_timed()
330
{
331 332 333 334 335 336 337 338 339 340
}


/*
  Constructor

  SYNOPSIS
    Event_job_data::Event_job_data()
*/

341
Event_job_data::Event_job_data()
342
  :sql_mode(0)
unknown's avatar
unknown committed
343
{
unknown's avatar
unknown committed
344 345 346 347 348 349 350 351 352 353 354 355 356 357
}

/*
  Init all member variables

  SYNOPSIS
    Event_timed::init()
*/

void
Event_timed::init()
{
  DBUG_ENTER("Event_timed::init");

358 359
  definer_user.str= definer_host.str= body.str= comment.str= NULL;
  definer_user.length= definer_host.length= body.length= comment.length= 0;
unknown's avatar
unknown committed
360 361 362 363

  sql_mode= 0;

  DBUG_VOID_RETURN;
unknown's avatar
unknown committed
364 365 366
}


367 368
/**
  Load an event's body from a row from mysql.event.
unknown's avatar
unknown committed
369

370 371 372 373 374 375 376
  @details This method is silent on errors and should behave like that.
  Callers should handle throwing of error messages. The reason is that the
  class should not know about how to deal with communication.

  @return Operation status
    @retval FALSE OK
    @retval TRUE  Error
unknown's avatar
unknown committed
377 378
*/

379
bool
380
Event_job_data::load_from_row(THD *thd, TABLE *table)
unknown's avatar
unknown committed
381 382
{
  char *ptr;
383
  size_t len;
384 385
  LEX_STRING tz_name;

386
  DBUG_ENTER("Event_job_data::load_from_row");
unknown's avatar
unknown committed
387 388

  if (!table)
389
    DBUG_RETURN(TRUE);
unknown's avatar
unknown committed
390

391
  if (table->s->fields < ET_FIELD_COUNT)
392
    DBUG_RETURN(TRUE);
unknown's avatar
unknown committed
393

394 395 396 397 398 399 400
  if (load_string_fields(table->field,
                         ET_FIELD_DB, &dbname,
                         ET_FIELD_NAME, &name,
                         ET_FIELD_BODY, &body,
                         ET_FIELD_DEFINER, &definer,
                         ET_FIELD_TIME_ZONE, &tz_name,
                         ET_FIELD_COUNT))
401
    DBUG_RETURN(TRUE);
402

403
  if (load_time_zone(thd, tz_name))
404
    DBUG_RETURN(TRUE);
405

unknown's avatar
unknown committed
406 407 408
  Event_creation_ctx::load_from_db(thd, &mem_root, dbname.str, name.str, table,
                                   &creation_ctx);

409 410 411 412
  ptr= strchr(definer.str, '@');

  if (! ptr)
    ptr= definer.str;
unknown's avatar
unknown committed
413

414 415 416 417 418
  len= ptr - definer.str;
  definer_user.str= strmake_root(&mem_root, definer.str, len);
  definer_user.length= len;
  len= definer.length - len - 1;
  /* 1:because of @ */
unknown's avatar
unknown committed
419
  definer_host.str= strmake_root(&mem_root, ptr + 1, len);
420
  definer_host.length= len;
unknown's avatar
unknown committed
421

422 423
  sql_mode= (ulong) table->field[ET_FIELD_SQL_MODE]->val_int();

424
  DBUG_RETURN(FALSE);
425 426 427
}


428 429
/**
  Load an event's body from a row from mysql.event.
430

431 432 433
  @details This method is silent on errors and should behave like that.
  Callers should handle throwing of error messages. The reason is that the
  class should not know about how to deal with communication.
434

435 436 437
  @return Operation status
    @retval FALSE OK
    @retval TRUE  Error
438 439
*/

440
bool
441
Event_queue_element::load_from_row(THD *thd, TABLE *table)
442 443
{
  char *ptr;
444
  MYSQL_TIME time;
445
  LEX_STRING tz_name;
unknown's avatar
unknown committed
446

447
  DBUG_ENTER("Event_queue_element::load_from_row");
unknown's avatar
unknown committed
448

449
  if (!table)
450
    DBUG_RETURN(TRUE);
unknown's avatar
unknown committed
451

452
  if (table->s->fields < ET_FIELD_COUNT)
453
    DBUG_RETURN(TRUE);
454 455 456 457 458 459 460

  if (load_string_fields(table->field,
                         ET_FIELD_DB, &dbname,
                         ET_FIELD_NAME, &name,
                         ET_FIELD_DEFINER, &definer,
                         ET_FIELD_TIME_ZONE, &tz_name,
                         ET_FIELD_COUNT))
461
    DBUG_RETURN(TRUE);
unknown's avatar
unknown committed
462

463
  if (load_time_zone(thd, tz_name))
464
    DBUG_RETURN(TRUE);
unknown's avatar
unknown committed
465

466
  starts_null= table->field[ET_FIELD_STARTS]->is_null();
467
  my_bool not_used= FALSE;
468 469 470
  if (!starts_null)
  {
    table->field[ET_FIELD_STARTS]->get_date(&time, TIME_NO_ZERO_DATE);
471
    starts= my_tz_OFFSET0->TIME_to_gmt_sec(&time,&not_used);
472
  }
473

474
  ends_null= table->field[ET_FIELD_ENDS]->is_null();
475 476 477
  if (!ends_null)
  {
    table->field[ET_FIELD_ENDS]->get_date(&time, TIME_NO_ZERO_DATE);
478
    ends= my_tz_OFFSET0->TIME_to_gmt_sec(&time,&not_used);
479
  }
unknown's avatar
unknown committed
480

unknown's avatar
unknown committed
481
  if (!table->field[ET_FIELD_INTERVAL_EXPR]->is_null())
482
    expression= table->field[ET_FIELD_INTERVAL_EXPR]->val_int();
483
  else
484
    expression= 0;
unknown's avatar
unknown committed
485
  /*
486
    If neigher STARTS and ENDS is set, then both fields are empty.
487
    Hence, if ET_FIELD_EXECUTE_AT is empty there is an error.
unknown's avatar
unknown committed
488
  */
489 490
  execute_at_null= table->field[ET_FIELD_EXECUTE_AT]->is_null();
  DBUG_ASSERT(!(starts_null && ends_null && !expression && execute_at_null));
491 492 493 494
  if (!expression && !execute_at_null)
  {
    if (table->field[ET_FIELD_EXECUTE_AT]->get_date(&time,
                                                    TIME_NO_ZERO_DATE))
495
      DBUG_RETURN(TRUE);
496
    execute_at= my_tz_OFFSET0->TIME_to_gmt_sec(&time,&not_used);
497
  }
unknown's avatar
unknown committed
498 499

  /*
500 501 502 503
    We load the interval type from disk as string and then map it to
    an integer. This decouples the values of enum interval_type
    and values actually stored on disk. Therefore the type can be
    reordered without risking incompatibilities of data between versions.
unknown's avatar
unknown committed
504
  */
unknown's avatar
unknown committed
505
  if (!table->field[ET_FIELD_TRANSIENT_INTERVAL]->is_null())
506 507 508 509 510 511 512 513
  {
    int i;
    char buff[MAX_FIELD_WIDTH];
    String str(buff, sizeof(buff), &my_charset_bin);
    LEX_STRING tmp;

    table->field[ET_FIELD_TRANSIENT_INTERVAL]->val_str(&str);
    if (!(tmp.length= str.length()))
514
      DBUG_RETURN(TRUE);
515 516 517 518 519

    tmp.str= str.c_ptr_safe();

    i= find_string_in_array(interval_type_to_name, &tmp, system_charset_info);
    if (i < 0)
520
      DBUG_RETURN(TRUE);
521 522
    interval= (interval_type) i;
  }
unknown's avatar
unknown committed
523

524 525 526 527
  if (!table->field[ET_FIELD_LAST_EXECUTED]->is_null())
  {
    table->field[ET_FIELD_LAST_EXECUTED]->get_date(&time,
                                                   TIME_NO_ZERO_DATE);
528
    last_executed= my_tz_OFFSET0->TIME_to_gmt_sec(&time,&not_used);
529
  }
530
  last_executed_changed= FALSE;
unknown's avatar
unknown committed
531

532
  if ((ptr= get_field(&mem_root, table->field[ET_FIELD_STATUS])) == NullS)
533
    DBUG_RETURN(TRUE);
534

535
  DBUG_PRINT("load_from_row", ("Event [%s] is [%s]", name.str, ptr));
536 537 538 539 540

  /* Set event status (ENABLED | SLAVESIDE_DISABLED | DISABLED) */
  switch (ptr[0])
  {
  case 'E' :
541
    status = Event_parse_data::ENABLED;
542 543
    break;
  case 'S' :
544
    status = Event_parse_data::SLAVESIDE_DISABLED;
545 546
    break;
  case 'D' :
547
  default:
548
    status = Event_parse_data::DISABLED;
549 550 551
    break;
  }
  if ((ptr= get_field(&mem_root, table->field[ET_FIELD_ORIGINATOR])) == NullS)
552
    DBUG_RETURN(TRUE);
553
  originator = table->field[ET_FIELD_ORIGINATOR]->val_int(); 
unknown's avatar
unknown committed
554

unknown's avatar
unknown committed
555
  /* ToDo : Andrey . Find a way not to allocate ptr on event_mem_root */
556
  if ((ptr= get_field(&mem_root,
557
                      table->field[ET_FIELD_ON_COMPLETION])) == NullS)
558
    DBUG_RETURN(TRUE);
559

560 561
  on_completion= (ptr[0]=='D'? Event_parse_data::ON_COMPLETION_DROP:
                               Event_parse_data::ON_COMPLETION_PRESERVE);
unknown's avatar
unknown committed
562

563
  DBUG_RETURN(FALSE);
564 565 566
}


567 568
/**
  Load an event's body from a row from mysql.event.
569

570 571 572
  @details This method is silent on errors and should behave like that.
  Callers should handle throwing of error messages. The reason is that the
  class should not know about how to deal with communication.
573

574 575 576
  @return Operation status
    @retval FALSE OK
    @retval TRUE  Error
577 578
*/

579
bool
580
Event_timed::load_from_row(THD *thd, TABLE *table)
581 582
{
  char *ptr;
583
  size_t len;
584 585 586

  DBUG_ENTER("Event_timed::load_from_row");

587
  if (Event_queue_element::load_from_row(thd, table))
588
    DBUG_RETURN(TRUE);
unknown's avatar
unknown committed
589

590 591
  if (load_string_fields(table->field,
                         ET_FIELD_BODY, &body,
unknown's avatar
unknown committed
592
                         ET_FIELD_BODY_UTF8, &body_utf8,
593
                         ET_FIELD_COUNT))
594
    DBUG_RETURN(TRUE);
595

unknown's avatar
unknown committed
596 597 598 599 600 601 602 603 604 605
  if (Event_creation_ctx::load_from_db(thd, &mem_root, dbname.str, name.str,
                                       table, &creation_ctx))
  {
    push_warning_printf(thd,
                        MYSQL_ERROR::WARN_LEVEL_WARN,
                        ER_EVENT_INVALID_CREATION_CTX,
                        ER(ER_EVENT_INVALID_CREATION_CTX),
                        (const char *) dbname.str,
                        (const char *) name.str);
  }
unknown's avatar
unknown committed
606

607
  ptr= strchr(definer.str, '@');
unknown's avatar
unknown committed
608

609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625
  if (! ptr)
    ptr= definer.str;

  len= ptr - definer.str;
  definer_user.str= strmake_root(&mem_root, definer.str, len);
  definer_user.length= len;
  len= definer.length - len - 1;
  /* 1:because of @ */
  definer_host.str= strmake_root(&mem_root, ptr + 1,  len);
  definer_host.length= len;

  created= table->field[ET_FIELD_CREATED]->val_int();
  modified= table->field[ET_FIELD_MODIFIED]->val_int();

  comment.str= get_field(&mem_root, table->field[ET_FIELD_COMMENT]);
  if (comment.str != NullS)
    comment.length= strlen(comment.str);
unknown's avatar
unknown committed
626
  else
627
    comment.length= 0;
628

629
  sql_mode= (ulong) table->field[ET_FIELD_SQL_MODE]->val_int();
630

631
  DBUG_RETURN(FALSE);
unknown's avatar
unknown committed
632 633 634
}


unknown's avatar
unknown committed
635
/*
636 637 638 639 640 641 642
  add_interval() adds a specified interval to time 'ltime' in time
  zone 'time_zone', and returns the result converted to the number of
  seconds since epoch (aka Unix time; in UTC time zone).  Zero result
  means an error.
*/
static
my_time_t
643
add_interval(MYSQL_TIME *ltime, const Time_zone *time_zone,
644 645 646 647 648 649 650 651 652 653 654 655
             interval_type scale, INTERVAL interval)
{
  if (date_add_interval(ltime, scale, interval))
    return 0;

  my_bool not_used;
  return time_zone->TIME_to_gmt_sec(ltime, &not_used);
}


/*
  Computes the sum of a timestamp plus interval.
656 657

  SYNOPSIS
658 659
    get_next_time()
      time_zone     event time zone
660 661
      next          the sum
      start         add interval_value to this time
662
      time_now      current time
663 664
      i_value       quantity of time type interval to add
      i_type        type of interval to add (SECOND, MINUTE, HOUR, WEEK ...)
unknown's avatar
unknown committed
665

666
  RETURN VALUE
667 668 669 670 671 672 673 674 675
    0  OK
    1  Error

  NOTES
    1) If the interval is conversible to SECOND, like MINUTE, HOUR, DAY, WEEK.
       Then we use TIMEDIFF()'s implementation as underlying and number of
       seconds as resolution for computation.
    2) In all other cases - MONTH, QUARTER, YEAR we use MONTH as resolution
       and PERIOD_DIFF()'s implementation
676
*/
unknown's avatar
unknown committed
677

678
static
679 680
bool get_next_time(const Time_zone *time_zone, my_time_t *next,
                   my_time_t start, my_time_t time_now,
681
                   int i_value, interval_type i_type)
682
{
683
  DBUG_ENTER("get_next_time");
684
  DBUG_PRINT("enter", ("start: %lu  now: %lu", (long) start, (long) time_now));
685

686 687 688
  DBUG_ASSERT(start <= time_now);

  longlong months=0, seconds=0;
689 690 691

  switch (i_type) {
  case INTERVAL_YEAR:
692
    months= i_value*12;
693 694
    break;
  case INTERVAL_QUARTER:
695
    /* Has already been converted to months */
696 697
  case INTERVAL_YEAR_MONTH:
  case INTERVAL_MONTH:
698
    months= i_value;
699 700
    break;
  case INTERVAL_WEEK:
701
    /* WEEK has already been converted to days */
702
  case INTERVAL_DAY:
703
    seconds= i_value*24*3600;
704 705 706
    break;
  case INTERVAL_DAY_HOUR:
  case INTERVAL_HOUR:
707
    seconds= i_value*3600;
708 709 710 711
    break;
  case INTERVAL_DAY_MINUTE:
  case INTERVAL_HOUR_MINUTE:
  case INTERVAL_MINUTE:
712
    seconds= i_value*60;
713 714 715 716 717
    break;
  case INTERVAL_DAY_SECOND:
  case INTERVAL_HOUR_SECOND:
  case INTERVAL_MINUTE_SECOND:
  case INTERVAL_SECOND:
718
    seconds= i_value;
719 720 721 722 723 724
    break;
  case INTERVAL_DAY_MICROSECOND:
  case INTERVAL_HOUR_MICROSECOND:
  case INTERVAL_MINUTE_MICROSECOND:
  case INTERVAL_SECOND_MICROSECOND:
  case INTERVAL_MICROSECOND:
725 726 727 728 729
    /*
     We should return an error here so SHOW EVENTS/ SELECT FROM I_S.EVENTS
     would give an error then.
    */
    DBUG_RETURN(1);
730
    break;
731 732
  case INTERVAL_LAST:
    DBUG_ASSERT(0);
733
  }
unknown's avatar
unknown committed
734
  DBUG_PRINT("info", ("seconds: %ld  months: %ld", (long) seconds, (long) months));
735

736 737
  MYSQL_TIME local_start;
  MYSQL_TIME local_now;
738 739 740 741 742 743 744 745 746 747 748

  /* Convert times from UTC to local. */
  {
    time_zone->gmt_sec_to_TIME(&local_start, start);
    time_zone->gmt_sec_to_TIME(&local_now, time_now);
  }

  INTERVAL interval;
  bzero(&interval, sizeof(interval));
  my_time_t next_time= 0;

749 750 751 752
  if (seconds)
  {
    longlong seconds_diff;
    long microsec_diff;
753 754 755 756 757 758 759 760 761 762 763 764 765 766
    bool negative= calc_time_diff(&local_now, &local_start, 1,
                                  &seconds_diff, &microsec_diff);
    if (!negative)
    {
      /*
        The formula below returns the interval that, when added to
        local_start, will always give the time in the future.
      */
      interval.second= seconds_diff - seconds_diff % seconds + seconds;
      next_time= add_interval(&local_start, time_zone,
                              INTERVAL_SECOND, interval);
      if (next_time == 0)
        goto done;
    }
767

768
    if (next_time <= time_now)
769
    {
770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832
      /*
        If 'negative' is true above, then 'next_time == 0', and
        'next_time <= time_now' is also true.  If negative is false,
        then next_time was set, but perhaps to the value that is less
        then time_now.  See below for elaboration.
      */
      DBUG_ASSERT(negative || next_time > 0);

      /*
        If local_now < local_start, i.e. STARTS time is in the future
        according to the local time (it always in the past according
        to UTC---this is a prerequisite of this function), then
        STARTS is almost always in the past according to the local
        time too.  However, in the time zone that has backward
        Daylight Saving Time shift, the following may happen: suppose
        we have a backward DST shift at certain date after 2:59:59,
        i.e. local time goes 1:59:59, 2:00:00, ... , 2:59:59, (shift
        here) 2:00:00 (again), ... , 2:59:59 (again), 3:00:00, ... .
        Now suppose the time has passed the first 2:59:59, has been
        shifted backward, and now is (the second) 2:20:00.  The user
        does CREATE EVENT with STARTS 'current-date 2:40:00'.  Local
        time 2:40:00 from create statement is treated by time
        functions as the first such time, so according to UTC it comes
        before the second 2:20:00.  But according to local time it is
        obviously in the future, so we end up in this branch.

        Since we are in the second pass through 2:00:00--2:59:59, and
        any local time form this interval is treated by system
        functions as the time from the first pass, we have to find the
        time for the next execution that is past the DST-affected
        interval (past the second 2:59:59 for our example,
        i.e. starting from 3:00:00).  We do this in the loop until the
        local time is mapped onto future UTC time.  'start' time is in
        the past, so we may use 'do { } while' here, and add the first
        interval right away.

        Alternatively, it could be that local_now >= local_start.  Now
        for the example above imagine we do CREATE EVENT with STARTS
        'current-date 2:10:00'.  Local start 2:10 is in the past (now
        is local 2:20), so we add an interval, and get next execution
        time, say, 2:40.  It is in the future according to local time,
        but, again, since we are in the second pass through
        2:00:00--2:59:59, 2:40 will be converted into UTC time in the
        past.  So we will end up in this branch again, and may add
        intervals in a 'do { } while' loop.

        Note that for any given event we may end up here only if event
        next execution time will map to the time interval that is
        passed twice, and only if the server was started during the
        second pass, or the event is being created during the second
        pass.  After that, we never will get here (unless we again
        start the server during the second pass).  In other words,
        such a condition is extremely rare.
      */
      interval.second= seconds;
      do
      {
        next_time= add_interval(&local_start, time_zone,
                                INTERVAL_SECOND, interval);
        if (next_time == 0)
          goto done;
      }
      while (next_time <= time_now);
833 834 835 836
    }
  }
  else
  {
837 838
    long diff_months= (long) (local_now.year - local_start.year)*12 +
                      (local_now.month - local_start.month);
839
    /*
840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855
      Unlike for seconds above, the formula below returns the interval
      that, when added to the local_start, will give the time in the
      past, or somewhere in the current month.  We are interested in
      the latter case, to see if this time has already passed, or is
      yet to come this month.

      Note that the time is guaranteed to be in the past unless
      (diff_months % months == 0), but no good optimization is
      possible here, because (diff_months % months == 0) is what will
      happen most of the time, as get_next_time() will be called right
      after the execution of the event.  We could pass last_executed
      time to this function, and see if the execution has already
      happened this month, but for that we will have to convert
      last_executed from seconds since epoch to local broken-down
      time, and this will greatly reduce the effect of the
      optimization.  So instead we keep the code simple and clean.
856
    */
857
    interval.month= (ulong) (diff_months - diff_months % months);
858 859 860
    next_time= add_interval(&local_start, time_zone,
                            INTERVAL_MONTH, interval);
    if (next_time == 0)
861
      goto done;
862

863 864
    if (next_time <= time_now)
    {
865
      interval.month= (ulong) months;
866 867 868
      next_time= add_interval(&local_start, time_zone,
                              INTERVAL_MONTH, interval);
      if (next_time == 0)
869 870 871
        goto done;
    }
  }
872

873 874 875 876
  DBUG_ASSERT(time_now < next_time);

  *next= next_time;

877
done:
878 879
  DBUG_PRINT("info", ("next_time: %ld", (long) next_time));
  DBUG_RETURN(next_time == 0);
880 881 882 883
}


/*
884 885 886
  Computes next execution time.

  SYNOPSIS
887
    Event_queue_element::compute_next_execution_time()
888

889 890 891 892
  RETURN VALUE
    FALSE  OK
    TRUE   Error

893
  NOTES
894 895
    The time is set in execute_at, if no more executions the latter is
    set to 0.
unknown's avatar
unknown committed
896 897
*/

unknown's avatar
unknown committed
898
bool
899
Event_queue_element::compute_next_execution_time()
unknown's avatar
unknown committed
900
{
901
  my_time_t time_now;
902
  DBUG_ENTER("Event_queue_element::compute_next_execution_time");
unknown's avatar
unknown committed
903
  DBUG_PRINT("enter", ("starts: %lu  ends: %lu  last_executed: %lu  this: 0x%lx",
904
                       (long) starts, (long) ends, (long) last_executed,
unknown's avatar
unknown committed
905
                       (long) this));
unknown's avatar
unknown committed
906

907
  if (status != Event_parse_data::ENABLED)
unknown's avatar
unknown committed
908 909
  {
    DBUG_PRINT("compute_next_execution_time",
unknown's avatar
unknown committed
910
               ("Event %s is DISABLED", name.str));
unknown's avatar
unknown committed
911 912
    goto ret;
  }
913
  /* If one-time, no need to do computation */
unknown's avatar
unknown committed
914
  if (!expression)
unknown's avatar
unknown committed
915
  {
916
    /* Let's check whether it was executed */
917
    if (last_executed)
unknown's avatar
unknown committed
918
    {
919
      DBUG_PRINT("info",("One-time event %s.%s of was already executed",
unknown's avatar
unknown committed
920
                         dbname.str, name.str));
921
      dropped= (on_completion == Event_parse_data::ON_COMPLETION_DROP);
unknown's avatar
unknown committed
922
      DBUG_PRINT("info",("One-time event will be dropped: %d.", dropped));
923

924
      status= Event_parse_data::DISABLED;
925
      status_changed= TRUE;
unknown's avatar
unknown committed
926 927 928
    }
    goto ret;
  }
929

unknown's avatar
unknown committed
930
  time_now= (my_time_t) current_thd->query_start();
931

932
  DBUG_PRINT("info",("NOW: [%lu]", (ulong) time_now));
933

unknown's avatar
unknown committed
934
  /* if time_now is after ends don't execute anymore */
935
  if (!ends_null && ends < time_now)
unknown's avatar
unknown committed
936
  {
937
    DBUG_PRINT("info", ("NOW after ENDS, don't execute anymore"));
938
    /* time_now is after ends. don't execute anymore */
939
    execute_at= 0;
940
    execute_at_null= TRUE;
941
    if (on_completion == Event_parse_data::ON_COMPLETION_DROP)
942
      dropped= TRUE;
unknown's avatar
unknown committed
943
    DBUG_PRINT("info", ("Dropped: %d", dropped));
944
    status= Event_parse_data::DISABLED;
945
    status_changed= TRUE;
unknown's avatar
unknown committed
946 947 948

    goto ret;
  }
949 950 951 952 953

  /*
    Here time_now is before or equals ends if the latter is set.
    Let's check whether time_now is before starts.
    If so schedule for starts.
unknown's avatar
unknown committed
954
  */
955
  if (!starts_null && time_now <= starts)
unknown's avatar
unknown committed
956
  {
957
    if (time_now == starts && starts == last_executed)
unknown's avatar
unknown committed
958
    {
959
      /*
unknown's avatar
unknown committed
960
        do nothing or we will schedule for second time execution at starts.
unknown's avatar
unknown committed
961 962 963 964
      */
    }
    else
    {
965
      DBUG_PRINT("info", ("STARTS is future, NOW <= STARTS,sched for STARTS"));
unknown's avatar
unknown committed
966 967 968 969 970
      /*
        starts is in the future
        time_now before starts. Scheduling for starts
      */
      execute_at= starts;
971
      execute_at_null= FALSE;
unknown's avatar
unknown committed
972 973 974
      goto ret;
    }
  }
unknown's avatar
unknown committed
975

976
  if (!starts_null && !ends_null)
unknown's avatar
unknown committed
977
  {
978
    /*
unknown's avatar
unknown committed
979
      Both starts and m_ends are set and time_now is between them (incl.)
980
      If last_executed is set then increase with m_expression. The new MYSQL_TIME is
unknown's avatar
unknown committed
981
      after m_ends set execute_at to 0. And check for on_completion
unknown's avatar
unknown committed
982 983
      If not set then schedule for now.
    */
984
    DBUG_PRINT("info", ("Both STARTS & ENDS are set"));
985
    if (!last_executed)
986
    {
987
      DBUG_PRINT("info", ("Not executed so far."));
988
    }
989

unknown's avatar
unknown committed
990
    {
991
      my_time_t next_exec;
992

993
      if (get_next_time(time_zone, &next_exec, starts, time_now,
994
                        (int) expression, interval))
995
        goto err;
996 997

      /* There was previous execution */
998
      if (ends < next_exec)
unknown's avatar
unknown committed
999
      {
1000 1001
        DBUG_PRINT("info", ("Next execution of %s after ENDS. Stop executing.",
                   name.str));
1002
        /* Next execution after ends. No more executions */
1003
        execute_at= 0;
1004
        execute_at_null= TRUE;
1005
        if (on_completion == Event_parse_data::ON_COMPLETION_DROP)
1006
          dropped= TRUE;
1007
        status= Event_parse_data::DISABLED;
1008
        status_changed= TRUE;
unknown's avatar
unknown committed
1009 1010
      }
      else
1011
      {
1012
        DBUG_PRINT("info",("Next[%lu]", (ulong) next_exec));
1013
        execute_at= next_exec;
1014 1015
        execute_at_null= FALSE;
      }
unknown's avatar
unknown committed
1016 1017 1018
    }
    goto ret;
  }
1019
  else if (starts_null && ends_null)
unknown's avatar
unknown committed
1020
  {
1021
    /* starts is always set, so this is a dead branch !! */
1022
    DBUG_PRINT("info", ("Neither STARTS nor ENDS are set"));
1023 1024 1025 1026
    /*
      Both starts and m_ends are not set, so we schedule for the next
      based on last_executed.
    */
1027
    if (last_executed)
1028
    {
1029 1030
      my_time_t next_exec;
      if (get_next_time(time_zone, &next_exec, starts, time_now,
1031
                        (int) expression, interval))
1032
        goto err;
1033
      execute_at= next_exec;
1034
      DBUG_PRINT("info",("Next[%lu]", (ulong) next_exec));
1035 1036
    }
    else
1037 1038
    {
      /* last_executed not set. Schedule the event for now */
1039
      DBUG_PRINT("info", ("Execute NOW"));
unknown's avatar
unknown committed
1040
      execute_at= time_now;
1041
    }
1042
    execute_at_null= FALSE;
unknown's avatar
unknown committed
1043 1044 1045
  }
  else
  {
unknown's avatar
unknown committed
1046
    /* either starts or m_ends is set */
1047
    if (!starts_null)
unknown's avatar
unknown committed
1048
    {
1049
      DBUG_PRINT("info", ("STARTS is set"));
unknown's avatar
unknown committed
1050
      /*
unknown's avatar
unknown committed
1051 1052 1053 1054
        - starts is set.
        - starts is not in the future according to check made before
        Hence schedule for starts + m_expression in case last_executed
        is not set, otherwise to last_executed + m_expression
unknown's avatar
unknown committed
1055
      */
1056
      if (!last_executed)
1057 1058 1059 1060
      {
        DBUG_PRINT("info", ("Not executed so far."));
      }

1061
      {
1062 1063
        my_time_t next_exec;
        if (get_next_time(time_zone, &next_exec, starts, time_now,
1064
                          (int) expression, interval))
1065
          goto err;
1066
        execute_at= next_exec;
1067
        DBUG_PRINT("info",("Next[%lu]", (ulong) next_exec));
1068
      }
1069
      execute_at_null= FALSE;
unknown's avatar
unknown committed
1070 1071 1072
    }
    else
    {
1073
      /* this is a dead branch, because starts is always set !!! */
1074
      DBUG_PRINT("info", ("STARTS is not set. ENDS is set"));
unknown's avatar
unknown committed
1075 1076 1077
      /*
        - m_ends is set
        - m_ends is after time_now or is equal
unknown's avatar
unknown committed
1078 1079
        Hence check for m_last_execute and increment with m_expression.
        If last_executed is not set then schedule for now
unknown's avatar
unknown committed
1080 1081
      */

1082
      if (!last_executed)
unknown's avatar
unknown committed
1083
        execute_at= time_now;
unknown's avatar
unknown committed
1084 1085
      else
      {
1086
        my_time_t next_exec;
1087

1088
        if (get_next_time(time_zone, &next_exec, starts, time_now,
1089
                          (int) expression, interval))
1090 1091
          goto err;

1092
        if (ends < next_exec)
unknown's avatar
unknown committed
1093
        {
1094
          DBUG_PRINT("info", ("Next execution after ENDS. Stop executing."));
1095
          execute_at= 0;
1096
          execute_at_null= TRUE;
1097
          status= Event_parse_data::DISABLED;
1098
          status_changed= TRUE;
1099
          if (on_completion == Event_parse_data::ON_COMPLETION_DROP)
1100
            dropped= TRUE;
unknown's avatar
unknown committed
1101 1102
        }
        else
1103
        {
1104
          DBUG_PRINT("info", ("Next[%lu]", (ulong) next_exec));
1105
          execute_at= next_exec;
1106 1107
          execute_at_null= FALSE;
        }
unknown's avatar
unknown committed
1108 1109 1110 1111 1112
      }
    }
    goto ret;
  }
ret:
1113
  DBUG_PRINT("info", ("ret: 0 execute_at: %lu", (long) execute_at));
1114
  DBUG_RETURN(FALSE);
1115
err:
1116
  DBUG_PRINT("info", ("ret=1"));
1117
  DBUG_RETURN(TRUE);
unknown's avatar
unknown committed
1118 1119 1120
}


1121
/*
1122
  Set the internal last_executed MYSQL_TIME struct to now. NOW is the
1123
  time according to thd->query_start(), so the THD's clock.
1124 1125

  SYNOPSIS
1126
    Event_queue_element::mark_last_executed()
1127
      thd   thread context
1128 1129
*/

unknown's avatar
unknown committed
1130
void
1131
Event_queue_element::mark_last_executed(THD *thd)
unknown's avatar
unknown committed
1132
{
unknown's avatar
unknown committed
1133
  last_executed= (my_time_t) thd->query_start();
1134
  last_executed_changed= TRUE;
1135

unknown's avatar
unknown committed
1136
  execution_count++;
unknown's avatar
unknown committed
1137 1138 1139
}


1140 1141
/*
  Saves status and last_executed_at to the disk if changed.
1142 1143

  SYNOPSIS
1144
    Event_queue_element::update_timing_fields()
1145 1146
      thd - thread context

1147
  RETURN VALUE
1148
    FALSE   OK
1149 1150
    TRUE    Error while opening mysql.event for writing or during
            write on disk
1151 1152
*/

unknown's avatar
unknown committed
1153
bool
1154
Event_queue_element::update_timing_fields(THD *thd)
unknown's avatar
unknown committed
1155
{
1156 1157
  Event_db_repository *db_repository= Events::get_db_repository();
  int ret;
unknown's avatar
unknown committed
1158

1159
  DBUG_ENTER("Event_queue_element::update_timing_fields");
unknown's avatar
unknown committed
1160

1161
  DBUG_PRINT("enter", ("name: %*s", (int) name.length, name.str));
1162 1163

  /* No need to update if nothing has changed */
unknown's avatar
unknown committed
1164
  if (!(status_changed || last_executed_changed))
1165
    DBUG_RETURN(0);
1166

1167 1168 1169 1170 1171 1172 1173
  ret= db_repository->update_timing_fields_for_event(thd,
                                                     dbname, name,
                                                     last_executed_changed,
                                                     last_executed,
                                                     status_changed,
                                                     (ulonglong) status);
  last_executed_changed= status_changed= FALSE;
unknown's avatar
unknown committed
1174 1175 1176 1177
  DBUG_RETURN(ret);
}


1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190
static
void
append_datetime(String *buf, Time_zone *time_zone, my_time_t secs,
                const char *name, uint len)
{
  char dtime_buff[20*2+32];/* +32 to make my_snprintf_{8bit|ucs2} happy */
  buf->append(STRING_WITH_LEN(" "));
  buf->append(name, len);
  buf->append(STRING_WITH_LEN(" '"));
  /*
    Pass the buffer and the second param tells fills the buffer and
    returns the number of chars to copy.
  */
1191
  MYSQL_TIME time;
1192 1193 1194 1195 1196 1197
  time_zone->gmt_sec_to_TIME(&time, secs);
  buf->append(dtime_buff, my_datetime_to_str(&time, dtime_buff));
  buf->append(STRING_WITH_LEN("'"));
}


1198 1199
/*
  Get SHOW CREATE EVENT as string
1200 1201

  SYNOPSIS
unknown's avatar
unknown committed
1202
    Event_timed::get_create_event(THD *thd, String *buf)
1203 1204 1205 1206
      thd    Thread
      buf    String*, should be already allocated. CREATE EVENT goes inside.

  RETURN VALUE
1207 1208 1209 1210
    0                       OK
    EVEX_MICROSECOND_UNSUP  Error (for now if mysql.event has been
                            tampered and MICROSECONDS interval or
                            derivative has been put there.
1211 1212 1213
*/

int
unknown's avatar
unknown committed
1214
Event_timed::get_create_event(THD *thd, String *buf)
unknown's avatar
unknown committed
1215
{
1216 1217
  char tmp_buf[2 * STRING_BUFFER_USUAL_SIZE];
  String expr_buf(tmp_buf, sizeof(tmp_buf), system_charset_info);
1218 1219 1220
  expr_buf.length(0);

  DBUG_ENTER("get_create_event");
1221 1222
  DBUG_PRINT("ret_info",("body_len=[%d]body=[%s]",
                         (int) body.length, body.str));
unknown's avatar
unknown committed
1223

1224 1225
  if (expression && Events::reconstruct_interval_expression(&expr_buf, interval,
                                                            expression))
1226
    DBUG_RETURN(EVEX_MICROSECOND_UNSUP);
1227 1228 1229 1230 1231 1232

  buf->append(STRING_WITH_LEN("CREATE EVENT "));
  append_identifier(thd, buf, name.str, name.length);

  if (expression)
  {
1233
    buf->append(STRING_WITH_LEN(" ON SCHEDULE EVERY "));
1234
    buf->append(expr_buf);
unknown's avatar
unknown committed
1235 1236 1237
    buf->append(' ');
    LEX_STRING *ival= &interval_type_to_name[interval];
    buf->append(ival->str, ival->length);
1238 1239 1240 1241 1242 1243

    if (!starts_null)
      append_datetime(buf, time_zone, starts, STRING_WITH_LEN("STARTS"));

    if (!ends_null)
      append_datetime(buf, time_zone, ends, STRING_WITH_LEN("ENDS"));
1244 1245 1246
  }
  else
  {
1247 1248
    append_datetime(buf, time_zone, execute_at,
                    STRING_WITH_LEN("ON SCHEDULE AT"));
1249 1250
  }

1251
  if (on_completion == Event_parse_data::ON_COMPLETION_DROP)
1252 1253 1254 1255
    buf->append(STRING_WITH_LEN(" ON COMPLETION NOT PRESERVE "));
  else
    buf->append(STRING_WITH_LEN(" ON COMPLETION PRESERVE "));

1256
  if (status == Event_parse_data::ENABLED)
1257
    buf->append(STRING_WITH_LEN("ENABLE"));
1258
  else if (status == Event_parse_data::SLAVESIDE_DISABLED)
1259
    buf->append(STRING_WITH_LEN("DISABLE ON SLAVE"));
1260 1261 1262 1263 1264 1265 1266 1267 1268
  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 "));
1269
  buf->append(body.str, body.length);
1270 1271

  DBUG_RETURN(0);
unknown's avatar
unknown committed
1272 1273 1274
}


1275 1276
/**
  Get an artificial stored procedure to parse as an event definition.
1277 1278
*/

1279 1280
bool
Event_job_data::construct_sp_sql(THD *thd, String *sp_sql)
1281
{
1282 1283
  LEX_STRING buffer;
  const uint STATIC_SQL_LENGTH= 44;
1284

1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319
  DBUG_ENTER("Event_job_data::construct_sp_sql");

  /*
    Allocate a large enough buffer on the thread execution memory
    root to avoid multiple [re]allocations on system heap
  */
  buffer.length= STATIC_SQL_LENGTH + name.length + body.length;
  if (! (buffer.str= (char*) thd->alloc(buffer.length)))
    DBUG_RETURN(TRUE);

  sp_sql->set(buffer.str, buffer.length, system_charset_info);
  sp_sql->length(0);


  sp_sql->append(C_STRING_WITH_LEN("CREATE "));
  sp_sql->append(C_STRING_WITH_LEN("PROCEDURE "));
  /*
    Let's use the same name as the event name to perhaps produce a
    better error message in case it is a part of some parse error.
    We're using append_identifier here to successfully parse
    events with reserved names.
  */
  append_identifier(thd, sp_sql, name.str, name.length);

  /*
    The default SQL security of a stored procedure is DEFINER. We
    have already activated the security context of the event, so
    let's execute the procedure with the invoker rights to save on
    resets of security contexts.
  */
  sp_sql->append(C_STRING_WITH_LEN("() SQL SECURITY INVOKER "));

  sp_sql->append(body.str, body.length);

  DBUG_RETURN(thd->is_fatal_error);
1320 1321 1322
}


1323 1324 1325
/**
  Get DROP EVENT statement to binlog the drop of ON COMPLETION NOT
  PRESERVE event.
1326 1327
*/

1328 1329
bool
Event_job_data::construct_drop_event_sql(THD *thd, String *sp_sql)
1330
{
1331 1332
  LEX_STRING buffer;
  const uint STATIC_SQL_LENGTH= 14;
1333

1334
  DBUG_ENTER("Event_job_data::construct_drop_event_sql");
1335

1336 1337 1338
  buffer.length= STATIC_SQL_LENGTH + name.length*2 + dbname.length*2;
  if (! (buffer.str= (char*) thd->alloc(buffer.length)))
    DBUG_RETURN(TRUE);
1339

1340 1341
  sp_sql->set(buffer.str, buffer.length, system_charset_info);
  sp_sql->length(0);
unknown's avatar
unknown committed
1342

1343 1344 1345 1346
  sp_sql->append(C_STRING_WITH_LEN("DROP EVENT "));
  append_identifier(thd, sp_sql, dbname.str, dbname.length);
  sp_sql->append('.');
  append_identifier(thd, sp_sql, name.str, name.length);
unknown's avatar
unknown committed
1347

1348 1349
  DBUG_RETURN(thd->is_fatal_error);
}
unknown's avatar
unknown committed
1350

1351 1352
/**
  Compiles and executes the event (the underlying sp_head object)
unknown's avatar
unknown committed
1353

1354 1355
  @retval TRUE  error (reported to the error log)
  @retval FALSE success
unknown's avatar
unknown committed
1356 1357
*/

1358
bool
1359
Event_job_data::execute(THD *thd, bool drop)
unknown's avatar
unknown committed
1360
{
1361
  String sp_sql;
1362
#ifndef NO_EMBEDDED_ACCESS_CHECKS
1363
  Security_context event_sctx, *save_sctx= NULL;
1364
#endif
1365 1366
  List<Item> empty_item_list;
  bool ret= TRUE;
unknown's avatar
unknown committed
1367 1368 1369

  DBUG_ENTER("Event_job_data::execute");

1370
  mysql_reset_thd_for_next_command(thd);
unknown's avatar
unknown committed
1371 1372

  /*
1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386
    MySQL parser currently assumes that current database is either
    present in THD or all names in all statements are fully specified.
    And yet not fully specified names inside stored programs must be 
    be supported, even if the current database is not set:
    CREATE PROCEDURE db1.p1() BEGIN CREATE TABLE t1; END//
    -- in this example t1 should be always created in db1 and the statement
    must parse even if there is no current database.

    To support this feature and still address the parser limitation,
    we need to set the current database here.
    We don't have to call mysql_change_db, since the checks performed
    in it are unnecessary for the purpose of parsing, and
    mysql_change_db will be invoked anyway later, to activate the
    procedure database before it's executed.
unknown's avatar
unknown committed
1387
  */
1388
  thd->set_db(dbname.str, dbname.length);
1389

1390 1391 1392 1393
#ifndef NO_EMBEDDED_ACCESS_CHECKS
  if (event_sctx.change_security_context(thd,
                                         &definer_user, &definer_host,
                                         &dbname, &save_sctx))
unknown's avatar
unknown committed
1394
  {
1395 1396 1397 1398
    sql_print_error("Event Scheduler: "
                    "[%s].[%s.%s] execution failed, "
                    "failed to authenticate the user.",
                    definer.str, dbname.str, name.str);
1399
    goto end_no_lex_start;
unknown's avatar
unknown committed
1400
  }
1401 1402 1403
#endif

  if (check_access(thd, EVENT_ACL, dbname.str,
1404
                   0, 0, 0, is_schema_db(dbname.str, dbname.length)))
1405 1406
  {
    /*
1407 1408 1409 1410
      This aspect of behavior is defined in the worklog,
      and this is how triggers work too: if TRIGGER
      privilege is revoked from trigger definer,
      triggers are not executed.
1411
    */
1412 1413 1414 1415
    sql_print_error("Event Scheduler: "
                    "[%s].[%s.%s] execution failed, "
                    "user no longer has EVENT privilege.",
                    definer.str, dbname.str, name.str);
1416
    goto end_no_lex_start;
1417
  }
unknown's avatar
unknown committed
1418

1419
  if (construct_sp_sql(thd, &sp_sql))
1420
    goto end_no_lex_start;
unknown's avatar
unknown committed
1421

1422 1423 1424 1425 1426 1427 1428
  /*
    Set up global thread attributes to reflect the properties of
    this Event. We can simply reset these instead of usual
    backup/restore employed in stored programs since we know that
    this is a top level statement and the worker thread is
    allocated exclusively to execute this event.
  */
1429

1430 1431
  thd->variables.sql_mode= sql_mode;
  thd->variables.time_zone= time_zone;
1432

Gleb Shchepa's avatar
Gleb Shchepa committed
1433
  thd->set_query(sp_sql.c_ptr_safe(), sp_sql.length());
unknown's avatar
unknown committed
1434

unknown's avatar
unknown committed
1435
  {
1436
    Parser_state parser_state(thd, thd->query(), thd->query_length());
1437 1438
    lex_start(thd);

1439
    if (parse_sql(thd, & parser_state, creation_ctx))
1440 1441 1442 1443 1444 1445 1446
    {
      sql_print_error("Event Scheduler: "
                      "%serror during compilation of %s.%s",
                      thd->is_fatal_error ? "fatal " : "",
                      (const char *) dbname.str, (const char *) name.str);
      goto end;
    }
unknown's avatar
unknown committed
1447
  }
1448

1449 1450
  {
    sp_head *sphead= thd->lex->sphead;
unknown's avatar
unknown committed
1451

1452
    DBUG_ASSERT(sphead);
1453

1454 1455 1456
    if (thd->enable_slow_log)
      sphead->m_flags|= sp_head::LOG_SLOW_STATEMENTS;
    sphead->m_flags|= sp_head::LOG_GENERAL_LOG;
1457

1458
    sphead->set_info(0, 0, &thd->lex->sp_chistics, sql_mode);
unknown's avatar
unknown committed
1459
    sphead->set_creation_ctx(creation_ctx);
1460
    sphead->optimize();
unknown's avatar
unknown committed
1461

1462 1463 1464 1465 1466 1467 1468 1469
    ret= sphead->execute_procedure(thd, &empty_item_list);
    /*
      There is no pre-locking and therefore there should be no
      tables open and locked left after execute_procedure.
    */
  }

end:
1470 1471 1472 1473 1474 1475 1476
  if (thd->lex->sphead)                        /* NULL only if a parse error */
  {
    delete thd->lex->sphead;
    thd->lex->sphead= NULL;
  }

end_no_lex_start:
1477 1478 1479 1480 1481 1482
  if (drop && !thd->is_fatal_error)
  {
    /*
      We must do it here since here we're under the right authentication
      ID of the event definer.
    */
1483 1484 1485 1486 1487 1488 1489
    sql_print_information("Event Scheduler: Dropping %s.%s",
                          (const char *) dbname.str, (const char *) name.str);
    /*
      Construct a query for the binary log, to ensure the event is dropped
      on the slave
    */
    if (construct_drop_event_sql(thd, &sp_sql))
1490
      ret= 1;
1491 1492
    else
    {
1493
      ulong saved_master_access;
Gleb Shchepa's avatar
Gleb Shchepa committed
1494 1495

      thd->set_query(sp_sql.c_ptr_safe(), sp_sql.length());
1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508

      /*
        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= Events::drop_event(thd, dbname, name, FALSE);

      thd->security_ctx->master_access= saved_master_access;
1509
    }
1510 1511 1512 1513 1514 1515 1516 1517 1518
  }
#ifndef NO_EMBEDDED_ACCESS_CHECKS
  if (save_sctx)
    event_sctx.restore_security_context(thd, save_sctx);
#endif
  lex_end(thd->lex);
  thd->lex->unit.cleanup();
  thd->end_statement();
  thd->cleanup_after_query();
1519
  /* Avoid races with SHOW PROCESSLIST */
Gleb Shchepa's avatar
Gleb Shchepa committed
1520
  thd->set_query(NULL, 0);
unknown's avatar
unknown committed
1521

1522
  DBUG_PRINT("info", ("EXECUTED %s.%s  ret: %d", dbname.str, name.str, ret));
unknown's avatar
unknown committed
1523

unknown's avatar
unknown committed
1524
  DBUG_RETURN(ret);
unknown's avatar
unknown committed
1525 1526
}

1527 1528

/*
1529 1530 1531
  Checks whether two events are in the same schema

  SYNOPSIS
1532
    event_basic_db_equal()
1533 1534
      db  Schema
      et  Compare et->dbname to `db`
1535 1536

  RETURN VALUE
1537 1538
    TRUE   Equal
    FALSE  Not equal
1539 1540 1541
*/

bool
1542
event_basic_db_equal(LEX_STRING db, Event_basic *et)
1543
{
1544
  return !sortcmp_lex_string(et->dbname, db, system_charset_info);
1545 1546 1547 1548
}


/*
1549
  Checks whether an event has equal `db` and `name`
1550 1551

  SYNOPSIS
1552
    event_basic_identifier_equal()
1553 1554 1555
      db   Schema
      name Name
      et   The event object
1556 1557

  RETURN VALUE
1558 1559
    TRUE   Equal
    FALSE  Not equal
1560 1561 1562
*/

bool
1563
event_basic_identifier_equal(LEX_STRING db, LEX_STRING name, Event_basic *b)
1564
{
unknown's avatar
unknown committed
1565 1566
  return !sortcmp_lex_string(name, b->name, system_charset_info) &&
         !sortcmp_lex_string(db, b->dbname, system_charset_info);
1567
}
1568 1569 1570 1571

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