/* 
 * Copyright (c) 2009, 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 "stdafx.h"

#ifndef _WIN32
#include <pcre.h>
#include <stdio.h>
#endif

#include "base/sqlstring.h"

#include "grt/common.h"
#include "grt/grt_manager.h"

#include "module_db_mysql.h"
#include "diffchange.h"
#include "grtdiff.h"
#include "grtdb/diff_dbobjectmatch.h"
#include "db_mysql_diffsqlgen.h"
#include "db_mysql_params.h"

#include "db_mysql_diffsqlgen_grant.h"
#include "db_mysql_catalog_report.h"
#include "base/string_utilities.h"
#include "base/sqlstring.h"

#include "grtsqlparser/sql_specifics.h"
#include "sqlide/recordset_table_inserts_storage.h"
#include "sqlide/recordset_be.h"

#include "grts/structs.workbench.h"
#include "module_db_mysql_shared_code.h"
#include "grtdb/db_helpers.h"

using namespace grt;
using namespace base;

static std::string get_table_old_name(db_mysql_TableRef table)
{
  return std::string("`").append(table->owner()->name().c_str()).append("`.`").append(table->oldName().c_str()).append("` ");
}

inline std::string get_name(const GrtNamedObjectRef object, const bool use_short_names){
  return use_short_names?
    std::string("`").append(object->name().c_str()).append("`"):
    get_qualified_schema_object_name(object);
  
};


DiffSQLGeneratorBEActionInterface::~DiffSQLGeneratorBEActionInterface()
{}

namespace {

    inline std::string generate_comment(const std::string& comment_text, size_t comment_lenght, const std::string& quote = std::string())
    {
        std::string comment = escape_sql_string(bec::TableHelper::get_sync_comment(comment_text, comment_lenght));
        if (!quote.empty() && !comment.empty())
            comment = quote+comment+quote;
        if (!bec::TableHelper::get_document_comment(comment_text, comment_lenght).empty())
            comment.append(" /* comment truncated */ /*")
            .append(bec::TableHelper::get_document_comment(comment_text, comment_lenght))
            .append("*/");
        return comment; 
    }

class TextPadding
{
  int padding;
  int increment;

  std::string padding_text;

  void rebuild_padding_text() 
  { 
    padding_text= std::string(padding, ' '); 
  }

public:
  explicit TextPadding(int inc) 
    : padding(0), increment(inc)
  {}

  TextPadding& operator ++() 
  { 
    padding += increment;
    rebuild_padding_text();
    return *this;
  }
  
  TextPadding& operator --()  
  { 
    padding -= increment; 
    rebuild_padding_text();
    return *this;
  }
  
  std::string& pad(std::string& text)
  {
    return text.append(padding_text);
  }
};

static std::string generate_single_partition(db_mysql_PartitionDefinitionRef part, bool is_range)
{
  class Partition_options
  {
  public:
    static void generate(db_mysql_PartitionDefinitionRef part, std::string& sql)
    {
      if(strlen(part->comment().c_str()))
        sql.append(" COMMENT = '").append(base::escape_sql_string(part->comment().c_str())).append("'");

      if(strlen(part->dataDirectory().c_str()))
        sql.append(" DATA DIRECTORY = '").append(base::escape_sql_string(part->dataDirectory().c_str())).append("'");

      if(strlen(part->indexDirectory().c_str()))
        sql.append(" INDEX DIRECTORY = '").append(base::escape_sql_string(part->indexDirectory().c_str())).append("'");

      if(strlen(part->maxRows().c_str()))
        sql.append(" MAX_ROWS = ").append(base::escape_sql_string(part->maxRows().c_str()));

      if(strlen(part->minRows().c_str()))
        sql.append(" MIN_ROWS = ").append(base::escape_sql_string(part->minRows().c_str()));

      // TODO: process TABLESPACE and NODEGROUP cluster-specific options
    }
  };

  std::string sql;

  sql.append(" PARTITION ");

  sql.append(part->name().c_str()).append(" VALUES ");

  if(is_range)
    sql.append("LESS THAN (").append(part->value().c_str()).append(")");
  else
    sql.append("IN (").append(part->value().c_str()).append(")");

  Partition_options::generate(part, sql);

  if(part->subpartitionDefinitions().count() > 0)
  {
    sql.append(" (");
    for(size_t subsz= part->subpartitionDefinitions().count(), j= 0; j < subsz; j++)
    {
      if(j > 0)
        sql.append(",");

      db_mysql_PartitionDefinitionRef subpart= part->subpartitionDefinitions().get(j);
      sql.append(" SUBPARTITION ").append(subpart->name().c_str());

      Partition_options::generate(subpart, sql);
    }
    sql.append(")");
  }

  return sql;
}

static std::string generate_drop_partitions(const std::list<std::string>& part_names)
{
  std::string sql(" DROP PARTITION ");
  bool first= true;

  for(std::list<std::string>::const_iterator e= part_names.end(), 
    it= part_names.begin(); it != e; it++)
  {
    if(first)
      first= false;
    else
      sql.append(", ");

    sql.append(*it);
  }

  return sql;
}

static std::string generate_create(db_mysql_ForeignKeyRef fk, TextPadding& padding, bool use_short_names)
{
  std::string sql;

  sql.append("CONSTRAINT `").append(fk->name().c_str()).append("`\n");

  ++padding;
  padding.pad(sql).append("FOREIGN KEY (");

  grt::ListRef<db_Column> fk_columns= fk->columns();
  for(size_t fk_column_count= fk_columns.count(), j= 0; j < fk_column_count; j++)
  {
    if(j != 0)
      sql.append(", ");

    db_ColumnRef fk_column= fk_columns.get(j);

    sql.append("`").append(fk_column->name().c_str()).append("` ");
  }
  sql = base::trim_right(sql);
  
  sql.append(")\n");
  padding.pad(sql).append("REFERENCES `");
  if(fk->referencedTable().is_valid())
  {
    // Omit schema name if we use short names and the referenced table is in the same schema as the
    // referencing table.
    if (use_short_names && (fk->owner()->owner() == fk->referencedTable()->owner()))
      sql.append(fk->referencedTable()->name().c_str());
    else
      sql.append(fk->referencedTable()->owner()->name().c_str()).append("`.`").append(fk->referencedTable()->name().c_str());
  }
  sql.append("` (");

  grt::ListRef<db_Column> ref_fk_columns= fk->referencedColumns();
  for(size_t ref_fk_column_count= ref_fk_columns.count(), j= 0; j < ref_fk_column_count; j++)
  {
    if(j > 0)
      sql.append(", ");

    db_ColumnRef ref_fk_column= ref_fk_columns.get(j);
    if(!ref_fk_column.is_valid())
      continue;
    sql.append("`").append(ref_fk_column->name().c_str()).append("` ");
  }
  sql = base::trim_right(sql);
  sql.append(")");

  if(strlen(fk->deleteRule().c_str()))
  {
    sql.append("\n");
    padding.pad(sql).append("ON DELETE ").append(fk->deleteRule().c_str());
  }

  if(strlen(fk->updateRule().c_str()))
  {
    sql.append("\n");
    padding.pad(sql).append("ON UPDATE ").append(fk->updateRule().c_str());
  }

  --padding;
  return sql;
}

static std::string generate_drop_index(db_mysql_IndexRef index)
{
/*
  | DROP {INDEX|KEY} index_name
*/
  if(index->isPrimary())
    return std::string("DROP PRIMARY KEY");

  std::string index_name;
  {
    if (index->oldName().empty())
      index_name= " ";
    else
      index_name= strfmt("`%s` ", index->oldName().c_str());
  }    
  return strfmt("DROP INDEX %s", index_name.c_str());
}

class ActionGenerateSQL : public DiffSQLGeneratorBEActionInterface
{
  TextPadding padding;

  bool _case_sensitive;
  bool _use_oids_as_dict_key;
  int _maxTableCommentLength;
  int _maxIndexCommentLength;
  int _maxColumnCommentLength;
  std::string _algorithm_type;
  std::string _lock_type;

  std::string sql;
  std::string comma;
  std::string table_q_name;
  char itoa_buf[32];
  size_t empty_length;
  bool first_column, first_index,
  first_change, first_fk_create, first_fk_drop;
  std::string _non_std_sql_delimiter;

  std::string fk_add_sql;
  std::string fk_drop_sql;

  std::list<std::string> partitions_to_drop;
  std::list<std::string> partitions_to_change;
  std::list<std::string> partitions_to_add;

  grt::DictRef target_map;
  grt::StringListRef target_list;
  grt::ListRef<GrtNamedObject> target_object_list;
  bool disable_object_list;

  void remember_alter(const GrtNamedObjectRef &obj, const std::string &sql);
  void remember(const GrtNamedObjectRef &obj, const std::string &sql,const bool front = false);

  void alter_table_property(std::string& to, const std::string& name, const std::string& value);

public:
  ActionGenerateSQL(grt::ValueRef target, grt::ListRef<GrtNamedObject> obj_list, grt::GRT *grt, 
                    const grt::DictRef options, bool use_oids_as_key);
  virtual ~ActionGenerateSQL();

  // create table
  void create_table_props_begin(db_mysql_TableRef);
  void create_table_props_end(db_mysql_TableRef);

  void create_table_columns_begin(db_mysql_TableRef);
  void create_table_column(db_mysql_ColumnRef);
  void create_table_columns_end(db_mysql_TableRef);
  std::string generate_create(db_mysql_ColumnRef column);

  void create_table_indexes_begin(db_mysql_TableRef);
  void create_table_index(db_mysql_IndexRef, bool gen_create_index);
  void create_table_indexes_end(db_mysql_TableRef);
  std::string generate_create(db_mysql_IndexRef index, std::string table_q_name, bool separate_index);

  void create_table_fks_begin(db_mysql_TableRef);
  void create_table_fk(db_mysql_ForeignKeyRef);
  void create_table_fks_end(db_mysql_TableRef);

  void create_table_engine(grt::StringRef);
  void create_table_next_auto_inc(grt::StringRef);
  void create_table_password(grt::StringRef);
  void create_table_delay_key_write(grt::IntegerRef);
  void create_table_charset(grt::StringRef);
  void create_table_collate(grt::StringRef);
  void create_table_merge_union(grt::StringRef);
  void create_table_merge_insert(grt::StringRef);
  void create_table_pack_keys(grt::StringRef);
  void create_table_checksum(grt::IntegerRef);
  void create_table_row_format(grt::StringRef);
  void create_table_key_block_size(grt::StringRef);
  void create_table_avg_row_length(grt::StringRef);
  void create_table_min_rows(grt::StringRef);
  void create_table_max_rows(grt::StringRef);
  void create_table_comment(grt::StringRef);
  void create_table_data_dir(grt::StringRef);
  void create_table_index_dir(grt::StringRef);

  // drop table
  void drop_table(db_mysql_TableRef);

  // schema drop/create
  void create_schema(db_mysql_SchemaRef);
  void drop_schema(db_mysql_SchemaRef);

  // alter schema
  void alter_schema_props_begin(db_mysql_SchemaRef);
  void alter_schema_default_charset(db_mysql_SchemaRef, grt::StringRef value);
  void alter_schema_default_collate(db_mysql_SchemaRef, grt::StringRef value);
  void alter_schema_name(db_mysql_SchemaRef, grt::StringRef value);
  void alter_schema_props_end(db_mysql_SchemaRef);

