/* Copyright (C) 2004-2006 MySQL AB

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   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 */

#include "mysql_priv.h"
#include "event_db_repository.h"
#include "event_data_objects.h"
#include "events.h"
#include "sql_show.h"
#include "sp.h"
#include "sp_head.h"


static
time_t mysql_event_last_create_time= 0L;

static
const TABLE_FIELD_W_TYPE event_table_fields[ET_FIELD_COUNT] =
{
  {
    { C_STRING_WITH_LEN("db") },
    { C_STRING_WITH_LEN("char(64)") },
    { C_STRING_WITH_LEN("utf8") }
  },
  {
    { C_STRING_WITH_LEN("name") },
    { C_STRING_WITH_LEN("char(64)") },
    { C_STRING_WITH_LEN("utf8") }
  },
  {
    { C_STRING_WITH_LEN("body") },
    { C_STRING_WITH_LEN("longblob") },
    {NULL, 0}
  },
  {
    { C_STRING_WITH_LEN("definer") },
    { C_STRING_WITH_LEN("char(77)") },
    { C_STRING_WITH_LEN("utf8") }
  },
  {
    { C_STRING_WITH_LEN("execute_at") },
    { C_STRING_WITH_LEN("datetime") },
    {NULL, 0}
  },
  {
    { C_STRING_WITH_LEN("interval_value") },
    { C_STRING_WITH_LEN("int(11)") },
    {NULL, 0}
  },
  {
    { C_STRING_WITH_LEN("interval_field") },
    { C_STRING_WITH_LEN("enum('YEAR','QUARTER','MONTH','DAY',"
    "'HOUR','MINUTE','WEEK','SECOND','MICROSECOND','YEAR_MONTH','DAY_HOUR',"
    "'DAY_MINUTE','DAY_SECOND','HOUR_MINUTE','HOUR_SECOND','MINUTE_SECOND',"
    "'DAY_MICROSECOND','HOUR_MICROSECOND','MINUTE_MICROSECOND',"
    "'SECOND_MICROSECOND')") },
    {NULL, 0}
  },
  {
    { C_STRING_WITH_LEN("created") },
    { C_STRING_WITH_LEN("timestamp") },
    {NULL, 0}
  },
  {
    { C_STRING_WITH_LEN("modified") },
    { C_STRING_WITH_LEN("timestamp") },
    {NULL, 0}
  },
  {
    { C_STRING_WITH_LEN("last_executed") },
    { C_STRING_WITH_LEN("datetime") },
    {NULL, 0}
  },
  {
    { C_STRING_WITH_LEN("starts") },
    { C_STRING_WITH_LEN("datetime") },
    {NULL, 0}
  },
  {
    { C_STRING_WITH_LEN("ends") },
    { C_STRING_WITH_LEN("datetime") },
    {NULL, 0}
  },
  {
    { C_STRING_WITH_LEN("status") },
    { C_STRING_WITH_LEN("enum('ENABLED','DISABLED')") },
    {NULL, 0}
  },
  {
    { C_STRING_WITH_LEN("on_completion") },
    { C_STRING_WITH_LEN("enum('DROP','PRESERVE')") },
    {NULL, 0}
  },
  {
    { C_STRING_WITH_LEN("sql_mode") },
    { C_STRING_WITH_LEN("set('REAL_AS_FLOAT','PIPES_AS_CONCAT','ANSI_QUOTES',"
    "'IGNORE_SPACE','NOT_USED','ONLY_FULL_GROUP_BY','NO_UNSIGNED_SUBTRACTION',"
    "'NO_DIR_IN_CREATE','POSTGRESQL','ORACLE','MSSQL','DB2','MAXDB',"
    "'NO_KEY_OPTIONS','NO_TABLE_OPTIONS','NO_FIELD_OPTIONS','MYSQL323','MYSQL40',"
    "'ANSI','NO_AUTO_VALUE_ON_ZERO','NO_BACKSLASH_ESCAPES','STRICT_TRANS_TABLES',"
    "'STRICT_ALL_TABLES','NO_ZERO_IN_DATE','NO_ZERO_DATE','INVALID_DATES',"
    "'ERROR_FOR_DIVISION_BY_ZERO','TRADITIONAL','NO_AUTO_CREATE_USER',"
    "'HIGH_NOT_PRECEDENCE')") },
    {NULL, 0}
  },
  {
    { C_STRING_WITH_LEN("comment") },
    { C_STRING_WITH_LEN("char(64)") },
    { C_STRING_WITH_LEN("utf8") }
  }
};


