/* 
 * Copyright (c) 2007, 2012, 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"

#include "editor_table.h"
#include "sqlide/recordset_table_inserts_storage.h"
#include "grtui/inserts_export_form.h"

#include "db_object_helpers.h"
#include "grtpp_util.h"
#include "grt/validation_manager.h"

#include "base/string_utilities.h"
#include "base/file_utilities.h"

#ifdef __APPLE__
#include "base/log.h"
#endif

#include "mforms/utilities.h"

using namespace bec;
using namespace grt;
using namespace base;

DEFAULT_LOG_DOMAIN("Table Editor backend")

/*
 The big mess of user datatypes, synonyms and column flags.
 
 - A column can have a datatype, a list of flags specific for the type (eg INT UNSIGNED ZEROFILL)
 and other parameters.
 - Some datatypes have aliases or synonyms, eg: INTEGER is just an alias for INT in MySQL
 - Users can define User Defined Datatypes and use them in columns. If a column type is set to a UDT
 no other parameters can be changed. Flags like UNSIGNED cannot be changed for the column that
 uses the UDT anymore. However, the UDT definition can be changed and if that happens, all uses
 of that UDT will be updated automatically.
 - Originally, server aliases were handled by a "synonyms" list for each simple datatype.
 So, INT had a list of synonyms that contained INTEGER. Both were basically treated as if they were
 the same, the same kind of parameters and flags could be toggled for either.
 - At some point, for a reason I don't know, someone removed the synonyms list and made
 such aliases be implemented as UDTs.
 - That caused all sorts of problems, mostly because aliases/synonyms can have parameters changed
 at will in the column definition while UDTs can't.
 - Now, to bring back sanity to this whole mess, we have to either:
   - revert the removal of aliases/synonyms and make things pretty again; or
   - introduce a hack to distinguish between real UDTs and UDTs that were corrupted into aliases
 - So, to distinguish between real UDTs and corrupt-UDTs you should check if the object id.
 */


/****************************************************************************
 * @brief Column list backend for table editors. Internal use.
 *
 ****************************************************************************
 */
TableColumnsListBE::TableColumnsListBE(TableEditorBE *owner)
  : _owner(owner)
{
  _editing_placeholder_row= -1;
}


static bool sort_simple_type(const db_SimpleDatatypeRef &a, const db_SimpleDatatypeRef &b)
{
  int i= strcmp(a->group()->name().c_str(), b->group()->name().c_str());
  if (i == 0)
    i= strcmp(a->name().c_str(), b->name().c_str());
  return i < 0;
}


std::vector<std::string> TableColumnsListBE::get_datatype_names()
{
  std::vector<std::string> types;
  
  //TODO replace this hardcoded list with a dynamically generated top-used types list (same for charset)
  types.push_back("INT");
  types.push_back("VARCHAR()");
  types.push_back("DECIMAL()");
  types.push_back("DATETIME");       
  types.push_back("BLOB");
  
  std::vector<db_SimpleDatatypeRef> sorted_types;
  grt::ListRef<db_SimpleDatatype> stypes(_owner->get_catalog()->simpleDatatypes());
  for (grt::ListRef<db_SimpleDatatype>::const_iterator iter= stypes.begin(); iter != stypes.end(); ++iter)
    sorted_types.push_back(*iter);
  
  // sort by group and name
  std::sort(sorted_types.begin(), sorted_types.end(), sort_simple_type);
  
  // insert grouped by group
  db_DatatypeGroupRef group;
  for (std::vector<db_SimpleDatatypeRef>::const_iterator iter= sorted_types.begin(); iter != sorted_types.end(); ++iter)
  {
    if (group != (*iter)->group())
    {
      group= (*iter)->group();
      types.push_back("-");
    }
    if ((*iter)->parameterFormatType() == 1 ||
        (*iter)->parameterFormatType() == 3 ||
        (*iter)->parameterFormatType() == 4 ||
        (*iter)->parameterFormatType() == 10)
      types.push_back(*(*iter)->name()+"()");
    else
      types.push_back((*iter)->name());
  }
  
  // insert UDTs
  types.push_back("-");
  grt::ListRef<db_UserDatatype> utypes(_owner->get_catalog()->userDatatypes());
  for (grt::ListRef<db_UserDatatype>::const_iterator iter= utypes.begin(); iter != utypes.end(); ++iter)
    types.push_back(*(*iter)->name());
    
  /*
  types.push_back("BIT");
  types.push_back("TINYINT");
  types.push_back("SMALLINT");
  types.push_back("MEDIUMINT");
  types.push_back("INT");
  types.push_back("BIGINT");
  types.push_back("FLOAT");
  types.push_back("DOUBLE");
  types.push_back("-");
  types.push_back("DATE");
  types.push_back("TIME");
  types.push_back("TIMESTAMP");
  types.push_back("YEAR");
  types.push_back("-");
  types.push_back("CHAR()");
  types.push_back("VARCHAR()");
  types.push_back("BINARY()");
  types.push_back("TINYBLOB");
  types.push_back("BLOB");
  types.push_back("MEDIUMBLOB");
  types.push_back("LONGBLOB");
  types.push_back("TINYTEXT");
  types.push_back("TEXT");
  types.push_back("MEDIUMTEXT");
  types.push_back("LONGTEXT");
  types.push_back("ENUM()");
  types.push_back("SET()");
  */
  return types;
}


bec::ColumnNamesSet TableColumnsListBE::get_column_names_completion_list() const
{
  bec::ColumnNamesSet column_names;
  
  db_SchemaRef schema = db_SchemaRef::cast_from(_owner->get_table()->owner());
  if (schema.is_valid())
  {
    grt::ListRef<db_Table> tables = schema->tables();
    for (int i = tables.count() - 1; i >=0; --i)
    {
      grt::ListRef<db_Column> columns = tables[i]->columns();
      for (int j = columns.count() - 1; j >=0; --j)
      {
        column_names.insert(columns[j]->name().c_str());
      }
    }
  }
  
  return column_names;
}

/** 
 ****************************************************************************
 * @brief Get info for a table column
 *
 * @param node - identifier of the column node
 * @param name - returns name of column
 * @param type - returns formatted datatype of the column
 * @param ispk - returns true if column is part of a PK
 * @param notnull - returns true if column is NOT NULL
 * @param flags - returns comma separated list of additional flags
 * @param defvalue - returns default value of column
 * @param defnull - returns true if default value of column is NULL
 * @param charset - returns character set name of column
 * @param collation - returns collation name for column
 *
 * @return false if row is invalid
 ****************************************************************************
 */
bool TableColumnsListBE::get_row(const NodeId &node,
                                 std::string &name,
                                 std::string &type,
                                 bool &ispk,
                                 bool &notnull,
                                 bool &isunique,
                                 bool &isbinary,
                                 bool &isunsigned,
                                 bool &iszerofill,
                                 std::string &flags,
                                 std::string &defvalue,
                                 std::string &charset,
                                 std::string &collation,
                                 std::string &comment
                                 )
{
  if (node[0] < real_count())
  {
    db_ColumnRef col= _owner->get_table()->columns().get(node[0]);

    name= col->name();
    type= _owner->format_column_type(col);
    ispk= _owner->get_table()->isPrimaryKeyColumn(col) != 0;
    notnull= (col->isNotNull() != 0);
    isbinary= get_column_flag(node, "BINARY") != 0;
    isunsigned= get_column_flag(node, "UNSIGNED") != 0;
    iszerofill= get_column_flag(node, "ZEROFILL") != 0;
    flags= ""; //XXX
    defvalue= col->defaultValue();
    charset= col->characterSetName();
    collation= col->collationName();
    comment= col->comment();

    return true;
  }
  return false;
}


void TableColumnsListBE::reorder(const NodeId &node, int nindex)
{
  if (node[0] < real_count())
  {
    AutoUndoEdit undo(_owner);

    _owner->get_table()->columns().reorder(node[0], nindex);
    update_primary_index_order();
  
    _owner->update_change_date();
    
    (*_owner->get_table()->signal_refreshDisplay())("column");
    undo.end(strfmt(_("Reorder Column '%s.%s'"), _owner->get_name().c_str(), _owner->get_table()->columns()[node[0]]->name().c_str()));

    _owner->do_partial_ui_refresh(::bec::TableEditorBE::RefreshColumnList);
  }
}


void TableColumnsListBE::reorder_many(const std::vector<int> &rows, int targetIndex)
{
  if (!rows.empty())
  {
    std::vector<int> indices(rows);
    std::sort(indices.begin(), indices.end());

    AutoUndoEdit undo(_owner);

    for (size_t i = 0; i < indices.size(); i++)
    {
      // Watch out how the backend does the reorder. If the source index is smaller than the target
      // index we will end up one node after the one we intended (the node is first removed
      // moving so all following indices one index down).
      _owner->get_table()->columns().reorder(indices[i],
        (indices[i] < targetIndex) ? targetIndex - 1 : targetIndex);

      // Update all those indices in our list between the current index and the target index
      // if the current index is below that value. This is because remaining indices move down in the list
      // if a node is removed from there and inserted after them in the list.
      if (indices[i] < targetIndex)
      {
        for (size_t j = i + 1; j < indices.size(); j++)
          if (indices[j] > indices[i] && indices[j] < targetIndex)
            indices[j]--;
      }
      else
        // If the current is above the target index then increase the latter to ensure
        // that the multiple indices stay in the same order.
        targetIndex++;
    }

    update_primary_index_order();
    
    _owner->update_change_date();
    (*_owner->get_table()->signal_refreshDisplay())("column");
    undo.end(strfmt(_("Reorder Columns in '%s'"), _owner->get_name().c_str()));

    _owner->do_partial_ui_refresh(::bec::TableEditorBE::RefreshColumnList);
  }
}


void TableColumnsListBE::update_primary_index_order()
{
  if (_owner->get_table()->primaryKey().is_valid())
  {
    ListRef<db_Column> columns(_owner->get_table()->columns());
    ListRef<db_IndexColumn> icolumns(_owner->get_table()->primaryKey()->columns());
    if (icolumns.count() > 1)
    {
      size_t pos= 0;

      // update the PRIMARY index columns order to match the order they appear in the table
      for (size_t c= real_count(), i= 0; i < c; i++)
      {
        for (size_t ic= icolumns.count(), ii= pos; ii < ic; ii++)
        {
          if (icolumns[ii]->referencedColumn() == columns[i])
          {
            if (ii != pos)
            {
              // out of place, reorder
              icolumns->reorder(ii, pos);
            } 
            pos++;
            break;
          }
        }
        if (pos >= icolumns.count())
          break;
      }
    }
  }
}


