/*
   Copyright (c) 2014, 2021, Oracle and/or its affiliates.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License, version 2.0,
   as published by the Free Software Foundation.

   This program is also distributed with certain software (including
   but not limited to OpenSSL) that is licensed under separate terms,
   as designated in a particular file or component or in included license
   documentation.  The authors of MySQL hereby grant you an additional
   permission to link the program and your derivative works with the
   separately licensed software that they have included with MySQL.

   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, version 2.0, 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,
   51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */

#include "my_global.h"
#include "trigger_loader.h"
#include "sql_class.h"
#include "sp_head.h"      // sp_name
#include "sql_base.h"     // is_equal(LEX_STRING, LEX_STRING)
#include "sql_table.h"    // build_table_filename()
#include <mysys_err.h>    // EE_OUTOFMEMORY
#include "parse_file.h"   // File_option
#include "trigger.h"

#include "pfs_file_provider.h"
#include "mysql/psi/mysql_file.h"

#include "mysql/psi/mysql_sp.h"

///////////////////////////////////////////////////////////////////////////

const char * const TRN_EXT= ".TRN";
const char * const TRG_EXT= ".TRG";

///////////////////////////////////////////////////////////////////////////

/**
  This must be kept up to date whenever a new option is added to the list
  above, as it specifies the number of required parameters of the trigger in
  .trg file.
*/

static const int TRG_NUM_REQUIRED_PARAMETERS= 8;

const LEX_STRING trg_file_type= { C_STRING_WITH_LEN("TRIGGERS") };

const LEX_STRING trn_file_type= { C_STRING_WITH_LEN("TRIGGERNAME") };

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

/*
  Structure representing contents of .TRN file which are used to support
  database wide trigger namespace.
*/

struct Trn_file_data
{
  LEX_STRING trigger_table;
};

///////////////////////////////////////////////////////////////////////////

static File_option trn_file_parameters[]=
{
  {
    { C_STRING_WITH_LEN("trigger_table")},
    offsetof(struct Trn_file_data, trigger_table),
    FILE_OPTIONS_ESTRING
  },
  { { 0, 0 }, 0, FILE_OPTIONS_STRING }
};

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

/**
  Structure representing contents of .TRG file.
*/

struct Trg_file_data
{
  /// List of CREATE TRIGGER statements.
  List<LEX_STRING>  definitions;

  /// List of 'sql mode' values.
  List<ulonglong> sql_modes;

  /// List of 'definer' values.
  List<LEX_STRING>  definers_list;

  /// List of client character set names.
  List<LEX_STRING> client_cs_names;

  /// List of connection collation names.
  List<LEX_STRING> connection_cl_names;

  /// List of database collation names.
  List<LEX_STRING> db_cl_names;

  /// List of trigger creation time stamps
  List<longlong> created_timestamps;

  bool append_trigger(Trigger *t, MEM_ROOT *m)
  {
    return
      definitions.push_back(t->get_definition_ptr(), m) ||
      sql_modes.push_back(t->get_sql_mode_ptr(), m) ||
      definers_list.push_back(t->get_definer_ptr(), m) ||
      client_cs_names.push_back(t->get_client_cs_name_ptr(), m) ||
      connection_cl_names.push_back(t->get_connection_cl_name_ptr(), m) ||
      db_cl_names.push_back(t->get_db_cl_name_ptr(), m) ||
      created_timestamps.push_back(t->get_created_timestamp_ptr(), m);
  }
};

///////////////////////////////////////////////////////////////////////////

/**
  Table of .TRG file field descriptors.
*/