/*
  Puts some data common to CREATE and ALTER EVENT into a row.

  SYNOPSIS
    mysql_event_fill_row()
      thd        THD
      table      The row to fill out
      et         Event's data
      is_update  CREATE EVENT or ALTER EVENT

  RETURN VALUE
    0                       OK
    EVEX_GENERAL_ERROR      Bad data
    EVEX_GET_FIELD_FAILED   Field count does not match. table corrupted?

  DESCRIPTION 
    Used both when an event is created and when it is altered.
*/

static int
mysql_event_fill_row(THD *thd, TABLE *table, Event_parse_data *et,
                     my_bool is_update)
{
  CHARSET_INFO *scs= system_charset_info;
  enum enum_events_table_field f_num;
  Field **fields= table->field;

  DBUG_ENTER("mysql_event_fill_row");

  DBUG_PRINT("info", ("dbname=[%s]", et->dbname.str));
  DBUG_PRINT("info", ("name  =[%s]", et->name.str));
  DBUG_PRINT("info", ("body  =[%s]", et->body.str));

  if (fields[f_num= ET_FIELD_DEFINER]->
                              store(et->definer.str, et->definer.length, scs))
    goto err_truncate;

  if (fields[f_num= ET_FIELD_DB]->store(et->dbname.str, et->dbname.length, scs))
    goto err_truncate;

  if (fields[f_num= ET_FIELD_NAME]->store(et->name.str, et->name.length, scs))
    goto err_truncate;

  /* both ON_COMPLETION and STATUS are NOT NULL thus not calling set_notnull()*/
  fields[ET_FIELD_ON_COMPLETION]->store((longlong)et->on_completion, TRUE);

  fields[ET_FIELD_STATUS]->store((longlong)et->status, TRUE);

  /*
    Change the SQL_MODE only if body was present in an ALTER EVENT and of course
    always during CREATE EVENT.
  */ 
  if (et->body.str)
  {
    fields[ET_FIELD_SQL_MODE]->store((longlong)thd->variables.sql_mode, TRUE);
    if (fields[f_num= ET_FIELD_BODY]->store(et->body.str, et->body.length, scs))
      goto err_truncate;
  }

  if (et->expression)
  {
    fields[ET_FIELD_INTERVAL_EXPR]->set_notnull();
    fields[ET_FIELD_INTERVAL_EXPR]->store((longlong)et->expression, TRUE);

    fields[ET_FIELD_TRANSIENT_INTERVAL]->set_notnull();

    fields[ET_FIELD_TRANSIENT_INTERVAL]->
                            store(interval_type_to_name[et->interval].str,
                                  interval_type_to_name[et->interval].length,
                                  scs);

    fields[ET_FIELD_EXECUTE_AT]->set_null();

    if (!et->starts_null)
    {
      fields[ET_FIELD_STARTS]->set_notnull();
      fields[ET_FIELD_STARTS]->store_time(&et->starts, MYSQL_TIMESTAMP_DATETIME);
    }

    if (!et->ends_null)
    {
      fields[ET_FIELD_ENDS]->set_notnull();
      fields[ET_FIELD_ENDS]->store_time(&et->ends, MYSQL_TIMESTAMP_DATETIME);
    }
  }
  else if (et->execute_at.year)
  {
    fields[ET_FIELD_INTERVAL_EXPR]->set_null();
    fields[ET_FIELD_TRANSIENT_INTERVAL]->set_null();
    fields[ET_FIELD_STARTS]->set_null();
    fields[ET_FIELD_ENDS]->set_null();
    
    fields[ET_FIELD_EXECUTE_AT]->set_notnull();
    fields[ET_FIELD_EXECUTE_AT]->
                        store_time(&et->execute_at, MYSQL_TIMESTAMP_DATETIME);
  }
  else
  {
    DBUG_ASSERT(is_update);
    /*
      it is normal to be here when the action is update
      this is an error if the action is create. something is borked
    */
  }
    
  ((Field_timestamp *)fields[ET_FIELD_MODIFIED])->set_time();

  if (et->comment.str)
  {
    if (fields[f_num= ET_FIELD_COMMENT]->
                          store(et->comment.str, et->comment.length, scs))
      goto err_truncate;
  }

  DBUG_RETURN(0);

err_truncate:
  my_error(ER_EVENT_DATA_TOO_LONG, MYF(0), fields[f_num]->field_name);
  DBUG_RETURN(EVEX_GENERAL_ERROR);
}