bool TableColumnsListBE::set_column_type(const NodeId &node, const GrtObjectRef &type)
{
  if (type.is_instance(db_UserDatatype::static_class_name()))
  {
    db_UserDatatypeRef utype(db_UserDatatypeRef::cast_from(type));

    AutoUndoEdit undo(_owner);

    if (node[0] >= real_count())
      _owner->add_column(grt::get_name_suggestion_for_list_object(_owner->get_table()->columns(), utype->name(), false));

    bool flag= set_field(node, Type, utype->name());

    undo.end(strfmt(_("Add Column to '%s'"), _owner->get_name().c_str()));

    return flag;
  }
  return false;
}


IconId TableColumnsListBE::get_field_icon(const NodeId &node, int column, IconSize size)
{
  if (node[0] < real_count())
  {
    if (column == Type)
    {
      return 0;
    }
    else if (column == Name)
    {
      db_ColumnRef col= _owner->get_table()->columns().get(node[0]);

      if (_owner->get_table()->isPrimaryKeyColumn(col))
        return IconManager::get_instance()->get_icon_id(col, Icon11, "pk");
      else if (_owner->get_table()->isForeignKeyColumn(col))
      {
        if (*col->isNotNull())
          return IconManager::get_instance()->get_icon_id(col, Icon11, "fknn");
        else
          return IconManager::get_instance()->get_icon_id(col, Icon11, "fk");
      }
      else
      {
        if (*col->isNotNull())
          return IconManager::get_instance()->get_icon_id(col, Icon11, "nn");
        else
          return IconManager::get_instance()->get_icon_id(col, Icon11);
      }
    }
  }
  return 0;
}


void TableColumnsListBE::refresh()
{
}


int TableColumnsListBE::real_count()
{
  return (int)_owner->get_table()->columns().count();
}

int TableColumnsListBE::count()
{
  return (int)_owner->get_table()->columns().count() + 1;
}


bool TableColumnsListBE::set_column_type_from_string(db_ColumnRef &col, const std::string &type)
{
  bool flag= _owner->parse_column_type(type, col);
  if (flag)
  {
    if (col->simpleType().is_valid())
    {
      // Remove flags that don't exist in new datatype.
      if (col->flags().count() > 0)
      {
        grt::StringListRef valid_flags(col->simpleType()->flags());

        for (int i= (int) col->flags().count() - 1; i >= 0; i--)
        {
          if (valid_flags.get_index(col->flags().get(i)) == BaseListRef::npos)
            col->flags().remove(i);
        }
      }
    }
    else if (col->userType().is_valid())
    {
      col->flags().remove_all();
      
      // Read top of file for note on column flag handling
      
      // Don't set flags from userType or we have trouble when userType is updated, that was
      // incorrect behavior.
    }
  }
  else
    log_warning("%s is not a valid column type", type.c_str());
  return flag;
}


bool TableColumnsListBE::has_unique_index(const db_ColumnRef &col)
{
  db_TableRef table(_owner->get_table());

  for (size_t c= table->indices().count(), i= 0; i < c; i++)
  {
    db_IndexRef index(table->indices()[i]);

    if (index->indexType() == "UNIQUE" && index->columns().count() == 1 &&
        index->columns()[0]->referencedColumn() == col)
      return true;
  }
  return false;
}


bool TableColumnsListBE::make_unique_index(const db_ColumnRef &col, bool flag)
{
  if (flag == has_unique_index(col))
    return false;
  
  db_TableRef table(_owner->get_table());
  
  if (flag)
  {
    db_IndexRef index= _owner->get_grt()->create_object<db_Index>(table->indices().content_class_name());
    
    index->name(*col->name()+"_UNIQUE");
    index->owner(table);
    
    index->indexType("UNIQUE");
    index->unique(1);
    
    db_IndexColumnRef icolumn=
        _owner->get_grt()->create_object<db_IndexColumn>(index->columns().content_class_name());
    icolumn->owner(index);
    icolumn->referencedColumn(col);

    index->columns().insert(icolumn);
    
    AutoUndoEdit undo(_owner);
    
    _owner->update_change_date();
    table->indices().insert(index);

    undo.end(strfmt(_("Add Unique Index for '%s'.'%s'"), _owner->get_name().c_str(), col->name().c_str()));
  }
  else
  {    
    bool found = false;
    AutoUndoEdit undo(_owner);
   
    for (size_t c= table->indices().count(), i= 0; i < c; i++)
    {
      db_IndexRef index(table->indices()[i]);
      
      if (index->indexType() == "UNIQUE" && index->columns().count() == 1 &&
          index->columns()[0]->referencedColumn() == col)
      {
        table->indices().remove(i);
        found = true;
        break;
      }
    }
    
    _owner->update_change_date();

    if (found)
      undo.end(strfmt(_("Remove Unique Index for '%s'.'%s'"), _owner->get_name().c_str(), col->name().c_str()));
    else
      return false;
  }
  return true;
}


bool TableColumnsListBE::set_field(const NodeId &node, int column, const std::string &value)
{
  RefreshUI::Blocker __centry(*_owner);

  db_ColumnRef col;
  std::string old;

  // either the row was edited or cancelled (sent "" as value)
  if (node[0] == count()-1)
  {
    // not a placeholder anymore
    _editing_placeholder_row= -1;
  }

  // if the user enters a new column name in the editing placeholder row
  if (node[0] == count()-1 && ((ColumnListColumns)column == Name || (ColumnListColumns)column == Type) 
    && !value.empty())
  {
    AutoUndoEdit undo(_owner);
     // add new column

    std::string col_name;

    if ((ColumnListColumns)column == Name)
    {
      col_name= rtrim(value);
      _owner->add_column(col_name);

      db_ColumnRef col(_owner->get_table()->columns()[node[0]]);
      
      if (node[0] == 0) 
      {
        _owner->get_table()->addPrimaryKeyColumn(col);
                                                 
        set_column_type_from_string(col, grt::StringRef::cast_from(_owner->get_grt_manager()->get_app_option("DefaultPkColumnType")));
      }
      else
        set_column_type_from_string(col, grt::StringRef::cast_from(_owner->get_grt_manager()->get_app_option("DefaultColumnType")));
    }
    else
    {
      std::string name;

      _editing_placeholder_row= node[0];
      get_field(node, Name, name);
      _editing_placeholder_row= -1;

      _owner->add_column(name);

      db_ColumnRef col(_owner->get_table()->columns()[node[0]]);
      col_name= col->name();
      
      if (node[0] == 0) 
        _owner->get_table()->addPrimaryKeyColumn(col);
                                                 
      set_column_type_from_string(col, grt::StringRef(value));
    }

    undo.end(strfmt(_("Add column '%s.%s'"), _owner->get_name().c_str(), col_name.c_str()));
    return true;
  }
  else if (node[0] >= real_count())
    return false;

  col= _owner->get_table()->columns().get(node[0]);

  get_field(node, column, old);

  switch ((ColumnListColumns)column)
  {
  case Name:
    {
      std::string value_= rtrim(value);
      if (old != value_)
        _owner->rename_column(col, value_);
    }
    return true;
  case Type:
    {
      bool result = true;
      if (value != old)
      {
        AutoUndoEdit undo(_owner);

        if (set_column_type_from_string(col, value))
        {
          _owner->update_change_date();
          undo.end(strfmt(_("Set Type of '%s.%s'"), _owner->get_name().c_str(), col->name().c_str()));
        }
        else
        {
          undo.cancel();
          result = false;
        }

        _owner->do_partial_ui_refresh(::bec::TableEditorBE::RefreshColumnCollation);
      }
      return result;
    }

  case IsPK:
    return false;
  case IsNotNull:
    return false;
  case Flags:
    return false;
  case Default:
    if (old != value)
    {
      AutoUndoEdit undo(_owner, col, "defaultValue");

      std::string value_= rtrim(value);
      bec::ColumnHelper::set_default_value(col, value_.empty() ? "" : quote_value_if_needed(col, value_));
      _owner->update_change_date();

      undo.end(strfmt(_("Set Default Value of '%s.%s'"), _owner->get_name().c_str(), col->name().c_str()));
    }
    return true;
  case CharsetCollation:
    if (old != value)
    {
      std::string charset, collation;
      _owner->parse_charset_collation(value, charset, collation);
      if (charset != *col->characterSetName() || collation != *col->collationName())
      {
        AutoUndoEdit undo(_owner);

        col->characterSetName(charset);
        col->collationName(collation);
        _owner->update_change_date();

        undo.end(strfmt(_("Set Collation/Charset of '%s.%s'"), _owner->get_name().c_str(), col->name().c_str()));
      }
    }
    return true;
  case Charset:
    if (old != value)
    {
      AutoUndoEdit undo(_owner);

      col->characterSetName(value);
      _owner->update_change_date();

      undo.end(
        strfmt(_("Set Character Set of '%s.%s'"), _owner->get_name().c_str(), col->name().c_str()));
    }
    return true;
  case Collation:
    if (old != value)
    {
      AutoUndoEdit undo(_owner);

      col->collationName(value);
      _owner->update_change_date();

      undo.end(
        strfmt(_("Set Collation of '%s.%s'"), _owner->get_name().c_str(), col->name().c_str()));
    }
    return true;
  case HasCharset:
    return false;
  case Comment:
    if (old != value)
    {
      AutoUndoEdit undo(_owner, col, "comment");

      col->comment(value);
      _owner->update_change_date();

      undo.end(strfmt(_("Set Comment of '%s.%s'"), _owner->get_name().c_str(), col->name().c_str()));
    }
    return true;
  case LastColumn:
    return false;
  default:
    return false;
  }
  return false;
}