static File_option trg_file_parameters[]=
{
  {
    { C_STRING_WITH_LEN("triggers") },
    my_offsetof(struct Trg_file_data, definitions),
    FILE_OPTIONS_STRLIST
  },
  {
    { C_STRING_WITH_LEN("sql_modes") },
    my_offsetof(struct Trg_file_data, sql_modes),
    FILE_OPTIONS_ULLLIST
  },
  {
    { C_STRING_WITH_LEN("definers") },
    my_offsetof(struct Trg_file_data, definers_list),
    FILE_OPTIONS_STRLIST
  },
  {
    { C_STRING_WITH_LEN("client_cs_names") },
    my_offsetof(struct Trg_file_data, client_cs_names),
    FILE_OPTIONS_STRLIST
  },
  {
    { C_STRING_WITH_LEN("connection_cl_names") },
    my_offsetof(struct Trg_file_data, connection_cl_names),
    FILE_OPTIONS_STRLIST
  },
  {
    { C_STRING_WITH_LEN("db_cl_names") },
    my_offsetof(struct Trg_file_data, db_cl_names),
    FILE_OPTIONS_STRLIST
  },
  {
    { C_STRING_WITH_LEN("created") },
    my_offsetof(struct Trg_file_data, created_timestamps),
    FILE_OPTIONS_ULLLIST
  },
  { { 0, 0 }, 0, FILE_OPTIONS_STRING }
};

///////////////////////////////////////////////////////////////////////////

static File_option sql_modes_parameters=
{
  { C_STRING_WITH_LEN("sql_modes") },
  my_offsetof(struct Trg_file_data, sql_modes),
  FILE_OPTIONS_ULLLIST
};

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

LEX_STRING Trigger_loader::build_trn_path(char *trn_file_name_buffer,
                                          int trn_file_name_buffer_size,
                                          const char *db_name,
                                          const char *trigger_name)
{
  bool was_truncated= false;
  LEX_STRING trn_file_name;

  trn_file_name.str= trn_file_name_buffer;
  trn_file_name.length= build_table_filename(trn_file_name_buffer,
                                             trn_file_name_buffer_size - 1,
                                             db_name, trigger_name,
                                             TRN_EXT, 0, &was_truncated);

  if (!was_truncated)
    return trn_file_name;

  my_error(ER_IDENT_CAUSES_TOO_LONG_PATH, MYF(0),
           sizeof (trn_file_name_buffer) - 1,
           trn_file_name_buffer);

  trn_file_name= NULL_STR;

  return trn_file_name;
}

/**
  This method saves .TRG file for the table specified by arguments.

  @param db_name     Name of database for subject table
  @param table_name  Name of subject table

  @return Operation status.
    @retval false  Success
    @retval true   Failure
*/

static bool save_trg_file(const char *db_name,
                          const char *table_name,
                          const Trg_file_data *trg)
{
  char trg_file_name_buffer[FN_REFLEN];
  LEX_STRING trg_file_name;
  bool was_truncated= false;

  trg_file_name.length= build_table_filename(trg_file_name_buffer,
                                             FN_REFLEN - 1,
                                             db_name, table_name,
                                             TRG_EXT, 0, &was_truncated);

  if (was_truncated)
  {
    my_error(ER_IDENT_CAUSES_TOO_LONG_PATH, MYF(0),
             sizeof (trg_file_name_buffer) - 1,
             trg_file_name_buffer);
    return true;
  }

  trg_file_name.str= trg_file_name_buffer;
  return sql_create_definition_file(NULL, &trg_file_name, &trg_file_type,
                                    (uchar*) trg, trg_file_parameters);
}


/**
  Deletes the .TRN file for a trigger.

  @param [in] db_name        trigger's database name
  @param [in] trigger_name   trigger's name

  @return Operation status.
    @retval true   Failure
    @retval false  Success
*/

static bool rm_trn_file(const char *db_name, const char *trigger_name)
{
  char path[FN_REFLEN];

  build_table_filename(path, FN_REFLEN - 1, db_name, trigger_name, TRN_EXT, 0);
  return mysql_file_delete(key_file_trn, path, MYF(MY_WME));
}


/**
  Deletes the .TRG file for a table.

  @param db_name      table's database name
  @param table_name   table's name

  @return Operation status.
    @retval true   Failure
    @retval false  Success
*/

static bool rm_trg_file(const char *db_name, const char *table_name)
{
  char path[FN_REFLEN];

  build_table_filename(path, FN_REFLEN - 1, db_name, table_name, TRG_EXT, 0);
  return mysql_file_delete(key_file_trg, path, MYF(MY_WME));
}