  void alter_table_props_begin(db_mysql_TableRef);
  void alter_table_name(db_mysql_TableRef, grt::StringRef);
  void alter_table_engine(db_mysql_TableRef, grt::StringRef);
  void alter_table_next_auto_inc(db_mysql_TableRef, grt::StringRef);
  void alter_table_password(db_mysql_TableRef, grt::StringRef);
  void alter_table_delay_key_write(db_mysql_TableRef, grt::IntegerRef);
  void alter_table_charset(db_mysql_TableRef, grt::StringRef);
  void alter_table_collate(db_mysql_TableRef, grt::StringRef);
  void alter_table_comment(db_mysql_TableRef, grt::StringRef);
  void alter_table_merge_union(db_mysql_TableRef, grt::StringRef);
  void alter_table_merge_insert(db_mysql_TableRef, grt::StringRef);
  void alter_table_pack_keys(db_mysql_TableRef, grt::StringRef);
  void alter_table_checksum(db_mysql_TableRef, grt::IntegerRef);
  void alter_table_row_format(db_mysql_TableRef, grt::StringRef);
  void alter_table_key_block_size(db_mysql_TableRef, grt::StringRef);
  void alter_table_avg_row_length(db_mysql_TableRef, grt::StringRef);
  void alter_table_min_rows(db_mysql_TableRef, grt::StringRef);
  void alter_table_max_rows(db_mysql_TableRef, grt::StringRef);
  void alter_table_connection_string(db_mysql_TableRef, grt::StringRef);
  void alter_table_generate_partitioning(db_mysql_TableRef table, 
                                         const std::string& part_type,
                                         const std::string& part_expr, 
                                         int part_count,
                                         const std::string& subpart_type, 
                                         const std::string& subpart_expr,
                                         grt::ListRef<db_mysql_PartitionDefinition> part_defs);
  void alter_table_drop_partitioning(db_mysql_TableRef table);
  void alter_table_add_partition(db_mysql_PartitionDefinitionRef part, bool is_range);
  void alter_table_reorganize_partition(db_mysql_PartitionDefinitionRef old_part,
                                        db_mysql_PartitionDefinitionRef new_part,
                                        bool is_range);
  void alter_table_drop_partition(const std::string& part_name);
  void alter_table_partition_count(db_mysql_TableRef, grt::IntegerRef);
  void alter_table_partition_definitions(db_mysql_TableRef, grt::StringRef);
  void alter_table_props_end(db_mysql_TableRef);

  void alter_table_columns_begin(db_mysql_TableRef);
  void alter_table_add_column(db_mysql_TableRef, std::map<std::string, std::string>, 
                              db_mysql_ColumnRef, db_mysql_ColumnRef after);
  void alter_table_drop_column(db_mysql_TableRef, db_mysql_ColumnRef);
  void alter_table_change_column(db_mysql_TableRef table, db_mysql_ColumnRef org_col, 
                                 db_mysql_ColumnRef mod_col, db_mysql_ColumnRef after,
                                 bool modified, 
                                 std::map<std::string, std::string> column_rename_map);
  void alter_table_columns_end(db_mysql_TableRef);

  void alter_table_indexes_begin(db_mysql_TableRef);
  void alter_table_add_index(db_mysql_IndexRef);
  void alter_table_drop_index(db_mysql_IndexRef);
  void alter_table_indexes_end(db_mysql_TableRef);

  void alter_table_fks_begin(db_mysql_TableRef);
  void alter_table_add_fk(db_mysql_ForeignKeyRef);
  void alter_table_drop_fk(db_mysql_ForeignKeyRef);
  void alter_table_fks_end(db_mysql_TableRef);

  // triggers
  void create_trigger(db_mysql_TriggerRef, bool for_alter);
  void drop_trigger(db_mysql_TriggerRef, bool for_alter);

  // views
  void create_view(db_mysql_ViewRef);
  void drop_view(db_mysql_ViewRef);

  // routines
  void create_routine(db_mysql_RoutineRef, bool for_alter);
  void drop_routine(db_mysql_RoutineRef, bool for_alter);

  // users
  void create_user(db_UserRef);
  void drop_user(db_UserRef);

  std::string get_name(GrtNamedObjectRef object) const{return ::get_name(object,_use_short_names);};
  std::string generate_add_index(db_mysql_IndexRef index);
  
  virtual void disable_list_insert(const bool flag){disable_object_list = flag;};
};

ActionGenerateSQL::ActionGenerateSQL(grt::ValueRef target, grt::ListRef<GrtNamedObject> obj_list, grt::GRT *grt, 
                                     const grt::DictRef options, bool use_oids_as_key = false)
  : padding(2), _use_oids_as_dict_key(use_oids_as_key),disable_object_list(false)
{
  _case_sensitive = options.get_int("CaseSensitive") != 0;
  _maxTableCommentLength = options.get_int("maxTableCommentLength");
  _maxIndexCommentLength = options.get_int("maxIndexCommentLength");
  _maxColumnCommentLength = options.get_int("maxColumnCommentLength");
  _algorithm_type = options.get_string("AlterAlgorithm");
  _lock_type = options.get_string("AlterLock");

  _use_oids_as_dict_key = options.get_int("UseOIDAsResultDictKey", use_oids_as_key) != 0;
  
  SqlFacade::Ref sql_facade = SqlFacade::instance_for_rdbms_name(grt, "Mysql");
  Sql_specifics::Ref sql_specifics = sql_facade ->sqlSpecifics();
  _non_std_sql_delimiter = sql_specifics->non_std_sql_delimiter();

  if(target.type() == DictType)
  {
    this->target_list= grt::StringListRef();
    this->target_map= grt::DictRef::cast_from(target);
  }
  else if(target.type() == ListType)
  {
    this->target_list= grt::StringListRef::cast_from(target);
    this->target_map= grt::DictRef();
  }
  this->target_object_list= obj_list;
}

ActionGenerateSQL::~ActionGenerateSQL()
{}

// create table methods

void ActionGenerateSQL::create_table_props_begin(db_mysql_TableRef table)
{
  sql.assign("CREATE");

  table_q_name = get_name(table);

  if(table->isTemporary())
    sql.append(" TEMPORARY");

  sql.append(" TABLE");
  if(_put_if_exists)
    sql.append(" IF NOT EXISTS");
  sql.append(" ").append(table_q_name).append(" (\n");
  ++padding;
}

void ActionGenerateSQL::create_table_props_end(db_mysql_TableRef table)
{
  remember(table, sql);
}

void ActionGenerateSQL::create_table_columns_begin(db_mysql_TableRef)
{
  first_column= true;
}

void ActionGenerateSQL::create_table_column(db_mysql_ColumnRef column)
{
  if(first_column)
    first_column= false;
  else
    sql.append(",\n");

  padding.pad(sql).append(generate_create(column));
}

void ActionGenerateSQL::create_table_columns_end(db_mysql_TableRef)
{
}

std::string ActionGenerateSQL::generate_create(db_mysql_ColumnRef column)
{
  std::string sql;

  sql.append("`").append(column->name().c_str()).append("` ");

  sql.append(column->formattedType());

  sql.append(" ");

  if (column->simpleType().is_valid())
    if (!column->simpleType()->group().is_valid() || *column->simpleType()->group()->name() == "string" || *column->simpleType()->group()->name() == "text"
      || *column->simpleType()->name() == "ENUM")
    {
      if (!(*column->characterSetName()).empty())
        sql
        .append("CHARACTER SET '")
        .append(column->characterSetName())
        .append("' ");
      if (!(*column->collationName()).empty() && (get_collation_cs(column->collationName()) ==(*column->characterSetName())))
        sql
        .append("COLLATE '")
        .append(column->collationName())
        .append("' ");
    }

  if (column->simpleType().is_valid())
  {
    grt::StringListRef flags= column->flags();
    size_t flags_count= flags.count();
    for(size_t j= 0; j < flags_count; j++)
      sql.append(flags.get(j).c_str()).append(" ");
  }else if (column->userType().is_valid() && !column->userType()->flags().empty())
    sql.append(column->userType()->flags()).append(" ");

  if(column->isNotNull())
    sql.append("NOT NULL ");
  else
    sql.append("NULL ");

  if(column->defaultValueIsNull())
    sql.append("DEFAULT NULL ");
  else if(column->defaultValue().is_valid() && column->defaultValue().c_str() && (strlen(column->defaultValue().c_str()) > 0))
  {
    std::string default_value = toupper (column->defaultValue());
    if (!((column->simpleType().is_valid())&&(column->simpleType()->name() == "TIMESTAMP")&&(default_value.find("ON UPDATE") == 0)))
      sql.append("DEFAULT ");
    sql.append(column->defaultValue().c_str()).append(" ");
  }

  if (column->autoIncrement())
  {
    db_SimpleDatatypeRef columnType;

    // Determine actually used column type first.
    if (column->userType().is_valid() && column->userType()->actualType().is_valid())
      columnType= column->userType()->actualType();
    else
      if (column->simpleType().is_valid() && column->simpleType()->group().is_valid())
        columnType= column->simpleType();
    
    if (columnType.is_valid() && columnType->group().is_valid()
      && !strcmp(columnType->group()->name().c_str(), "numeric"))
      sql.append("AUTO_INCREMENT ");
  }
  

  std::string comment = generate_comment(column->comment(), _maxColumnCommentLength);
  if (!comment.empty())
    sql.append("COMMENT '").append(comment).append("' ");

  // TODO:
  // (?) [reference_definition]
  // [COLUMN_FORMAT {FIXED|DYNAMIC|DEFAULT}]
  // [STORAGE {DISK|MEMORY}]

  return base::trim_right(sql);
}


void ActionGenerateSQL::create_table_indexes_begin(db_mysql_TableRef)
{
}

void ActionGenerateSQL::create_table_index(db_mysql_IndexRef index, bool gen_create_index)
{
  std::string index_sql(generate_create(index, table_q_name, gen_create_index));

  if(gen_create_index)
  {
    index_sql= std::string("CREATE ").append(index_sql);
    remember(index, index_sql);
  }
  else
  {
    sql.append(",\n");
    padding.pad(sql).append(index_sql);
  }
}

void ActionGenerateSQL::create_table_indexes_end(db_mysql_TableRef)
{
}

std::string ActionGenerateSQL::generate_create(db_mysql_IndexRef index, std::string table_q_name, bool separate_index)
{
  std::string index_sql;
  bool pk= (index->isPrimary() != 0);
  char itoa_buf[32];

  separate_index= (!pk && separate_index);  // pk cannot be added via CREATE INDEX

  if(pk)
  {
    index_sql.append("PRIMARY KEY ");
  }
  else if(index->unique() != 0)
  {
    index_sql.append("UNIQUE INDEX ");
  }
  else if(strlen(index->indexType().c_str()) > 0)
  {
    if(stricmp(index->indexType().c_str(), "PRIMARY") == 0)
      index_sql.append("PRIMARY KEY ");
    if (stricmp(index->indexType().c_str(), "FOREIGN") == 0)
      index_sql.append("INDEX ");
    else
    {
      index_sql.append(index->indexType().c_str()).append(" ");

      if(stricmp(index->indexType().c_str(), "INDEX") != 0)
        index_sql.append("INDEX ");
    }
  }
  else
  {
    index_sql.append("INDEX ");
  }
  
  if (!pk && !index->name().empty())
    index_sql.append(strfmt("`%s` ", index->name().c_str()));

  if(strlen(index->indexKind().c_str()))
    index_sql.append("USING ").append(index->indexKind().c_str()).append(" ");

  if(separate_index)
    index_sql.append("ON ").append(table_q_name).append(" ");

  index_sql.append("(");

  grt::ListRef<db_mysql_IndexColumn> ind_columns= index->columns();
  for(size_t index_column_count= ind_columns.count(), j= 0; j < index_column_count; j++)
  {
    db_IndexColumnRef ind_column= ind_columns.get(j);
    
    if(j > 0)
      index_sql.append(", ");

    db_ColumnRef col= ind_column->referencedColumn();
    if(col.is_valid())
      index_sql.append("`").append(col->name().c_str()).append("`");
    
    if(ind_column->columnLength() > 0)
      index_sql.append("(").append(itoa(ind_column->columnLength(), itoa_buf, 10)).append(")");
    
    if(!pk)
      index_sql.append((ind_column->descend() == 0 ? " ASC" : " DESC"));
  }
  index_sql.append(") ");

  if (index->keyBlockSize())
    index_sql.append(" KEY_BLOCK_SIZE=").append(itoa(index->keyBlockSize(), itoa_buf, 10));

  if (index->withParser().is_valid() && *index->withParser().c_str())
    index_sql.append(" WITH PARSER ").append(index->withParser());

  std::string comment = generate_comment(index->comment(), _maxIndexCommentLength);
  if (!comment.empty())
    index_sql.append(" COMMENT '").append(comment).append("' ");

  return base::trim_right(index_sql);
}

void ActionGenerateSQL::create_table_fks_begin(db_mysql_TableRef)
{
}