bool TableColumnsListBE::set_field(const NodeId &node, int column, int value)
{
  RefreshUI::Blocker __centry(*_owner);
  db_ColumnRef col;

  // only entering a new name can create a new column
  if (node[0] == count()-1)
  {
    // hack to get notifications of starting to edit placeholder row
    if (value == 1)
      _editing_placeholder_row= node[0];
    else
      _editing_placeholder_row= -1;
    return false;
  }
  col= _owner->get_table()->columns().get(node[0]);

  switch ((ColumnListColumns)column)
  {
  case Name:
  case Type:
    return false;
  case IsPK:
    if (*_owner->get_table()->isPrimaryKeyColumn(col) != (value != 0))
    {
      AutoUndoEdit undo(_owner);

      if (value != 0)
        _owner->get_table()->addPrimaryKeyColumn(col);
      else
        _owner->get_table()->removePrimaryKeyColumn(col);

      bool nvalue= _owner->get_table()->isPrimaryKeyColumn(col) != 0;

      _owner->update_change_date();

      if (nvalue)
        undo.end(strfmt(_("Set '%s.%s' PK"), _owner->get_name().c_str(), (*col->name()).c_str()));
      else
        undo.end(strfmt(_("Unset '%s.%s' PK"), _owner->get_name().c_str(), (*col->name()).c_str()));
    }
    return true;
  case IsNotNull:
    {
      AutoUndoEdit undo(_owner);
      col->isNotNull(value != 0);

      // When setting the not-null flag then having a default value of NULL is meaningless.
      // Remove that if it is set.
      if (col->defaultValueIsNull())
        bec::ColumnHelper::set_default_value(col, "");
      
      TableHelper::update_foreign_keys_from_column_notnull(_owner->get_table(), col);
      _owner->update_change_date();
      (*_owner->get_table()->signal_refreshDisplay())("column");
      undo.end(strfmt(_("Set '%s.%s' NOT NULL"), _owner->get_name().c_str(), (*col->name()).c_str()));
    }
    return true;
  case IsBinary:
    return set_column_flag(node, "BINARY", value!=0);
  case IsUnsigned:
    return set_column_flag(node, "UNSIGNED", value!=0);
  case IsZerofill:
    return set_column_flag(node, "ZEROFILL", value!=0);
  case IsUnique:
    return make_unique_index(col, value!=0);
  case Flags:
  case Default:
    return false;
  case CharsetCollation:
  case Charset:
  case Collation:
  case Comment:
  case HasCharset:
    return false;
  case LastColumn:
    return false;
  }
  return false;
}


bool TableColumnsListBE::get_field_grt(const NodeId &node, int column, grt::ValueRef &value)
{
  if (node[0] < real_count())
  {
    db_ColumnRef col= _owner->get_table()->columns().get(node[0]);

    switch ((ColumnListColumns)column)
    {
    case Name:
      value= col->name();
      return true;
    case Type:
      value= grt::StringRef(_owner->format_column_type(col));
      return true;
    case IsPK:
      value= grt::IntegerRef(_owner->get_table()->isPrimaryKeyColumn(col) ? 1 : 0);
      return true;
    case IsNotNull:
      value= col->isNotNull();
      return true;
    case IsBinary:
      value= grt::IntegerRef(get_column_flag(node, "BINARY"));
      return true;
    case IsUnsigned:
      value= grt::IntegerRef(get_column_flag(node, "UNSIGNED"));
      return true;
    case IsZerofill:
      value= grt::IntegerRef(get_column_flag(node, "ZEROFILL"));
      return true;
    case IsUnique:
      value= grt::IntegerRef(has_unique_index(col) ? 1 : 0);
      return true;  
    case Flags:
      value= grt::StringRef("");
      return true;
    case Default:
      value= col->defaultValue();
      return true;
    case CharsetCollation:
      value= grt::StringRef(_owner->format_charset_collation(col->characterSetName(), col->collationName()));
      return true;
    case Charset:
      value= col->characterSetName();
      return true;
    case Collation:
      value= col->collationName();
      return true;
    case HasCharset:
      value= grt::IntegerRef(0);
      /* moved to db specific code
      if (col->simpleType().is_valid())
      {
        if (col->simpleType()->group()->name() == "string" || col->simpleType()->group()->name() == "text")
          value= grt::IntegerRef(1);
      }*/
      return true;
    case Comment:
      value= col->comment();
      return true;
    case LastColumn:
      return false;
    }
  }
  else if (node[0] == count()-1)
  {
    if (column == Name && _editing_placeholder_row == node[0])
    {
      if (node[0] == 0)
      {
        value= grt::StringRef(replace_variable(_owner->get_grt_manager()->get_app_option_string("PkColumnNameTemplate"),
                                             "%table%", _owner->get_name().c_str()));
      }
      else
      {
        std::string templ= replace_variable(_owner->get_grt_manager()->get_app_option_string("ColumnNameTemplate"),
                                          "%table%", _owner->get_name().c_str());
        
        value= grt::StringRef(grt::get_name_suggestion_for_list_object(_owner->get_table()->columns(), templ, false));
      }
    }
    else if (column == Type && _editing_placeholder_row == node[0])
    {
      if (node[0] == 0) 
        value= grt::StringRef::cast_from(_owner->get_grt_manager()->get_app_option("DefaultPkColumnType"));
      else
        value= grt::StringRef::cast_from(_owner->get_grt_manager()->get_app_option("DefaultColumnType"));

    }
    else
      value= grt::StringRef("");
    return true;
  }
  return false;
}


/** Returns list of flags that are available for column's type
 */
std::vector<std::string> TableColumnsListBE::get_datatype_flags(const ::bec::NodeId &node, bool all)
{
  db_ColumnRef col;
  std::vector<std::string> retval;

  if (node.is_valid())
  {
    if (node[0] < real_count())
      col= _owner->get_table()->columns().get(node[0]);

    if(!col.is_valid() || !col->simpleType().is_valid())
      return retval;

    grt::StringListRef datatype_flags;

    if (col->simpleType().is_valid())
      datatype_flags= col->simpleType()->flags();
    else if (col->userType().is_valid() && col->userType()->actualType().is_valid())
    {
      // Return flags only for type aliases. UDTs cannot have their types changed.
      // See note at top of file for more about UDTs vs aliases.
      
      // the method to identify a type alias is a hack
      if (g_str_has_prefix(col->userType().id().c_str(), "com.mysql.rdbms.mysql.userdatatype."))
          datatype_flags= col->userType()->actualType()->flags();
    }

    if(!datatype_flags.is_valid())
      return retval;

    size_t flags_count= datatype_flags.count();

    for(size_t i= 0; i < flags_count; i++)
    {
      std::string s= datatype_flags.get(i).c_str();
      if (!all)
      {
        if (s == "UNSIGNED" || s == "ZEROFILL" || s == "BINARY") continue;
      }
      retval.push_back(s);
    }
  }
  return retval;
}


bool TableColumnsListBE::set_column_flag(const ::bec::NodeId &node, const std::string& flag_name, int is_set)
{
  db_ColumnRef col;
  std::vector<std::string> retval;

  if (node.is_valid())
  {
    if (node[0] < real_count())
      col= _owner->get_table()->columns().get(node[0]);

    if(!col.is_valid())
      return false;

    bool found= false;
    grt::StringListRef col_flags= col->flags();
    size_t flags_count= col_flags.count();

    for(size_t i= 0; i < flags_count; i++)
    {
      grt::StringRef existing_flag= col_flags.get(i);
      if(flag_name.compare(existing_flag.c_str()) == 0)
      {
        found= true;
        if(!is_set)
        {
          AutoUndoEdit undo(_owner);

          col_flags.remove(i);
          _owner->update_change_date();
          (*_owner->get_table()->signal_refreshDisplay())("column");
          undo.end(strfmt(_("Unset %s of '%s.%s'"), flag_name.c_str(), _owner->get_name().c_str(), col->name().c_str()));
        }
        break;
      }
    }

    std::vector<std::string> allowed_flags(get_datatype_flags(node, true));
    if (!found && is_set && std::find(allowed_flags.begin(), allowed_flags.end(), flag_name) != allowed_flags.end())
    {
      AutoUndoEdit undo(_owner);

      col_flags.insert(grt::StringRef(flag_name));
      _owner->update_change_date();
      
      (*_owner->get_table()->signal_refreshDisplay())("column");
      
      undo.end(strfmt(_("Set %s of '%s.%s'"), flag_name.c_str(), _owner->get_name().c_str(), col->name().c_str()));
      
      return true;
    }
  }
  return false;
}

int TableColumnsListBE::get_column_flag(const ::bec::NodeId &node, const std::string& flag_name)
{
  db_ColumnRef col;
  std::vector<std::string> retval;

  if (node.is_valid())
  {
    if (node[0] < real_count())
      col= _owner->get_table()->columns().get(node[0]);

    if(!col.is_valid())
      return 0;

    if (col->simpleType().is_valid())
    {
      grt::StringListRef col_flags= col->flags();
      size_t flags_count= col_flags.count();

      for(size_t i= 0; i < flags_count; i++)
      {
        grt::StringRef existing_flag= col_flags.get(i);
        if(g_strcasecmp(flag_name.c_str(), existing_flag.c_str()) == 0)
          return 1;
      }
    }else if (col->userType().is_valid())
      return std::string(col->userType()->flags()).find(flag_name) != std::string::npos;
  }
  return 0;
}


std::string TableColumnsListBE::quote_value_if_needed(const db_ColumnRef &column, const std::string &value)
{
  std::string datatypeName;
  std::string datatypeGroupName;
  
  if (column->userType().is_valid() && column->userType()->actualType().is_valid())
    datatypeName= column->userType()->actualType()->name();
  else if (column->simpleType().is_valid())
  {
    datatypeName= column->simpleType()->name();
    datatypeGroupName= column->simpleType()->group()->name();
  }
  
  // Only quote (var)char and do it only if not already quoted and not given the special values 0/NULL.
  // Non-text types (like datetime) shouldn't be auto-quoted.
  // Using the "text" and "string" groups here includes more than we'd need (e.g. TEXT blobs), but as long
  // as this function is only used for default values (which blobs cannot have) we are fine.
  bool isChar = (g_strcasecmp(datatypeGroupName.c_str(), "string") == 0) || 
                (g_strcasecmp(datatypeGroupName.c_str(), "text") == 0);

  if (isChar && (g_strcasecmp(value.c_str(), "NULL") != 0) && (value != "0") && (value[0] != '\''))
    return std::string("'").append(escape_sql_string(value)).append("'");
  return value;
}