static bool fill_trg_data(Trg_file_data *trg,
                          MEM_ROOT *mem_root,
                          List<Trigger> *triggers)
{
  List_iterator<Trigger> it(*triggers);
  Trigger *t;

  while ((t= it++))
  {
    if (trg->append_trigger(t, mem_root))
      return true;
  }

  return false;
}

/**
  Change the subject table in the given list of triggers.

  @param db_name         Old database of subject table
  @param new_db_name         New database of subject table
  @param new_table_name      New subject table's name
  @param stopper             Pointer to a trigger_name for
                             which we should stop updating.

  @retval NULL      Success
  @retval not-NULL  Failure, pointer to Table_trigger_dispatcher::names_list
                    element for which update failed.
*/

static Trigger *change_table_name_in_trn_files(
  List<Trigger> *triggers,
  const char *db_name,
  const char *new_db_name,
  const LEX_STRING *new_table_name,
  const Trigger *stopper)
{
  List_iterator_fast<Trigger> it(*triggers);
  Trigger *t;

  while ((t= it++))
  {
    if (t == stopper)
      break;

    // Get TRN file name.

    char trn_file_name_buffer[FN_REFLEN];

    LEX_STRING trn_file_name=
      Trigger_loader::build_trn_path(trn_file_name_buffer, FN_REFLEN,
                                     new_db_name, t->get_trigger_name().str);

    if (!trn_file_name.str)
      return NULL; // FIXME: OOM

    // Prepare TRN data.

    Trn_file_data trn;
    trn.trigger_table= *new_table_name;

    // Create new TRN file.

    if (sql_create_definition_file(NULL, &trn_file_name, &trn_file_type,
                                   (uchar *) &trn, trn_file_parameters))
    {
      return t;
    }

    // Remove stale .TRN file in case of database upgrade.

    if (db_name)
    {
      if (rm_trn_file(db_name, t->get_trigger_name().str))
      {
        rm_trn_file(new_db_name, t->get_trigger_name().str);
        return t;
      }
    }
  }

  return NULL;
}

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

class Handle_old_incorrect_sql_modes_hook: public Unknown_key_hook
{
private:
  char *path;
public:
  Handle_old_incorrect_sql_modes_hook(char *file_path)
    :path(file_path)
  {};
  virtual bool process_unknown_string(const char *&unknown_key, uchar *base,
                                      MEM_ROOT *mem_root, const char *end);
};

///////////////////////////////////////////////////////////////////////////

class Handle_old_incorrect_trigger_table_hook: public Unknown_key_hook
{
public:
  Handle_old_incorrect_trigger_table_hook(char *file_path,
                                          LEX_STRING *trigger_table_arg)
    :path(file_path), trigger_table_value(trigger_table_arg)
  {};
  virtual bool process_unknown_string(const char *&unknown_key, uchar *base,
                                      MEM_ROOT *mem_root, const char *end);
private:
  char *path;
  LEX_STRING *trigger_table_value;
};

///////////////////////////////////////////////////////////////////////////

/**
  Trigger BUG#14090 compatibility hook.

  @param[in,out] unknown_key       reference on the line with unknown
    parameter and the parsing point
  @param[in]     base              base address for parameter writing
    (structure like TABLE)
  @param[in]     mem_root          MEM_ROOT for parameters allocation
  @param[in]     end               the end of the configuration

  @note
    NOTE: this hook process back compatibility for incorrectly written
    sql_modes parameter (see BUG#14090).

  @retval
    false OK
  @retval
    true  Error
*/

#define INVALID_SQL_MODES_LENGTH 13