void ActionGenerateSQL::create_table_fk(db_mysql_ForeignKeyRef fk)
{
  grt::StringRef ename = db_mysql_TableRef::cast_from(fk->owner())->tableEngine();
  db_mysql_StorageEngineRef engine = bec::TableHelper::get_engine_by_name(fk.get_grt(), ename);
  if (engine.is_valid() && !engine->supportsForeignKeys())
      return;

  sql.append(",\n");
  padding.pad(sql).append(::generate_create(fk, padding, _use_short_names));

}

void ActionGenerateSQL::create_table_fks_end(db_mysql_TableRef)
{
  sql.append(")");
  --padding;
}

void ActionGenerateSQL::create_table_engine(grt::StringRef value)
{
  sql.append("\nENGINE = ").append(value.c_str());
}

void ActionGenerateSQL::create_table_next_auto_inc(grt::StringRef value)
{
  sql.append("\nAUTO_INCREMENT = ").append(value.c_str());
}

void ActionGenerateSQL::create_table_password(grt::StringRef value)
{
  sql.append("\nPASSWORD = '").append(value.c_str()).append("'");
}

void ActionGenerateSQL::create_table_delay_key_write(grt::IntegerRef value)
{
  sql.append("\nDELAY_KEY_WRITE = ").append(itoa(value, itoa_buf, 10));
}

void ActionGenerateSQL::create_table_charset(grt::StringRef value)
{
  sql.append("\nDEFAULT CHARACTER SET = ").append(value.c_str());
}

void ActionGenerateSQL::create_table_collate(grt::StringRef value)
{
  sql.append("\nCOLLATE = ").append(value.c_str());
}

void ActionGenerateSQL::create_table_merge_union(grt::StringRef value)
{
  std::string s = *value;
  if (!s.empty() && s[0] == '(')
    sql.append("\nUNION = ").append(value.c_str());
  else
    sql.append("\nUNION = (").append(s).append(")");
}

void ActionGenerateSQL::create_table_merge_insert(grt::StringRef value)
{
  sql.append("\nINSERT_METHOD = ").append(value.c_str());
}

void ActionGenerateSQL::create_table_pack_keys(grt::StringRef value)
{
  sql.append("\nPACK_KEYS = ").append(value.c_str());
}

void ActionGenerateSQL::create_table_checksum(grt::IntegerRef value)
{
  sql.append("\nCHECKSUM = ").append(itoa(value, itoa_buf, 10));
}

void ActionGenerateSQL::create_table_row_format(grt::StringRef value)
{
  sql.append("\nROW_FORMAT = ").append(value.c_str());
}

void ActionGenerateSQL::create_table_key_block_size(grt::StringRef value)
{
  sql.append("\nKEY_BLOCK_SIZE = ").append(value.c_str());
}

void ActionGenerateSQL::create_table_avg_row_length(grt::StringRef value)
{
  sql.append("\nAVG_ROW_LENGTH = ").append(value.c_str());
}

void ActionGenerateSQL::create_table_min_rows(grt::StringRef value)
{
  sql.append("\nMIN_ROWS = ").append(value.c_str());
}

void ActionGenerateSQL::create_table_max_rows(grt::StringRef value)
{
  sql.append("\nMAX_ROWS = ").append(value.c_str());
}

void ActionGenerateSQL::create_table_comment(grt::StringRef value)
{
  sql.append("\nCOMMENT = '").append(generate_comment(value, _maxTableCommentLength)).append("'");
}

void ActionGenerateSQL::create_table_data_dir(grt::StringRef value)
{
  sql.append("\nDATA DIRECTORY = '").append(value.c_str()).append("'");
}

void ActionGenerateSQL::create_table_index_dir(grt::StringRef value)
{
  sql.append("\nINDEX DIRECTORY = '").append(value.c_str()).append("'");
}

// drop table
void ActionGenerateSQL::drop_table(db_mysql_TableRef table)
{
  sql.clear();
  sql.append("DROP TABLE IF EXISTS ").append(get_name(table)).append(" ");
  remember(table, sql);
}

// schema

void ActionGenerateSQL::create_schema(db_mysql_SchemaRef schema)
{
  std::string schema_sql;
  schema_sql.append("CREATE SCHEMA ");
  if(_put_if_exists)
    schema_sql.append("IF NOT EXISTS ");
  schema_sql.append("`").append(schema->name().c_str()).append("` ");

  if(schema->defaultCollationName().is_valid() && strlen(schema->defaultCharacterSetName().c_str()))
    schema_sql.append("DEFAULT CHARACTER SET ").append(schema->defaultCharacterSetName().c_str()).append(" ");

  if(schema->defaultCollationName().is_valid() && !schema->defaultCollationName().empty() && 
      (get_collation_cs(schema->defaultCollationName()) == (schema->defaultCharacterSetName().c_str())))
    schema_sql.append("COLLATE ").append(schema->defaultCollationName().c_str()).append(" ");

  remember(schema, schema_sql);
}

void ActionGenerateSQL::drop_schema(db_mysql_SchemaRef schema)
{
  std::string schema_sql;
  schema_sql.append("DROP SCHEMA IF EXISTS `").append(schema->name().c_str()).append("` ");
  remember(schema, schema_sql, true);
}

// alter schema methods

void ActionGenerateSQL::alter_schema_props_begin(db_mysql_SchemaRef schema)
{
  sql.clear();
}

void ActionGenerateSQL::alter_schema_name(db_mysql_SchemaRef schema, grt::StringRef value)
{
  std::string rename_sql("RENAME SCHEMA `");
  rename_sql += schema->name().c_str();
  rename_sql += "` TO `";
  rename_sql += value.c_str();
  rename_sql += "`";
  remember_alter(schema, rename_sql);
}

void ActionGenerateSQL::alter_schema_default_charset(db_mysql_SchemaRef schema, grt::StringRef value)
{
  sql.append(" DEFAULT CHARACTER SET ").append(value).append(" ");
}

void ActionGenerateSQL::alter_schema_default_collate(db_mysql_SchemaRef schema, grt::StringRef value)
{

  if(value.empty())
    sql.append(" DEFAULT COLLATE ").append(bec::get_default_collation_for_charset(schema, schema->defaultCharacterSetName().c_str())).append(" ");
  else
    sql.append(" DEFAULT COLLATE ").append(value).append(" ");
}

void ActionGenerateSQL::alter_schema_props_end(db_mysql_SchemaRef schema)
{
  if(!sql.empty())
  {
    sql= std::string("ALTER SCHEMA `").append(schema->name().c_str()).append("` ").append(sql);
    remember_alter(schema, sql);
  }
}

// alter table

void ActionGenerateSQL::alter_table_props_begin(db_mysql_TableRef table)
{
  comma.clear();
  sql.assign("ALTER TABLE ");
  sql += get_table_old_name(table)+"\n";
  empty_length= sql.length();

  partitions_to_drop.clear();
  partitions_to_change.clear();
  partitions_to_add.clear();
  first_change= true;
}

void ActionGenerateSQL::alter_table_property(std::string& to, const std::string& name, const std::string& value)
{
  if(first_change)
    first_change= false;
  else
    to.append(", ");

  to.append(name).append(value).append(" ");
}

void ActionGenerateSQL::alter_table_name(db_mysql_TableRef table, grt::StringRef str)
{
  alter_table_property(
    sql,
    "RENAME TO ", 
	_use_short_names?
	std::string(" `").append(str.c_str()).append("`"):
	std::string(" `").append(table->owner()->name().c_str()).append("`.`").append(str.c_str()).append("`")
	);
}

void ActionGenerateSQL::alter_table_engine(db_mysql_TableRef table, grt::StringRef str)
{
  alter_table_property(sql, "ENGINE = ", str.c_str());
}

void ActionGenerateSQL::alter_table_next_auto_inc(db_mysql_TableRef table, grt::StringRef str)
{
  alter_table_property(sql, "AUTO_INCREMENT = ", str.c_str());
}

void ActionGenerateSQL::alter_table_password(db_mysql_TableRef table, grt::StringRef str)
{
  alter_table_property(sql, "PASSWORD  = '", std::string(str.c_str()).append("' "));
}

void ActionGenerateSQL::alter_table_delay_key_write(db_mysql_TableRef table, grt::IntegerRef n)
{
  alter_table_property(sql, "DELAY_KEY_WRITE  = ", itoa(n, itoa_buf, 10));
}

void ActionGenerateSQL::alter_table_charset(db_mysql_TableRef table, grt::StringRef str)
{
  alter_table_property(sql, "CHARACTER SET = ", str.empty()?"DEFAULT":str.c_str());
}

void ActionGenerateSQL::alter_table_collate(db_mysql_TableRef table, grt::StringRef str)
{
  alter_table_property(sql, "COLLATE = ", str.empty()?"DEFAULT":str.c_str());
}

void ActionGenerateSQL::alter_table_comment(db_mysql_TableRef table, grt::StringRef str)
{
  alter_table_property(sql, "\nCOMMENT = ", "'" + generate_comment(str, _maxTableCommentLength) + "'");
}

void ActionGenerateSQL::alter_table_merge_union(db_mysql_TableRef table, grt::StringRef str)
{
  std::string s = *str;
  if (!s.empty() && s[0] == '(')
    s = s.substr(1, s.size()-2);

  if (!_use_short_names)
    s = bec::TableHelper::normalize_table_name_list(table->owner()->name(), s);

  alter_table_property(sql, "UNION = (", std::string(s).append(") "));
}

void ActionGenerateSQL::alter_table_merge_insert(db_mysql_TableRef table, grt::StringRef str)
{
  alter_table_property(sql, "INSERT_METHOD = ", str.c_str());
}

void ActionGenerateSQL::alter_table_pack_keys(db_mysql_TableRef table, grt::StringRef str)
{
  alter_table_property(sql, "PACK_KEYS = ", str.c_str());
}

void ActionGenerateSQL::alter_table_checksum(db_mysql_TableRef table, grt::IntegerRef n)
{
  alter_table_property(sql, "CHECKSUM = ", itoa(n, itoa_buf, 10));
}

void ActionGenerateSQL::alter_table_row_format(db_mysql_TableRef table, grt::StringRef str)
{
  alter_table_property(sql, "ROW_FORMAT = ", str.c_str());
}

void ActionGenerateSQL::alter_table_key_block_size(db_mysql_TableRef table, grt::StringRef str)
{
  alter_table_property(sql, "KEY_BLOCK_SIZE = ", str.c_str());
}

void ActionGenerateSQL::alter_table_avg_row_length(db_mysql_TableRef table, grt::StringRef str)
{
  alter_table_property(sql, "AVG_ROW_LENGTH = ", str.c_str());
}

void ActionGenerateSQL::alter_table_min_rows(db_mysql_TableRef table, grt::StringRef str)
{
  alter_table_property(sql, "MIN_ROWS = ", str.c_str());
}

void ActionGenerateSQL::alter_table_max_rows(db_mysql_TableRef table, grt::StringRef str)
{
  alter_table_property(sql, "MAX_ROWS = ", str.c_str());
}

void ActionGenerateSQL::alter_table_connection_string(db_mysql_TableRef table, grt::StringRef str)
{
  alter_table_property(sql, "CONNECTION = ", str.c_str());
}

// used by generate_create_partitioning and generate_add_partitioning
// generates full PARTITION BY clause
void ActionGenerateSQL::alter_table_generate_partitioning(
                                       db_mysql_TableRef table, 
                                       const std::string& part_type,
                                       const std::string& part_expr, 
                                       int part_count,
                                       const std::string& subpart_type, 
                                       const std::string& subpart_expr,
                                       grt::ListRef<db_mysql_PartitionDefinition> part_defs)
{
  if (!part_count)
  {
      alter_table_drop_partitioning(table);
      return;
  }
  bool is_range= (part_type.compare("RANGE") == 0);
  bool is_list= false;
  char itoa_buf[32];

  if(!is_range)
    is_list= (part_type.compare("LIST") == 0);

  std::string partition_sql(" PARTITION BY ");

  partition_sql
    .append(part_type)
    .append("(")
    .append(part_expr)
    .append(") PARTITIONS ")
    .append(itoa(part_count, itoa_buf, 10));

  if(is_range || is_list)
  {
    if(!subpart_type.empty())
    {
      partition_sql
        .append(" SUBPARTITION BY ")
        .append(subpart_type)
        .append("(")
        .append(subpart_expr)
        .append(") ");
    }

    partition_sql.append("(");

    for(size_t sz= part_defs.count(), i= 0; i < sz; i++)
    {
      if(i > 0)
        partition_sql.append(", ");

      db_mysql_PartitionDefinitionRef part= part_defs.get(i);
      partition_sql.append(generate_single_partition(part, is_range));
    }
    partition_sql.append(")");
  }

  sql.append(comma).append(partition_sql);  
}