//----------------------------------------------------------------------
MenuItemList TableColumnsListBE::get_popup_items_for_nodes(const std::vector<NodeId> &nodes)
{
  MenuItemList items;
    //struct WBPUBLICBACKEND_PUBLIC_FUNC MenuItem
    //{
    //  std::string caption;
    //  std::string shortcut;
    //  std::string name;
    //  MenuItemType type;
    //
    //  bool enabled;
    //  bool checked;
    //
    //  MenuItem() : type(MenuAction), enabled(true), checked(false) {}
    //};
  MenuItem sep;
  sep.type = MenuSeparator;

  MenuItem item;
  item.caption = "Move Up";
  item.name    = "moveUpToolStripMenuItem";
  item.enabled = nodes.size() == 1;
  items.push_back(item);

  item.caption = "Move Down";
  item.name    = "moveDownToolStripMenuItem";
  item.enabled = nodes.size() == 1;
  items.push_back(item);
  items.push_back(sep);

  item.caption = "Delete Selected Columns";
  item.name    = "deleteSelectedColumnsToolStripMenuItem";
  item.enabled = nodes.size() > 0;
  items.push_back(item);
  items.push_back(sep);

  item.caption = "Refresh";
  item.name    = "refreshGridToolStripMenuItem";
  item.enabled = true;
  items.push_back(item);
  items.push_back(sep);
  
  item.caption = "Clear Default";
  item.name    = "clearDefaultToolStripMenuItem";
  item.enabled = nodes.size() > 0;
  items.push_back(item);

  item.caption = "Default NULL";
  item.name    = "defaultNULLToolStripMenuItem";
  item.enabled = nodes.size() > 0;
  items.push_back(item);
  
  return items;
}


bool TableColumnsListBE::activate_popup_item_for_nodes(const std::string &name, const std::vector<NodeId> &orig_nodes)
{
  std::vector<NodeId> nodes(orig_nodes);
  std::sort(nodes.begin(), nodes.end());
  
  if (name == "moveUpToolStripMenuItem")
  {
    if (nodes.size() == 1)
    {
      int row= nodes.front()[0];
      reorder(nodes.front(), row-1);
    }
  }
  else if (name == "moveDownToolStripMenuItem")
  {
    if (nodes.size() == 1)
    {
      int row= nodes.front()[0];
      reorder(nodes.front(), row+1);
    }    
  }
  else if (name == "deleteSelectedColumnsToolStripMenuItem")
  {
    for (std::vector<NodeId>::reverse_iterator iter= nodes.rbegin();
         iter != nodes.rend(); ++iter)
    {
      delete_node(*iter);
    }
  }
  else if (name == "refreshGridToolStripMenuItem")
  {
    refresh();
    _owner->do_partial_ui_refresh(TableEditorBE::RefreshColumnList);
  }
  else if (name == "clearDefaultToolStripMenuItem")
  {
    AutoUndoEdit undo(_owner);
    bool changed= false;
    
    for (std::vector<NodeId>::reverse_iterator iter= nodes.rbegin();
         iter != nodes.rend(); ++iter)
    {
      if ((*iter)[0] < real_count())
      {
        db_ColumnRef col(_owner->get_table()->columns().get((*iter)[0]));
      
        if (col.is_valid())
        {
          col->defaultValue("");
          col->defaultValueIsNull(0);
          changed= true;
        }
      }
    }
    if (changed)
    {
      undo.end(_("Clear Column Default"));
      _owner->do_partial_ui_refresh(TableEditorBE::RefreshColumnList);
    }
    else
      undo.cancel();
  }
  else if (name == "defaultNULLToolStripMenuItem")
  {
    AutoUndoEdit undo(_owner);
    bool changed= false;
    
    for (std::vector<NodeId>::reverse_iterator iter= nodes.rbegin();
         iter != nodes.rend(); ++iter)
    {
      if ((*iter)[0] < real_count())
      {
        db_ColumnRef col(_owner->get_table()->columns().get((*iter)[0]));
        
        if (col.is_valid())
        {
          col->defaultValue("NULL");
          changed= true;
        }
      }
    }
    if (changed)
    {
      undo.end(_("Set Column Default to NULL"));
      _owner->do_partial_ui_refresh(TableEditorBE::RefreshColumnList);
    }
    else
      undo.cancel();
  }
  else
    return false;
  
  return true;
}


bool TableColumnsListBE::can_delete_node(const NodeId &node)
{
  return node.is_valid() && node[0] < real_count();
}


bool TableColumnsListBE::delete_node(const NodeId &node)
{
  if (!can_delete_node(node))
    return false;
  
  _owner->remove_column(node);
  
  return true;
}

//----------------------------------------------------------------------

IndexColumnsListBE::IndexColumnsListBE(IndexListBE *owner)
  : _owner(owner)
{
}


void IndexColumnsListBE::refresh()
{
}


int IndexColumnsListBE::count()
{
  return (int)_owner->get_owner()->get_table()->columns().count();
}


db_IndexColumnRef IndexColumnsListBE::get_index_column(const db_ColumnRef &column)
{
  if (column.is_valid() && _owner->get_selected_index().is_valid())
  {
    grt::ListRef<db_IndexColumn> index_columns(_owner->get_selected_index()->columns());

    for (size_t c= index_columns.count(), i= 0; i < c; i++)
    {
      // because of the way the index editing works, it's impossible
      // to have 2 index columns refering to the same table column
      if (index_columns[i]->referencedColumn() == column)
        return index_columns[i];
    }
  }
  return db_IndexColumnRef();
}


int IndexColumnsListBE::get_index_column_index(const db_ColumnRef &column)
{
  if (column.is_valid() && _owner->get_selected_index().is_valid())
  {
    grt::ListRef<db_IndexColumn> index_columns(_owner->get_selected_index()->columns());

    for (size_t c= index_columns.count(), i= 0; i < c; i++)
    {
      // because of the way the index editing works, it's impossible
      // to have 2 index columns refering to the same table column
      if (index_columns[i]->referencedColumn() == column)
        return (int)i;
    }
  }
  return -1;
}


void IndexColumnsListBE::set_index_column_order(const db_IndexColumnRef &column, int order)
{
  grt::ListRef<db_IndexColumn> index_columns(_owner->get_selected_index()->columns());
  size_t index= index_columns.get_index(column); // Check, we may get a BaseListRef::npos (-1) index

  g_return_if_fail(index != grt::BaseListRef::npos);
  index_columns.reorder(index, order);
}


void IndexColumnsListBE::set_column_enabled(const NodeId &node, bool flag)
{
  if (get_column_enabled(node) != flag)
  {
    if (flag)
      _owner->add_column(_owner->get_owner()->get_table()->columns()[node[0]]);
    else
      _owner->remove_column(node);
  }
}


bool IndexColumnsListBE::get_column_enabled(const NodeId &node)
{
  db_ColumnRef column(_owner->get_owner()->get_table()->columns()[node[0]]);
  return get_index_column(column).is_valid();
}


int IndexColumnsListBE::get_max_order_index()
{
  int order = 0;

  if ( _owner )
  {
    const db_IndexRef index = _owner->get_selected_index();

    if ( index.is_valid() )
      order = (int)index->columns().count();
  }

  return order;
}


bool IndexColumnsListBE::set_field(const NodeId &node, int column, int value)
{
  db_IndexColumnRef icolumn;

  if (node[0] >= count())
    return false;

  if (!_owner->index_editable(_owner->get_selected_index())
      && column != OrderIndex) // index order can be changed for PKs
        return false;

  icolumn= get_index_column(_owner->get_owner()->get_table()->columns()[node[0]]);

  switch (column)
  {
  case Name:
    return false;
  case Descending:
    if (icolumn.is_valid())
    {
      AutoUndoEdit undo(_owner->get_owner());

      set_column_enabled(node, true);
      icolumn->descend(value!=0);
      _owner->get_owner()->update_change_date();

      undo.end(strfmt(_("Set Storage Order of Index Column '%s.%s.%s'"),
        _owner->get_owner()->get_name().c_str(), _owner->get_selected_index()->name().c_str(), icolumn->name().c_str()));
    }
    return true;
  case Length:
    if (icolumn.is_valid())
    {
      AutoUndoEdit undo(_owner->get_owner());

      icolumn->columnLength(value);
      _owner->get_owner()->update_change_date();

      undo.end(
        strfmt(_("Set Length of Index Column '%s.%s.%s'"),
          _owner->get_owner()->get_name().c_str(), _owner->get_selected_index()->name().c_str(), icolumn->name().c_str()));

    }
    return true;
  case OrderIndex:
    if (icolumn.is_valid())
    {
      if (value >= 1 && value <= get_max_order_index())
      {
        AutoUndoEdit undo(_owner->get_owner());

        set_index_column_order(icolumn, value-1);
        _owner->get_owner()->update_change_date();

        undo.end(
          strfmt(_("Reorder for Index Column '%s.%s.%s'"),
          _owner->get_owner()->get_name().c_str(), _owner->get_selected_index()->name().c_str(), icolumn->name().c_str()));
      }
    }
    return true;
  }
  return false;
}


bool IndexColumnsListBE::set_field(const NodeId &node, int column, const std::string &value)
{
  db_IndexColumnRef icolumn;

  if (node[0] >= count())
    return false;

  if (!_owner->index_editable(_owner->get_selected_index())
      && column != OrderIndex) // index order can be changed for PKs
    return false;

  switch (column)
  {
  case Name:
  case Descending:
  case Length:
    return false;
  case OrderIndex:
    {
      int order= 0;
      if (sscanf(value.c_str(), "%i", &order) != 1)
        return false;

      return set_field(node, column, order);
    }
  }
  return false;
}


bool IndexColumnsListBE::get_field_grt(const NodeId &node, int column, grt::ValueRef &value)
{
  db_TableRef table= _owner->get_owner()->get_table();
  db_ColumnRef dbcolumn;

  if (node[0] < (int)table->columns().count())
    dbcolumn= table->columns()[node[0]];

  switch (column)
  {
  case Name:
    {
      if (dbcolumn.is_valid())
        value= dbcolumn->name();
      else
        value= grt::StringRef("");
    }
    return true;
  case Descending:
    {
      db_IndexColumnRef icolumn= get_index_column(dbcolumn);

      if (icolumn.is_valid())
        value= icolumn->descend();
      else
        value= grt::IntegerRef(0);
    }
    return true;
  case Length:
    {
      db_IndexColumnRef icolumn= get_index_column(dbcolumn);

      if (icolumn.is_valid())
        value= icolumn->columnLength();
      else
        value= grt::IntegerRef(0);
    }
    return true;
  case OrderIndex:
    {
      int i= get_index_column_index(dbcolumn);

      if (i < 0)
        value= grt::StringRef("");
      else
        value= grt::StringRef(strfmt("%i", i+1));
      return true;
    }
  }
  return false;
}

//----------------------------------------------------------------------

