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

#include "diff/diffchange.h"
#include "diff/grtdiff.h"
#include "diff/changeobjects.h"
#include "diff/changelistobjects.h"

#include "db_mysql_diffsqlgen_grant.h"

#include "grt/common.h"

#include <algorithm>
#include <ctype.h>

#include "module_db_mysql.h"
#include "module_db_mysql_shared_code.h"

void DiffSQLGeneratorBE::remember(const GrtNamedObjectRef &obj, const std::string &sql)
{
  if(target_list.is_valid())
  {
    target_list.insert(grt::StringRef(sql));
    if(target_object_list.is_valid())
      target_object_list.insert(obj);
  }
  else
  {
    target_map.set(_use_oid_as_dict_key ? obj.id() : get_full_object_name_for_key(obj,_case_sensitive), 
                   grt::StringRef(sql));
  }
}

void DiffSQLGeneratorBE::remember_alter(const GrtNamedObjectRef &obj, const std::string &sql)
{
  if(target_list.is_valid())
  {
    target_list.insert(grt::StringRef(sql));
    if(target_object_list.is_valid())
      target_object_list.insert(obj);
    return;
  }

  std::string key= _use_oid_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
      assert(0);
    }
  }
  else
  {
    target_map.set(key, grt::StringRef(sql));
  }
}


void DiffSQLGeneratorBE::generate_set_partitioning(db_mysql_TableRef table, 
                                                   const grt::DiffChange *table_diffchange)
{
  bool part_type_set= false, part_expr_set= false, 
    subpart_type_set= false, subpart_expr_set= false,
    part_count_set= false, part_defs_set= false;
  std::string part_type, part_expr, subpart_type, subpart_expr;
  grt::ListRef<db_mysql_PartitionDefinition> part_defs(table->get_grt());
  ssize_t part_count = 0;

  // gather all relevant attributes from change object
  const grt::ChangeSet *cs= table_diffchange->subchanges();
  for(grt::ChangeSet::const_iterator e= cs->end(), it= cs->begin(); it != e; it++)
  {
    const grt::ObjectAttrModifiedChange *attr_change= static_cast<const grt::ObjectAttrModifiedChange *>(it->get());

    if(attr_change->get_attr_name().compare("partitionType") == 0)
    {
      const grt::SimpleValueChange *change= 
        static_cast<const grt::SimpleValueChange *>(attr_change->get_subchange().get());
      part_type.assign(grt::StringRef::cast_from(change->get_new_value()));
      part_type_set= true;
    }
    else if(attr_change->get_attr_name().compare("partitionExpression") == 0)
    {
      const grt::SimpleValueChange *change= 
        static_cast<const grt::SimpleValueChange *>(attr_change->get_subchange().get());
      part_expr.assign(grt::StringRef::cast_from(change->get_new_value()));
      part_expr_set= true;
    }
    else if(attr_change->get_attr_name().compare("subpartitionType") == 0)
    {
      const grt::SimpleValueChange *change= 
        static_cast<const grt::SimpleValueChange *>(attr_change->get_subchange().get());
      subpart_type.assign(grt::StringRef::cast_from(change->get_new_value()));
      subpart_type_set= true;
    }
    else if(attr_change->get_attr_name().compare("subpartitionExpression") == 0)
    {
      const grt::SimpleValueChange *change= 
        static_cast<const grt::SimpleValueChange *>(attr_change->get_subchange().get());
      subpart_expr.assign(grt::StringRef::cast_from(change->get_new_value()));
      subpart_expr_set= true;
    }
    else if(attr_change->get_attr_name().compare("partitionCount") == 0)
    {
      const grt::SimpleValueChange *change= 
        static_cast<const grt::SimpleValueChange *>(attr_change->get_subchange().get());
      part_count= grt::IntegerRef::cast_from(change->get_new_value());
      part_count_set= true;
    }
    else if(attr_change->get_attr_name().compare("partitionDefinitions") == 0)
    {
      const grt::MultiChange *change= 
        static_cast<const grt::MultiChange *>(attr_change->get_subchange().get());
      const grt::ChangeSet *part_defs_cs= change->subchanges();

      for(grt::ChangeSet::const_iterator part_defs_e= part_defs_cs->end(), 
        part_defs_it= part_defs_cs->begin(); part_defs_it != part_defs_e; part_defs_it++)
      {
        grt::ValueRef part_def_grt_value;

        const boost::shared_ptr<grt::DiffChange> part_def_change= *part_defs_it;
        if(part_def_change->get_change_type() == grt::ListItemAdded)
        {
          const grt::ListItemAddedChange *added_change= 
              static_cast<const grt::ListItemAddedChange *>(part_def_change.get());

          part_def_grt_value= added_change->get_value();
        }
        else if(part_def_change->get_change_type() == grt::ListItemModified)
        {
          const grt::ListItemModifiedChange *modified_change= 
            static_cast<const grt::ListItemModifiedChange  *>(part_def_change.get());

          part_def_grt_value= modified_change->get_new_value();
        }

        db_mysql_PartitionDefinitionRef part_def_value(db_mysql_PartitionDefinitionRef::cast_from(part_def_grt_value));

        if (part_def_value.is_valid())
            part_defs.insert(part_def_value);
      }
      part_defs_set= true;
    }
  }

  if(!part_type_set)
    part_type.assign(table->partitionType().c_str());
    
  if(!part_expr_set)
    part_expr.assign(table->partitionExpression().c_str());

  if(!subpart_type_set)
    subpart_type.assign(table->subpartitionType().c_str());

  if(!subpart_expr_set)
    subpart_expr.assign(table->subpartitionExpression().c_str());

  if(!part_count_set)
    part_count= table->partitionCount();

  if(!part_defs_set)
    part_defs= table->partitionDefinitions();

  callback->alter_table_generate_partitioning(table, part_type, part_expr, (int)part_count,
                        subpart_type, subpart_expr, part_defs);
}

void DiffSQLGeneratorBE::generate_create_partitioning(db_mysql_TableRef table)
{
  callback->alter_table_generate_partitioning(table, 
    std::string(table->partitionType().is_valid() ? table->partitionType().c_str() : ""),
    std::string(table->partitionExpression().is_valid() ? table->partitionExpression().c_str() : ""),
    (int)table->partitionCount(),
    std::string(table->subpartitionType().is_valid() ? table->subpartitionType().c_str() : ""),
    std::string(table->subpartitionExpression().is_valid() ? table->subpartitionExpression().c_str() : ""),
    table->partitionDefinitions()
    );
}