/*
  Performs an index scan of event_table (mysql.event) and fills schema_table.

  SYNOPSIS
    Event_db_repository::index_read_for_db_for_i_s()
      thd          Thread
      schema_table The I_S.EVENTS table
      event_table  The event table to use for loading (mysql.event)
      db           For which schema to do an index scan.

  RETURN VALUE
    0  OK
    1  Error
*/

bool
Event_db_repository::index_read_for_db_for_i_s(THD *thd, TABLE *schema_table,
                                               TABLE *event_table,
                                               const char *db)
{
  int ret=0;
  CHARSET_INFO *scs= system_charset_info;
  KEY *key_info;
  uint key_len;
  byte *key_buf= NULL;
  LINT_INIT(key_buf);

  DBUG_ENTER("Event_db_repository::index_read_for_db_for_i_s");

  DBUG_PRINT("info", ("Using prefix scanning on PK"));
  event_table->file->ha_index_init(0, 1);
  event_table->field[ET_FIELD_DB]->store(db, strlen(db), scs);
  key_info= event_table->key_info;
  key_len= key_info->key_part[0].store_length;

  if (!(key_buf= (byte *)alloc_root(thd->mem_root, key_len)))
  {
    ret= 1;
    /* Don't send error, it would be done by sql_alloc_error_handler() */
  }
  else
  {
    key_copy(key_buf, event_table->record[0], key_info, key_len);
    if (!(ret= event_table->file->index_read(event_table->record[0], key_buf,
                                             key_len, HA_READ_PREFIX)))
    {
      DBUG_PRINT("info",("Found rows. Let's retrieve them. ret=%d", ret));
      do
      {
        ret= copy_event_to_schema_table(thd, schema_table, event_table);
        if (ret == 0)
          ret= event_table->file->index_next_same(event_table->record[0],
                                                  key_buf, key_len); 
      } while (ret == 0);
    }
    DBUG_PRINT("info", ("Scan finished. ret=%d", ret));
  }
  event_table->file->ha_index_end(); 
  /*  ret is guaranteed to be != 0 */
  if (ret == HA_ERR_END_OF_FILE || ret == HA_ERR_KEY_NOT_FOUND)
    DBUG_RETURN(FALSE);

  DBUG_RETURN(TRUE);
}


/*
  Performs a table scan of event_table (mysql.event) and fills schema_table.

  SYNOPSIS
    Events_db_repository::table_scan_all_for_i_s()
      thd          Thread
      schema_table The I_S.EVENTS in memory table
      event_table  The event table to use for loading.

  RETURN VALUE
    FALSE  OK
    TRUE   Error
*/