bool Handle_old_incorrect_sql_modes_hook::process_unknown_string(
  const char *&unknown_key,
  uchar *base,
  MEM_ROOT *mem_root,
  const char *end)
{
  DBUG_ENTER("Handle_old_incorrect_sql_modes_hook::process_unknown_string");
  DBUG_PRINT("info", ("unknown key: %60s", unknown_key));

  if (unknown_key + INVALID_SQL_MODES_LENGTH + 1 < end &&
      unknown_key[INVALID_SQL_MODES_LENGTH] == '=' &&
      !memcmp(unknown_key, STRING_WITH_LEN("sql_modes")))
  {
    const char *ptr= unknown_key + INVALID_SQL_MODES_LENGTH + 1;

    DBUG_PRINT("info", ("sql_modes affected by BUG#14090 detected"));
    push_warning_printf(current_thd,
                        Sql_condition::SL_NOTE,
                        ER_OLD_FILE_FORMAT,
                        ER(ER_OLD_FILE_FORMAT),
                        path, "TRIGGER");
    if (get_file_options_ulllist(ptr, end, unknown_key, base,
                                 &sql_modes_parameters, mem_root))
    {
      DBUG_RETURN(true);
    }
    /*
      Set parsing pointer to the last symbol of string (\n)
      1) to avoid problem with \0 in the junk after sql_modes
      2) to speed up skipping this line by parser.
    */
    unknown_key= ptr-1;
  }
  DBUG_RETURN(false);
}

///////////////////////////////////////////////////////////////////////////

#define INVALID_TRIGGER_TABLE_LENGTH 15

/**
  Trigger BUG#15921 compatibility hook. For details see
  Handle_old_incorrect_sql_modes_hook::process_unknown_string().
*/

bool Handle_old_incorrect_trigger_table_hook::process_unknown_string(
  const char *&unknown_key,
  uchar *base,
  MEM_ROOT *mem_root,
  const char *end)
{
  DBUG_ENTER("Handle_old_incorrect_trigger_table_hook::process_unknown_string");
  DBUG_PRINT("info", ("unknown key: %60s", unknown_key));

  if (unknown_key + INVALID_TRIGGER_TABLE_LENGTH + 1 < end &&
      unknown_key[INVALID_TRIGGER_TABLE_LENGTH] == '=' &&
      !memcmp(unknown_key, STRING_WITH_LEN("trigger_table")))
  {
    const char *ptr= unknown_key + INVALID_TRIGGER_TABLE_LENGTH + 1;

    DBUG_PRINT("info", ("trigger_table affected by BUG#15921 detected"));
    push_warning_printf(current_thd,
                        Sql_condition::SL_NOTE,
                        ER_OLD_FILE_FORMAT,
                        ER(ER_OLD_FILE_FORMAT),
                        path, "TRIGGER");

    if (!(ptr= parse_escaped_string(ptr, end, mem_root, trigger_table_value)))
    {
      my_error(ER_FPARSER_ERROR_IN_PARAMETER, MYF(0), "trigger_table",
               unknown_key);
      DBUG_RETURN(true);
    }

    /* Set parsing pointer to the last symbol of string (\n). */
    unknown_key= ptr-1;
  }
  DBUG_RETURN(false);
}

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

/*
  Module private variables to be used in Trigger_loader::load_triggers().
*/

static LEX_STRING default_definer= EMPTY_STR;

static LEX_STRING default_client_cs_name= NULL_STR;
static LEX_STRING default_connection_cl_name= NULL_STR;
static LEX_STRING default_db_cl_name= NULL_STR;

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

/**
  Check if TRN-file exists.

  @param trn_path path to TRN-file

  @return true if TRN-file does not exists, false otherwise.
*/

bool Trigger_loader::check_trn_exists(const LEX_STRING &trn_path)
{
  return access(trn_path.str, F_OK) != 0;
}

///////////////////////////////////////////////////////////////////////////

/**
  Check if the TRG-file for the given table exists.

  @param db_name    name of schema
  @param table_name name of trigger

  @return true if TRG-file exists, false otherwise.
*/
bool Trigger_loader::trg_file_exists(const char *db_name,
                                     const char *table_name)
{
  char path[FN_REFLEN];
  build_table_filename(path, FN_REFLEN - 1, db_name, table_name, TRG_EXT, 0);

  if (access(path, F_OK))
  {
    if (errno == ENOENT)
      return false;
  }

  return true;
}


/**
  Load table triggers from the data dictionary.

  @param [in]  thd                thread handle
  @param [in]  db_name            name of schema
  @param [in]  table_name         subject table name
  @param [out] triggers           pointer to the list where new Trigger
                                  objects will be inserted

  @return Operation status
    @retval true   Failure
    @retval false  Success
*/