void DiffSQLGeneratorBE::generate_create_stmt(db_mysql_TableRef table)
{
  if (table->isStub())
    return;

  bool process_table= true;
  std::string table_name_for_filter(get_old_object_name_for_key(table, _case_sensitive));
  if(_use_filtered_lists)
    if(_filtered_tables.find(table_name_for_filter) == _filtered_tables.end())
      process_table= false;

  if(process_table)
  {
    callback->create_table_props_begin(table);

    // note: for TIMESTAMP columns 'ON UPDATE' are stored in defaultValue field
    // see http://dev.mysql.com/doc/refman/5.1/en/timestamp.html for more info

    callback->create_table_columns_begin(table);

    grt::ListRef<db_mysql_Column> columns= table->columns();
    for(size_t columns_count= columns.count(), i= 0; i < columns_count; i++)
    {
      db_mysql_ColumnRef column= columns.get(i);
      callback->create_table_column(column);
    }

    callback->create_table_columns_end(table);

    // indices processing

    callback->create_table_indexes_begin(table);

    std::set<db_IndexRef> fk_indexes;
    std::set<db_IndexRef> fk_create_indexes;
    std::set<db_IndexRef> fk_skip_indexes;
    for (size_t fk_count = table->foreignKeys().count(), i= 0; i < fk_count; i++)
    {
      fk_indexes.insert(table->foreignKeys()[i]->index());
      (table->foreignKeys()[i]->modelOnly()?fk_skip_indexes:fk_create_indexes).insert(table->foreignKeys()[i]->index());
    }

    grt::ListRef<db_mysql_Index> indices= table->indices();
    for(size_t index_count= indices.count(), i= 0; i < index_count; i++)
    {
      db_mysql_IndexRef index= indices.get(i);

      if (index->isPrimary())
      {
        callback->create_table_index(index, false);
        continue;      
      }

      if (_skip_fk_indexes && (strcasecmp(index->indexType().c_str(), "FOREIGN") == 0
                                 || fk_indexes.find(index) != fk_indexes.end()))
        continue;

      if ((fk_skip_indexes.find(index) != fk_skip_indexes.end())&&(fk_create_indexes.find(index) == fk_create_indexes.end()))
        continue;
      callback->create_table_index(index, _gen_create_index);
    }

    callback->create_table_indexes_end(table);

    callback->create_table_fks_begin(table);

    if (!_skip_foreign_keys)
    {

      grt::ListRef<db_mysql_ForeignKey> fks= table->foreignKeys();
      for(size_t fk_count= fks.count(), i= 0; i < fk_count; i++)
      {
        db_mysql_ForeignKeyRef fk= fks.get(i);
        if (fk.is_valid() && fk->referencedTable().is_valid())
        {
          if (fk->modelOnly() || fk->referencedTable()->modelOnly())
            continue;

          callback->create_table_fk(fk);
        }
      }

    }
    callback->create_table_fks_end(table);

    // TODO: [TABLESPACE tablespace_name STORAGE DISK]

    if(strlen(table->tableEngine().c_str()))
      callback->create_table_engine(table->tableEngine());

    if(strlen(table->nextAutoInc().c_str()))
      callback->create_table_next_auto_inc(table->nextAutoInc());

    if(strlen(table->avgRowLength().c_str()))
      callback->create_table_avg_row_length(table->avgRowLength());

    if(table->checksum() != 0)
      callback->create_table_checksum(table->checksum());

    // Set a collation only if it differs from the default collation for that charset.
    // Ignore the collation if it doesn't match the charset, however.
    if (table->defaultCharacterSetName().is_valid())
    {
      std::string charset = table->defaultCharacterSetName();
      if (!charset.empty())
      {
        callback->create_table_charset(charset);

        if (table->defaultCollationName().is_valid())
        {
          std::string collation = table->defaultCollationName();
          if (!collation.empty()
            && (charsetForCollation(collation) == charset)
            && (defaultCollationForCharset(table->defaultCharacterSetName()) != collation))
            callback->create_table_collate(collation);
        }
      }
    }

    if (strlen(table->comment().c_str()))
      callback->create_table_comment(table->comment());
    
    // CONNECTION [=] 'connect_string'

    if(strlen(table->tableDataDir().c_str()))
      callback->create_table_data_dir(table->tableDataDir());

    if(table->delayKeyWrite() != 0)
      callback->create_table_delay_key_write(table->delayKeyWrite());

    if(strlen(table->tableIndexDir().c_str()))
      callback->create_table_index_dir(table->tableIndexDir());

    if(strlen(table->mergeInsert().c_str()))
      callback->create_table_merge_insert(table->mergeInsert());

    // KEY_BLOCK_SIZE [=] value

    if(strlen(table->maxRows().c_str()))
      callback->create_table_max_rows(table->maxRows());

    if(strlen(table->minRows().c_str()))
      callback->create_table_min_rows(table->minRows());

    if(strlen(table->packKeys().c_str()))
      callback->create_table_pack_keys(table->packKeys());

    if(strlen(table->password().c_str()))
      callback->create_table_password(table->password());

    if(strlen(table->rowFormat().c_str()))
      callback->create_table_row_format(table->rowFormat());

    if(strlen(table->keyBlockSize().c_str()))
      callback->create_table_key_block_size(table->keyBlockSize());

    if(strlen(table->mergeUnion().c_str()))
      callback->create_table_merge_union(table->mergeUnion());

    if(table->partitionType().is_valid() && strlen(table->partitionType().c_str())
    && table->partitionExpression().is_valid() /*&& strlen(table->partitionExpression().c_str())*/)
    {
      generate_create_partitioning(table);
    }

    callback->create_table_props_end(table);
  } // process_table

  grt::ListRef<db_mysql_Trigger> triggers= table->triggers();
  for(size_t c= triggers.count(), i= 0; i < c; i++)
  {
    db_mysql_TriggerRef trigger= triggers.get(i);
    generate_create_stmt(trigger);
  }
}

void DiffSQLGeneratorBE::generate_create_stmt(db_mysql_ViewRef view)
{
  std::string view_name_for_filter(get_old_object_name_for_key(view, _case_sensitive));
  if(_use_filtered_lists)
    if(_filtered_views.find(view_name_for_filter) == _filtered_views.end())
      return;

  callback->create_view(view);
}

void DiffSQLGeneratorBE::generate_create_stmt(db_mysql_RoutineRef routine, bool for_alter)
{
  std::string routine_name_for_filter(get_old_object_name_for_key(routine, _case_sensitive));
  if(_use_filtered_lists)
    if(_filtered_routines.find(routine_name_for_filter) == _filtered_routines.end())
      return;

  callback->create_routine(routine, for_alter);
}

void DiffSQLGeneratorBE::generate_create_stmt(db_mysql_TriggerRef trigger, bool for_alter)
{
  std::string trigger_name_for_filter(get_old_object_name_for_key(trigger, _case_sensitive));

  if(_use_filtered_lists)
    if(_filtered_triggers.find(trigger_name_for_filter) == _filtered_triggers.end())
      return;

  callback->create_trigger(trigger, for_alter);
}

void DiffSQLGeneratorBE::generate_create_stmt(db_mysql_SchemaRef schema)
{
  std::string schema_name_for_filter(get_old_object_name_for_key(schema, _case_sensitive));

  if(_use_filtered_lists)
    if(_filtered_schemata.find(schema_name_for_filter) == _filtered_schemata.end())
      return;

  callback->create_schema(schema);

  grt::ListRef<db_mysql_Table> tables= schema->tables();
  for(size_t count= tables.count(), i= 0; i < count; i++)
  {
    db_mysql_TableRef table= tables.get(i);
    generate_create_stmt(table);
  }

  grt::ListRef<db_mysql_View> views= schema->views();
  for(size_t count= views.count(), i= 0; i < count; i++)
  {
    db_mysql_ViewRef view= views.get(i);
    generate_create_stmt(view);
  }

  grt::ListRef<db_mysql_Routine> routines= schema->routines();
  for(size_t count= routines.count(), i= 0; i < count; i++)
  {
    db_mysql_RoutineRef routine= routines.get(i);
    generate_create_stmt(routine);
  }
}

void DiffSQLGeneratorBE::generate_create_stmt(db_UserRef user)
{
  std::string user_name_for_filter(get_old_object_name_for_key(user, _case_sensitive));

  if(_use_filtered_lists)
    if(_filtered_users.find(user_name_for_filter) == _filtered_users.end())
      return;

  callback->create_user(user);
}

void DiffSQLGeneratorBE::generate_create_stmt(db_mysql_CatalogRef catalog)
{
  grt::ListRef<db_mysql_Schema> schemata= catalog->schemata();
  for(size_t count= schemata.count(), i= 0; i < count; i++)
  {
    db_mysql_SchemaRef schema= schemata.get(i);
    generate_create_stmt(schema);
  }

  for(size_t count= catalog->users().count(), i= 0; i < count; i++)
  {
    db_UserRef user= catalog->users().get(i);
    generate_create_stmt(user);
  }
}

void DiffSQLGeneratorBE::generate_drop_stmt(db_mysql_TableRef table)
{
  if (table->isStub())
    return;

  std::string table_name_for_filter(get_old_object_name_for_key(table, _case_sensitive));
  if (!(_use_filtered_lists && (_filtered_tables.find(table_name_for_filter) == _filtered_tables.end())))
    callback->drop_table(table);

  grt::ListRef<db_mysql_Trigger> triggers= table->triggers();
  for(size_t c= triggers.count(), i= 0; i < c; i++)
  {
    db_mysql_TriggerRef trigger= triggers.get(i);
    generate_drop_stmt(trigger);
  }
}

void DiffSQLGeneratorBE::generate_drop_stmt(db_mysql_TriggerRef trigger, bool for_alter)
{
  std::string trigger_name_for_filter(get_old_object_name_for_key(trigger, _case_sensitive));
  if(_use_filtered_lists)
    if(_filtered_triggers.find(trigger_name_for_filter) == _filtered_triggers.end())
      return;

  callback->drop_trigger(trigger, for_alter);
}

void DiffSQLGeneratorBE::generate_drop_stmt(db_mysql_ViewRef view) 
{ 
  std::string view_name_for_filter(get_old_object_name_for_key(view, _case_sensitive)); 
  if(_use_filtered_lists) 
    if(_filtered_views.find(view_name_for_filter) == _filtered_views.end()) 
      return; 
 
  callback->drop_view(view); 
} 
 
void DiffSQLGeneratorBE::generate_drop_stmt(db_mysql_RoutineRef routine, bool for_alter) 
{ 
  std::string routine_name_for_filter(get_old_object_name_for_key(routine, _case_sensitive)); 
  if(_use_filtered_lists) 
    if(_filtered_routines.find(routine_name_for_filter) == _filtered_routines.end()) 
      return; 
 
  callback->drop_routine(routine, for_alter); 
} 

void DiffSQLGeneratorBE::generate_drop_stmt(db_UserRef user)
{
  callback->drop_user(user);
}