void ActionGenerateSQL::alter_table_drop_partitioning(db_mysql_TableRef table)
{
  sql.append(" REMOVE PARTITIONING");
}

void ActionGenerateSQL::alter_table_reorganize_partition(
                                         db_mysql_PartitionDefinitionRef old_part,
                                         db_mysql_PartitionDefinitionRef new_part,
                                         bool is_range)
{
  std::string part_sql(" REORGANIZE PARTITION ");
  
  part_sql.append(old_part->name().c_str())
     .append(" INTO (")
     .append(generate_single_partition(new_part, is_range)) 
     .append(")");

  partitions_to_change.push_back(part_sql);
}

void ActionGenerateSQL::alter_table_drop_partition(const std::string& part_name)
{
  partitions_to_drop.push_back(part_name.c_str());
}

void ActionGenerateSQL::alter_table_add_partition(db_mysql_PartitionDefinitionRef part, bool is_range)
{
  partitions_to_add.push_back(
    std::string(" ADD PARTITION (")
    .append(generate_single_partition(part, is_range))
    .append(") "));
}

void ActionGenerateSQL::alter_table_partition_count(db_mysql_TableRef table, grt::IntegerRef oldcount)
{
  // we get here only if partitionType was not changed, so we can rely on old type setting

  char buf[32];

  int newcount= table->partitionCount();
  std::string part_type(table->partitionType().c_str());

  // for range/list partitions we use add/drop/reorganize partitions
  if((oldcount == newcount) || ((part_type.find("HASH") == std::string::npos)
      && (part_type.find("KEY") == std::string::npos)))
    return;

  std::string part_count_sql;

  if(oldcount > newcount) // merge
    part_count_sql.append(" COALESCE PARTITION ").append(itoa((oldcount - newcount), buf, 10));
  else  // split
    part_count_sql.append(" ADD PARTITION PARTITIONS ").append(itoa((newcount - oldcount), buf, 10));

  // partition count alone can be changed only for HASH/KEY partitions
  // generate_change_partition_count() will return empty string otherwise
  // for RANGE/LIST we ignore change of this attribute and rely solely on
  // partition definitions change
  if(!part_count_sql.empty())
  {
    sql.append(comma).append(part_count_sql);
    //partitions_processed= true;
  }
}

void ActionGenerateSQL::alter_table_partition_definitions(db_mysql_TableRef table, grt::StringRef str)
{}

void ActionGenerateSQL::alter_table_props_end(db_mysql_TableRef table)
{
  if(sql.length() > empty_length)
  {
      if (!_algorithm_type.empty())
          sql.append(", ALGORITHM = ").append(_algorithm_type);

      if (!_lock_type.empty())
          sql.append(", LOCK = ").append(_lock_type);      
  }


  if(!partitions_to_drop.empty())
  {
    sql.append(generate_drop_partitions(partitions_to_drop));

    remember_alter(table, sql);
    sql.assign("ALTER TABLE ");
    sql.append(get_table_old_name(table));
  }

  if(!partitions_to_change.empty())
  {
    for(std::list<std::string>::const_iterator e= partitions_to_change.end(), 
      it= partitions_to_change.begin(); it != e; it++)
    {
      sql.append(*it);
      remember_alter(table, sql);
      sql.assign("ALTER TABLE ");
      sql.append(get_table_old_name(table));
    }
  }

  if(!partitions_to_add.empty())
  {
    for(std::list<std::string>::const_iterator e= partitions_to_add.end(), 
      it= partitions_to_add.begin(); it != e; it++)
    {
      sql.append(*it);
      remember_alter(table, sql);
      sql.assign("ALTER TABLE ");
      sql.append(get_table_old_name(table));
    }
  }

  if(sql.length() > empty_length)
    remember_alter(table, sql);
}

void ActionGenerateSQL::alter_table_columns_begin(db_mysql_TableRef)
{
  //first_column= true;
}

void ActionGenerateSQL::alter_table_add_column(db_mysql_TableRef table, 
                                               std::map<std::string, std::string> rename_map,
                                               db_mysql_ColumnRef column, 
                                               db_mysql_ColumnRef after)
{
  if(first_change)
    first_change= false;
  else
    sql.append(",\n");

/*
  | ADD [COLUMN] column_definition [FIRST | AFTER col_name ]
  | ADD [COLUMN] (column_definition,...)
*/

  sql.append("ADD COLUMN ");
  sql.append(generate_create(column));
  sql.append(" ");

  if(after.is_valid())
  {
    //const char *after_sql= after->name().c_str();
    std::string after_name(after->name().c_str());
    std::map<std::string, std::string>::const_iterator it= 
      rename_map.find(after_name);
    if(it != rename_map.end())
      after_name= it->second;
    sql.append("AFTER `").append(after_name).append("`");
  }
  else
  {
    sql.append("FIRST");
  }

  //return sql;
}

void ActionGenerateSQL::alter_table_drop_column(db_mysql_TableRef, db_mysql_ColumnRef column)
{
  if(first_change)
    first_change= false;
  else
    sql.append(",\n");

  sql += ("DROP COLUMN `");
  sql += column->name().c_str();
  sql += "`";
}

void ActionGenerateSQL::alter_table_change_column(db_mysql_TableRef table, db_mysql_ColumnRef org_col, 
                                                  db_mysql_ColumnRef mod_col, db_mysql_ColumnRef after,
                                                  bool modified, 
                                                  std::map<std::string, std::string> column_rename_map)
{
  if(first_change)
    first_change= false;
  else
    sql.append(",\n");

  /*
  | CHANGE [COLUMN] old_col_name column_definition
        [FIRST|AFTER col_name]
  */

  sql.append("CHANGE COLUMN `");
  sql.append(org_col->oldName().c_str()).append("` ");
  if(modified)
    sql.append(generate_create(org_col));
  else
    sql.append(generate_create(mod_col));
  sql.append(" ");

  if(!modified)
  {
    if(after.is_valid())
    {
      std::string after_name(after->name().c_str());
      std::map<std::string, std::string>::const_iterator it= 
        column_rename_map.find(after_name);
      if(it != column_rename_map.end())
        after_name= it->second;
      sql.append("AFTER `").append(after_name).append("`");
    }
    else
    {
      sql.append("FIRST");
    }
  }
}

void ActionGenerateSQL::alter_table_columns_end(db_mysql_TableRef)
{}

void ActionGenerateSQL::alter_table_indexes_begin(db_mysql_TableRef)
{}

void ActionGenerateSQL::alter_table_add_index(db_mysql_IndexRef index)
{
//  sql.append("\n");
  padding.pad(sql);

  if(first_change)
    first_change= false;
  else
    sql.append(",\n");

  sql.append(generate_add_index(index));
}

std::string ActionGenerateSQL::generate_add_index(db_mysql_IndexRef index)
{
/*
  | ADD {INDEX|KEY} [index_name] [index_type] (index_col_name,...)
*/
  return std::string("ADD ").append(generate_create(index, "", false));
}

void ActionGenerateSQL::alter_table_drop_index(db_mysql_IndexRef index)
{
//  sql.append("\n");
  padding.pad(sql);

  if(first_change)
    first_change= false;
  else
    sql.append(",\n");  

  sql.append(generate_drop_index(index));
}

void ActionGenerateSQL::alter_table_indexes_end(db_mysql_TableRef)
{
}

void ActionGenerateSQL::alter_table_fks_begin(db_mysql_TableRef)
{
  first_fk_create= true;
  first_fk_drop= true;
  fk_add_sql.clear();
  fk_drop_sql.clear();
}

void ActionGenerateSQL::alter_table_add_fk(db_mysql_ForeignKeyRef fk)
{
  grt::StringRef ename = db_mysql_TableRef::cast_from(fk->owner())->tableEngine();
  db_mysql_StorageEngineRef engine = bec::TableHelper::get_engine_by_name(fk.get_grt(), ename);
  if (engine.is_valid() && !engine->supportsForeignKeys())
      return;
  if(first_fk_create)
    first_fk_create= false;
  else
    fk_add_sql.append(",\n");

/*
  | ADD [CONSTRAINT [symbol]]
        FOREIGN KEY [index_name] (index_col_name,...)
        [reference_definition]
*/
  fk_add_sql += "ADD ";
  fk_add_sql += ::generate_create(fk, padding, _use_short_names);
}

void ActionGenerateSQL::alter_table_drop_fk(db_mysql_ForeignKeyRef fk)
{
  grt::StringRef ename = db_mysql_TableRef::cast_from(fk->owner())->tableEngine();
  db_mysql_StorageEngineRef engine = bec::TableHelper::get_engine_by_name(fk.get_grt(), ename);
  if (engine.is_valid() && !engine->supportsForeignKeys())
      return;

  if(first_fk_drop)
    first_fk_drop= false;
  else
    fk_drop_sql.append(",\n");

/*
  | DROP FOREIGN KEY fk_symbol
*/
  fk_drop_sql += "DROP FOREIGN KEY `";
  fk_drop_sql += fk->name().c_str();
  fk_drop_sql += "`";
}

void ActionGenerateSQL::alter_table_fks_end(db_mysql_TableRef table)
{
  if(!fk_add_sql.empty() && !fk_drop_sql.empty())
  {
    if(!first_change)
      sql.append(",\n");
    sql.append(fk_drop_sql);
    remember_alter(table, sql);  // let DROP go first
    sql.assign("ALTER TABLE ");
    sql.append(get_table_old_name(table));
    sql.append(fk_add_sql);
    comma.clear();
    first_change= false;
  }
  else if(!fk_add_sql.empty())
  {
    if(first_change)
      first_change= false;
    else
      sql.append(",\n");
    sql.append(fk_add_sql);    
  }
  else if(!fk_drop_sql.empty())
  {
    if(first_change)
      first_change= false;
    else
      sql.append(",\n");
    sql.append(fk_drop_sql);
  }
}

// triggers

void ActionGenerateSQL::create_trigger(db_mysql_TriggerRef trigger, bool for_alter)
{
  std::string trigger_sql;
  if (!_use_short_names || _gen_use)
    trigger_sql.append("USE `").append(trigger->owner()->owner()->name().c_str()).append("`").append(_non_std_sql_delimiter).append("\n");
  trigger_sql.append(trigger->sqlDefinition().c_str());

  if(for_alter)
    remember_alter(trigger, trigger_sql);
  else
    remember(trigger, trigger_sql);
}

void ActionGenerateSQL::drop_trigger(db_mysql_TriggerRef trigger, bool for_alter)
{
  std::string trigger_sql;
  if (!_use_short_names || _gen_use)
    trigger_sql.append("USE `").append(trigger->owner()->owner()->name().c_str()).append("`").append(_non_std_sql_delimiter).append("\n");
  trigger_sql.append("DROP TRIGGER IF EXISTS ").append(get_name(trigger)).append(" ");
  if(for_alter)
    remember_alter(trigger, trigger_sql);
  else
    remember(trigger, trigger_sql);
}

// views

void ActionGenerateSQL::create_view(db_mysql_ViewRef view)
{
  const char *errptr;
  int erroffs;
  bool or_replace_present= false;
  int patres[3];

  std::string view_def;
  view_def.append(view->sqlDefinition().c_str()); 

  pcre *patre= pcre_compile("^\\s*CREATE\\s+OR\\s+REPLACE\\s+", PCRE_CASELESS | PCRE_MULTILINE, &errptr, &erroffs, NULL);
  if (patre && (pcre_exec(patre, NULL, view_def.c_str(), view_def.length(), 0, 0, patres, sizeof(patres)/sizeof(int)) > 0))
  {
    or_replace_present= true;
  }

  if (patre)
    pcre_free(patre);

  if (!or_replace_present)
  {
    patre= pcre_compile("^\\s*CREATE\\s+", PCRE_CASELESS | PCRE_MULTILINE , &errptr, &erroffs, NULL);
    if (patre && (pcre_exec(patre, NULL, view_def.c_str(), view_def.length(), 0, 0, patres, sizeof(patres)/sizeof(int)) > 0))
      view_def.insert(patres[1], " OR REPLACE ");

    if (patre)
      pcre_free(patre);
  }

  if (_use_short_names)
  {
    SqlFacade* parser = SqlFacade::instance_for_rdbms_name(view.get_grt(), "Mysql");
    Sql_schema_rename::Ref renamer = parser->sqlSchemaRenamer();
    renamer->rename_schema_references(view_def, view->owner()->name(), "");
  }
  if (!_use_short_names || _gen_use)
  {
    std::string use_def;
    use_def.append("USE `").append(view->owner()->name()).append("`;\n");
    use_def.append(view_def);
    view_def = use_def;
  }
  remember(view, view_def);
}