#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable:4355) // 'this' : used in base member initializer list
                              // that's ok as we just store pointer for later use
#endif
IndexListBE::IndexListBE(TableEditorBE *owner)
  : _column_list(this), _owner(owner)
{
}
#ifdef _MSC_VER
#pragma warning(pop)
#endif

db_IndexRef IndexListBE::get_selected_index()
{
  if (_selected.is_valid() && _selected[0] < real_count())
    return _owner->get_table()->indices().get(_selected[0]);
  return db_IndexRef();
}


bool IndexListBE::index_editable(const db_IndexRef &index)
{
  if (index.is_valid())
  {
    // check if index is PRIMARY (primary key index) or FOREIGN (fk index)
    // FK indices can now have the type changed, as they dont depend on indexType for 
    // identifying them as FK index anymore --alfredo 10/04/16
    if (strcmp(index->indexType().c_str(), "PRIMARY") == 0)
      return false;
  }
  return true;
}


bool IndexListBE::index_belongs_to_fk(const db_IndexRef &index)
{
  // check if the index belongs to a FK, if so it cannot be edited
  if (index.is_valid())
  {
    ListRef<db_ForeignKey> fks(db_TableRef::cast_from(index->owner())->foreignKeys());
    for (size_t c= fks.count(), i= 0; i < c; i++)
      if (fks[i]->index() == index)
        return true;
  }
  return false;
}


MenuItemList IndexListBE::get_popup_items_for_nodes(const std::vector<NodeId> &nodes)
{
  db_IndexRef index(get_selected_index());
  
  MenuItemList items;
  MenuItem item;
  item.caption = _("Delete Selected Indices");
  item.name    = "deleteIndices";
  item.enabled = nodes.size() > 0 && !index_belongs_to_fk(index) && index_editable(index);
  items.push_back(item);

  return items;
}

bool IndexListBE::activate_popup_item_for_nodes(const std::string &name, const std::vector<NodeId> &unsorted_nodes)
{
  std::vector<NodeId> nodes(unsorted_nodes);
  std::sort(nodes.begin(), nodes.end());
  
  if (name == "deleteIndices")
  {
    for (std::vector<NodeId>::reverse_iterator node= nodes.rbegin(); node != nodes.rend(); ++node)
      _owner->remove_index(*node);
  }
  else
    return false;
  
  return true;
}


bool IndexListBE::can_delete_node(const NodeId &node)
{
  return node.is_valid() && node[0] < real_count();
}


bool IndexListBE::delete_node(const NodeId &node)
{
  if (!can_delete_node(node))
    return false;
  
  _owner->remove_index(node);
  
  return true;
}


NodeId IndexListBE::add_column(const db_ColumnRef &column, const db_IndexRef &aIndex)
{
  db_IndexRef index= aIndex.is_valid() ? aIndex : get_selected_index();

  if (!index.is_valid())
    return NodeId();

  if (!index_editable(index) || index_belongs_to_fk(index))
    return NodeId();

  std::string column_struct= index.get_metaclass()->get_member_type("columns").content.object_class;
  db_IndexColumnRef icolumn=
    _owner->get_grt()->create_object<db_IndexColumn>(column_struct);
  icolumn->owner(index);
  icolumn->referencedColumn(column);

  AutoUndoEdit undo(_owner);

  index->columns().insert(icolumn);
  _owner->update_change_date();

  undo.end(
      strfmt(_("Add column '%s' to index '%s.%s'"), 
        column->name().c_str(), _owner->get_name().c_str(), index->name().c_str()));

  _column_list.refresh();
  return NodeId(index->columns().count()-1);
}


void IndexListBE::remove_column(const NodeId &node)
{
  RefreshUI::Blocker __centry(*_owner);

  db_IndexRef index= get_selected_index();
  if (!index_editable(index) || index_belongs_to_fk(index))
    return;

  db_ColumnRef column(_owner->get_table()->columns().get(node[0]));

  for (size_t c= index->columns().count(), i= 0; i < c; i++)
  {
    if (index->columns().get(i)->referencedColumn() == column)
    {
      AutoUndoEdit undo(_owner);

      index->columns().remove(i);
      _owner->update_change_date();

      undo.end(
        strfmt(_("Remove column '%s' from index '%s.%s'"), 
            column->name().c_str(), _owner->get_name().c_str(), index->name().c_str()));
      _column_list.refresh();
      break;
    }
  }
}


void IndexListBE::refresh()
{
}


int IndexListBE::count()
{
  return real_count()+1;
}


int IndexListBE::real_count()
{
  return (int)_owner->get_table()->indices().count();
}


bool IndexListBE::set_field(const NodeId &node, int column, const std::string &value)
{
  if (!node.is_valid())
    return false;

  db_IndexRef index;
  std::vector<std::string> index_types;

  //if (node[0] == count() && column == Name)
  if (node[0] == real_count()) // a field added for new index placeholder
  {
    if (value.empty()) return false;
    if (column == Name)
    {
      _owner->add_index(value);
      return true;
    }
    else
      _owner->add_index(strfmt("index%i", count()).c_str());
  }

  index= _owner->get_table()->indices().get(node[0]);

  if (!index_editable(index))
    return false;

  switch (column)
  {
  case Name:
    if (index->name() != value)
    {
      AutoUndoEdit undo(_owner, index, "name");
      index->name(value);
      _owner->update_change_date();
      undo.end(strfmt(_("Rename Index '%s.%s'"), _owner->get_name().c_str(), index->name().c_str()));
      bec::ValidationManager::validate_instance(index, CHECK_NAME);
    }
    return true;
  case Type:
    index_types= _owner->get_index_types();
    if (std::find(index_types.begin(), index_types.end(), value) == index_types.end())
      return false;
    // fk indices or primary indices cant be created by user
    if (value == "PRIMARY")
      return false;
    if (index->indexType() != value)
    {
      AutoUndoEdit undo(_owner);

      index->indexType(value);
      index->unique(value == "UNIQUE");
      _owner->update_change_date();

      undo.end(strfmt(_("Set Type of Index '%s.%s'"), _owner->get_name().c_str(), index->name().c_str()));
    }
    return true;
  case Comment:
    if (index->comment() != value)
    {
      AutoUndoEdit undo(_owner, index, "comment");

      index->comment(value);
      _owner->update_change_date();

      undo.end(strfmt(_("Set Comment of Index '%s.%s'"), _owner->get_name().c_str(), index->name().c_str()));
    }
    return true;
  }
  return false;
}


bool IndexListBE::get_field_grt(const NodeId &node, int column, grt::ValueRef &value)
{
  db_IndexRef index;

  if (node[0] < real_count())
    index= _owner->get_table()->indices().get(node[0]);

  switch (column)
  {
  case Name:
    if (node[0] < real_count())
      value= index->name();
    else
      value= grt::StringRef("");
    return true;
  case Type:
    if (node[0] < real_count())
      value= index->indexType();
    else
      value= grt::StringRef("");
    return true;
  case Comment:
    if (node[0] < real_count())
      value= index->comment();
    else
      value= grt::StringRef("");
    return true;
  }
  return false;
}


void IndexListBE::select_index(const NodeId &node)
{
  _selected= node;
  _column_list.refresh();
}


//----------------------------------------------------------------------


FKConstraintColumnsListBE::FKConstraintColumnsListBE(FKConstraintListBE *owner)
  : _owner(owner)
{
}


void FKConstraintColumnsListBE::refresh()
{
  _referenced_columns.clear();
  
  db_ForeignKeyRef fk(_owner->get_selected_fk());
  if (fk.is_valid())
  {
    for (size_t i= fk->columns().count(); i >= 1; --i)
    {
      db_ColumnRef column(fk->columns()[i-1]);
      bool ok= false;
      if (column.is_valid() && i-1 < fk->referencedColumns().count())
      {
        db_ColumnRef refcolumn(fk->referencedColumns()[i-1]);
        if (refcolumn.is_valid())
          ok= true;
        _referenced_columns[column.id()]= refcolumn;
      }
      
      if (!ok)
      {
        // invalid! remove this entry
        fk->columns().remove(i-1);
        if (i-1 < fk->referencedColumns().count())
          fk->referencedColumns().remove(i-1);
        
        _owner->get_owner()->get_grt()->make_output_visible();
        _owner->get_owner()->get_grt()->send_warning("Removed corrupt column definition for Foreign Key "+*fk->name());
      }
    }
  }
}

static std::set<std::string> get_indexed_column_ids(const db_TableRef &table)
{
  std::set<std::string> set;
  for (size_t c = table->indices().count(), i = 0; i < c; i++)
  {
    db_IndexRef index(table->indices()[i]);
    for (size_t cc = index->columns().count(), j= 0; j < cc; j++)
      set.insert(index->columns()[j]->referencedColumn().id());
  }
  return set;
}

/** Return list of candidate referenced column names for the FK.
 *
 * @param node of the source column in the FK column list.
 * @return List of possible column names.
 */
std::vector<std::string> FKConstraintColumnsListBE::get_ref_columns_list(const NodeId &node)
{
  db_ForeignKeyRef fk(_owner->get_selected_fk());
  db_ColumnRef srccolumn(_owner->get_owner()->get_table()->columns()[node[0]]);

  if (fk.is_valid() && fk->referencedTable().is_valid())
  {
    std::vector<std::string> names, names2;
    db_TableRef table(fk->referencedTable());
    grt::ListRef<db_Column> columns(table->columns());
    std::set<std::string> indexed_column_ids(get_indexed_column_ids(table));

    for (size_t c= columns.count(), i= 0; i < c; i++)
    {      
      if (_owner->get_owner()->check_column_referenceable_by_fk(columns[i], srccolumn))
      {
        // column must have index to be in a Fk
        if (table->isPrimaryKeyColumn(columns[i]))
          names.push_back(columns[i]->name());
        else if (indexed_column_ids.find(columns[i]->id()) != indexed_column_ids.end())
          names2.push_back(columns[i]->name());
      }
    }
    names.insert(names.end(), names2.begin(), names2.end());

    return names;
  }

  return std::vector<std::string>();
}


int FKConstraintColumnsListBE::count()
{
  if (_owner->get_selected_fk().is_valid())
    return (int)_owner->get_owner()->get_table()->columns().count();
  return 0;
}