void DiffSQLGeneratorBE::generate_drop_stmt(db_mysql_SchemaRef schema)
{
  callback->drop_schema(schema);
  callback->disable_list_insert(true);
  grt::ListRef<db_mysql_Table> tables= schema->tables();
  for(size_t count= tables.count(), i= 0; i < count; i++)
  {
    db_mysql_TableRef table= tables.get(i);
    generate_drop_stmt(table);
  }

  grt::ListRef<db_mysql_View> views= schema->views();
  for(size_t count= views.count(), i= 0; i < count; i++)
  {
    db_mysql_ViewRef view= views.get(i);
    generate_drop_stmt(view);
  }

  grt::ListRef<db_mysql_Routine> routines= schema->routines();
  for(size_t count= routines.count(), i= 0; i < count; i++)
  {
    db_mysql_RoutineRef routine= routines.get(i);
    generate_drop_stmt(routine);
  }
  callback->disable_list_insert(false);

}

void DiffSQLGeneratorBE::generate_drop_stmt(db_mysql_CatalogRef catalog)
{
  grt::ListRef<db_mysql_Schema> schemata= catalog->schemata();
  for(size_t count= schemata.count(), i= 0; i < count; i++)
  {
    db_mysql_SchemaRef schema= schemata.get(i);
    generate_drop_stmt(schema);
  }

  for(size_t count= catalog->users().count(), i= 0; i < count; i++)
  {
    db_UserRef user= catalog->users().get(i);
    generate_drop_stmt(user);
  }
}

void DiffSQLGeneratorBE::generate_alter(grt::ListRef<db_mysql_Column> columns, const grt::MultiChange *diffchange)
{
  const grt::ChangeSet *columns_cs= diffchange->subchanges();

  if(columns.count() == 0)
    return;

  db_mysql_TableRef table= db_mysql_TableRef::cast_from(columns.get(0)->owner());

  callback->alter_table_columns_begin(table);

  Column_rename_map column_rename_map;

  // build a map of column renames
  // process CHANGE COLUMN (handles content change only)
  for(grt::ChangeSet::const_iterator e= columns_cs->end(), it= columns_cs->begin(); it != e; it++)
  {
    const boost::shared_ptr<grt::DiffChange> column_change= *it;

    if(column_change->get_change_type() == grt::ListItemModified)
    {
      const grt::ListItemModifiedChange *modified_change= static_cast<const grt::ListItemModifiedChange *>(column_change.get());
      db_mysql_ColumnRef column= db_mysql_ColumnRef::cast_from(grt::ValueRef(modified_change->get_old_value()));
      if(strcmp(column->name().c_str(), column->oldName().c_str())!=0)
        column_rename_map[std::string(column->oldName().c_str())]= std::string(column->name().c_str());
    }
    else if(column_change->get_change_type() == grt::ListItemOrderChanged)
    {
      const grt::ListItemOrderChange *order_change= static_cast<const grt::ListItemOrderChange *>(column_change.get());
      db_mysql_ColumnRef org_col= db_mysql_ColumnRef::cast_from(grt::ValueRef(order_change->get_old_value()));
      db_mysql_ColumnRef mod_col= db_mysql_ColumnRef::cast_from(grt::ValueRef(order_change->get_new_value()));
      if(strcmp(org_col->name().c_str(), mod_col->oldName().c_str())!=0)
        column_rename_map[std::string(org_col->oldName().c_str())]= std::string(org_col->name().c_str());
    }
  }

  // process DROP COLUMN
  for(grt::ChangeSet::const_iterator e= columns_cs->end(), it= columns_cs->begin(); it != e; it++)
  {
    const boost::shared_ptr<grt::DiffChange> column_change= *it;
    if(column_change->get_change_type() != grt::ListItemRemoved)
      continue;

    const grt::ListItemRemovedChange *removed_change= static_cast<const grt::ListItemRemovedChange *>(column_change.get());
    callback->alter_table_drop_column(table, db_mysql_ColumnRef::cast_from(removed_change->get_value()));
  }

  std::set<grt::ValueRef> processed_items;

  // process CHANGE COLUMN (handles both position and content change)
  for(grt::ChangeSet::const_iterator e= columns_cs->end(), it= columns_cs->begin(); it != e; it++)
  {
    const boost::shared_ptr<grt::DiffChange> column_change= *it;

    if(column_change->get_change_type() != grt::ListItemOrderChanged)
      continue;

    const grt::ListItemOrderChange *order_change= 
      static_cast<const grt::ListItemOrderChange *>(column_change.get());
    // index_pair.second is the new position
    callback->alter_table_change_column(
      table,
      db_mysql_ColumnRef::cast_from(grt::ValueRef(order_change->get_old_value())),
      db_mysql_ColumnRef::cast_from(grt::ValueRef(order_change->get_new_value())),
//      v == NULL ? db_mysql_ColumnRef() : db_mysql_ColumnRef::cast_from(grt::ValueRef(*v)),
      db_mysql_ColumnRef::cast_from(order_change->get_prev_item()),
      false,
      column_rename_map
    );
  }

  // process CHANGE COLUMN (handles content change only)
  for(grt::ChangeSet::const_iterator e= columns_cs->end(), it= columns_cs->begin(); it != e; it++)
  {
    const boost::shared_ptr<grt::DiffChange> column_change= *it;

    if(column_change->get_change_type() != grt::ListItemModified)
      continue;
  
    const grt::ListItemModifiedChange *modified_change= static_cast<const grt::ListItemModifiedChange *>(column_change.get());

    callback->alter_table_change_column(
      table,
      db_mysql_ColumnRef::cast_from(grt::ValueRef(modified_change->get_new_value())),
      db_mysql_ColumnRef(),
      db_mysql_ColumnRef(),
      true,
      column_rename_map
    );
  }

    // process ADD COLUMN
  for(grt::ChangeSet::const_iterator e= columns_cs->end(), it= columns_cs->begin(); it != e; it++)
  {
    const boost::shared_ptr<grt::DiffChange> column_change= *it;
    if(column_change->get_change_type() != grt::ListItemAdded)
      continue;

    const grt::ListItemAddedChange *added_change= static_cast<const grt::ListItemAddedChange *>(column_change.get());

    callback->alter_table_add_column(
      table, 
      column_rename_map,
      db_mysql_ColumnRef::cast_from(grt::ValueRef(added_change->get_value())),
      db_mysql_ColumnRef::cast_from(added_change->get_prev_item()));
  }

  callback->alter_table_columns_end(table);
}

void DiffSQLGeneratorBE::generate_alter(grt::ListRef<db_mysql_Index> indices, const grt::MultiChange *diffchange)
{
  const grt::ChangeSet *indices_cs= diffchange->subchanges();

  for(grt::ChangeSet::const_iterator e= indices_cs->end(), it= indices_cs->begin(); it != e; it++)
  {
    const boost::shared_ptr<grt::DiffChange> index_change= *it;
    
    switch(index_change->get_change_type())
    {
    case grt::ListItemAdded:  // ADD INDEX
      {
        const grt::ListItemAddedChange *added_change= static_cast<const grt::ListItemAddedChange *>(index_change.get());
        callback->alter_table_add_index(db_mysql_IndexRef::cast_from(added_change->get_value()));
      }
      break;
    case grt::ListItemRemoved:  // DROP INDEX
      {
        const grt::ListItemRemovedChange *removed_change= static_cast<const grt::ListItemRemovedChange *>(index_change.get());
        callback->alter_table_drop_index(db_mysql_IndexRef::cast_from(removed_change->get_value()));
      }
      break;
    case grt::ListItemOrderChanged: // DROP/ADD INDEX
      {
        const grt::ListItemOrderChange *order_change= static_cast<const grt::ListItemOrderChange *>(index_change.get());
        //Only position changed nothing to do
        if (!order_change->get_subchange())
          break;
        callback->alter_table_drop_index(db_mysql_IndexRef::cast_from(order_change->get_old_value()));
        callback->alter_table_add_index(db_mysql_IndexRef::cast_from(order_change->get_new_value()));
      }
      break;

    case grt::ListItemModified: 
      {
        const grt::ListItemModifiedChange *modified_change= static_cast<const grt::ListItemModifiedChange *>(index_change.get());
        callback->alter_table_drop_index(db_mysql_IndexRef::cast_from(modified_change->get_new_value()));
        callback->alter_table_add_index(db_mysql_IndexRef::cast_from(modified_change->get_new_value()));
      }
      break;
      default: break;
    }
  }
}

