/*
   Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.

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

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
*/

#include "ndb_local_schema.h"

#ifndef MYSQL_SERVER
#define MYSQL_SERVER
#endif

#include "sql_class.h"
#include "sql_table.h"
#include "mdl.h"
#include "log.h"
#include "table_trigger_dispatcher.h"
#include "sql_trigger.h"

static const char *ndb_ext=".ndb";


bool Ndb_local_schema::Base::mdl_try_lock(void) const
{
  MDL_request_list mdl_requests;
  MDL_request global_request;
  MDL_request schema_request;
  MDL_request mdl_request;

  MDL_REQUEST_INIT(&global_request,
                   MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE,
                   MDL_STATEMENT);
  MDL_REQUEST_INIT(&schema_request,
                   MDL_key::SCHEMA, m_db, "", MDL_INTENTION_EXCLUSIVE,
                   MDL_TRANSACTION);
  MDL_REQUEST_INIT(&mdl_request,
                   MDL_key::TABLE, m_db, m_name, MDL_EXCLUSIVE,
                   MDL_TRANSACTION);

  mdl_requests.push_front(&mdl_request);
  mdl_requests.push_front(&schema_request);
  mdl_requests.push_front(&global_request);

  if (m_thd->mdl_context.acquire_locks(&mdl_requests,
                                       0 /* don't wait for lock */))
  {
    // Check that an error has been pushed to thd and then
    // clear it since this is just a _try lock_
    assert(m_thd->is_error());
    m_thd->clear_error();

    log_warning("Failed to acquire metadata lock");

    return false;
  }
  DBUG_PRINT("info", ("acquired metadata lock"));
  return true;
}


void Ndb_local_schema::Base::mdl_unlock(void)
{
  m_thd->mdl_context.release_transactional_locks();
}


void Ndb_local_schema::Base::log_warning(const char* fmt, ...) const
{
  char buf[1024];
  va_list args;
  va_start(args, fmt);
  my_vsnprintf(buf, sizeof(buf), fmt, args);
  va_end(args);

  if (m_push_warnings)
  {
    // Append the error which caused the error to thd's warning list
    push_warning_printf(m_thd, Sql_condition::SL_NOTE,
                        ER_GET_ERRMSG, "Ndb schema[%s.%s]: %s",
                        m_db, m_name, buf);
  }
  else
  {
    // Print the warning to log file
    sql_print_warning("Ndb schema[%s.%s]: %s",
                      m_db, m_name, buf);
  }
}


Ndb_local_schema::Base::Base(THD* thd, const char* db, const char* name) :
  m_thd(thd),
  m_db(db), m_name(name)
{
  /*
    System(or daemon) threads report error to log file
    all other threads use push_warning
  */
  m_push_warnings = (thd->get_command() != COM_DAEMON);

  m_have_mdl_lock= mdl_try_lock();
}


Ndb_local_schema::Base::~Base()
{
  // Release MDL locks
  if (m_have_mdl_lock)
  {
   DBUG_PRINT("info", ("releasing mdl lock"));
    mdl_unlock();
  }
}


bool
Ndb_local_schema::Table::file_exists(const char* ext) const
{
  char buf[FN_REFLEN + 1];
  build_table_filename(buf, sizeof(buf)-1,
                       m_db, m_name, ext, 0);

  if (my_access(buf, F_OK))
  {
    DBUG_PRINT("info", ("File '%s' does not exist", buf));
    return false;
  }

  DBUG_PRINT("info", ("File '%s' exist", buf));
  return true;
}


bool
Ndb_local_schema::Table::remove_file(const char* ext) const
{
  char buf[FN_REFLEN + 1];
  build_table_filename(buf, sizeof(buf)-1,
                       m_db, m_name, ext, 0);

  int error = my_delete(buf, 0);
  if (!error || errno == ENOENT)
    return true;

  log_warning("Failed to remove file '%s', errno: %d", buf, errno);
  return false;
}


bool
Ndb_local_schema::Table::rename_file(const char* new_db, const char* new_name,
                             const char* ext) const
{
  char from[FN_REFLEN + 1];
  build_table_filename(from, sizeof(from)-1,
                       m_db, m_name, ext, 0);

  char to[FN_REFLEN + 1];
  build_table_filename(to, sizeof(to) - 1, new_db, new_name, ext, 0);

  int error = my_rename(from, to, 0);
  if (!error)
    return true;

  log_warning("Failed to rename file '%s' to '%s', errno: %d",
            from, to, errno);
  return false;
}


// Read the engine type from .frm and return true if it says NDB
bool
Ndb_local_schema::Table::frm_engine_is_ndb(void) const
{
  char buf[FN_REFLEN + 1];
  build_table_filename(buf, sizeof(buf)-1,
                       m_db, m_name, reg_ext, 0);

  legacy_db_type engine_type;
  (void)dd_frm_type(m_thd, buf, &engine_type);
  DBUG_PRINT("info", ("engine_type: %d", engine_type));

  return (engine_type == DB_TYPE_NDBCLUSTER);
}


Ndb_local_schema::Table::Table(THD* thd,
                               const char* db, const char* name) :
  Ndb_local_schema::Base(thd, db, name),
  m_ndb_file_exist(false),
  m_has_triggers(false)
{
  DBUG_ENTER("Ndb_local_table");
  DBUG_PRINT("enter", ("name: '%s.%s'", db, name));

  // Check if .frm file exist
  m_frm_file_exist = file_exists(reg_ext);
  if (!m_frm_file_exist)
  {
    // Check for stray .ndb file
    assert(!file_exists(ndb_ext));
    DBUG_VOID_RETURN;
  }

  // Check if .ndb file exist
  m_ndb_file_exist = file_exists(ndb_ext);

  // Check if there are trigger files
  m_has_triggers = file_exists(TRG_EXT);

  DBUG_VOID_RETURN;
}


bool
Ndb_local_schema::Table::is_local_table(void) const
{
  if (m_frm_file_exist && !m_ndb_file_exist)
  {
    // The .frm exist but no .ndb file , this is a "local" table

    // Double check that the engine type in .frm doesn't say NDB
    assert(!frm_engine_is_ndb());

    return true;
  }
  return false;
}


void
Ndb_local_schema::Table::remove_table(void) const
{
  (void)remove_file(reg_ext);
  (void)remove_file(ndb_ext);

  if (m_has_triggers)
  {
    // Copy to buffers since 'drop_all_triggers' want char*
    char db_name_buf[FN_REFLEN + 1], table_name_buf[FN_REFLEN + 1];
    my_stpcpy(db_name_buf, m_db);
    my_stpcpy(table_name_buf, m_name);

    if (drop_all_triggers(m_thd, db_name_buf, table_name_buf))
    {
      log_warning("Failed to drop all triggers");
    }
  }
}


void
Ndb_local_schema::Table::rename_table(const char* new_db,
                                      const char* new_name) const
{
  (void)rename_file(new_db, new_name, reg_ext);
  (void)rename_file(new_db, new_name, ndb_ext);

  if (m_has_triggers)
  {
    if (!have_mdl_lock())
    {
      // change_trigger_table_name() requires an EXLUSIVE mdl lock
      // so if the mdl lock was not aquired, skip this part
      log_warning("Can't rename triggers, no mdl lock");
    }
    else
    {
      if (change_trigger_table_name(m_thd,
                                    m_db, m_name, m_name,
                                    new_db, new_name))
      {
        log_warning("Failed to rename all triggers");
      }
    }
  }
}