bool
Event_db_repository::table_scan_all_for_i_s(THD *thd, TABLE *schema_table,
                                            TABLE *event_table)
{
  int ret;
  READ_RECORD read_record_info;
  DBUG_ENTER("Event_db_repository::table_scan_all_for_i_s");

  init_read_record(&read_record_info, thd, event_table, NULL, 1, 0);

  /*
    rr_sequential, in read_record(), returns 137==HA_ERR_END_OF_FILE,
    but rr_handle_error returns -1 for that reason. Thus, read_record()
    returns -1 eventually.
  */
  do
  {
    ret= read_record_info.read_record(&read_record_info);
    if (ret == 0)
      ret= copy_event_to_schema_table(thd, schema_table, event_table);
  } while (ret == 0);

  DBUG_PRINT("info", ("Scan finished. ret=%d", ret));
  end_read_record(&read_record_info);

  /*  ret is guaranteed to be != 0 */
  DBUG_RETURN(ret == -1? FALSE:TRUE);
}


/*
  Fills I_S.EVENTS with data loaded from mysql.event. Also used by
  SHOW EVENTS

  SYNOPSIS
    Event_db_repository::fill_schema_events()
      thd     Thread
      tables  The schema table
      db      If not NULL then get events only from this schema

  RETURN VALUE
    FALSE  OK
    TRUE   Error
*/

int
Event_db_repository::fill_schema_events(THD *thd, TABLE_LIST *tables,
                                        const char *db)
{
  TABLE *schema_table= tables->table;
  TABLE *event_table= NULL;
  Open_tables_state backup;
  int ret= 0;

  DBUG_ENTER("Event_db_repository::fill_schema_events");
  DBUG_PRINT("info",("db=%s", db? db:"(null)"));

  thd->reset_n_backup_open_tables_state(&backup);
  if (open_event_table(thd, TL_READ, &event_table))
  {
    sql_print_error("Table mysql.event is damaged.");
    thd->restore_backup_open_tables_state(&backup);
    DBUG_RETURN(1);
  }

  /*
    1. SELECT I_S => use table scan. I_S.EVENTS does not guarantee order
                     thus we won't order it. OTOH, SHOW EVENTS will be
                     ordered.
    2. SHOW EVENTS => PRIMARY KEY with prefix scanning on (db)
       Reasoning: Events are per schema, therefore a scan over an index
                  will save use from doing a table scan and comparing
                  every single row's `db` with the schema which we show.
  */
  if (db)
    ret= index_read_for_db_for_i_s(thd, schema_table, event_table, db);
  else
    ret= table_scan_all_for_i_s(thd, schema_table, event_table);

  close_thread_tables(thd);
  thd->restore_backup_open_tables_state(&backup);

  DBUG_PRINT("info", ("Return code=%d", ret));
  DBUG_RETURN(ret);
}


/*
  Open mysql.event table for read

  SYNOPSIS
    Events::open_event_table()
    thd         [in]  Thread context
    lock_type   [in]  How to lock the table
    table       [out] We will store the open table here

  RETURN VALUE
    1   Cannot lock table
    2   The table is corrupted - different number of fields
    0   OK
*/

int
Event_db_repository::open_event_table(THD *thd, enum thr_lock_type lock_type,
                                      TABLE **table)
{
  TABLE_LIST tables;
  DBUG_ENTER("Event_db_repository::open_event_table");

  bzero((char*) &tables, sizeof(tables));
  tables.db= (char*) "mysql";
  tables.table_name= tables.alias= (char*) "event";
  tables.lock_type= lock_type;

  if (simple_open_n_lock_tables(thd, &tables))
    DBUG_RETURN(1);

  if (table_check_intact(tables.table, ET_FIELD_COUNT,
                         event_table_fields,
                         &mysql_event_last_create_time,
                         ER_CANNOT_LOAD_FROM_TABLE))
  {
    close_thread_tables(thd);
    DBUG_RETURN(2);
  }
  *table= tables.table;
  tables.table->use_all_columns();
  DBUG_RETURN(0);
}


/*
  Checks parameters which we got from the parsing phase.

  SYNOPSIS
    check_parse_params()
      thd         Thread context
      parse_data  Event's data
  
  RETURN VALUE
    FALSE  OK
    TRUE   Error (reported)
*/