void DiffSQLGeneratorBE::generate_alter_drop(
  grt::ListRef<db_mysql_ForeignKey> fks, const grt::MultiChange *diffchange)
{
/*
  You cannot add a foreign key and drop a foreign key in separate 
  clauses of a single ALTER TABLE  statement. You must use separate 
  statements.
*/

  const grt::ChangeSet *fks_cs= diffchange->subchanges();

  for(grt::ChangeSet::const_iterator e= fks_cs->end(), it= fks_cs->begin(); it != e; it++)
  {
    const boost::shared_ptr<grt::DiffChange> fk_change= *it;
    
    db_mysql_ForeignKeyRef fk1;
    switch(fk_change->get_change_type())
    {
    case grt::ListItemRemoved:  // DROP FK
      {
        const grt::ListItemRemovedChange *removed_change= static_cast<const grt::ListItemRemovedChange *>(fk_change.get());
        fk1= db_mysql_ForeignKeyRef::cast_from(removed_change->get_value());
      }
      break;
    case grt::ListItemModified: 
      {
        const grt::ListItemModifiedChange *modified_change= static_cast<const grt::ListItemModifiedChange *>(fk_change.get());
        fk1= db_mysql_ForeignKeyRef::cast_from(modified_change->get_old_value());
      }
      break;
    default:
      break;
    }
    if (fk1.is_valid() && (fk1->modelOnly() || !fk1->referencedTable().is_valid() || fk1->referencedTable()->modelOnly()))
      continue;

    switch(fk_change->get_change_type())
    {
    case grt::ListItemRemoved:  // DROP FK
      {
        const grt::ListItemRemovedChange *removed_change= static_cast<const grt::ListItemRemovedChange *>(fk_change.get());
        callback->alter_table_drop_fk(db_mysql_ForeignKeyRef::cast_from(removed_change->get_value()));
      }
      break;
    case grt::ListItemModified: 
      {
        const grt::ListItemModifiedChange *modified_change= static_cast<const grt::ListItemModifiedChange *>(fk_change.get());
        callback->alter_table_drop_fk(db_mysql_ForeignKeyRef::cast_from(modified_change->get_old_value()));
      }
      break;
      default: break;
    }
  }
}

void DiffSQLGeneratorBE::generate_alter(
  grt::ListRef<db_mysql_ForeignKey> fks, const grt::MultiChange *diffchange)
{
/*
  You cannot add a foreign key and drop a foreign key in separate 
  clauses of a single ALTER TABLE  statement. You must use separate 
  statements.
*/

  const grt::ChangeSet *fks_cs= diffchange->subchanges();

  for(grt::ChangeSet::const_iterator e= fks_cs->end(), it= fks_cs->begin(); it != e; it++)
  {
    const boost::shared_ptr<grt::DiffChange> fk_change= *it;
    
    db_mysql_ForeignKeyRef fk1, fk2;
    switch(fk_change->get_change_type())
    {
    case grt::ListItemAdded:  // ADD FK
      {
        const grt::ListItemAddedChange *added_change= static_cast<const grt::ListItemAddedChange *>(fk_change.get());
        fk1= db_mysql_ForeignKeyRef::cast_from(added_change->get_value());
      }
      break;
    case grt::ListItemRemoved:  // DROP FK
      {
        const grt::ListItemRemovedChange *removed_change= static_cast<const grt::ListItemRemovedChange *>(fk_change.get());
        fk1= db_mysql_ForeignKeyRef::cast_from(removed_change->get_value());
      }
      break;
    case grt::ListItemOrderChanged: // DROP/ADD FK
      {
        const grt::ListItemOrderChange *order_change= static_cast<const grt::ListItemOrderChange *>(fk_change.get());
        //Only position changed nothing to do
        if (!order_change->get_subchange())
          break;
        fk1= db_mysql_ForeignKeyRef::cast_from(order_change->get_old_value());
        fk2= db_mysql_ForeignKeyRef::cast_from(order_change->get_new_value());
      }
      break;
    case grt::ListItemModified: 
      {
        const grt::ListItemModifiedChange *modified_change= static_cast<const grt::ListItemModifiedChange *>(fk_change.get());
        fk1= db_mysql_ForeignKeyRef::cast_from(modified_change->get_old_value());
        fk2= db_mysql_ForeignKeyRef::cast_from(modified_change->get_new_value());
      }
      break;
    default:
      break;
    }
    if (fk1.is_valid() && (fk1->modelOnly() || !fk1->referencedTable().is_valid() || fk1->referencedTable()->modelOnly()))
      continue;
    if (fk2.is_valid() && (fk2->modelOnly() || !fk2->referencedTable().is_valid() || fk2->referencedTable()->modelOnly()))
      continue;

    switch(fk_change->get_change_type())
    {
    case grt::ListItemAdded:  // ADD FK
      {
        const grt::ListItemAddedChange *added_change= static_cast<const grt::ListItemAddedChange *>(fk_change.get());
        callback->alter_table_add_fk(db_mysql_ForeignKeyRef::cast_from(added_change->get_value()));
      }
      break;
    case grt::ListItemRemoved:  // DROP FK
/*      {
        const grt::ListItemRemovedChange *removed_change= static_cast<const grt::ListItemRemovedChange *>(fk_change);
        callback->alter_table_drop_fk(db_mysql_ForeignKeyRef::cast_from(removed_change->get_old_value()));
      }*/
      break;
    case grt::ListItemOrderChanged: // DROP/ADD FK
      {
        const grt::ListItemOrderChange *order_change= static_cast<const grt::ListItemOrderChange *>(fk_change.get());
        //Only position changed nothing to do
        if (!order_change->get_subchange())
          break;
        callback->alter_table_drop_fk(db_mysql_ForeignKeyRef::cast_from(order_change->get_old_value()));
        callback->alter_table_add_fk(db_mysql_ForeignKeyRef::cast_from(order_change->get_new_value()));
      }
      break;

    case grt::ListItemModified: 
      {
        const grt::ListItemModifiedChange *modified_change= static_cast<const grt::ListItemModifiedChange *>(fk_change.get());
//        callback->alter_table_drop_fk(db_mysql_ForeignKeyRef::cast_from(modified_change->get_old_value()));
        callback->alter_table_add_fk(db_mysql_ForeignKeyRef::cast_from(modified_change->get_new_value()));
      }
      break;
      default: break;
    }
  }
}

void DiffSQLGeneratorBE::generate_alter_stmt_drops(db_mysql_TableRef table, const grt::DiffChange *diffchange)
{
  if (table->isStub())
    return;

  bool process_table= true;

  std::string table_name_for_filter(get_old_object_name_for_key(table, _case_sensitive));
  if(_use_filtered_lists)
    if(_filtered_tables.find(table_name_for_filter) == _filtered_tables.end())
      process_table= false;
  if(!process_table)
    return;
  const grt::ChangeSet* cs= diffchange->subchanges();
  bool gen_alter = false;

  for(grt::ChangeSet::const_iterator e= cs->end(), it= cs->begin(); it != e; it++)
  {
    const grt::ObjectAttrModifiedChange *attr_change= 
        static_cast<const grt::ObjectAttrModifiedChange *>(it->get());

    if(attr_change->get_attr_name().compare("foreignKeys") == 0)
    {
      const grt::MultiChange *list_change= 
        static_cast<const grt::MultiChange *>(attr_change->get_subchange().get());
      if (!gen_alter)
      {
          gen_alter = true;
          callback->alter_table_props_begin(table);      
      }

      callback->alter_table_fks_begin(table);
      generate_alter_drop(table->foreignKeys(), list_change);
      callback->alter_table_fks_end(table);
    }
  }
  if (gen_alter)
    callback->alter_table_props_end(table);

}