void ActionGenerateSQL::drop_view(db_mysql_ViewRef view)
{
  std::string view_sql;
  view_sql.append("DROP VIEW IF EXISTS ").append(get_name(view)).append(" ");
  remember(view, view_sql);
}

// routines
void ActionGenerateSQL::create_routine(db_mysql_RoutineRef routine, bool for_alter)
{

  std::string routine_sql;
  routine_sql = "\nDELIMITER ";
  routine_sql.append(_non_std_sql_delimiter).append("\n");

  if (!_use_short_names || _gen_use)
  {
    routine_sql.append("USE `");
    routine_sql.append(routine->owner()->name()).append("`").append(_non_std_sql_delimiter).append("\n");
  }
  routine_sql.append(routine->sqlDefinition().c_str()).append(_non_std_sql_delimiter).append("\n");

  if (_use_short_names)
  {
      SqlFacade* parser = SqlFacade::instance_for_rdbms_name(routine.get_grt(), "Mysql");
      Sql_schema_rename::Ref renamer = parser->sqlSchemaRenamer();
      renamer->rename_schema_references(routine_sql, routine->owner()->name(), "");
  }

  routine_sql.append("\nDELIMITER ;\n");

  //remove_delims(routine_sql);
  if(for_alter)
    remember_alter(routine, routine_sql);
  else
    remember(routine, routine_sql);
}

void ActionGenerateSQL::drop_routine(db_mysql_RoutineRef routine, bool for_alter)
{
  std::string routine_sql;
  
  if (!_use_short_names || _gen_use)
  {
    routine_sql = "\nUSE `";
    routine_sql.append(routine->owner()->name()).append("`;\n");
  }

  routine_sql.append("DROP ").append(routine->routineType().c_str())
    .append(" IF EXISTS ").append(get_name(routine)).append(";\n");

  if(for_alter)
    remember_alter(routine, routine_sql);
  else
    remember(routine, routine_sql);
}

// users
void ActionGenerateSQL::create_user(db_UserRef user)
{
  std::string sql;

  sql.append("CREATE USER ").append(quote_user(user->name()));
  if (user->password().is_valid() && *user->password().c_str())
    sql.append(" IDENTIFIED BY '").append(*user->password()).append("'");

  sql.append(";\n\n");

  std::list<std::string> grants;
  gen_grant_sql(db_CatalogRef::cast_from(user->owner()), user, grants, _use_short_names);

  std::list<std::string>::iterator iter= grants.begin();
  for(; iter != grants.end(); ++iter)
    sql.append(*iter).append(";\n");

  remember(user, sql);
}

void ActionGenerateSQL::drop_user(db_UserRef user)
{
  sql.clear();
  //There is no IF EXISTS for DROP USER, so do a grant to create if there is no any
  //NO_AUTO_CREATE_USER in traditional sql_mode prevents grant trick from working
  //So there is need of additinal sql_mode change prior to using this
  sql.append("GRANT USAGE ON *.* TO ").append(user->name().c_str()).append(";\n DROP USER ").append(user->name().c_str());
  remember(user, sql);
}

void ActionGenerateSQL::remember(const GrtNamedObjectRef &obj, const std::string &sql, const bool front)
{
  if(target_list.is_valid())
  {
    if(disable_object_list)
        return;
    target_list.insert(grt::StringRef(sql), front ? 0 : (size_t)StringListRef::npos);
    if(target_object_list.is_valid())
      target_object_list.insert(obj, front ? 0 : (size_t)StringListRef::npos);
  }
  else
  {
    target_map.set(_use_oids_as_dict_key ? obj.id() : get_full_object_name_for_key(obj,_case_sensitive), grt::StringRef(sql));
  }
}

// in case of ALTERs there could be > 1 statement to remember
// so we use grt::StringListRefs as needed
void ActionGenerateSQL::remember_alter(const GrtNamedObjectRef &obj, const std::string &sql)
{
  if(target_list.is_valid())
  {
    if(disable_object_list)
      return;
    target_list.insert(grt::StringRef(sql));
    if(target_object_list.is_valid())
      target_object_list.insert(obj);
    return;
  }

  std::string key= _use_oids_as_dict_key ? obj.id() : get_full_object_name_for_key(obj,_case_sensitive);

  if(target_map.has_key(key))
  {
    grt::ValueRef value= target_map.get(key);
    if(grt::StringRef::can_wrap(value))
    {
      grt::StringListRef list_value(target_map.get_grt());
      list_value.insert(grt::StringRef::cast_from(value));
      list_value.insert(grt::StringRef(sql));
      target_map.set(key, list_value);
    }
    else if(grt::StringListRef::can_wrap(value))
    {
      grt::StringListRef::cast_from(value).insert(grt::StringRef(sql));
    }
    else
    {
      // a bug
    }
  }
  else
  {
    target_map.set(key, grt::StringRef(sql));
  }
}

} // namespace

DbMySQLImpl::DbMySQLImpl(grt::CPPModuleLoader *ldr) : grt::ModuleImplBase(ldr), _default_traits(get_grt())
{
    _default_traits.set("CaseSensitive", grt::IntegerRef(1));
    _default_traits.set("maxTableCommentLength", grt::IntegerRef(60));
    _default_traits.set("maxIndexCommentLength", grt::IntegerRef(0));
    _default_traits.set("maxColumnCommentLength", grt::IntegerRef(255));
}

int DbMySQLImpl::generateSQL(GrtNamedObjectRef org_object, 
                             const grt::DictRef& options, 
                             boost::shared_ptr<DiffChange> changes)
{
  grt::ValueRef result= options.get("OutputContainer");
  grt::ListRef<GrtNamedObject> obj_list;

  if(options.has_key("OutputObjectContainer"))
    obj_list= grt::ListRef<GrtNamedObject>::cast_from(options.get("OutputObjectContainer"));
  if(result.type() == DictType)
  {
    ActionGenerateSQL generator = ActionGenerateSQL(result, obj_list, get_grt(), 
                                                    grt::DictRef::cast_from(options.get("DBSettings", getDefaultTraits())),
                                                    options.get_int("UseOIDAsResultDictKey", 0) != 0);
    DiffSQLGeneratorBE(options, grt::DictRef::cast_from(options.get("DBSettings", getDefaultTraits())), &generator)
      .process_diff_change(org_object, changes.get(), grt::DictRef::cast_from(result));
  }
  else if(result.type() == ListType)
  {
    ActionGenerateSQL generator = ActionGenerateSQL(result, obj_list, get_grt(), 
                                                    grt::DictRef::cast_from(options.get("DBSettings", getDefaultTraits())),
                                                    options.get_int("UseOIDAsResultDictKey", 0) != 0);
    DiffSQLGeneratorBE(options, grt::DictRef::cast_from(options.get("DBSettings", getDefaultTraits())), &generator)
      .process_diff_change(org_object, changes.get(), grt::StringListRef::cast_from(result), obj_list);
  }

  return 0;
}


grt::StringRef DbMySQLImpl::generateReport(GrtNamedObjectRef org_object, const grt::DictRef& options,
                                           boost::shared_ptr<DiffChange> changes)
{
  grt::StringRef tpl_file= grt::StringRef::cast_from(options.get("TemplateFile"));
  
  {
    ActionGenerateReport r(tpl_file);
    
    DiffSQLGeneratorBE(options, grt::DictRef::cast_from(options.get("DBSettings", getDefaultTraits())), &r)
    .process_diff_change(org_object, changes.get(), grt::StringListRef(), grt::ListRef<GrtNamedObject>());
    
    grt::StringRef retval(r.generate_output());
    
    return retval;
  }
}


grt::StringRef DbMySQLImpl::generateReportForDifferences(GrtNamedObjectRef org_object, GrtNamedObjectRef oth_object, 
                                                         const grt::DictRef& options)
{
  grt::DbObjectMatchAlterOmf omf;
  omf.dontdiff_mask = options.get_int("OMFDontDiffMask", omf.dontdiff_mask);
  grt::NormalizedComparer normalizer(get_grt());
  normalizer.init_omf(&omf);
  boost::shared_ptr<DiffChange> alter_change= diff_make(org_object, oth_object, &omf);
  
  grt::StringRef tpl_file= grt::StringRef::cast_from(options.get("TemplateFile"));
  
  {
    if (alter_change == NULL) // There are no changes, user probably selected the same schema on the same instance.
      return grt::StringRef("");

    ActionGenerateReport r(tpl_file);

    DiffSQLGeneratorBE(options, grt::DictRef::cast_from(options.get("DBSettings", getDefaultTraits())), &r)
    .process_diff_change(org_object, alter_change.get(), grt::StringListRef(), grt::ListRef<GrtNamedObject>());

    grt::StringRef retval(r.generate_output());

    return retval;
  }
}


grt::DictRef DbMySQLImpl::generateSQLForDifferences(GrtNamedObjectRef srcobj, GrtNamedObjectRef dstobj, grt::DictRef options)
{
  grt::DictRef sql_map(get_grt());
    
  default_omf omf;
  grt::NormalizedComparer normalizer(get_grt());
  normalizer.init_omf(&omf);
  boost::shared_ptr<DiffChange> changes= diff_make(srcobj, dstobj, &omf);
  
  options.set("DiffCaseSensitiveness", grt::IntegerRef(normalizer.is_case_sensitive()));
  
  // the SQL generator has a weird inverted logic where filter lists are used by default
  // even if they're not set
  if (!options.has_key("UseFilteredLists"))
    options.gset("UseFilteredLists", 0);

  if (changes)
  {
    options.set("OutputContainer", sql_map);
    generateSQL(srcobj, options, changes);
  }

  return sql_map;
}


static bool exists_in_map(const GrtNamedObjectRef& object, const DictRef& dict, const bool case_sensitive)
{
  std::string qname(get_full_object_name_for_key(object, case_sensitive));
  return dict.has_key(qname);
}

static std::string string_from_map(const GrtNamedObjectRef& object, const DictRef& dict, const bool case_sensitive)
{
  std::string qname(get_full_object_name_for_key(object, case_sensitive));
  StringRef res= dict.get_string(qname);

  //if (!res.is_valid())
  //  return std::string();
  return *res;
}

class TableSorterByFK {
    std::set<db_mysql_TableRef> generated_tables;
public:
    void perform(db_mysql_TableRef table, std::vector<db_mysql_TableRef>& result)
    {
        if (table->modelOnly() || table->isStub() || (generated_tables.find(table) != generated_tables.end()))
            return;
        generated_tables.insert(table);
        const grt::ListRef<db_mysql_ForeignKey> fks= table->foreignKeys();
        for(size_t c= fks.count(), i= 0; i < c; i++)
        {
            const db_mysql_ForeignKeyRef fk= fks.get(i);
            if (fk.is_valid() && fk->referencedTable().is_valid() && !fk->modelOnly())
                perform(fk->referencedTable(), result);
        }
        result.push_back(table);
    }
};

class SQLComposer
{
protected:
    std::string sql_mode;
    std::string non_std_sql_delimiter;
    grt::GRT *grt;
    bool show_warnings;
    bool use_short_names;
    typedef std::map<std::string, std::vector<std::pair<std::string,std::string> > > alias_map_t;
    alias_map_t alias_map;

    SQLComposer(const grt::DictRef options, grt::GRT *pgrt):grt(pgrt)
    {
        sql_mode = options.get_string("SQL_MODE", "TRADITIONAL");
        SqlFacade::Ref sql_facade = SqlFacade::instance_for_rdbms_name(grt, "Mysql");
        Sql_specifics::Ref sql_specifics = sql_facade->sqlSpecifics();
        non_std_sql_delimiter = sql_specifics->non_std_sql_delimiter();
        show_warnings= options.get_int("GenerateWarnings") != 0;
        use_short_names= options.get_int("UseShortNames") != 0;
    };