static int
check_parse_params(THD *thd, Event_parse_data *parse_data)
{
  DBUG_ENTER("check_parse_params");

  if (parse_data->check_parse_data(thd))
    DBUG_RETURN(EVEX_BAD_PARAMS);

  if (!parse_data->dbname.str ||
      (thd->lex->sql_command == SQLCOM_ALTER_EVENT && thd->lex->spname &&
       !thd->lex->spname->m_db.str))
  {
    my_message(ER_NO_DB_ERROR, ER(ER_NO_DB_ERROR), MYF(0));
    DBUG_RETURN(EVEX_BAD_PARAMS);
  }

  if (check_access(thd, EVENT_ACL, parse_data->dbname.str, 0, 0, 0,
                   is_schema_db(parse_data->dbname.str)) ||
      (thd->lex->sql_command == SQLCOM_ALTER_EVENT && thd->lex->spname &&
       (check_access(thd, EVENT_ACL, thd->lex->spname->m_db.str, 0, 0, 0,
                     is_schema_db(thd->lex->spname->m_db.str)))))
    DBUG_RETURN(EVEX_BAD_PARAMS);

  DBUG_RETURN(0);
}


/*
  Creates an event in mysql.event

  SYNOPSIS
    Event_db_repository::create_event()
      thd             [in]  THD
      parse_data      [in]  Object containing info about the event
      create_if_not   [in]  Whether to generate anwarning in case event exists

  RETURN VALUE
    0                   OK
    EVEX_GENERAL_ERROR  Failure

  DESCRIPTION 
    Creates an event. Relies on mysql_event_fill_row which is shared with
    ::update_event. The name of the event is inside "et".
*/

bool
Event_db_repository::create_event(THD *thd, Event_parse_data *parse_data,
                                  my_bool create_if_not)
{
  int ret= 0;
  CHARSET_INFO *scs= system_charset_info;
  TABLE *table= NULL;
  char old_db_buf[NAME_LEN+1];
  LEX_STRING old_db= { old_db_buf, sizeof(old_db_buf) };
  bool dbchanged= FALSE;

  DBUG_ENTER("Event_db_repository::create_event");

  if (check_parse_params(thd, parse_data))
    goto err;

  DBUG_PRINT("info", ("open mysql.event for update"));
  if (open_event_table(thd, TL_WRITE, &table))
  {
    my_error(ER_EVENT_OPEN_TABLE_FAILED, MYF(0));
    goto err;
  }


  DBUG_PRINT("info", ("name: %.*s", parse_data->name.length,
             parse_data->name.str));

  DBUG_PRINT("info", ("check existance of an event with the same name"));
  if (!find_named_event(thd, parse_data->dbname, parse_data->name, table))
  {
    if (create_if_not)
    {
      push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE,
                          ER_EVENT_ALREADY_EXISTS, ER(ER_EVENT_ALREADY_EXISTS),
                          parse_data->name.str);
      goto ok;
    }
    my_error(ER_EVENT_ALREADY_EXISTS, MYF(0), parse_data->name.str);
    goto err;
  }

  DBUG_PRINT("info", ("non-existant, go forward"));

  if ((ret= sp_use_new_db(thd, parse_data->dbname, &old_db, 0, &dbchanged)))
  {
    my_error(ER_BAD_DB_ERROR, MYF(0));
    goto err;
  }

  restore_record(table, s->default_values);     // Get default values for fields

  if (system_charset_info->cset->
        numchars(system_charset_info, parse_data->dbname.str,
                 parse_data->dbname.str + parse_data->dbname.length) >
      table->field[ET_FIELD_DB]->char_length())
  {
    my_error(ER_TOO_LONG_IDENT, MYF(0), parse_data->dbname.str);
    goto err;
  }

  if (system_charset_info->cset->
        numchars(system_charset_info, parse_data->name.str,
                 parse_data->name.str + parse_data->name.length) >
      table->field[ET_FIELD_NAME]->char_length())
  {
    my_error(ER_TOO_LONG_IDENT, MYF(0), parse_data->name.str);
    goto err;
  }

  if (parse_data->body.length > table->field[ET_FIELD_BODY]->field_length)
  {
    my_error(ER_TOO_LONG_BODY, MYF(0), parse_data->name.str);
    goto err;
  }

  if (!(parse_data->expression) && !(parse_data->execute_at.year))
  {
    DBUG_PRINT("error", ("neither expression nor execute_at are set!"));
    my_error(ER_EVENT_NEITHER_M_EXPR_NOR_M_AT, MYF(0));
    goto err;
  }

  ((Field_timestamp *)table->field[ET_FIELD_CREATED])->set_time();

  /*
    mysql_event_fill_row() calls my_error() in case of error so no need to
    handle it here
  */
  if ((ret= mysql_event_fill_row(thd, table, parse_data, FALSE)))
    goto err; 

  /* Close active transaction only if We are going to modify disk */
  if (end_active_trans(thd))
    goto err;

  if (table->file->ha_write_row(table->record[0]))
  {
    my_error(ER_EVENT_STORE_FAILED, MYF(0), parse_data->name.str, ret);
    goto err;
  }