void DiffSQLGeneratorBE::generate_alter_stmt(db_mysql_TableRef table, const grt::DiffChange *diffchange, AlterTableFlags alter_table_flags)
{
  if (table->isStub())
    return;

  const grt::ChangeSet *cs= diffchange->subchanges();

  if ((alter_table_flags & EverythingButForeignKeys))
  {
    // process table triggers
    for(grt::ChangeSet::const_iterator e= cs->end(), it= cs->begin(); it != e; it++)
    {
      const grt::ObjectAttrModifiedChange *attr_change= 
        static_cast<const grt::ObjectAttrModifiedChange *>(it->get());

      if(attr_change->get_attr_name().compare("triggers") == 0)
      {
        const grt::MultiChange *list_change= 
          static_cast<const grt::MultiChange *>(attr_change->get_subchange().get());
        
        const grt::ChangeSet *triggers_cs= list_change->subchanges();
        
        for(grt::ChangeSet::const_iterator e2= triggers_cs->end(), jt= triggers_cs->begin(); jt != e2; jt++)
        {
          const grt::DiffChange *trigger_change= jt->get();

          switch(trigger_change->get_change_type())
          {
          case grt::ListItemAdded:
            {
              db_mysql_TriggerRef trigger(db_mysql_TriggerRef::cast_from(
                static_cast<const grt::ListItemAddedChange *>(trigger_change)->get_value()));
              generate_create_stmt(trigger, true);
            }
            break;
          case grt::ListItemRemoved:
            {
              db_mysql_TriggerRef trigger(db_mysql_TriggerRef::cast_from(
                  static_cast<const grt::ListItemRemovedChange *>(trigger_change)->get_value()));
              generate_drop_stmt(trigger, true);
            }
            break;
          case grt::ListItemModified:
            {
              db_mysql_TriggerRef old_trigger= db_mysql_TriggerRef::cast_from(
                grt::ValueRef(static_cast<const grt::ListItemModifiedChange *>
                (trigger_change)->get_old_value()));
              db_mysql_TriggerRef new_trigger= db_mysql_TriggerRef::cast_from(
                grt::ValueRef(static_cast<const grt::ListItemModifiedChange *>
                (trigger_change)->get_new_value()));

              generate_drop_stmt(old_trigger, true);
              generate_create_stmt(new_trigger, true);
            }
            break;
          case grt::ListItemOrderChanged:
            {
              const grt::ListItemOrderChange *order_change = dynamic_cast<const grt::ListItemOrderChange*>(trigger_change);

              if (order_change)// && order_change->get_subchange())
              {
//                const grt::ListItemModifiedChange *change = dynamic_cast<const grt::ListItemModifiedChange *>(order_change->get_subchange().get());
                
//                if (change)
                {
                  generate_drop_stmt(db_mysql_TriggerRef::cast_from(order_change->get_old_value()), true);
                  generate_create_stmt(db_mysql_TriggerRef::cast_from(order_change->get_new_value()), true);
                }
              }
            }
          default: break;
          }
        }
      }
    }
  }

  const std::string table_name_for_filter(get_old_object_name_for_key(table, _case_sensitive));
  if(_use_filtered_lists && (_filtered_tables.find(table_name_for_filter) == _filtered_tables.end()))
      return;

  bool partitions_processed= false;
  cs= diffchange->subchanges();

  // check whether all changes detected for this table are dummy changes (index reorders or fk reorders)
  bool dummy_changes_only = true;
  for(grt::ChangeSet::const_iterator e= cs->end(), it= cs->begin(); it != e; it++)
  {
    const grt::ObjectAttrModifiedChange *attr_change=
    dynamic_cast<const grt::ObjectAttrModifiedChange *>(it->get());

    if (attr_change->get_attr_name().compare("indices") == 0
        || attr_change->get_attr_name().compare("foreignKeys") == 0)
    {
      const grt::MultiChange *list_change=
      dynamic_cast<const grt::MultiChange *>(attr_change->get_subchange().get());
      // ignore changes if they're only reorderings (with no subchanges)
      if (list_change && list_change->subchanges())
      {
        const grt::ListItemOrderChange *order_change = NULL;
        if (list_change->subchanges()->changes.size() == 1)
          order_change = dynamic_cast<const grt::ListItemOrderChange *>(list_change->subchanges()->begin()->get());
        if (!order_change || order_change->get_subchange())
        {
          dummy_changes_only = false;
          break;
        }
      }
    }
    else
      dummy_changes_only = false;
  }
  if (dummy_changes_only)
    return;

  callback->alter_table_props_begin(table);

  for(grt::ChangeSet::const_iterator e= cs->end(), it= cs->begin(); it != e; it++)
  {
    const grt::ObjectAttrModifiedChange *attr_change= 
        static_cast<const grt::ObjectAttrModifiedChange *>(it->get());

    if (!(alter_table_flags & OnlyForeignKeys) && attr_change->get_attr_name().compare("foreignKeys") == 0)
      continue;
    else if (!(alter_table_flags & EverythingButForeignKeys) && attr_change->get_attr_name().compare("foreignKeys") != 0)
      continue;

    if(attr_change->get_attr_name().compare("name") == 0)
    {
      const grt::SimpleValueChange *change= 
        static_cast<const grt::SimpleValueChange *>(attr_change->get_subchange().get());
      grt::StringRef str= grt::StringRef::cast_from(change->get_new_value());
      callback->alter_table_name(table, str);
    }
    if(attr_change->get_attr_name().compare("columns") == 0)
    {
      const grt::MultiChange *list_change= 
        static_cast<const grt::MultiChange *>(attr_change->get_subchange().get());
      generate_alter(table->columns(), list_change);
    }
    else if(attr_change->get_attr_name().compare("indices") == 0)
    {
      const grt::MultiChange *list_change= 
        static_cast<const grt::MultiChange *>(attr_change->get_subchange().get());
      // ignore changes if they're only reorderings
      if (list_change && list_change->subchanges())
      {
        const grt::ListItemOrderChange *order_change = NULL;
        if (list_change->subchanges()->changes.size() == 1)
          order_change = dynamic_cast<const grt::ListItemOrderChange *>(list_change->subchanges()->begin()->get());
        if (!order_change || order_change->get_subchange())
        {
          callback->alter_table_indexes_begin(table);
          generate_alter(table->indices(), list_change);
          callback->alter_table_indexes_end(table);
        }
      }
    }
    else if(attr_change->get_attr_name().compare("foreignKeys") == 0)
    {
      const grt::MultiChange *list_change= 
        static_cast<const grt::MultiChange *>(attr_change->get_subchange().get());
      // ignore changes if they're only reorderings
      if (list_change && list_change->subchanges())
      {
        const grt::ListItemOrderChange *order_change = NULL;
        if (list_change->subchanges()->changes.size() == 1)
          order_change = dynamic_cast<const grt::ListItemOrderChange *>(list_change->subchanges()->begin()->get());
        if (!order_change || order_change->get_subchange())
        {
          callback->alter_table_fks_begin(table);
          generate_alter(table->foreignKeys(), list_change);
          callback->alter_table_fks_end(table);
        }
      }
    }
    else if(attr_change->get_attr_name().compare("tableEngine") == 0)
    {
      const grt::SimpleValueChange *change= 
        static_cast<const grt::SimpleValueChange *>(attr_change->get_subchange().get());
      grt::StringRef str= grt::StringRef::cast_from(change->get_new_value());
      if(!str.empty())
        callback->alter_table_engine(table, str);
    }
    else if(attr_change->get_attr_name().compare("nextAutoInc") == 0)
    {
      const grt::SimpleValueChange *change= 
        static_cast<const grt::SimpleValueChange *>(attr_change->get_subchange().get());
      grt::StringRef str= grt::StringRef::cast_from(change->get_new_value());
      callback->alter_table_next_auto_inc(table, str);
    }
    else if(attr_change->get_attr_name().compare("password") == 0)
    {
      const grt::SimpleValueChange *change= 
        static_cast<const grt::SimpleValueChange *>(attr_change->get_subchange().get());
      grt::StringRef str= grt::StringRef::cast_from(change->get_new_value());
      callback->alter_table_password(table, str);
    }
    else if(attr_change->get_attr_name().compare("delayKeyWrite") == 0)
    {
      const grt::SimpleValueChange *change= 
        static_cast<const grt::SimpleValueChange *>(attr_change->get_subchange().get());
      grt::IntegerRef n= grt::IntegerRef::cast_from(change->get_new_value());
      callback->alter_table_delay_key_write(table, n);
    }
    else if(attr_change->get_attr_name().compare("defaultCharacterSetName") == 0)
    {
      const grt::SimpleValueChange *change= 
        static_cast<const grt::SimpleValueChange *>(attr_change->get_subchange().get());
      grt::StringRef str= grt::StringRef::cast_from(change->get_new_value());
      callback->alter_table_charset(table, str);
    }
    else if(attr_change->get_attr_name().compare("defaultCollationName") == 0)
    {
      const grt::SimpleValueChange *change= 
        static_cast<const grt::SimpleValueChange *>(attr_change->get_subchange().get());
      grt::StringRef str= grt::StringRef::cast_from(change->get_new_value());
      callback->alter_table_collate(table, str);
    }
    else if(attr_change->get_attr_name().compare("mergeUnion") == 0)
    {
      const grt::SimpleValueChange *change= 
        static_cast<const grt::SimpleValueChange *>(attr_change->get_subchange().get());
      grt::StringRef str= grt::StringRef::cast_from(change->get_new_value());
      callback->alter_table_merge_union(table, str);
    }
    else if(attr_change->get_attr_name().compare("mergeInsert") == 0)
    {
      const grt::SimpleValueChange *change= 
        static_cast<const grt::SimpleValueChange *>(attr_change->get_subchange().get());
      grt::StringRef str= grt::StringRef::cast_from(change->get_new_value());
      callback->alter_table_merge_insert(table, str);
    }
    // ALTER TABLE silently ignores these attributes
#if 0
    else if(attr_change->get_attr_name().compare("tableDataDir") == 0)
    {
      const grt::SimpleValueChange *change= static_cast<const grt::SimpleValueChange *>(attr_change->get_subchange().get());
      grt::StringRef str= grt::StringRef::cast_from(change->get_new_value());
      alter_sql.append("DATA DIRECTORY = '").append(str.c_str()).append("' ");
    }
    else if(attr_change->get_attr_name().compare("tableIndexDir") == 0)
    {
      const grt::SimpleValueChange *change= static_cast<const grt::SimpleValueChange *>(attr_change->get_subchange().get());
      grt::StringRef str= grt::StringRef::cast_from(change->get_new_value());
      alter_sql.append("INDEX DIRECTORY = '").append(str.c_str()).append("' ");
    }
#endif
    else if(attr_change->get_attr_name().compare("packKeys") == 0)
    {
      const grt::SimpleValueChange *change= 
        static_cast<const grt::SimpleValueChange *>(attr_change->get_subchange().get());
      grt::StringRef str= grt::StringRef::cast_from(change->get_new_value());
      callback->alter_table_pack_keys(table, str);
    }
    else if(attr_change->get_attr_name().compare("checksum") == 0)
    {
      const grt::SimpleValueChange *change= 
        static_cast<const grt::SimpleValueChange *>(attr_change->get_subchange().get());
      grt::IntegerRef n= grt::IntegerRef::cast_from(change->get_new_value());
      callback->alter_table_checksum(table, n);
    }
    else if(attr_change->get_attr_name().compare("comment") == 0)
    {
      const grt::SimpleValueChange *change= 
        static_cast<const grt::SimpleValueChange *>(attr_change->get_subchange().get());
      grt::StringRef str= grt::StringRef::cast_from(change->get_new_value());
      callback->alter_table_comment(table, str);
    }
    else if(attr_change->get_attr_name().compare("rowFormat") == 0)
    {
      const grt::SimpleValueChange *change= static_cast<const grt::SimpleValueChange *>(attr_change->get_subchange().get());
      grt::StringRef str= grt::StringRef::cast_from(change->get_new_value());
      callback->alter_table_row_format(table, str);
    }
    else if(attr_change->get_attr_name().compare("keyBlockSize") == 0)
    {
      const grt::SimpleValueChange *change= static_cast<const grt::SimpleValueChange *>(attr_change->get_subchange().get());
      grt::StringRef str= grt::StringRef::cast_from(change->get_new_value());
      callback->alter_table_key_block_size(table, str);
    }
    else if(attr_change->get_attr_name().compare("avgRowLength") == 0)
    {
      const grt::SimpleValueChange *change= 
        static_cast<const grt::SimpleValueChange *>(attr_change->get_subchange().get());
      grt::StringRef str= grt::StringRef::cast_from(change->get_new_value());
      callback->alter_table_avg_row_length(table, str);
    }
    else if(attr_change->get_attr_name().compare("minRows") == 0)
    {
      const grt::SimpleValueChange *change= 
        static_cast<const grt::SimpleValueChange *>(attr_change->get_subchange().get());
      grt::StringRef str= grt::StringRef::cast_from(change->get_new_value());
      callback->alter_table_min_rows(table, str);
    }
    else if(attr_change->get_attr_name().compare("maxRows") == 0)
    {
      const grt::SimpleValueChange *change= 
        static_cast<const grt::SimpleValueChange *>(attr_change->get_subchange().get());
      grt::StringRef str= grt::StringRef::cast_from(change->get_new_value());
      callback->alter_table_max_rows(table, str);
    }
    // to be added later, probably needs support for CREATE/ALTER SERVER
#if 0
    else if(attr_change->get_attr_name().compare("connection") == 0)
    {
    }
#endif
    else if(attr_change->get_attr_name().compare("connectionString") == 0)
    {
      const grt::SimpleValueChange *change= 
        static_cast<const grt::SimpleValueChange *>(attr_change->get_subchange().get());
      grt::StringRef str= grt::StringRef::cast_from(change->get_new_value());
      callback->alter_table_connection_string(table, str);
    }
    else if(!partitions_processed &&
       ( (attr_change->get_attr_name().compare("partitionType") == 0) 
       || (attr_change->get_attr_name().compare("partitionExpression") == 0)
       || (attr_change->get_attr_name().compare("subpartitionType") == 0)
       || (attr_change->get_attr_name().compare("subpartitionExpression") == 0)
       || (attr_change->get_attr_name().compare("subpartitionCount") == 0)))
    {
      generate_set_partitioning(table, diffchange);
      partitions_processed = true;
    }
  }

  if (alter_table_flags & EverythingButForeignKeys)
  {
    bool is_range= (strcmp(table->partitionType().c_str(), "RANGE") == 0);

    // partitioning options that dont require PARTITION BY clause
    for(grt::ChangeSet::const_iterator e= cs->end(), it= cs->begin(); 
      (it != e) && !partitions_processed; it++)
    {
      const grt::ObjectAttrModifiedChange *attr_change= 
        static_cast<const grt::ObjectAttrModifiedChange *>(it->get());

      if(attr_change->get_attr_name().compare("partitionCount") == 0)
      {
        const grt::SimpleValueChange *change= 
          static_cast<const grt::SimpleValueChange *>(attr_change->get_subchange().get());
        grt::IntegerRef old_count= grt::IntegerRef::cast_from(change->get_old_value());
        
        //std::string part_count_sql(generate_change_partition_count(table, new_count));

        //// 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
        std::string part_type(table->partitionType().c_str());
        if((part_type.find("HASH") != std::string::npos)
            || (part_type.find("KEY") != std::string::npos))
        {
          callback->alter_table_partition_count(table, old_count);
          partitions_processed= true;
        }
      }
      else if(attr_change->get_attr_name().compare("partitionDefinitions") == 0)
      {
        const grt::MultiChange *list_change= static_cast<const grt::MultiChange *>(attr_change->get_subchange().get());
        const grt::ChangeSet *part_cs= list_change->subchanges();
        for(grt::ChangeSet::const_iterator e2= part_cs->end(), jt= part_cs->begin(); jt != e2; jt++)
        {
          const grt::DiffChange *part_change= jt->get();
          switch(part_change->get_change_type())
          {
          case grt::ListItemAdded:
            {
              db_mysql_PartitionDefinitionRef part(db_mysql_PartitionDefinitionRef::cast_from(
                static_cast<const grt::ListItemAddedChange *>(part_change)->get_value()));
              callback->alter_table_add_partition(part, is_range);
            }
            break;
          case grt::ListItemRemoved:
            {
              db_mysql_PartitionDefinitionRef part= db_mysql_PartitionDefinitionRef::cast_from(
                static_cast<const grt::ListItemRemovedChange *>(part_change)->get_value());

              callback->alter_table_drop_partition(part->name().c_str());
            }
            break;
          case grt::ListItemModified:
            {
              db_mysql_PartitionDefinitionRef old_part(db_mysql_PartitionDefinitionRef::cast_from(
                static_cast<const grt::ListItemModifiedChange *>(part_change)->get_old_value()));
              db_mysql_PartitionDefinitionRef new_part(db_mysql_PartitionDefinitionRef::cast_from(
                static_cast<const grt::ListItemModifiedChange *>(part_change)->get_new_value()));

              callback->alter_table_reorganize_partition(old_part, new_part, is_range);
            }
            break;
            default: break;
          }

          partitions_processed= true;
        }
      }
    }
  }

  callback->alter_table_props_end(table);
}