bool Trigger_loader::load_triggers(THD *thd,
                                   MEM_ROOT *mem_root,
                                   const char *db_name,
                                   const char *table_name,
                                   List<Trigger> *triggers)
{
  DBUG_ENTER("Trigger_loader::load_triggers");

  // Construct TRG-file name.

  char trg_file_path_buffer[FN_REFLEN];
  LEX_STRING trg_file_path;

  trg_file_path.length= build_table_filename(trg_file_path_buffer,
                                             FN_REFLEN - 1,
                                             db_name, table_name, TRG_EXT, 0);
  trg_file_path.str= trg_file_path_buffer;

  // The TRG-file exists so we got to load triggers.

  File_parser *parser=
    sql_parse_prepare(&trg_file_path, mem_root, true);

  if (!parser)
    DBUG_RETURN(true);

  if (!is_equal(&trg_file_type, parser->type()))
  {
    my_error(ER_WRONG_OBJECT, MYF(0), table_name, TRG_EXT + 1, "TRIGGER");
    DBUG_RETURN(true);
  }

  Handle_old_incorrect_sql_modes_hook sql_modes_hook(trg_file_path.str);

  Trg_file_data trg;

  if (parser->parse((uchar*) &trg,
                    mem_root,
                    trg_file_parameters,
                    TRG_NUM_REQUIRED_PARAMETERS,
                    &sql_modes_hook))
    DBUG_RETURN(true);

  if (trg.definitions.is_empty())
  {
    assert(trg.sql_modes.is_empty());
    assert(trg.definers_list.is_empty());
    assert(trg.client_cs_names.is_empty());
    assert(trg.connection_cl_names.is_empty());
    assert(trg.db_cl_names.is_empty());
    DBUG_RETURN(false);
  }

  // Make sure character set properties are filled.

  if (trg.client_cs_names.is_empty() ||
      trg.connection_cl_names.is_empty() ||
      trg.db_cl_names.is_empty())
  {
    if (!trg.client_cs_names.is_empty() ||
        !trg.connection_cl_names.is_empty() ||
        !trg.db_cl_names.is_empty())
    {
      my_error(ER_TRG_CORRUPTED_FILE, MYF(0),
               db_name,
               table_name);

      DBUG_RETURN(true);
    }

    push_warning_printf(thd, Sql_condition::SL_WARNING,
                        ER_TRG_NO_CREATION_CTX,
                        ER(ER_TRG_NO_CREATION_CTX),
                        db_name,
                        table_name);


    /*
      Backward compatibility: assume that the query is in the current
      character set.
    */

    lex_string_set(&default_client_cs_name,
                   thd->variables.character_set_client->csname);

    lex_string_set(&default_connection_cl_name,
                   thd->variables.collation_connection->name);

    lex_string_set(&default_db_cl_name,
                   thd->variables.collation_database->name);
  }

  LEX_CSTRING db_name_str= {db_name, strlen(db_name)};

  LEX_CSTRING table_name_str= {table_name, strlen(table_name)};

  List_iterator_fast<LEX_STRING> it_definition(trg.definitions);
  List_iterator_fast<sql_mode_t> it_sql_mode(trg.sql_modes);
  List_iterator_fast<LEX_STRING> it_definer(trg.definers_list);
  List_iterator_fast<LEX_STRING> it_client_cs_name(trg.client_cs_names);
  List_iterator_fast<LEX_STRING> it_connect_cl_name(trg.connection_cl_names);
  List_iterator_fast<LEX_STRING> it_db_cl_name(trg.db_cl_names);
  List_iterator_fast<longlong>  it_created_timestamps(trg.created_timestamps);

  while (true)
  {
    const LEX_STRING *definition= it_definition++;

    if (!definition)
      break;

    const sql_mode_t *sql_mode= it_sql_mode++;
    const LEX_STRING *definer= it_definer++;
    const LEX_STRING *client_cs_name= it_client_cs_name++;
    const LEX_STRING *connection_cl_name= it_connect_cl_name++;
    const LEX_STRING *db_cl_name= it_db_cl_name++;
    const longlong *created_timestamp= it_created_timestamps++;

    // Backward compatibility: use default settings if attributes are missing.

    if (!sql_mode)
      sql_mode= &global_system_variables.sql_mode;

    if (!definer)
      definer= &default_definer;

    if (!client_cs_name)
      client_cs_name= &default_client_cs_name;

    if (!connection_cl_name)
      connection_cl_name= &default_connection_cl_name;

    if (!db_cl_name)
      db_cl_name= &default_db_cl_name;

    // Create a new trigger instance.

    Trigger *t= Trigger::create_from_dd(mem_root,
                                        db_name_str,
                                        table_name_str,
                                        *definition,
                                        *sql_mode,
                                        *definer,
                                        *client_cs_name,
                                        *connection_cl_name,
                                        *db_cl_name,
                                        created_timestamp);

    // NOTE: new trigger object is not fully initialized here.

    if (triggers->push_back(t, mem_root))
    {
      delete t;
      DBUG_RETURN(true);
    }
  }

  DBUG_RETURN(false);
}