ok:
  if (dbchanged)
    (void) mysql_change_db(thd, old_db.str, 1);
  /*
    This statement may cause a spooky valgrind warning at startup
    inside init_key_cache on my system (ahristov, 2006/08/10) 
  */
  close_thread_tables(thd);
  DBUG_RETURN(FALSE);

err:
  if (dbchanged)
    (void) mysql_change_db(thd, old_db.str, 1);
  if (table)
    close_thread_tables(thd);
  DBUG_RETURN(TRUE);
}


/*
  Used to execute ALTER EVENT. Pendant to Events::update_event().

  SYNOPSIS
    Event_db_repository::update_event()
      thd      THD
      sp_name  the name of the event to alter
      et       event's data

  RETURN VALUE
    FALSE  OK
    TRUE   Error (reported)

  NOTES
    sp_name is passed since this is the name of the event to
    alter in case of RENAME TO.
*/

bool
Event_db_repository::update_event(THD *thd, Event_parse_data *parse_data,
                                  LEX_STRING *new_dbname, LEX_STRING *new_name)
{
  CHARSET_INFO *scs= system_charset_info;
  TABLE *table= NULL;
  DBUG_ENTER("Event_db_repository::update_event");

  if (open_event_table(thd, TL_WRITE, &table))
  {
    my_error(ER_EVENT_OPEN_TABLE_FAILED, MYF(0));
    goto err;
  }

  if (check_parse_params(thd, parse_data))
    goto err;

  DBUG_PRINT("info", ("dbname: %s", parse_data->dbname.str));
  DBUG_PRINT("info", ("name: %s", parse_data->name.str));
  DBUG_PRINT("info", ("user: %s", parse_data->definer.str));
  if (new_dbname)
    DBUG_PRINT("info", ("rename to: %s@%s", new_dbname->str, new_name->str));

  /* first look whether we overwrite */
  if (new_name)
  {
    if (!sortcmp_lex_string(parse_data->name, *new_name, scs) &&
        !sortcmp_lex_string(parse_data->dbname, *new_dbname, scs))
    {
      my_error(ER_EVENT_SAME_NAME, MYF(0), parse_data->name.str);
      goto err;
    }

    if (!find_named_event(thd, *new_dbname, *new_name, table))
    {
      my_error(ER_EVENT_ALREADY_EXISTS, MYF(0), new_name->str);
      goto err;
    }
  }
  /*
    ...and then if there is such an event. Don't exchange the blocks
    because you will get error 120 from table handler because new_name will
    overwrite the key and SE will tell us that it cannot find the already found
    row (copied into record[1] later
  */
  if (find_named_event(thd, parse_data->dbname, parse_data->name, table))
  {
    my_error(ER_EVENT_DOES_NOT_EXIST, MYF(0), parse_data->name.str);
    goto err;
  }

  store_record(table,record[1]);

  /* Don't update create on row update. */
  table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET;

  /*
    mysql_event_fill_row() calls my_error() in case of error so no need to
    handle it here
  */
  if (mysql_event_fill_row(thd, table, parse_data, TRUE))
    goto err;

  if (new_dbname)
  {
    table->field[ET_FIELD_DB]->store(new_dbname->str, new_dbname->length, scs);
    table->field[ET_FIELD_NAME]->store(new_name->str, new_name->length, scs);
  }

  /* Close active transaction only if We are going to modify disk */
  if (end_active_trans(thd))
    goto err;

  int res;
  if ((res= table->file->ha_update_row(table->record[1], table->record[0])))
  {
    my_error(ER_EVENT_STORE_FAILED, MYF(0), parse_data->name.str, res);
    goto err;
  }

  /* close mysql.event or we crash later when loading the event from disk */
  close_thread_tables(thd);
  DBUG_RETURN(FALSE);

err:
  if (table)
    close_thread_tables(thd);
  DBUG_RETURN(TRUE);
}