void DiffSQLGeneratorBE::generate_alter_stmt(db_mysql_ViewRef old_view, db_mysql_ViewRef new_view, const grt::DiffChange *diffchange)
{
  std::string view_name_for_filter(get_old_object_name_for_key(new_view, _case_sensitive));
  if(_use_filtered_lists)
    if(_filtered_views.find(view_name_for_filter) == _filtered_views.end())
      return;

  generate_create_stmt(new_view);

  std::string new_view_name = _case_sensitive ? *new_view->name() : base::toupper(new_view->name());
  std::string old_view_name = _case_sensitive ? *old_view->name() : base::toupper(old_view->name());
  if(strcmp(new_view_name.c_str(), old_view_name.c_str())) // name changed - need to drop old view
    generate_drop_stmt(old_view);
}

void DiffSQLGeneratorBE::generate_routine_alter_stmt(db_mysql_RoutineRef old_routine, db_mysql_RoutineRef new_routine, const grt::DiffChange *diffchange)
{
  std::string routine_name_for_filter(get_old_object_name_for_key(new_routine, _case_sensitive));
  if(_use_filtered_lists)
    if(_filtered_routines.find(routine_name_for_filter) == _filtered_routines.end())
      return;

  generate_drop_stmt(old_routine);

  generate_create_stmt(new_routine); 
}