///////////////////////////////////////////////////////////////////////////

/**
  Store a table trigger into the data dictionary.

  @param [in]  tables       pointer to trigger's table
  @param [in]  new_trigger  trigger to save
  @param [in]  triggers     pointer to the list where new trigger object has to
                            be added

  @return Operation status
    @retval true   Failure
    @retval false  Success
*/

bool Trigger_loader::store_trigger(const LEX_STRING &db_name,
                                   const LEX_STRING &table_name,
                                   MEM_ROOT *mem_root,
                                   Trigger *new_trigger,
                                   List<Trigger> *triggers)
{
  // Fill TRN-data structure.

  Trn_file_data trn;

  trn.trigger_table= table_name;

  // Fill TRG-data structure.

  Trg_file_data trg;

  if (fill_trg_data(&trg, mem_root, triggers))
    return true;

  // Get TRN file name.

  char trn_file_name_buffer[FN_REFLEN];

  LEX_STRING trn_file_name=
    Trigger_loader::build_trn_path(trn_file_name_buffer, FN_REFLEN,
                                   db_name.str,
                                   new_trigger->get_trigger_name().str);

  if (!trn_file_name.str)
    return true; // my_error() has already been called.

  // Save TRN file.

  if (sql_create_definition_file(NULL, &trn_file_name, &trn_file_type,
                                 (uchar *) &trn, trn_file_parameters))
  {
    return true; // my_error() has already been called.
  }

  // Save TRG file.

  if (save_trg_file(db_name.str, table_name.str, &trg))
  {
    mysql_file_delete(key_file_trn, trn_file_name.str, MYF(MY_WME));
    return true;
  }

  return false;
}

///////////////////////////////////////////////////////////////////////////

/**
  Drop trigger in the data dictionary.

  @param [in]  tables         pointer to trigger's table
  @param [in]  trigger_name   name of the trigger to drop
  @param [in]  triggers       list of all table triggers
  @param [out] trigger_found  flag to store a result whether
                              the named trigger was found

  @return Operation status.
    @retval true   Failure
    @retval false  Success
*/

bool Trigger_loader::drop_trigger(const LEX_STRING &db_name,
                                  const LEX_STRING &table_name,
                                  const LEX_STRING &trigger_name,
                                  MEM_ROOT *mem_root,
                                  List<Trigger> *triggers,
                                  bool *trigger_found)
{
  // Create TRG-data with all table triggers but the trigger to drop.

  Trg_file_data trg;
  *trigger_found= false;

  {
    List_iterator<Trigger> it(*triggers);
    Trigger *t;

    while ((t= it++))
    {
      if (my_strcasecmp(table_alias_charset,
                        t->get_trigger_name().str,
                        trigger_name.str) == 0)
      {
        delete t;
        it.remove(); // Remove trigger from the list.
        *trigger_found= true;
        continue;
      }

      if (trg.append_trigger(t, mem_root))
        return true;
    }
  }

  // Remove TRN file.

  if (rm_trn_file(db_name.str, trigger_name.str))
    return true;

  // If we've just dropped the last trigger, remove TRG file. Otherwise, save
  // new TRG file.

  return triggers->is_empty() ?
         rm_trg_file(db_name.str, table_name.str) :
         save_trg_file(db_name.str, table_name.str, &trg);
}