    void send_output(const std::string& msg) const
    {
        if (grt) 
            grt->send_output(msg);
    };

    std::string show_warnings_sql() const
    {
        return show_warnings?"SHOW WARNINGS;\n":"";
    }

    std::string set_server_vars() const
    {
        std::string result;
        result.append("SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0;\n");
        result.append("SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0;\n");
        result.append(base::sqlstring("SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE=?;\n\n", 0) << sql_mode);
        return result;
    }

    std::string restore_server_vars() const
    {
        std::string result;

        result.append("\n");
        result.append("SET SQL_MODE=@OLD_SQL_MODE;\n");
        result.append("SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;\n");
        result.append("SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS;\n");
        return result;
    }

    std::string generate_view_placeholder(const db_mysql_ViewRef view)
    {
        std::string sql;
        std::string view_q_name(get_name(view,use_short_names));

        SelectStatement::Ref select_statement(new SelectStatement());
        SqlFacade* parser = SqlFacade::instance_for_rdbms_name(grt, "Mysql");
        parser->sqlStatementDecomposer()->decompose_view(view,select_statement);

        sql.append("\n-- -----------------------------------------------------\n")
            .append("-- Placeholder table for view ").append(view_q_name)
            .append("\n-- -----------------------------------------------------\n");

        sql.append("CREATE TABLE IF NOT EXISTS ");
        sql.append(view_q_name).append(" (");
        if(select_statement->select_items.empty())
            sql.append("`id` INT");
        else
        {
            std::vector<std::string> used_colnames;
            used_colnames.reserve(select_statement->select_items.size());

            bool first_col = true;
            for (SelectItems::const_iterator it = select_statement->select_items.begin(); it != select_statement->select_items.end(); ++it)
                if (std::find(used_colnames.begin(),used_colnames.end(),it->effective_alias()) == used_colnames.end())
                {
                    std::string alias_name = it->effective_alias();
                    if(alias_name.size() > 64)
                    {
                        std::string new_name = get_name_suggestion(
                            boost::bind(std::not_equal_to<std::vector<std::string>::iterator>(),
                            boost::bind(
                            std::find<std::vector<std::string>::iterator, std::string>,used_colnames.begin(),used_colnames.end(), _1),
                            used_colnames.end()),
                            "Col_placeholder", true);
                        alias_map[view.id()].push_back(std::pair<std::string,std::string>(new_name,alias_name));
                        alias_name = new_name;
                    };

                    if(!first_col)
                        sql.append(", ");
                    else
                        first_col = false;
                    sql.append("`").append(alias_name).append("` INT" );
                    used_colnames.push_back(alias_name);
                }
        }
        sql.append(");\n");

        sql.append(show_warnings_sql());
        return sql;
    }

    std::string generate_view_ddl(const db_mysql_ViewRef view, 
        std::string create_view, std::string drop_view = std::string())
    {
        std::string sql;
        std::string view_q_name(get_name(view,use_short_names));

        // create view
        sql.append("\n");
        sql.append("-- -----------------------------------------------------\n");
        sql.append("-- View ").append(view_q_name).append("\n");
        sql.append("-- -----------------------------------------------------\n");

        if(!drop_view.empty())
            sql.append(drop_view).append(";\n").append(show_warnings_sql());

        // remove placehoder
        sql.append("DROP TABLE IF EXISTS ").append(view_q_name).append(";\n");
        sql.append(show_warnings_sql());

        // view DDL
        if(!create_view.empty())
        {
            if (alias_map.find(view.id()) != alias_map.end())
            {
                std::string create_view_with_aliases = create_view;
                size_t idx = 0;
                const alias_map_t::mapped_type aliases = alias_map[view.id()];
                for(alias_map_t::mapped_type::const_iterator It = aliases.begin(); It != aliases.end(); ++It)
                {
                    idx = create_view_with_aliases.find(It->second, idx);
                    if (idx == std::string::npos)
                        continue;
                    idx += It->second.size();
                    std::string alias(" AS '");
                    alias.append(It->first).append("' ");
                    create_view_with_aliases.insert(idx, alias);
                    idx+= alias.size();
                }
                sql.append(create_view_with_aliases);
            }
            else
                sql.append(create_view);

          if (!bec::has_suffix(base::trim_right(create_view, "\n"), ";"))
              sql.append(";");
            sql.append("\n");
        }
        sql.append(show_warnings_sql());
        return sql;
    }
};

class SQLExportComposer : public SQLComposer
{
    bool gen_create_index;
    bool gen_use;
    bool gen_drops;
    bool gen_schema_drops;
    bool no_user_just_privileges;
    bool gen_inserts;
    bool case_sensitive;
    bool no_view_placeholders;
    bool no_FK_for_inserts;
    bool triggers_after_inserts;
    grt::DictRef create_map;
    grt::DictRef drop_map;
public:
    SQLExportComposer(const grt::DictRef options, grt::DictRef cmap, 
        grt::DictRef dmap, grt::GRT *pgrt):SQLComposer(options, pgrt), create_map(cmap), drop_map(dmap)
    {
        gen_create_index= options.get_int("GenerateCreateIndex") != 0;
        gen_use = options.get_int("GenerateUse") != 0;
        gen_drops= options.get_int("GenerateDrops") != 0;
        gen_schema_drops = options.get_int("GenerateSchemaDrops") != 0;
        no_user_just_privileges = options.get_int("NoUsersJustPrivileges") != 0;
        no_view_placeholders = options.get_int("NoViewPlaceholders") != 0;
        gen_inserts= options.get_int("GenerateInserts") != 0;
        case_sensitive= options.get_int("CaseSensitive") != 0;
        no_FK_for_inserts = options.get_int("NoFKForInserts") != 0;
        triggers_after_inserts = true;//options.get_int("TriggersAfterInserts") != 0;
    }

protected:
    std::string schemata_sql(const grt::ListRef<db_mysql_Schema>& schemata) const
    {
        std::string result;
        for(size_t c1= schemata.count(), i= 0; i < c1; i++)
        {
            db_mysql_SchemaRef schema= schemata.get(i);
            if (schema->modelOnly())
                continue;
            if ((!use_short_names || gen_use) && (create_map.has_key(get_old_object_name_for_key(schema, case_sensitive))))
            {
                if (gen_schema_drops)
                    result.append("DROP SCHEMA IF EXISTS `").append(schema->name().c_str()).append("` ;\n");
                result.append(string_from_map(schema, create_map, case_sensitive)).append(";\n");
            }
            result.append(show_warnings_sql());
        }
        return result;    
    };

    std::string table_sql(const db_mysql_TableRef table) const
    {
        std::string result;
        std::string create_table_sql= string_from_map(table, create_map, case_sensitive);

        result.append("\n");
        result.append("-- -----------------------------------------------------\n");
        result.append("-- Table ").append(get_name(table,use_short_names)).append("\n");
        result.append("-- -----------------------------------------------------\n");

        if(gen_drops)
            result.append(string_from_map(table, drop_map, case_sensitive)).append(";\n\n").append(show_warnings_sql());

        result.append(create_table_sql).append(";\n\n");
        result.append(show_warnings_sql());
        send_output(std::string("Processing Table ").append(table->owner()->name()).append(".").append(table->name()).append("\n"));

        // table indices
        if(gen_create_index)
        {
            grt::ListRef<db_mysql_Index> indices= table->indices();
            for(size_t c3= indices.count(), k= 0; k < c3; k++)
            {
                std::string index_sql= string_from_map(indices.get(k), create_map, case_sensitive);
                if(!index_sql.empty())
                    result.append(index_sql).append(";\n\n").append(show_warnings_sql());
            }
        }
        return result;
    }

    std::string table_inserts_sql(const db_mysql_TableRef table) const
    {
        std::string result;
        std::string use_code;
        if (!use_short_names || gen_use)
            use_code.append("USE `").append(table->owner()->name().c_str()).append("`;\n");

        std::string table_inserts_sql;
        {
            bec::GRTManager *grtm= bec::GRTManager::get_instance_for(grt);
            Recordset_table_inserts_storage::Ref input_storage= Recordset_table_inserts_storage::create(grtm);
            input_storage->table(table);

            Recordset::Ref rs= Recordset::create(grtm);
            rs->data_storage(input_storage);
            rs->reset();

            Recordset_sql_storage::Ref output_storage= Recordset_sql_storage::create(grtm);
            output_storage->table_name(table->name());
            output_storage->rdbms(db_mgmt_RdbmsRef::cast_from(table->owner()/*schema*/->owner()/*catalog*/->owner()/*phys.model*/->get_member("rdbms")));
            output_storage->schema_name(table->owner()->name());
            output_storage->omit_schema_qualifier(use_short_names);
            output_storage->binding_blobs(false);
            output_storage->serialize(rs);
            table_inserts_sql= output_storage->sql_script();          
        }
        if (table_inserts_sql.empty())
            return table_inserts_sql;
        result
            .append("\n-- -----------------------------------------------------\n-- Data for table ")
            .append(get_name(table,use_short_names))
            .append("\n-- -----------------------------------------------------\nSTART TRANSACTION;\n")
            .append(use_code)
            .append(table_inserts_sql)
            .append("\nCOMMIT;\n");
        return result;
    }

    std::string view_placeholder(const db_mysql_ViewRef view)
    {
        if (view->modelOnly())
            return "";
        if(exists_in_map(view, create_map, case_sensitive))
            return generate_view_placeholder(view);
        return "";
    }

    std::string routine_sql(const db_mysql_RoutineRef routine) const
    {
        std::string result;
        send_output(std::string("Processing Routine ").append(routine->owner()->name()).append(".").append(routine->name()).append("\n"));

        if (routine->modelOnly())
            return "";
        std::string create_routine_sql= string_from_map(routine, create_map, case_sensitive);
        if(create_routine_sql.empty())
            return "";

        result.append("\n");
        result.append("-- -----------------------------------------------------\n");
        result.append("-- ").append(routine->routineType().c_str()).append(" ").append(routine->name().c_str()).append("\n");
        result.append("-- -----------------------------------------------------\n");

        std::string drop_string= string_from_map(routine, drop_map, case_sensitive);
        if (!drop_string.empty())
            result.append(drop_string).append(show_warnings_sql());

        std::string create_string= string_from_map(routine, create_map, case_sensitive);
        if (!create_string.empty())
            result.append(create_string).append(show_warnings_sql());

        return result;
    }

    std::string view_sql(const db_mysql_ViewRef view)
    {
        send_output(std::string("Processing View ").append(view->owner()->name()).append(".").append(view->name()).append("\n"));

        if (view->modelOnly() || !exists_in_map(view, create_map, case_sensitive))
            return "";

        return generate_view_ddl(view, 
            string_from_map(view, create_map, case_sensitive), 
            string_from_map(view, drop_map, case_sensitive));    
    }

    std::string trigger_sql(const db_mysql_TriggerRef trigger) const
    {
        std::string result;

        send_output(std::string("Processing Trigger ") 
            .append(trigger->owner()->owner()->name()).append(".").append(trigger->owner()->name()).append(".").append(trigger->name()).append("\n"));

        if (trigger->modelOnly() || !exists_in_map(trigger, create_map, case_sensitive))
            return "";

        //if(gen_drops)
        {
            std::string drop_trigger(string_from_map(trigger, drop_map, case_sensitive));
            if(!drop_trigger.empty())
                result.append("\n").append(drop_trigger).append(non_std_sql_delimiter).append("\n");
            if(show_warnings)
                result.append("SHOW WARNINGS").append(non_std_sql_delimiter).append("\n");
        }
        result.append(string_from_map(trigger, create_map, case_sensitive)).append(non_std_sql_delimiter).append("\n\n");
        if(show_warnings)
            result.append("SHOW WARNINGS").append(non_std_sql_delimiter).append("\n");

        return result;    
    }