bool FKConstraintColumnsListBE::set_fk_column_pair(const db_ColumnRef &column, const db_ColumnRef &refcolumn)
{
  _referenced_columns[column.id()]= refcolumn;
  
  db_ForeignKeyRef fk(_owner->get_selected_fk());

  AutoUndoEdit undo(_owner->get_owner());
  bool changed_real_fk= false;
  
  // check if the srccolumn is already in FK
  size_t index= fk->columns().get_index(column);
  if (index != grt::BaseListRef::npos)
  {
    if (!refcolumn.is_valid())
    {
      // remove real
      size_t table_column_index= _owner->get_owner()->get_table()->columns().get_index(column);
      if (table_column_index != grt::BaseListRef::npos)
      {
        _owner->remove_column((int)table_column_index);
        changed_real_fk= true;
      }
    }
    else
    {
      // update real
      fk->referencedColumns().set(index, refcolumn);
      changed_real_fk= true;
    }
  }
  else
  {
    // if everything is ok, create it for real
    if (column.is_valid() && refcolumn.is_valid())
    {
      _owner->add_column(column, refcolumn, fk);  
      changed_real_fk= true;
    }
  }
  
  if (changed_real_fk)
  {
    _owner->get_owner()->update_change_date();
    undo.end(strfmt(_("Set Ref. Column for FK '%s.%s'"), 
                    _owner->get_owner()->get_name().c_str(), fk->name().c_str()));
  }
  else
    undo.cancel();
  
  return changed_real_fk;
}


bool FKConstraintColumnsListBE::set_column_is_fk(const NodeId &node, bool flag)
{
  if (get_column_is_fk(node) != flag)
  {
    if (flag)
    {
      // disallow 
      if (flag && get_ref_columns_list(node).empty())
        return false;
      
      // guess some column from the ref table
      db_ForeignKeyRef fk(_owner->get_selected_fk());

      //std::string type;
      db_ColumnRef srccolumn(_owner->get_owner()->get_table()->columns()[node[0]]);
      db_ColumnRef refcolumn;

      //type= ColumnHelper::format_column_type(srccolumn, 0, true);

      if (fk.is_valid() && fk->referencedTable().is_valid())
      {
        db_TableRef table(fk->referencedTable());
        grt::ListRef<db_Column> columns(table->columns());

        for (size_t c= columns.count(), i= 0; i < c; i++)
        {
          if (srccolumn != columns[i])
          {
            //if (ColumnHelper::format_column_type(columns[i], 0, true) == type)
            if (_owner->get_owner()->check_column_referenceable_by_fk(columns[i], srccolumn))
            {
              if (table->isPrimaryKeyColumn(columns[i]))
              {
                refcolumn= columns[i];
                break;
              }
              else
              {
                if (!refcolumn.is_valid())
                  refcolumn= columns[i];
              }
            }
          }
        }
      }

      // create it virtually only unless whole definition is valid
      set_fk_column_pair(srccolumn, refcolumn);
    }
    else
    {
      db_ColumnRef srccolumn(_owner->get_owner()->get_table()->columns()[node[0]]);
      
      // remove it virtually
      if (_referenced_columns.find(srccolumn.id()) != _referenced_columns.end())
        _referenced_columns.erase(srccolumn.id());
      
      _owner->remove_column(node);
    }
    return true;
  }
  return false;
}


int FKConstraintColumnsListBE::get_fk_column_index(const NodeId &node)
{
  db_TableRef table= _owner->get_owner()->get_table();
  db_ForeignKeyRef fk(_owner->get_selected_fk());

  if (fk.is_valid() && node[0] < (int)table->columns().count())
  {
    db_ColumnRef column= table->columns()[node[0]];

    // find index in list of columns for the FK
    for (size_t c= fk->columns().count(), i= 0; i < c; i++)
    {
      if (fk->columns().get(i) == column)
        return (int)i;
    }
  }
  return -1;
}


bool FKConstraintColumnsListBE::get_column_is_fk(const NodeId &node)
{
  if (node[0] < (int) _owner->get_owner()->get_table()->columns().count())
  {
    db_ColumnRef srccolumn(_owner->get_owner()->get_table()->columns()[node[0]]);

    return get_fk_column_index(node) >= 0 || _referenced_columns.find(srccolumn.id()) != _referenced_columns.end();
  }
  return false;
}


bool FKConstraintColumnsListBE::set_field(const NodeId &node, int column, const std::string &value)
{
  db_ForeignKeyRef fk(_owner->get_selected_fk());
  db_ColumnRef tcolumn;

  switch (column)
  {
  case Enabled:
    return false;

  case Column:
    // this is not changeable
    return false;

  case RefColumn:
    if (fk.is_valid() && fk->referencedTable().is_valid())
    {
      int index;
      
      // check if column is acceptable
      std::vector<std::string> allowed_list = get_ref_columns_list(node);
      if (std::find(allowed_list.begin(), allowed_list.end(), value) == allowed_list.end())
        return false;

      tcolumn= grt::find_named_object_in_list(fk->referencedTable()->columns(), value);

      index= get_fk_column_index(node);

      // if columns is not enabled yet, enable it
      if (index < 0)
      {
        set_field(node, Enabled, 1);
        index= get_fk_column_index(node);
        if (index < 0)
          return false;
      }

      set_fk_column_pair(fk->columns()[index], tcolumn);
    }
    return true;
  }
  return false;
}


bool FKConstraintColumnsListBE::set_field(const NodeId &node, int column, int value)
{
  db_ForeignKeyRef fk(_owner->get_selected_fk());
  db_ColumnRef tcolumn;

  switch (column)
  {
  case Enabled:
    if (fk.is_valid())
    {
      AutoUndoEdit undo(_owner->get_owner());
      if (set_column_is_fk(node, value!=0))
      {
        _owner->get_owner()->update_change_date();
        undo.end(value ? 
                 strfmt(_("Add Column to FK '%s.%s'"), _owner->get_owner()->get_name().c_str(), fk->name().c_str()) :
                 strfmt(_("Remove Column from FK '%s.%s'"), _owner->get_owner()->get_name().c_str(), fk->name().c_str()));
      }
      else
        undo.cancel();
      return true;
    }
    return false;

  case Column:
    // this is not changeable
    return false;

  case RefColumn:
    return false;
  }
  return false;
}


bool FKConstraintColumnsListBE::get_field_grt(const NodeId &node, int column,
                                              grt::ValueRef &value)
{
  switch (column)
  {
  case Enabled:
    value= grt::IntegerRef(get_column_is_fk(node)?1:0);
    return true;

  case Column:
    if (node[0] == count())
      value= grt::StringRef("");
    else
      value= _owner->get_owner()->get_table()->columns()[node[0]]->name();
    return true;

  case RefColumn:
    {
      db_ForeignKeyRef fk(_owner->get_selected_fk());
      db_ColumnRef tcolumn;
      int index= get_fk_column_index(node);

      if (fk.is_valid() && index >= 0 && index < (int)fk->referencedColumns().count())
      {
        tcolumn= fk->referencedColumns().get(index);
        if (tcolumn.is_valid())
          value= tcolumn->name();
        else
          value= grt::StringRef("");
      }
      else
        value= grt::StringRef("");
    }
    return true;
  }
  return false;
}


//----------------------------------------------------------------------
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable:4355) // 'this' : used in base member initializer list
                              // that's ok as we just store pointer for later use
#endif
FKConstraintListBE::FKConstraintListBE(TableEditorBE *owner)
  : _column_list(this), _owner(owner), _editing_placeholder_row(-1)
{
}
#ifdef _MSC_VER
#pragma warning(pop)
#endif

NodeId FKConstraintListBE::add_column(const db_ColumnRef &column, const db_ColumnRef &refcolumn,
                                      const db_ForeignKeyRef &aFk)
{
  db_ForeignKeyRef fk= aFk.is_valid() ? aFk : get_selected_fk();

  if (fk.is_valid())
  {
    AutoUndoEdit undo(_owner);

    fk->columns().insert(column);
    fk->referencedColumns().insert(refcolumn);

    TableHelper::update_foreign_key_index(fk.get_grt(), fk); //!

    _owner->update_change_date();

    undo.end(
      strfmt(_("Add Column to FK '%s.%s'"), _owner->get_name().c_str(), fk->name().c_str()));

    _column_list.refresh();

    // force a refresh of the columns list in the table figure
    //not needed anymore ((db_ColumnRef)column)->name(column->name());

    // force refresh of relationships
    //not needed anymore fk->referencedTable(fk->referencedTable());

    return NodeId(fk->columns().count()-1);
  }
  return NodeId();
}


void FKConstraintListBE::remove_column(const NodeId &node)
{
  db_ForeignKeyRef fk= get_selected_fk();

  db_ColumnRef column(_owner->get_table()->columns().get(node[0]));

  const size_t i= fk->columns().get_index(column);
  if (i == BaseListRef::npos)
  {
    // column is not set
    return;
  }

  AutoUndoEdit undo(_owner);

  fk->columns().remove(i);
  if (fk->referencedColumns().count() > i)
  {
    fk->referencedColumns().remove(i);
  }
  TableHelper::update_foreign_key_index(fk.get_grt(), fk);//!

  _owner->update_change_date();
  undo.end(
    strfmt(_("Remove Column From FK '%s.%s'"), _owner->get_name().c_str(), fk->name().c_str()));

  _column_list.refresh();

  // force a refresh of the columns list in the table figure
  //not needed anymore column->name(column->name());
}


void FKConstraintListBE::select_fk(const NodeId &node)
{
  _selected_fk= node;
  if (_owner->is_editing_live_object())
  {
    db_ForeignKeyRef fkey= get_selected_fk();
    if (fkey.is_valid())
    {
      db_TableRef ref_table= fkey->referencedTable();
      if (ref_table.is_valid())
      {
        std::string schema_name= *ref_table->owner()->name();
        std::string ref_table_name= *ref_table->name();
        _owner->on_expand_live_table_stub(_owner, schema_name, ref_table_name);
      }
    }
  }
  _column_list.refresh();
}


db_ForeignKeyRef FKConstraintListBE::get_selected_fk()
{
  if (_selected_fk.is_valid() && _selected_fk[0] < real_count())
    return _owner->get_table()->foreignKeys().get(_selected_fk[0]);
  else
    return db_ForeignKeyRef();
}


void FKConstraintListBE::refresh()
{
}


int FKConstraintListBE::count()
{
  return real_count()+1;
}


int FKConstraintListBE::real_count()
{
  return (int)_owner->get_table()->foreignKeys().count();
}