void DiffSQLGeneratorBE::generate_alter_stmt(db_mysql_SchemaRef schema, const grt::DiffChange *diffchange)
{
  bool process_alter_schema= true;
  std::string schema_name_for_filter(get_old_object_name_for_key(schema, _case_sensitive));

  if(_use_filtered_lists)
    if(_filtered_schemata.find(schema_name_for_filter) == _filtered_schemata.end())
      process_alter_schema= false;

  const grt::ChangeSet *cs= diffchange->subchanges();

  callback->alter_schema_props_begin(schema);

  if(process_alter_schema)
  {
    for(grt::ChangeSet::const_iterator e= cs->end(), it= cs->begin(); it != e; it++)
    {
      const grt::ObjectAttrModifiedChange *attr_change= static_cast<const grt::ObjectAttrModifiedChange *>(it->get());

      if(attr_change->get_attr_name().compare("name") == 0)
      {
        const grt::SimpleValueChange *change= 
          static_cast<const grt::SimpleValueChange *>(attr_change->get_subchange().get());
        grt::StringRef str= grt::StringRef::cast_from(change->get_new_value());
        callback->alter_schema_name(schema, str);
      }
      if(attr_change->get_attr_name().compare("defaultCharacterSetName") == 0)
      {
        grt::ValueRef value= static_cast<const grt::SimpleValueChange *>(attr_change->get_subchange().get())->get_new_value();
        callback->alter_schema_default_charset(schema, grt::StringRef::cast_from(value));
      }
      else if(attr_change->get_attr_name().compare("defaultCollationName") == 0)
      {
        grt::ValueRef value= static_cast<const grt::SimpleValueChange *>(attr_change->get_subchange().get())->get_new_value();
        callback->alter_schema_default_collate(schema, grt::StringRef::cast_from(value));
      }
    }
  }

  callback->alter_schema_props_end(schema);

  for(grt::ChangeSet::const_iterator e= cs->end(), it= cs->begin(); it != e; it++)
  {
    const grt::ObjectAttrModifiedChange *attr_change= static_cast<const grt::ObjectAttrModifiedChange *>(it->get());
    if(attr_change->get_attr_name().compare("tables") == 0)
    {
      const grt::MultiChange *list_change= static_cast<const grt::MultiChange *>(attr_change->get_subchange().get());
      const grt::ChangeSet *tables_cs= list_change->subchanges();
      for(grt::ChangeSet::const_iterator e2= tables_cs->end(), jt= tables_cs->begin(); jt != e2; jt++)
      {
        const grt::DiffChange *table_change= jt->get();
        if (table_change->get_change_type() == grt::ListItemModified)
        {
          generate_alter_stmt_drops(
            db_mysql_TableRef::cast_from(static_cast<const grt::ListItemModifiedChange *>(table_change)->get_new_value()),
            static_cast<const grt::ListItemModifiedChange *>(table_change)->get_subchange().get());
        }
        else if (table_change->get_change_type() == grt::ListItemOrderChanged)
        {
            const grt::ListItemOrderChange *oc= 
              static_cast<const grt::ListItemOrderChange *>(table_change);
            if(oc->get_subchange())
              generate_alter_stmt_drops(db_mysql_TableRef::cast_from(oc->get_subchange()->get_new_value()), oc->get_subchange()->get_subchange().get());
        }
      }
    }
  }


  for(grt::ChangeSet::const_iterator e= cs->end(), it= cs->begin(); it != e; it++)
  {
    const grt::ObjectAttrModifiedChange *attr_change= static_cast<const grt::ObjectAttrModifiedChange *>(it->get());
    if(attr_change->get_attr_name().compare("tables") == 0)
    {
      const grt::MultiChange *list_change= static_cast<const grt::MultiChange *>(attr_change->get_subchange().get());
      const grt::ChangeSet *tables_cs= list_change->subchanges();

      // 1st pass, do everything except FKs
      for(grt::ChangeSet::const_iterator e2= tables_cs->end(), jt= tables_cs->begin(); jt != e2; jt++)
      {
        const grt::DiffChange *table_change= jt->get();
        switch(table_change->get_change_type())
        {
        case grt::ListItemAdded:
          generate_create_stmt(
            db_mysql_TableRef::cast_from(static_cast<const grt::ListItemAddedChange *>(table_change)->get_value()));
          break;
        case grt::ListItemRemoved:
          generate_drop_stmt(db_mysql_TableRef::cast_from(
            static_cast<const grt::ListItemRemovedChange *>(table_change)->get_value()));
          break;
        case grt::ListItemModified:
          generate_alter_stmt(db_mysql_TableRef::cast_from(
            static_cast<const grt::ListItemModifiedChange *>(table_change)->get_new_value()),
            static_cast<const grt::ListItemModifiedChange *>(table_change)->get_subchange().get(),
                              _separate_foreign_keys ? EverythingButForeignKeys : Everything); // everything but FK 1st
          break;
        case grt::ListItemOrderChanged:
          {
            const grt::ListItemOrderChange *oc= 
              static_cast<const grt::ListItemOrderChange *>(table_change);
            if(oc->get_subchange())
              generate_alter_stmt(db_mysql_TableRef::cast_from(oc->get_subchange()->get_new_value()), oc->get_subchange()->get_subchange().get(), _separate_foreign_keys ? EverythingButForeignKeys : Everything);
          }
          break;
          default: 
            break;
        }
      }

      if (_separate_foreign_keys)
      {
        // 2nd pass, do FKs only
        for(grt::ChangeSet::const_iterator e2= tables_cs->end(), jt= tables_cs->begin(); jt != e2; jt++)
        {
          const grt::DiffChange *table_change= jt->get();
          switch(table_change->get_change_type())
          {
            case grt::ListItemAdded:
            case grt::ListItemRemoved:
              break;
            case grt::ListItemModified:
              generate_alter_stmt(db_mysql_TableRef::cast_from(
                                  static_cast<const grt::ListItemModifiedChange *>(table_change)->get_new_value()),
                                  static_cast<const grt::ListItemModifiedChange *>(table_change)->get_subchange().get(),
                                  OnlyForeignKeys); // FK only
              break;
            case grt::ListItemOrderChanged:
            {
              const grt::ListItemOrderChange *oc=
              static_cast<const grt::ListItemOrderChange *>(table_change);
              if(oc->get_subchange())
                generate_alter_stmt(db_mysql_TableRef::cast_from(oc->get_subchange()->get_new_value()), oc->get_subchange()->get_subchange().get(), OnlyForeignKeys);
            }
              break;
            default: 
              break;
          }
        }
      }
    }
    else if(attr_change->get_attr_name().compare("views") == 0)
    {
      const grt::MultiChange *list_change= static_cast<const grt::MultiChange *>(attr_change->get_subchange().get());
      const grt::ChangeSet *views_cs= list_change->subchanges();
      for(grt::ChangeSet::const_iterator e2= views_cs->end(), jt= views_cs->begin(); jt != e2; jt++)
      {
        const grt::DiffChange *view_change= jt->get();
        switch(view_change->get_change_type())
        {
        case grt::ListItemAdded:
          {
            db_mysql_ViewRef new_view= db_mysql_ViewRef::cast_from(
              static_cast<const grt::ListItemAddedChange *>(view_change)->get_value());
            generate_create_stmt(new_view);
          }
          break;
        case grt::ListItemRemoved:
          {
            generate_drop_stmt(db_mysql_ViewRef::cast_from(static_cast<const grt::ListItemRemovedChange *>(view_change)->get_value()));
          }
          break;
        case grt::ListItemModified:
          {
            db_mysql_ViewRef old_view= db_mysql_ViewRef::cast_from(
              grt::ValueRef(static_cast<const grt::ListItemModifiedChange *>(view_change)->get_old_value()));
            db_mysql_ViewRef new_view= db_mysql_ViewRef::cast_from(
              grt::ValueRef(static_cast<const grt::ListItemModifiedChange *>(view_change)->get_new_value()));
            generate_alter_stmt(old_view, new_view, view_change);
          }
          break;
          case grt::ListItemOrderChanged:
            // list item position change is not relevant but may hold diff inside
          {
            if (!static_cast<const grt::ListItemOrderChange *>(view_change)->get_subchange())
              break;
            db_mysql_ViewRef old_view= db_mysql_ViewRef::cast_from(
                             grt::ValueRef(static_cast<const grt::ListItemOrderChange *>(view_change)->get_subchange()->get_old_value()));
            db_mysql_ViewRef new_view= db_mysql_ViewRef::cast_from(
                             grt::ValueRef(static_cast<const grt::ListItemOrderChange *>(view_change)->get_subchange()->get_new_value()));

            generate_alter_stmt(old_view, new_view, view_change);
            break;
          }
          default: break;
        }
      }
    }
    else if(attr_change->get_attr_name().compare("routines") == 0)
    {
      const grt::MultiChange *list_change= static_cast<const grt::MultiChange *>(attr_change->get_subchange().get());
      const grt::ChangeSet *routines_cs= list_change->subchanges();
      for(grt::ChangeSet::const_iterator e2= routines_cs->end(), jt= routines_cs->begin(); jt != e2; jt++)
      {
        const grt::DiffChange *routine_change= jt->get();
        switch(routine_change->get_change_type())
        {
        case grt::ListItemAdded:
          {
            db_mysql_RoutineRef new_routine= db_mysql_RoutineRef::cast_from(
              static_cast<const grt::ListItemAddedChange *>(routine_change)->get_value());
            generate_create_stmt(new_routine);
          }
          break;
        case grt::ListItemRemoved:
          {
            generate_drop_stmt(db_mysql_RoutineRef::cast_from(static_cast<const grt::ListItemRemovedChange *>(routine_change)->get_value()));
          }
          break;
        case grt::ListItemModified:
          {
            db_mysql_RoutineRef old_routine= db_mysql_RoutineRef::cast_from(
              grt::ValueRef(static_cast<const grt::ListItemModifiedChange *>(routine_change)->get_old_value()));
            db_mysql_RoutineRef new_routine= db_mysql_RoutineRef::cast_from(
              grt::ValueRef(static_cast<const grt::ListItemModifiedChange *>(routine_change)->get_new_value()));
            generate_routine_alter_stmt(old_routine, new_routine, routine_change);
          }
          break;
        case grt::ListItemOrderChanged:
        // list item position change is not relevant but may hold diff inside
            {
            if (!static_cast<const grt::ListItemOrderChange *>(routine_change)->get_subchange())
                break;
            db_mysql_RoutineRef old_routine= db_mysql_RoutineRef::cast_from(
                grt::ValueRef(static_cast<const grt::ListItemOrderChange *>(routine_change)->get_subchange()->get_old_value()));
            db_mysql_RoutineRef new_routine= db_mysql_RoutineRef::cast_from(
                grt::ValueRef(static_cast<const grt::ListItemOrderChange *>(routine_change)->get_subchange()->get_new_value()));
            generate_routine_alter_stmt(old_routine, new_routine, routine_change);
            }
            break;
          default: break;        
        }
      }
    }
  }
}