///////////////////////////////////////////////////////////////////////////

/**
  Load trigger table name from TRN-file.

  @param [in]  thd              thread handle
  @param [in]  trigger_name     name of trigger
  @param [in]  trn_path         path to the corresponding TRN-file
  @param [out] tbl_name         variable to store retrieved table name

  @return Operation status
    @retval true   Failure.
    @retval false  Success.
*/

bool Trigger_loader::load_trn_file(THD *thd,
                                   const LEX_STRING &trigger_name,
                                   const LEX_STRING &trn_path,
                                   LEX_STRING *tbl_name)
{
  DBUG_ENTER("Trigger_loader::get_table_name_for_trigger()");

  /* Prepare the File_parser to parse the TRN-file. */

  File_parser *parser= sql_parse_prepare(&trn_path, thd->mem_root, true);

  if (!parser)
    DBUG_RETURN(true);

  if (!is_equal(&trn_file_type, parser->type()))
  {
    my_error(ER_WRONG_OBJECT, MYF(0),
             trigger_name.str,
             TRN_EXT + 1,
             "TRIGGERNAME");

    DBUG_RETURN(true);
  }

  /* Parse the TRN-file. */

  Trn_file_data trn;

  Handle_old_incorrect_trigger_table_hook trigger_table_hook(
                                          trn_path.str,
                                          &trn.trigger_table);


  if (parser->parse((uchar *) &trn, thd->mem_root,
                    trn_file_parameters, 1,
                    &trigger_table_hook))
    DBUG_RETURN(true);

  /* Copy trigger table name. */

  *tbl_name= trn.trigger_table;

  /* That's all. */

  DBUG_RETURN(false);
}

///////////////////////////////////////////////////////////////////////////

/**
  Drop all triggers for the given table.
*/

bool Trigger_loader::drop_all_triggers(const char *db_name,
                                       const char *table_name,
                                       List<Trigger> *triggers)
{
  bool rc= false;

  List_iterator_fast<Trigger> it(*triggers);
  Trigger *t;

  while ((t= it++))
  {
    LEX_STRING trigger_name= t->get_trigger_name();
    if (rm_trn_file(db_name, trigger_name.str))
    {
      rc= true;
      continue;
    }
#ifdef HAVE_PSI_SP_INTERFACE                                                    
    LEX_CSTRING db_name= t->get_db_name();
    /* Drop statistics for this stored program from performance schema. */      
    MYSQL_DROP_SP(SP_TYPE_TRIGGER,                                       
                  db_name.str, db_name.length,    
                  trigger_name.str, trigger_name.length);
#endif
  }

  return rm_trg_file(db_name, table_name) || rc;
}

///////////////////////////////////////////////////////////////////////////

bool Trigger_loader::rename_subject_table(MEM_ROOT *mem_root,
                                          List<Trigger> *triggers,
                                          const char *db_name,
                                          LEX_STRING *table_name,
                                          const char *new_db_name,
                                          LEX_STRING *new_table_name,
                                          bool upgrading50to51)
{
  // Prepare TRG-data. Do it here so that OOM-error will not cause data
  // inconsistency.

  Trg_file_data trg;

  if (fill_trg_data(&trg, mem_root, triggers))
    return true;

  // Change the subject table name in TRN files for all triggers.

  Trigger *err_trigger=
    change_table_name_in_trn_files(triggers,
                                   upgrading50to51 ? db_name : NULL,
                                   new_db_name, new_table_name,
                                   NULL);

  if (err_trigger)
  {
    /*
      If we were unable to update one of .TRN files properly we will
      revert all changes that we have done and report about error.
      We assume that we will be able to undo our changes without errors
      (we can't do much if there will be an error anyway).
    */
    change_table_name_in_trn_files(
      triggers,
      upgrading50to51 ? new_db_name : NULL,
      db_name, table_name,
      err_trigger);
    return true;
  }

  // Save new TRG file.

  if (save_trg_file(new_db_name, new_table_name->str, &trg))
    return true;

  // Remove old TRG file.

  if (rm_trg_file(db_name, table_name->str))
  {
    rm_trg_file(new_db_name, new_table_name->str);
    return true;
  }

  return false;
}