bool FKConstraintListBE::set_field(const NodeId &node, int column, int value)
{
  db_ForeignKeyRef fk;

  if (node[0] == count() - 1)
    _editing_placeholder_row = node[0];
  else
    _editing_placeholder_row = -1;

  if (node[0] >= real_count())
    return false;
  
  fk= _owner->get_table()->foreignKeys().get(node[0]);

  switch (column)
  {
    case ModelOnly:
      if ((*fk->modelOnly()!=0) != (value!=0))
      {
         AutoUndoEdit undo(_owner, fk, "modelOnly");
         fk->modelOnly(value!=0);
         undo.end(strfmt(_("Toggle SQL generation for '%s.%s'"), _owner->get_name().c_str(), fk->name().c_str()));
      }
      return true;
    default:
      break;
  }
  return false;
}

bool FKConstraintListBE::set_field(const NodeId &node, int column, const std::string &value)
{
  db_ForeignKeyRef fk;

  if (node[0] == real_count() && column == Name)
  {
    if (value.empty()) return false;
    _editing_placeholder_row = -1;
    _owner->add_fk(value);
    return true;
  }
  else if (node[0] >= real_count())
    return false;

  fk= _owner->get_table()->foreignKeys().get(node[0]);

  switch (column)
  {
  case Name:
    if (fk->name() != value)
    {
      AutoUndoEdit undo(_owner, fk, "name");
      TableHelper::rename_foreign_key(_owner->get_table(), fk, value);
      _owner->update_change_date();
      undo.end(_("Rename FK"));
    }
    return true;
  case OnDelete:
    if (fk->deleteRule() != value)
    {
      AutoUndoEdit undo(_owner);

      fk->deleteRule(value);
      _owner->update_change_date();

      undo.end(
        strfmt(_("Change ON DELETE for FK '%s.%s'"), _owner->get_name().c_str(), fk->name().c_str()));
    }
    return true;
  case OnUpdate:
    if (fk->updateRule() != value)
    {
      AutoUndoEdit undo(_owner);

      fk->updateRule(value);
      _owner->update_change_date();

      undo.end(
        strfmt(_("Change ON UPDATE for FK '%s.%s'"), _owner->get_name().c_str(), fk->name().c_str()));
    }
    return true;
  case RefTable:
    {
      std::vector<std::string> parts= base::split_qualified_identifier(value);
      std::string table;
      db_SchemaRef schema;

      if (!parts.empty())
      {
        if (value == "Specify Manually...")
        {
          std::string table_name;
          if (mforms::Utilities::request_input(_("Specify Referenced Table Manually"), _("Qualified Table Name:"),
            "", table_name))
          {
            parts= base::split_qualified_identifier(table_name);
          }
          else
          {
            parts.clear();
          }
          if (parts.empty())
            return false;
        }

        if (_owner->is_editing_live_object())
        {
          if (parts.size()==1)
            parts.insert(parts.begin(), *_owner->get_schema()->name());
          if (!_owner->on_expand_live_table_stub(_owner, parts[0], parts[1]))
            return false;
        }

        if (parts.size()==1)
        {
          table= parts[0];
          schema= _owner->get_schema();
        }
        else
        {
          table= parts[1];
          schema= _owner->get_schema_with_name(parts[0]);
        }

        if (!schema.is_valid())
          return false;

        db_TableRef dbtable= grt::find_named_object_in_list(schema->tables(), table, "name");

        if (dbtable.is_valid() && dbtable != fk->referencedTable())
        {
          AutoUndoEdit undo(_owner);

          fk->referencedTable(dbtable);
          
          // reset the currently set columns
          fk->columns().remove_all();
          fk->referencedColumns().remove_all();
          
          get_columns()->refresh();

          TableHelper::update_foreign_key_index(fk.get_grt(), fk); //!

          _owner->update_change_date();
          bec::ValidationManager::validate_instance(_owner->get_table(), "chk_fk_lgc");
          bec::ValidationManager::validate_instance(dbtable, "chk_fk_lgc");

          undo.end(strfmt(_("Change Ref. Table for FK '%s.%s'"), _owner->get_name().c_str(), fk->name().c_str()));
        }
      }
    }
    return true;
  case Comment:
    if (fk->comment() != value)
    {
      AutoUndoEdit undo(_owner, fk, "comment");

      fk->comment(value);
      _owner->update_change_date();

      undo.end(strfmt(_("Change Comment for FK '%s.%s'"), _owner->get_name().c_str(), fk->name().c_str()));
    }
    return true;
      
    case Index:
      // cannot change index, its auto-created/maintained
      return false;
    case ModelOnly: // handled as int
      return false;
  }
  return false;
}


bool FKConstraintListBE::get_field_grt(const NodeId &node, int column, grt::ValueRef &value)
{
  db_ForeignKeyRef fk;

  if (node[0] < real_count())
    fk= _owner->get_table()->foreignKeys().get(node[0]);

  switch (column)
  {
  case Name:
    if (fk.is_valid())
      value= fk->name();
    else if (_editing_placeholder_row == node[0])
    {
      std::string temp = replace_string(_owner->get_grt_manager()->get_app_option_string("FKNameTemplate"),
                                                    "%stable%", _owner->get_name().c_str());

      value = grt::StringRef(get_name_suggestion_for_list_object(_owner->get_table()->foreignKeys(), replace_string(temp, "%dtable%", ""), true));
    }
    else 
      value= grt::StringRef("");
    return true;
  case OnDelete:
    if (fk.is_valid())
    {
      if (fk->deleteRule().empty())
        value = grt::StringRef("RESTRICT"); // empty means default means RESTRICT
      else
        value= fk->deleteRule();
    }
    else
      value= grt::StringRef("");
    return true;
  case OnUpdate:
    if (fk.is_valid())
    {
      if (fk->updateRule().empty())
        value = grt::StringRef("RESTRICT"); // empty means default means RESTRICT
      else
        value= fk->updateRule();
    }
    else
      value= grt::StringRef("");
    return true;
  case RefTable:
    if (fk.is_valid() && fk->referencedTable().is_valid())
      value= grt::StringRef("`" + *fk->referencedTable()->owner()->name() + "`.`" + *fk->referencedTable()->name() + "`");
    else
      value= grt::StringRef("");
    return true;
  case Comment:
    if (fk.is_valid())
      value= fk->comment();
    else
      value= grt::StringRef("");
    return true;
  case Index:
    if (fk.is_valid())
    {
      if (fk->index().is_valid())
        value = fk->index()->name();
      else
        value = grt::StringRef(""); // a FK with no index means it just didn't have one created for it
        //value = grt::StringRef("INVALID");
    }
    else
      value = grt::StringRef("");
    return true;
  case ModelOnly:
    if (fk.is_valid())
    {
      value= grt::IntegerRef(*fk->modelOnly());
      return true;
    }
  }
  return false;
}


bool FKConstraintListBE::can_delete_node(const NodeId &node)
{
  return node.is_valid() && node[0] < real_count();
}


bool FKConstraintListBE::delete_node(const NodeId &node)
{
  if (!can_delete_node(node))
    return false;
  
  _owner->remove_fk(node);
  
  return true;
}


MenuItemList FKConstraintListBE::get_popup_items_for_nodes(const std::vector<NodeId> &nodes)
{
  MenuItemList items;

  MenuItem item;
  item.caption = _("Delete selected FKs");
  item.name    = "deleteSelectedFKs";
  item.enabled = (nodes.size() >= 1);
  items.push_back(item);

  return items;
}


bool FKConstraintListBE::activate_popup_item_for_nodes(const std::string &name, const std::vector<NodeId>& nodes)
{
  std::vector<NodeId> sorted_nodes(nodes);
  std::sort(sorted_nodes.begin(), sorted_nodes.end());

  if (name == "deleteSelectedFKs")
  {
    for (int i = sorted_nodes.size() - 1; i >= 0; --i)
      delete_node(sorted_nodes[i]);
  }
  else
    return false;

  return true;
}


//----------------------------------------------------------------------

#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable:4355) // 'this' : used in base member initializer list
                              // that's ok as we just store pointer for later use
#endif
TableEditorBE::TableEditorBE(GRTManager *grtm, const db_TableRef &table, const db_mgmt_RdbmsRef &rdbms)
:
DBObjectEditorBE(grtm, table, rdbms),
_table(table),
_fk_list(this)
{
  if (table.class_name() == "db.Table") throw std::logic_error("table object is abstract");
  
  scoped_connect(table->signal_refreshDisplay(),(boost::bind(&BaseEditor::on_object_changed, this)));
}
#ifdef _MSC_VER
#pragma warning(pop)
#endif


void TableEditorBE::set_name(const std::string &name)
{
  if (name != get_name())
  {
    RefreshUI::Blocker __centry(*this);

    AutoUndoEdit undo(this, get_object(), "name");
    bec::ValidationManager::validate_instance(_table, CHECK_NAME);
    std::string name_= rtrim(name);
    //DBObjectEditorBE::set_name(name_);
    get_dbobject()->name(name_);
    update_change_date();
    undo.end(strfmt(_("Rename Table to '%s'"), name_.c_str()));
  }
}


NodeId TableEditorBE::add_column(const std::string &name)
{
  db_ColumnRef column;

  column= get_grt()->create_object<db_Column>(get_table().get_metaclass()->get_member_type("columns").content.object_class);

  column->name(name);
  
  column->owner(get_table());

  AutoUndoEdit undo(this);

  get_table()->addColumn(column);

  update_change_date();

  undo.end(strfmt(_("Add Column '%s' to '%s'"), name.c_str(), get_name().c_str()));

  get_columns()->refresh();

  bec::ValidationManager::validate_instance(column, CHECK_NAME);
  bec::ValidationManager::validate_instance(_table, "columns-count");
  return NodeId(get_table()->columns().count()-1);
}


db_ColumnRef TableEditorBE::get_column_with_name(const std::string &name)
{
  return grt::find_named_object_in_list<db_Column>(get_table()->columns(), name, "name");
}


void TableEditorBE::rename_column(const db_ColumnRef &column, const std::string &name)
{
  std::string old_name= column->name();

  AutoUndoEdit undo(this);

  ((db_ColumnRef)column)->name(name);
  update_change_date();

  undo.end(strfmt(_("Rename '%s.%s' to '%s'"), get_name().c_str(), old_name.c_str(), name.c_str()));
  bec::ValidationManager::validate_instance(column, CHECK_NAME);
}