    std::string user_sql(const db_UserRef user) const
    {
        std::string result;
        if (user->modelOnly() || !exists_in_map(user, create_map, case_sensitive))
            return "";

        std::string create_user_sql= string_from_map(user, create_map, case_sensitive);

        //if(gen_drops)
        if (exists_in_map(user, drop_map, case_sensitive))
        {
            //There is no DROP USER IF EXISTS clause so we create one with
            //GRANT which will fail in traditional mode due to NO_AUTO_CREATE_USER 
            result.append("SET SQL_MODE = '';\n");
            result.append(string_from_map(user, drop_map, case_sensitive)).append(";\n");
            result.append(base::sqlstring("SET SQL_MODE=?;\n", 0) << sql_mode).append(show_warnings_sql());
        }
        result.append(string_from_map(user, create_map, case_sensitive)).append(show_warnings_sql());
        send_output(std::string("Processing User ").append(user->name()).append("\n"));
        return result;

    }
public:
    std::string get_export_sql(const db_mysql_CatalogRef cat)
    {
        std::string out_sql;

        std::string inserts_sql; // separate from main sql script & append to it as a last step,
        // to separate creation of structures from data loading.
        std::string triggers_sql;//Triggers DDLs could be prioir or after INSERTs depending on settings

        send_output("Generating Script\n");
        out_sql.append(set_server_vars());
        TableSorterByFK sorter;

        // schemata
        grt::ListRef<db_mysql_Schema> schemata= cat->schemata();
        out_sql.append(schemata_sql(schemata));
        for(size_t c1= schemata.count(), i= 0; i < c1; i++)
        {
            std::string objects_sql;
            std::string schema_triggers_sql;

            db_mysql_SchemaRef schema= schemata.get(i);
            if (schema->modelOnly())
                continue;

            send_output(std::string("Processing Schema ").append(schema->name()).append("\n"));

            if ((!use_short_names || gen_use) && (create_map.has_key(get_old_object_name_for_key(schema, case_sensitive))))
                out_sql.append("USE `").append(schema->name().c_str()).append("` ;\n");

            // tables
            grt::ListRef<db_mysql_Table> tables= schema->tables();
            std::vector<db_mysql_TableRef> sorted_tables;
            for(size_t c2= tables.count(), j= 0; j < c2; j++)
                sorter.perform(tables.get(j), sorted_tables);
            for(std::vector<db_mysql_TableRef>::iterator It = sorted_tables.begin(); It != sorted_tables.end(); ++It)
            {
                db_mysql_TableRef table = *It;
                if (table->modelOnly() || table->isStub())
                    continue;
                if(exists_in_map(table, create_map, case_sensitive))
                {
                    out_sql.append(table_sql(table));
                    if (gen_inserts)
                    {
                      std::string tmp = table_inserts_sql(table);
                      if (!tmp.empty())
                        inserts_sql.append(tmp).append("\n");
                    }
                } // process table

                // Fill triggers_sql with triggers DDLs and append it to out_sql later
                // tables
                grt::ListRef<db_mysql_Trigger> triggers= table->triggers();
                for(size_t c3= triggers.count(), k= 0; k < c3; k++)
                    schema_triggers_sql.append(trigger_sql(triggers.get(k)));
            }
            if(!schema_triggers_sql.empty())
            {
                if (!use_short_names || gen_use)
                    triggers_sql.append("USE `").append(schema->name().c_str()).append("`;\n");
                triggers_sql.append("\nDELIMITER ").append(non_std_sql_delimiter).append("\n");
                triggers_sql.append(schema_triggers_sql);
                triggers_sql.append("\nDELIMITER ;\n");
            }

        }

        for(size_t c1= schemata.count(), i= 0; i < c1; i++)
        {
            std::string objects_sql;
            db_mysql_SchemaRef schema= schemata.get(i);
            if (schema->modelOnly())
                continue;

            send_output(std::string("Processing Schema ").append(schema->name()).append("\n"));
            
            grt::ListRef<db_mysql_View> views= schema->views();
            // views placeholder tables
            if(!no_view_placeholders)
            {
            for(size_t c2= views.count(), j= 0; j < c2; j++)
                objects_sql.append(view_placeholder(views.get(j)));
            }

            // routines
            grt::ListRef<db_mysql_Routine> routines= schema->routines();
            if(schema->routines().count() > 0)
                for(size_t c2= routines.count(), j= 0; j < c2; j++)
                    objects_sql.append(routine_sql(routines.get(j)));

            // views DDL
            for(size_t c2= views.count(), j= 0; j < c2; j++)
                objects_sql.append(view_sql(views.get(j)));

            if (!objects_sql.empty() && create_map.has_key(get_old_object_name_for_key(schema, case_sensitive)))
            {
              if (!use_short_names || gen_use)
                out_sql.append("USE `").append(schema->name().c_str()).append("` ;\n");
              out_sql.append(objects_sql);
            }
        }

        if(!triggers_after_inserts)
            out_sql.append(triggers_sql);

        if (no_user_just_privileges)
        {
            std::list<std::string> grants;
            gen_grant_sql(cat, grants);

            for(std::list<std::string>::iterator iter= grants.begin(); iter != grants.end(); ++iter)
                out_sql.append(*iter).append(";\n");
        }
        else
        {
            grt::ListRef<db_User> users= cat->users();
            for(size_t c1= users.count(), i= 0; i < c1; i++)
                out_sql.append(user_sql(users.get(i)));
        }


        if(!no_FK_for_inserts)
            out_sql.append(restore_server_vars());

        if(gen_inserts)
            out_sql.append(inserts_sql);

        if(triggers_after_inserts)
            out_sql.append(triggers_sql);

        if(no_FK_for_inserts)
            out_sql.append(restore_server_vars());

        return out_sql;
    }
};

int DbMySQLImpl::makeSQLExportScript(GrtNamedObjectRef dbobject, grt::DictRef options, 
    const grt::DictRef& createSQL, const grt::DictRef& dropSQL)
{
    // now only catalog supported
    if (!db_mysql_CatalogRef::can_wrap(dbobject))
        return 1;

    db_mysql_CatalogRef catalog= db_mysql_CatalogRef::cast_from(dbobject);
    SQLExportComposer composer(options, createSQL, dropSQL, get_grt());
    options.set("OutputScript", grt::StringRef(composer.get_export_sql(catalog)));
    return 0;
}


class SQLSyncComposer : public SQLComposer
{

public:
    SQLSyncComposer(const grt::DictRef options, grt::GRT *pgrt):SQLComposer(options, pgrt)
    {

    };

    std::string get_sync_sql(const grt::StringListRef& sql_list, const grt::ListRef<GrtNamedObject>& obj_list)
    {
        std::string sql;
        std::list<int> views_indices;
        std::string view_placeholders;
        std::string views;
        std::string routines;
        std::string triggers;

        sql.append(set_server_vars());
        for(size_t sz= sql_list.count(), i= 0; i < sz; i++)
        {
            GrtNamedObjectRef obj= obj_list.get(i);
            if(db_TriggerRef::can_wrap(obj))
            {
                triggers.append(sql_list[i]).append(non_std_sql_delimiter).append("\n\n");
            }
            else if(db_RoutineRef::can_wrap(obj))
            {
                routines.append(sql_list[i]);
            }
            else if(db_ViewRef::can_wrap(obj))
            {
                std::string view_ddl(sql_list[i]);
                if(view_ddl.empty())
                    continue;
                views_indices.push_back(i);
                db_mysql_ViewRef view= db_mysql_ViewRef::cast_from(obj);
                view_placeholders.append(generate_view_placeholder(view));
            }
            else
            {
                sql.append(sql_list.get(i)).append(";\n\n");
            }
        }

        // views DDL
        //2nd loop on views, 1st one creates view placeholders and filles alias_map
        for(std::list<int>::const_iterator e= views_indices.end(), it= views_indices.begin(); 
            it != e; it++)
        {
            db_mysql_ViewRef view= db_mysql_ViewRef::cast_from(obj_list.get(*it));
            std::string view_ddl(sql_list.get(*it));
            views.append("\n\nUSE `").append(view->owner()->name()).append("`;\n");
            views.append(generate_view_ddl(view, view_ddl));
        }

        sql.append(view_placeholders);
        sql.append(views);
        sql.append(routines);
        if (!triggers.empty())
        {
          sql.append("\nDELIMITER ").append(non_std_sql_delimiter).append("\n\n");
          sql.append(triggers);
          sql.append("\nDELIMITER ;\n\n");
        }
        sql.append(restore_server_vars());
        return sql;
    };
};

int DbMySQLImpl::makeSQLSyncScript(grt::DictRef options, const grt::StringListRef& sql_list,
                                   const grt::ListRef<GrtNamedObject>& obj_list)
{
  SQLSyncComposer composer(options, get_grt());
  options.set("OutputScript", grt::StringRef(composer.get_sync_sql(sql_list, obj_list)));
  return 0;
}

std::string DbMySQLImpl::makeAlterScript(GrtNamedObjectRef source, GrtNamedObjectRef target, const grt::DictRef& diff_options)
{
  grt::DbObjectMatchAlterOmf omf;
  omf.dontdiff_mask = 3;
  grt::NormalizedComparer normalizer(get_grt(), grt::DictRef::cast_from(diff_options.get("DBSettings")));
  normalizer.init_omf(&omf);

  boost::shared_ptr<DiffChange> diff = diff_make(source, target, &omf);
  if (!diff.get())
      return "";

  grt::DictRef options(get_grt());
  grt::StringListRef alter_list(get_grt());
  options.set("OutputContainer", alter_list);
  options.set("UseFilteredLists", grt::IntegerRef(0));
  options.set("KeepOrder", grt::IntegerRef(1));
  grt::ListRef<GrtNamedObject> alter_object_list(get_grt());
  options.set("OutputObjectContainer", alter_object_list);

  generateSQL(source, options, diff);

  int res= makeSQLSyncScript(options, alter_list, alter_object_list);
  if (res)
    return "";

  grt::StringRef script= grt::StringRef::cast_from(options.get("OutputScript"));
  if (!script.is_valid())
      return "";

  return script;
}