void DiffSQLGeneratorBE::generate_alter_stmt(db_mysql_CatalogRef catalog, const grt::DiffChange *diffchange)
{
  // process changes in schemata
  for(grt::ChangeSet::const_iterator e= diffchange->subchanges()->end(),
    it= diffchange->subchanges()->begin(); it != e; it++)
  {
    const grt::DiffChange *subchange= it->get();
    if(subchange->get_change_type() == grt::ObjectAttrModified)
    {
      const grt::ObjectAttrModifiedChange *objattrchange= 
        static_cast<const grt::ObjectAttrModifiedChange *>(subchange);

      if(objattrchange->get_attr_name().compare("schemata") == 0)
      {
        const grt::DiffChange *objattr_subchange= objattrchange->get_subchange().get();
        if(objattr_subchange->get_change_type() == grt::ListModified)
        {
          const grt::MultiChange *schemata_list_change=
            static_cast<const grt::MultiChange *>(objattr_subchange);

          for(grt::ChangeSet::const_iterator schemata_e= schemata_list_change->subchanges()->end(),
            schemata_it= schemata_list_change->subchanges()->begin(); schemata_it != schemata_e; schemata_it++)
          {
            const grt::DiffChange *schema_subchange= schemata_it->get();
            switch(schema_subchange->get_change_type())
            {
            case grt::ListItemAdded:
              generate_create_stmt(
                db_mysql_SchemaRef::cast_from(static_cast<const grt::ListItemAddedChange *>(schema_subchange)->get_value()));
              break;
            case grt::ListItemRemoved:
              generate_drop_stmt(db_mysql_SchemaRef::cast_from(
                static_cast<const grt::ListItemRemovedChange *>(schema_subchange)->get_value()));
              break;
            case grt::ListItemModified:
              generate_alter_stmt(db_mysql_SchemaRef::cast_from(
                static_cast<const grt::ListItemModifiedChange *>(schema_subchange)->get_new_value()), 
                static_cast<const grt::ListItemModifiedChange *>(schema_subchange)->get_subchange().get());
              break;
            case grt::ListItemOrderChanged:
              {
                const grt::ListItemOrderChange *oc= 
                  static_cast<const grt::ListItemOrderChange *>(schema_subchange);
                if(oc->get_subchange())
                  generate_alter_stmt(db_mysql_SchemaRef::cast_from(
                    oc->get_subchange()->get_new_value()), oc->get_subchange()->get_subchange().get());
              }
              break;
              default: 
                break;
            }
          }
        }
      }
    }
  }
}

static void fill_set_from_list(grt::StringListRef string_list, std::set<std::string>& string_set)
{
  for(size_t count= string_list.count(), i= 0; i < count; i++)
    string_set.insert(std::string(string_list.get(i).c_str()));
}

DiffSQLGeneratorBE::DiffSQLGeneratorBE(grt::DictRef options, grt::DictRef dbtraits, DiffSQLGeneratorBEActionInterface *cb)
    : callback(cb), _gen_create_index(false), _use_filtered_lists(true),
    _skip_foreign_keys(false), _skip_fk_indexes(false), _case_sensitive(false), _use_oid_as_dict_key(false), _separate_foreign_keys(true)
{
    if (!options.is_valid())
        return;
    _case_sensitive = (dbtraits.get_int("CaseSensitive", _case_sensitive) != 0);

    grt::StringListRef empty_list(options.get_grt());
    _use_oid_as_dict_key = options.get_int("UseOIDAsResultDictKey", _use_oid_as_dict_key) != 0;
    _skip_foreign_keys = options.get_int("SkipForeignKeys", _skip_foreign_keys) != 0;
    _skip_fk_indexes = options.get_int("SkipFKIndexes", _skip_fk_indexes) != 0;
    _gen_create_index = (options.get_int("GenerateCreateIndex", _gen_create_index) != 0);
    _use_filtered_lists = options.get_int("UseFilteredLists", _use_filtered_lists)!= 0;
    _separate_foreign_keys = options.get_int("SeparateForeignKeys", _separate_foreign_keys)!= 0;
    cb->set_short_names(options.get_int("UseShortNames", 0) != 0);
    cb->set_gen_use(options.get_int("GenerateUse", 0) != 0);
    fill_set_from_list(grt::StringListRef::cast_from(options.get("UserFilterList", empty_list)), _filtered_users);
    fill_set_from_list(grt::StringListRef::cast_from(options.get("SchemaFilterList", empty_list)), _filtered_schemata);
    fill_set_from_list(grt::StringListRef::cast_from(options.get("TableFilterList", empty_list)), _filtered_tables);
    fill_set_from_list(grt::StringListRef::cast_from(options.get("ViewFilterList", empty_list)), _filtered_views);
    fill_set_from_list(grt::StringListRef::cast_from(options.get("RoutineFilterList", empty_list)), _filtered_routines);
    fill_set_from_list(grt::StringListRef::cast_from(options.get("TriggerFilterList", empty_list)), _filtered_triggers);
}

void DiffSQLGeneratorBE::process_diff_change(grt::ValueRef org_object, grt::DiffChange *diff, grt::DictRef map)
{
  this->target_list= grt::StringListRef();
  this->target_map= map;

  do_process_diff_change(org_object, diff);
}

void DiffSQLGeneratorBE::process_diff_change(grt::ValueRef org_object, 
                                             grt::DiffChange *diff, 
                                             grt::StringListRef list, 
                                             grt::ListRef<GrtNamedObject> objlist)
{
  this->target_map= grt::DictRef();
  this->target_list= list;
  this->target_object_list= objlist;

  do_process_diff_change(org_object, diff);
}

void DiffSQLGeneratorBE::do_process_diff_change(grt::ValueRef org_object, grt::DiffChange *diff)
{
  switch(diff->get_change_type())
  {
  //case SimpleValue:
  case grt::ValueAdded:
    generate_create_stmt(db_mysql_CatalogRef::cast_from(dynamic_cast<grt::ValueAddedChange*>(diff)->get_value()));
    break;
  case grt::DictItemAdded:
    break;
  case grt::ListItemAdded:
    generate_create_stmt(db_mysql_CatalogRef::cast_from(dynamic_cast<grt::ListItemAddedChange*>(diff)->get_value()));
    break;

  case grt::ValueRemoved:
  case grt::ListItemRemoved:
  case grt::DictItemRemoved:
    generate_drop_stmt(db_mysql_CatalogRef::cast_from(org_object));
    break;

  case grt::ObjectModified:
  case grt::ObjectAttrModified:
  case grt::ListModified:
  case grt::ListItemModified:
  case grt::ListItemOrderChanged:
  case grt::DictModified:
  case grt::DictItemModified:
    generate_alter_stmt(db_mysql_CatalogRef::cast_from(org_object), diff);
    break;
  
  default:
    break;
  }
}