void TableEditorBE::remove_column(const NodeId &node)
{
  db_TableRef table = get_table();
  if (node[0] >= (int)table->columns().count())
    return;

  db_ColumnRef column= get_table()->columns()[node[0]];

  AutoUndoEdit undo(this);

  table->removeColumn(column);

  undo.end(strfmt(_("Remove '%s.%s'"), get_name().c_str(), column->name().c_str()));

  get_columns()->refresh();
  bec::ValidationManager::validate_instance(_table, "columns-count");
}


NodeId TableEditorBE::add_fk(const std::string &name)
{
  ListRef<db_ForeignKey> fklist= get_table()->foreignKeys();
  db_ForeignKeyRef fk;

  AutoUndoEdit undo(this);

  fk= TableHelper::create_empty_foreign_key(get_grt(), get_table(), name);

  fk->updateRule(StringRef::cast_from(_grtm->get_app_option
    ("db.ForeignKey:updateRule")));
  fk->deleteRule(StringRef::cast_from(_grtm->get_app_option("db.ForeignKey:deleteRule")));

  update_change_date();

  undo.end(strfmt(_("Add Foreign Key '%s' to '%s'"), name.c_str(), get_name().c_str()));

  _fk_list.refresh();

  bec::ValidationManager::validate_instance(fk, CHECK_NAME);

  return NodeId(fklist.count()-1);
}


bool TableEditorBE::remove_fk(const NodeId &fk)
{
  ListRef<db_ForeignKey> fklist= get_table()->foreignKeys();

  if(fk[0] >= (int)fklist.count())
    return false;
  
  const db_TableRef ref_table = fklist[fk[0]]->referencedTable();

  AutoUndoEdit undo(this);
  std::string name= fklist[fk[0]]->name();
  // needs to execute from GRT thread so that side-effects get executed properly
  get_table()->removeForeignKey(fklist[fk[0]], false);
//  run_from_grt(boost::bind(&db_Table::removeForeignKey, get_table().content(), fklist[fk[0]], 0));
  //bec::TableHelper::remove_foreign_key(get_table(), fklist[fk[0]], false, get_undo_manager());
  update_change_date();
  undo.end(strfmt(_("Remove Foreign Key '%s'.'%s'"), get_name().c_str(), name.c_str()));

  _fk_list.refresh();

  // There might be no referenced table yet.
  if (ref_table.is_valid())
    bec::ValidationManager::validate_instance(ref_table, "chk_fk_lgc");
  bec::ValidationManager::validate_instance(_table, "chk_fk_lgc");

  return true;
}


NodeId TableEditorBE::add_index(const std::string &name)
{
  ListRef<db_Index> indices= get_table()->indices();
  db_IndexRef index;

  if (indices.content_class_name() == "db.Index") throw std::logic_error("internal bug");

  index= get_grt()->create_object<db_Index>(indices.content_class_name());
  index->name(name);
  index->owner(get_table());

  std::vector<std::string> types;
  types= get_index_types();

  index->indexType(types[0]);

  AutoUndoEdit undo(this);

  update_change_date();
  indices.insert(index);

  undo.end(strfmt(_("Add Index '%s' to '%s'"), name.c_str(), get_name().c_str()));

  get_indexes()->refresh();

  bec::ValidationManager::validate_instance(index, CHECK_NAME);
  bec::ValidationManager::validate_instance(_table, CHECK_EFFICIENCY);

  return NodeId(indices.count()-1);
}


bool TableEditorBE::remove_index(const NodeId &index)
{
  if(index[0] >= (int)get_table()->indices().count())
    return false;

  db_IndexRef indexobj(get_table()->indices()[index[0]]);

  // do not allow removal of PRIMARY or FOREIGN indices by user
  if (!get_indexes()->index_editable(indexobj) ||
      get_indexes()->index_belongs_to_fk(indexobj))
    return false;

  AutoUndoEdit undo(this);
  get_table()->indices().remove_value(indexobj);
  get_indexes()->refresh();
  update_change_date();
  undo.end(strfmt(_("Remove Index '%s'.'%s'"), indexobj->name().c_str(), get_name().c_str()));

  bec::ValidationManager::validate_instance(_table, CHECK_EFFICIENCY);

  return true;
}


NodeId TableEditorBE::add_index_with_columns(const std::vector<NodeId> &columns)
{
  AutoUndoEdit undo(this);

  NodeId id= add_index(get_name_suggestion_for_list_object(get_table()->indices(), "index"));
  db_TableRef table= get_table();
  db_IndexRef index= table->indices()[id[0]];
  ListRef<db_Column> clist= table->columns();

  for (std::vector<NodeId>::const_iterator i= columns.begin();
       i != columns.end(); ++i)
  {
    db_ColumnRef column= clist[(*i)[0]];

    get_indexes()->add_column(column, index);
  }

  update_change_date();
  undo.end(strfmt(_("Add Index '%s' to '%s'"), index->name().c_str(), get_name().c_str()));

  bec::ValidationManager::validate_instance(index, CHECK_NAME);

  return id;
}


NodeId TableEditorBE::add_fk_with_columns(const std::vector<NodeId> &columns)
{
  AutoUndoEdit undo(this);

  NodeId id= add_fk(get_name_suggestion_for_list_object(get_table()->foreignKeys(), "fk"));
  db_TableRef table= get_table();
  db_ForeignKeyRef fk= table->foreignKeys()[id[0]];
  ListRef<db_Column> clist= table->columns();

  for (std::vector<NodeId>::const_iterator i= columns.begin();
       i != columns.end(); ++i)
  {
    db_ColumnRef column= clist[(*i)[0]];

    _fk_list.add_column(column, db_ColumnRef(), fk);
  }

  update_change_date();
  undo.end(strfmt(_("Add Foreign Key '%s' to '%s'"), fk->name().c_str(), get_name().c_str()));

  bec::ValidationManager::validate_instance(fk, CHECK_NAME);
  
  return id;
}


void TableEditorBE::undo_called(grt::UndoAction *action, grt::UndoAction *expected)
{
  if (action == expected)
    do_ui_refresh();
}


bool TableEditorBE::parse_column_type(const std::string &str, db_ColumnRef &column)
{
  db_CatalogRef catalog(get_catalog());

  //bool flag= ColumnHelper::parse_column_type(get_rdbms(), catalog.is_valid() ? catalog->userDatatypes() : grt::ListRef<db_UserDatatype>(), str, column);
  bool flag= column->setParseType(str, catalog->simpleDatatypes()) == 1;
  if (flag)
  {
    grt::UndoManager *um= get_grt()->get_undo_manager();

    // call _refresh_ui when this parse column type action is undone
    scoped_connect(um->signal_undo(),boost::bind(&TableEditorBE::undo_called, this, _1, um->get_latest_undo_action()));
    scoped_connect(um->signal_redo(),boost::bind(&TableEditorBE::undo_called, this, _1, um->get_latest_undo_action()));
  }
  return flag;
}


std::string TableEditorBE::format_column_type(db_ColumnRef &column)
{
  return column->formattedRawType();
}


void TableEditorBE::set_triggers_sql(const std::string &sql, bool sync)
{
  set_sql_parser_task_cb(boost::bind(&TableEditorBE::parse_triggers_sql, this, _1, _2));
  DBObjectEditorBE::set_sql(sql, sync, _table, "triggers");
}


grt::ValueRef TableEditorBE::parse_triggers_sql(grt::GRT* grt, grt::StringRef sql)
{
  //grt::AutoUndo undo(grt);
  AutoUndoEdit undo(this);

  if (!_sql_parser.get())
    throw std::logic_error("SQL parser is not initialzed");

  grt::IntegerRef res= _sql_parser->parse_triggers(_table, sql.c_str());

  undo.end(strfmt(_("Edit triggers of table `%s`.`%s`"), get_schema_name().c_str(), get_name().c_str()));

  check_sql();

  return res;
}

//--------------------------------------------------------------------------------------------------

Recordset::Ref TableEditorBE::get_inserts_model()
{
  if (!_inserts_model)
  {
    if (_table.class_name() == "db.Table")
      throw std::logic_error("table object is abstract");

    _inserts_storage= Recordset_table_inserts_storage::create(_grtm);
    _inserts_storage->table(_table);

    _inserts_model= Recordset::create(_grtm);
    _inserts_model->show_apply_buttons(true);
    _inserts_model->data_storage(_inserts_storage);
    _inserts_model->export_wizard = boost::bind(&TableEditorBE::show_export_wizard, this, (mforms::Form*)0);
    _inserts_model->refresh();
  }
  return _inserts_model;
}

//--------------------------------------------------------------------------------------------------

void TableEditorBE::show_export_wizard(mforms::Form *owner)
{
  grt::ValueRef option(_grtm->get_app_option("TableEditor:LastExportDirectory"));
  std::string path = option.is_valid() ? grt::StringRef::cast_from(option) : "";
  option = _grtm->get_app_option("TableEditor:LastExportExtension");
  std::string extension = option.is_valid() ? grt::StringRef::cast_from(option) : "";
  InsertsExportForm exporter(owner, _inserts_model, extension);
  exporter.set_title(strfmt(_("Export Inserts for %s"), get_name().c_str()));
  if (!path.empty())
  {
    path = bec::make_path(path, get_name());
    exporter.set_path(path);
  }  
  path = exporter.run();
  if (path.empty())
    _grtm->replace_status_text(_("Export inserts canceled"));
  else
  {
    _grtm->replace_status_text(strfmt(_("Exported inserts to %s"), path.c_str()));
    _grtm->set_app_option("TableEditor:LastExportDirectory", grt::StringRef(exporter.get_directory()));
    extension = base::extension(path);
    if (!extension.empty() && extension[0] == '.')
      extension = extension.substr(1);
    if (!extension.empty())
      _grtm->set_app_option("TableEditor:LastExportExtension", grt::StringRef(extension));
  }
}

//--------------------------------------------------------------------------------------------------

Sql_editor::Ref TableEditorBE::get_sql_editor()
{
  Sql_editor::Ref sql_editor= DBObjectEditorBE::get_sql_editor();
  if (sql_editor)
  {
    sql_editor->sql_checker()->only_object_type_of(Sql_syntax_check::ot_trigger);
    sql_editor->sql_checker()->context_object(_table);

    // this enables highlighting of semantic errors in addition to syntax errors
    // semantic check requires generation of statement AST, while pure syntax (default) check doesn't
    sql_editor->sql_checker()->is_ast_generation_enabled(true);
  }
  return sql_editor;
}