std::string DbMySQLImpl::makeAlterScriptForObject(GrtNamedObjectRef source, GrtNamedObjectRef target, GrtNamedObjectRef obj, const grt::DictRef& diff_options)
{
  grt::DbObjectMatchAlterOmf omf;
  omf.dontdiff_mask = 5;

  DictRef options(get_grt());
  DictRef result(get_grt());

  options.set("UseFilteredLists", IntegerRef(0));
  grt::NormalizedComparer normalizer(get_grt(),grt::DictRef::cast_from(diff_options.get("DBSettings",getDefaultTraits())));
  normalizer.init_omf(&omf);
  bool case_sensitive = omf.case_sensitive;
  boost::shared_ptr<DiffChange> diff = diff_make(source, target, &omf);

  std::string sql;
  std::string non_std_sql_delimiter("$$");
  if (ObjectRef::can_wrap(target))
  {
    SqlFacade::Ref sql_facade = SqlFacade::instance_for_rdbms_name(ObjectRef::cast_from(target)->get_grt(), "Mysql");
    Sql_specifics::Ref sql_specifics = sql_facade ->sqlSpecifics();
    non_std_sql_delimiter = sql_specifics->non_std_sql_delimiter();
  }


  if (diff.get())
  {
    ActionGenerateSQL generator = ActionGenerateSQL(result, grt::ListRef<GrtNamedObject>(),get_grt(),grt::DictRef::cast_from(diff_options.get("DBSettings",getDefaultTraits())));
    generator.set_put_if_exists(false);
    DiffSQLGeneratorBE(options, grt::DictRef::cast_from(diff_options.get("DBSettings", getDefaultTraits())), &generator).process_diff_change(source, diff.get(), result);
    std::string objname = get_old_object_name_for_key(obj, omf.case_sensitive);
    ValueRef change = result.get(objname, StringRef(""));
    if (StringRef::can_wrap(change))
    {
      sql = StringRef::cast_from(change);
      if (!sql.empty() && !db_RoutineRef::can_wrap(obj))
        sql.append(";\n");
    }
    else if (StringListRef::can_wrap(change))
    {
      grt::StringListRef list= grt::StringListRef::cast_from(change);
      for (size_t listcount= list.count(), j= 0; j < listcount; j++)
        sql.append(list.get(j)).append(";\n");
    }

    if((obj->name() != obj->oldName()) && !obj->oldName().empty())
    {
      std::string objname = get_full_object_name_for_key(obj,case_sensitive != 0);
      ValueRef new_change = result.get(objname, StringRef(""));
      if (StringRef::can_wrap(new_change))
      {
        std::string obj_ddl = StringRef::cast_from(new_change);
        sql.append(obj_ddl);
        if (!obj_ddl.empty())
          sql.append(";\n");
      }
      else if (StringListRef::can_wrap(new_change))
      {
        grt::StringListRef list= grt::StringListRef::cast_from(new_change);
        for (size_t listcount= list.count(), j= 0; j < listcount; j++)
          sql.append(list.get(j)).append(";\n");
      }
    }

    if (db_TableRef::can_wrap(obj))
    {
      db_mysql_TableRef table = db_mysql_TableRef::cast_from(obj);
      grt::ListRef<db_mysql_Trigger> triggers= table->triggers();
      if (db_CatalogRef::can_wrap(source))
      {
        db_CatalogRef src_cat = db_CatalogRef::cast_from(source);
        for(size_t sz= src_cat->schemata().count(), i= 0; i < sz; i++)
        {
          if(strcmp(src_cat->schemata().get(i)->name().c_str(), table->owner()->name().c_str()) == 0)
          {
            db_SchemaRef schema = src_cat->schemata().get(i);
            for(size_t sz= schema->tables().count(), i= 0; i < sz; i++)
              if(strcmp(schema->tables().get(i)->name().c_str(), table->oldName().c_str()) == 0)
              {
                db_TableRef db_table = db_TableRef::cast_from(schema->tables().get(i));
                grt::ListRef<db_Trigger> db_triggers= db_table->triggers();
                for(size_t c= db_triggers.count(), i= 0; i < c; i++)
                {
                  bool trigger_found = false;
                  for(size_t c1= triggers.count(), j= 0; j < c1; j++)
                    if (db_triggers.get(i)->name() == triggers.get(j)->name())
                    {
                      trigger_found = true;
                      break;
                    }
                    if (!trigger_found)
                    {
                      std::string trigger_code = result.get_string(get_full_object_name_for_key(db_triggers.get(i),case_sensitive != 0), "");
                      if (!trigger_code.empty())
                      {
                        sql.append("USE `").append(db_table->owner()->name()).append("`;\n").append("\nDELIMITER ").append(non_std_sql_delimiter).append("\n\n");
                        sql.append(trigger_code).append(non_std_sql_delimiter).append("\nDELIMITER ;\n");
                      }
                    }
                    
                }
              }
          }
        }

      }
      for(size_t c= triggers.count(), i= 0; i < c; i++)
      {
        std::string trigger_code = result.get_string(get_full_object_name_for_key(triggers.get(i),case_sensitive != 0), "");
        if (!trigger_code.empty())
        {
          sql.append("USE `").append(table->owner()->name()).append("`;\n").append("\nDELIMITER ").append(non_std_sql_delimiter).append("\n\n");
          sql.append("DROP TRIGGER IF EXISTS ").append(table->owner()->name()).append(".");
          sql.append(triggers.get(i)->name()).append(non_std_sql_delimiter).append("\n");
          sql.append(trigger_code).append(non_std_sql_delimiter).append("\nDELIMITER ;\n");
        }
      }

    };
    if (!sql.empty()) 
      if (db_RoutineRef::can_wrap(obj) && ((obj->name() == obj->oldName()) || obj->oldName().empty()) ) 
      { 
        db_mysql_RoutineRef routine = db_mysql_RoutineRef::cast_from(obj); 
        if(routine->routineType().empty()) 
          routine->routineType("procedure"); 
        std::string routine_sql("USE `"); 
        routine_sql.append(routine->owner()->name()).append("`;\n"); 
        routine_sql.append("DROP ").append(routine->routineType().c_str()) 
          .append(" IF EXISTS `").append(routine->name()).append("`;\n"); 
        sql =  routine_sql + sql + std::string("\n"); 
      } 
  }

  return sql;
}


// This function is used from scripts and HTML report generator.
std::string DbMySQLImpl::makeCreateScriptForObject(GrtNamedObjectRef object)
{
  DictRef options(get_grt());
  DictRef result(get_grt());

  ValueRef parent;

  //TODO: check how this list is expected to be used
  //there should be a way to generate one table SQL only not sql for whole doc
//  StringListRef list(get_grt());
//  list.insert(get_old_object_name_for_key(object));

  if (object.is_instance(db_Schema::static_class_name()))
    parent= object->owner();
  else if (object.is_instance(db_Table::static_class_name()))
    parent= object->owner()->owner();
  else if (object.is_instance(db_Trigger::static_class_name()))
    parent= object->owner()->owner()->owner();
  else if (object.is_instance(db_View::static_class_name()))
    parent= object->owner()->owner();
  else if (object.is_instance(db_Routine::static_class_name()))
    parent= object->owner()->owner();
  else if (object.is_instance(db_RoutineGroup::static_class_name()))
    parent= object->owner()->owner();
  else if (object.is_instance(db_User::static_class_name()))
    parent= object->owner();
  else if (object.is_instance(db_Role::static_class_name()))
    parent= object->owner();
  else
    return "";

  options.set("UseFilteredLists", IntegerRef(0));
  default_omf omf;
  grt::NormalizedComparer normalizer(get_grt());
  normalizer.init_omf(&omf);
  boost::shared_ptr<DiffChange> diff = diff_make(ValueRef(), parent, &omf, true); // do a diff without cloning the catalog

  std::string sql;

  if (diff.get())
  {
    ActionGenerateSQL generator = ActionGenerateSQL(result, grt::ListRef<GrtNamedObject>(),get_grt(),getDefaultTraits());
    DiffSQLGeneratorBE(options, grt::DictRef::cast_from(options.get("DBSettings", getDefaultTraits())), &generator).process_diff_change(ValueRef(), diff.get(), result);
    sql= result.get_string(get_full_object_name_for_key(object, omf.case_sensitive), "");
  }

  return sql;
}


db_mgmt_RdbmsRef DbMySQLImpl::initializeDBMSInfo()
{
  bec::GRTManager *grtm(bec::GRTManager::get_instance_for(get_grt()));
  db_mgmt_RdbmsRef rdbms= db_mgmt_RdbmsRef::cast_from(get_grt()->unserialize(make_path(grtm->get_basedir(), "modules/data/mysql_rdbms_info.xml")));

  workbench_WorkbenchRef::cast_from(get_grt()->get("/wb"))->rdbmsMgmt()->rdbms().insert(rdbms);
  return rdbms;
}

grt::StringRef DbMySQLImpl::quoteIdentifier(grt::StringRef ident)
{
  return grt::StringRef(base::sqlstring("!", 0) << *ident);
}


grt::StringRef DbMySQLImpl::fullyQualifiedObjectName(GrtNamedObjectRef object)
{
  GrtNamedObjectRef owner = GrtNamedObjectRef::cast_from(object->owner());
  if (owner.is_valid())
  {
    if (db_SchemaRef::can_wrap(owner))
      return grt::StringRef(base::sqlstring("!.!", 0) << owner->name() << *object->name());
  }
  return grt::StringRef(base::sqlstring("!", 0) << *object->name());
}


grt::ListRef<db_mysql_StorageEngine> DbMySQLImpl::getKnownEngines()
{
  if (!_known_engines.is_valid())
    _known_engines = dbmysql::get_known_engines(this->get_grt());
  return _known_engines;
}

// checks whether the 2nd version number is the same or newer than the 1st one
inline bool match_version(int ref_major, int ref_minor, int ref_revision,
                          int major, int minor, int revision)
{
  if (major > ref_major)
    return true;
  if (minor < ref_minor)
    return false;
  if (minor > ref_minor)
    return true;
  if (revision >= ref_revision)
    return true;
  return false;
}

grt::DictRef DbMySQLImpl::getTraitsForServerVersion(const int major, const int minor, const int revision)
{
  grt::DictRef traits(get_grt());

  if (match_version(5, 5, 3,  major, minor, revision))
  {
    traits.set("maxTableCommentLength", grt::IntegerRef(2048));
    traits.set("maxIndexCommentLength", grt::IntegerRef(1024));
    traits.set("maxColumnCommentLength", grt::IntegerRef(1024));
  }
  else
  {
    traits.set("maxTableCommentLength", grt::IntegerRef(60));
    traits.set("maxIndexCommentLength", grt::IntegerRef(0));
    traits.set("maxColumnCommentLength", grt::IntegerRef(255));
  }
  
  return traits;
}

grt::DictRef DbMySQLImpl::getTraitsFromServerVariables(const grt::DictRef &variables)
{
  int major = 0, minor = 0, revision = 0;
  std::string version;
  if (variables.has_key("version"))
    version = variables.get_string("version");
  
  sscanf(version.c_str(), "%i.%i.%i", &major, &minor, &revision);
  return getTraitsForServerVersion(major, minor, revision);
}

grt::ListRef<db_UserDatatype> DbMySQLImpl::getDefaultUserDatatypes(db_mgmt_RdbmsRef rdbms)
{
  static struct
  {
    const char *oid;
    const char *name;
    const char *sql_def;
  }
  type_init_data[] = {
    // These are type aliases, not UDTs
    {"com.mysql.rdbms.mysql.userdatatype.bool", "BOOL", "TINYINT(1)"},
    {"com.mysql.rdbms.mysql.userdatatype.boolean", "BOOLEAN", "TINYINT(1)"},
    {"com.mysql.rdbms.mysql.userdatatype.fixed", "FIXED", "DECIMAL(10,0)"},
    {"com.mysql.rdbms.mysql.userdatatype.float4", "FLOAT4", "FLOAT"},
    {"com.mysql.rdbms.mysql.userdatatype.float8", "FLOAT8", "DOUBLE"},
    {"com.mysql.rdbms.mysql.userdatatype.int1", "INT1", "TINYINT(4)"},
    {"com.mysql.rdbms.mysql.userdatatype.int2", "INT2", "SMALLINT(6)"},
    {"com.mysql.rdbms.mysql.userdatatype.int3", "INT3", "MEDIUMINT(9)"},
    {"com.mysql.rdbms.mysql.userdatatype.int4", "INT4", "INT(11)"},
    {"com.mysql.rdbms.mysql.userdatatype.int8", "INT8", "BIGINT(20)"},
    {"com.mysql.rdbms.mysql.userdatatype.integer", "INTEGER", "INT(11)"},
    {"com.mysql.rdbms.mysql.userdatatype.longvarbinary", "LONG VARBINARY", "MEDIUMBLOB"},
    {"com.mysql.rdbms.mysql.userdatatype.longvarchar", "LONG VARCHAR", "MEDIUMTEXT"},
    {"com.mysql.rdbms.mysql.userdatatype.long", "LONG", "MEDIUMTEXT"},
    {"com.mysql.rdbms.mysql.userdatatype.middleint", "MIDDLEINT", "MEDIUMINT(9)"},
    {"com.mysql.rdbms.mysql.userdatatype.numeric", "NUMERIC", "DECIMAL(10,0)"},
    {"com.mysql.rdbms.mysql.userdatatype.dec", "DEC", "DECIMAL(10,0)"},
    {"com.mysql.rdbms.mysql.userdatatype.character", "CHARACTER", "CHAR(1)"}
    // End type aliases
  };
  
  grt::ListRef<db_UserDatatype> list(get_grt());
  
  for (size_t i= 0; i < sizeof(type_init_data)/sizeof(*type_init_data); i++)
  {
    std::string type= type_init_data[i].sql_def;
    std::string::size_type paren= type.find('(');
    if (paren != std::string::npos)
      type= type.substr(0, paren);
    
    db_SimpleDatatypeRef simpletype(bec::CatalogHelper::get_datatype(rdbms->simpleDatatypes(), 
                                                                     type));
    
    if (!simpletype.is_valid()) // unlikely
    {
      g_warning("Could not define built-in userdatatype <%s> %s (%s)", 
                type_init_data[i].oid, type_init_data[i].name, type_init_data[i].sql_def);
      continue;
    }
    db_UserDatatypeRef udata(get_grt());
    
    udata->__set_id(type_init_data[i].oid);

    udata->name(type_init_data[i].name);
    udata->sqlDefinition(type_init_data[i].sql_def);
    udata->actualType(simpletype);
    
    list.insert(udata);
  }
  
  return list;
}


GRT_MODULE_ENTRY_POINT(DbMySQLImpl);