/*
  Drops an event

  SYNOPSIS
    Event_db_repository::drop_event()
      thd             [in]  THD
      db              [in]  Database name
      name            [in]  Event's name
      drop_if_exists  [in]  If set and the event not existing => warning
                            onto the stack

  RETURN VALUE
    FALSE  OK
    TRUE   Error (reported)
*/

bool
Event_db_repository::drop_event(THD *thd, LEX_STRING db, LEX_STRING name,
                                bool drop_if_exists)
{
  TABLE *table= NULL;
  Open_tables_state backup;
  int ret;

  DBUG_ENTER("Event_db_repository::drop_event");
  DBUG_PRINT("enter", ("%s@%s", db.str, name.str));

  thd->reset_n_backup_open_tables_state(&backup);
  if ((ret= open_event_table(thd, TL_WRITE, &table)))
  {
    my_error(ER_EVENT_OPEN_TABLE_FAILED, MYF(0));
    goto done;
  }

  if (!(ret= find_named_event(thd, db, name, table)))
  {
    /* Close active transaction only if we are actually going to modify disk */
    if (!(ret= end_active_trans(thd)) &&
        (ret= table->file->ha_delete_row(table->record[0])))
      my_error(ER_EVENT_CANNOT_DELETE, MYF(0));
  }
  else
  {
    if (drop_if_exists)
    {
      push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE,
                          ER_SP_DOES_NOT_EXIST, ER(ER_SP_DOES_NOT_EXIST),
                          "Event", name.str);
      ret= 0;
    } else
      my_error(ER_EVENT_DOES_NOT_EXIST, MYF(0), name.str);
  }

done:
  if (table)
    close_thread_tables(thd);
  thd->restore_backup_open_tables_state(&backup);

  DBUG_RETURN(ret);
}


/*
  Positions the internal pointer of `table` to the place where (db, name)
  is stored.

  SYNOPSIS
    Event_db_repository::find_named_event()
      thd    Thread
      db     Schema
      name   Event name
      table  Opened mysql.event

  RETURN VALUE
    FALSE  OK
    TRUE   No such event
*/

bool
Event_db_repository::find_named_event(THD *thd, LEX_STRING db, LEX_STRING name,
                                     TABLE *table)
{
  byte key[MAX_KEY_LENGTH];
  DBUG_ENTER("Event_db_repository::find_named_event");
  DBUG_PRINT("enter", ("name: %.*s", name.length, name.str));

  /*
    Create key to find row. We have to use field->store() to be able to
    handle VARCHAR and CHAR fields.
    Assumption here is that the two first fields in the table are
    'db' and 'name' and the first key is the primary key over the
    same fields.
  */
  if (db.length > table->field[ET_FIELD_DB]->field_length ||
      name.length > table->field[ET_FIELD_NAME]->field_length)
    DBUG_RETURN(TRUE);

  table->field[ET_FIELD_DB]->store(db.str, db.length, &my_charset_bin);
  table->field[ET_FIELD_NAME]->store(name.str, name.length, &my_charset_bin);

  key_copy(key, table->record[0], table->key_info, table->key_info->key_length);

  if (table->file->index_read_idx(table->record[0], 0, key,
                                  table->key_info->key_length,
                                  HA_READ_KEY_EXACT))
  {
    DBUG_PRINT("info", ("Row not found"));
    DBUG_RETURN(TRUE);
  }

  DBUG_PRINT("info", ("Row found!"));
  DBUG_RETURN(FALSE);
}


/*
  Drops all events in the selected database, from mysql.event.

  SYNOPSIS
    Event_db_repository::drop_schema_events()
      thd     Thread
      schema  The database to clean from events
*/

void
Event_db_repository::drop_schema_events(THD *thd, LEX_STRING schema)
{
  DBUG_ENTER("Event_db_repository::drop_schema_events");
  drop_events_by_field(thd, ET_FIELD_DB, schema);
  DBUG_VOID_RETURN;
}


/*
  Drops all events by field which has specific value of the field

  SYNOPSIS
    Event_db_repository::drop_events_by_field()
      thd         Thread
      table       mysql.event TABLE
      field       Which field of the row to use for matching
      field_value The value that should match
*/

void
Event_db_repository::drop_events_by_field(THD *thd,  
                                          enum enum_events_table_field field,
                                          LEX_STRING field_value)
{
  int ret= 0;
  TABLE *table= NULL;
  READ_RECORD read_record_info;
  DBUG_ENTER("Event_db_repository::drop_events_by_field");  
  DBUG_PRINT("enter", ("field=%d field_value=%s", field, field_value.str));

  if (open_event_table(thd, TL_WRITE, &table))
  {
    /*
      Currently being used only for DROP DATABASE - In this case we don't need
      error message since the OK packet has been sent. But for DROP USER we
      could need it.

      my_error(ER_EVENT_OPEN_TABLE_FAILED, MYF(0));
    */
    DBUG_VOID_RETURN;
  }

  /* only enabled events are in memory, so we go now and delete the rest */
  init_read_record(&read_record_info, thd, table, NULL, 1, 0);
  while (!ret && !(read_record_info.read_record(&read_record_info)) )
  {
    char *et_field= get_field(thd->mem_root, table->field[field]);

    LEX_STRING et_field_lex= { et_field, strlen(et_field) };
    DBUG_PRINT("info", ("Current event %s name=%s", et_field,
               get_field(thd->mem_root, table->field[ET_FIELD_NAME])));

    if (!sortcmp_lex_string(et_field_lex, field_value, system_charset_info))
    {
      DBUG_PRINT("info", ("Dropping"));
      ret= table->file->ha_delete_row(table->record[0]);
    }
  }
  end_read_record(&read_record_info);
  close_thread_tables(thd);

  DBUG_VOID_RETURN;
}


/*
  Looks for a named event in mysql.event and then loads it from
  the table, compiles and inserts it into the cache.

  SYNOPSIS
    Event_db_repository::load_named_event()
      thd      [in]  Thread context
      dbname   [in]  Event's db name
      name     [in]  Event's name
      etn      [out] The loaded event

  RETURN VALUE
    FALSE  OK
    TRUE   Error (reported)
*/

bool
Event_db_repository::load_named_event(THD *thd, LEX_STRING dbname,
                                      LEX_STRING name, Event_basic *etn)
{
  TABLE *table= NULL;
  int ret= 0;
  Open_tables_state backup;

  DBUG_ENTER("Event_db_repository::load_named_event");
  DBUG_PRINT("enter",("thd=0x%lx name:%*s",thd, name.length, name.str));

  thd->reset_n_backup_open_tables_state(&backup);

  if ((ret= open_event_table(thd, TL_READ, &table)))
    my_error(ER_EVENT_OPEN_TABLE_FAILED, MYF(0));
  else if ((ret= find_named_event(thd, dbname, name, table)))
    my_error(ER_EVENT_DOES_NOT_EXIST, MYF(0), name.str);
  else if ((ret= etn->load_from_row(table)))
    my_error(ER_CANNOT_LOAD_FROM_TABLE, MYF(0), "event");

  if (table)
    close_thread_tables(thd);

  thd->restore_backup_open_tables_state(&backup);
  /* In this case no memory was allocated so we don't need to clean */

  DBUG_RETURN(ret);
}
