/* 
 * © 2007-2008 MySQL AB, 2008-2009 Sun Microsystems, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */
#include "stdafx.h"
#include "editor_table.h"
#include "db_object_helpers.h"
#include <grtpp_util.h>

using namespace bec;
using namespace grt;


/*
 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 &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);
    _owner->get_inserts_grid()->refresh();
    _owner->update_from_inserts_grid();
    _owner->update_change_date();
    
    _owner->get_table()->signal_refreshDisplay().emit("column");
    
    undo.end(strfmt(_("Reorder column '%s.%s'"), _owner->get_name().c_str(), _owner->get_table()->columns()[node[0]]->name().c_str()));
  }
}


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 dont 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 behaviour
      // set flags list to the ones defined in userType
      if (col->userType()->actualType().is_valid())
      {
        gchar **tokens= g_strsplit(col->userType()->flags().c_str(), " ", 0);
        if (tokens)
        {
          grt::StringListRef flags(col->userType()->actualType()->flags());
          
          for (size_t j= 0; tokens[j] != NULL; j++)
          {
            for (size_t c= flags.count(), i= 0; i < c; i++)
            {
              if (g_strcasecmp(flags.get(i).c_str(), tokens[j]) == 0)
              {
                col->flags().insert(flags[i]);
                break;
              }
            }
          }
          g_strfreev(tokens);
        }
      }
       */
    }
  }
  return flag;
}


bool TableColumnsListBE::set_field(const NodeId &node, int column, const std::string &value)
{
  RefreshCentry __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:
    if (value != old)
    {
      AutoUndoEdit undo(_owner);

      if (set_column_type_from_string(col, value))
      {
        _owner->update_change_date();
        undo.end(strfmt(_("Change type of '%s.%s'"), _owner->get_name().c_str(), col->name().c_str()));
      }
      else
        undo.cancel();
      
      _owner->do_partial_ui_refresh(::bec::TableEditorBE::RefreshColumnCollation);
    }
    return true;
  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(_("Change 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(_("Change 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(_("Change 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(_("Change 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(_("Change 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)
{
  RefreshCentry __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);
      TableHelper::update_foreign_keys_from_column_notnull(_owner->get_table(), col);
      _owner->update_change_date();
      _owner->get_table()->signal_refreshDisplay().emit("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 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 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())
      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().emit("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().emit("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;

    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;
      }
    }
  }
  return 0;
}


std::string TableColumnsListBE::quote_value_if_needed(const db_ColumnRef &column, const std::string &value)
{
  std::string datatypeGroupName= "";

  if (column->userType().is_valid() && column->userType()->actualType().is_valid())
    datatypeGroupName= column->userType()->actualType()->group()->name();
  else
    if (column->simpleType().is_valid())
      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("");
          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 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)
    if (strcmp(index->indexType().c_str(), "PRIMARY") == 0 ||
      strcmp(index->indexType().c_str(), "FOREIGN") == 0)
      return false;
  }
  return true;
}


MenuItemList IndexListBE::get_popup_items_for_nodes(const std::vector<NodeId> &nodes)
{
  MenuItemList items;
  MenuItem item;
  item.caption = _("Delete Selected Indices");
  item.name    = "deleteIndices";
  item.enabled = nodes.size() > 0;
  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))
    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)
{
  RefreshCentry __centry(*_owner);

  db_IndexRef index= get_selected_index();
  if (!index_editable(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) 
      && !(index->indexType() == "FOREIGN" && column == Name)) // allow renaming FK index
    return false;

  switch (column)
  {
  case Name:
    if (index->name() != value)
    {
      AutoUndoEdit undo(_owner, index, "name");
      TableHelper::rename_index(_owner->get_table(), index, value);
      _owner->update_change_date();
      undo.end(strfmt(_("Rename index '%s.%s'"), _owner->get_name().c_str(), index->name().c_str()));
    }
    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 == "FOREIGN" || 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(_("Change 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(_("Change 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());
      }
    }
  }
}


/** 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]]);

//  const std::string type= srccolumn->formattedType();

  if (fk.is_valid() && fk->referencedTable().is_valid())
  {
    std::vector<std::string> names, names2;//, rest;
    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 (columns[i]->formattedType() == type)
        if (ColumnHelper::compare_column_types(columns[i], srccolumn) == COLUMNS_TYPES_EQUAL)
        {
          if (table->isPrimaryKeyColumn(columns[i]))
            names.push_back(columns[i]->name());
          else
            names2.push_back(columns[i]->name());
        }
//        else
//          rest.push_back(columns[i]->name());
      }
    }

    names.insert(names.end(), names2.begin(), names2.end());
//    names.insert(names.end(), rest.begin(), rest.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 (ColumnHelper::compare_column_types(columns[i], srccolumn) == COLUMNS_TYPES_EQUAL)
            {
              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)
{
  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();
}


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;

      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;
  _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)
{
  if (node[0] == count() - 1)
    _editing_placeholder_row = node[0];
  else
    _editing_placeholder_row = -1;

  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 foreign key"));
    }
    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= split_string(value, ".");
      std::string table;
      db_SchemaRef schema;

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

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

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

          fk->referencedTable(dbtable);

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

          _owner->update_change_date();

          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;
  }
  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())
      value= fk->deleteRule();
    else
      value= grt::StringRef("");
    return true;
  case OnUpdate:
    if (fk.is_valid())
      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;
  }
  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), _inserts_grid(this)
{
  if (table.class_name() == "db.Table") throw std::logic_error("table object is abstract");
}
#ifdef _MSC_VER
#pragma warning(pop)
#endif


void TableEditorBE::set_name(const std::string &name)
{//XXX REMOVE THIS after inserts are replaced with CSV
  if (get_name() != name)
  {
    grt::AutoUndo undo(get_grt());
    update_inserts_grid();
    std::string name_= rtrim(name);
    DBObjectEditorBE::set_name(name_);
    get_inserts_grid()->refresh();
    update_from_inserts_grid();
    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);

  update_inserts_grid();
  
  get_table()->addColumn(column);

  get_inserts_grid()->refresh();
  update_from_inserts_grid();
  update_change_date();

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

  get_columns()->refresh();

  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_inserts_grid();
  update_from_inserts_grid();
  update_change_date();

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


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);

  update_inserts_grid();
  
  table->removeColumn(column);

  get_inserts_grid()->refresh();
  update_from_inserts_grid();
  update_change_date();

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


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();

  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(sigc::bind(sigc::mem_fun(get_table().content(), &db_Table::removeForeignKey), 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();

  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();

  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))
    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()));

  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()));

  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()));

  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= 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
    um->signal_undo().connect(sigc::bind(sigc::mem_fun(this, &TableEditorBE::undo_called),
                                         um->get_latest_undo_action()));
    um->signal_redo().connect(sigc::bind(sigc::mem_fun(this, &TableEditorBE::undo_called),
                                         um->get_latest_undo_action()));
  }
  return flag;
}


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


bool TableEditorBE::update_inserts_grid()
{
  return _inserts_grid.set_inserts_text(get_table()->inserts());
}


void TableEditorBE::update_from_inserts_grid()
{
  std::string inserts= _inserts_grid.get_inserts_text();

  if (get_table()->inserts() != inserts)
  {
    AutoUndoEdit undo(this);

    get_table()->inserts(inserts);
    update_change_date();

    undo.end(strfmt(_("Set INSERTs for '%s'"), get_name().c_str()));
  }
}


bool TableEditorBE::set_inserts(const std::string &text)
{
  if (text.compare(get_table()->inserts().c_str()) != 0)
  {
    AutoUndoEdit undo(this);

    get_table()->inserts(text);
    _inserts_grid.set_inserts_text(text);
    update_change_date();
    
    undo.end(strfmt(_("Set INSERTs for '%s'"), get_name().c_str()));
  }
  return true;
}


std::string TableEditorBE::get_inserts()
{
  return get_table()->inserts();
}


void TableEditorBE::set_triggers_sql(const std::string &sql)
{
  set_sql_parser_task_cb(sigc::mem_fun(this, &TableEditorBE::parse_triggers_sql));
  DBObjectEditorBE::set_sql(sql, _table, "triggers");
}


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

  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()));

  return res;
}


Sql_editor * TableEditorBE::get_sql_editor()
{
  Sql_editor * 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);
  }
  return sql_editor;
}
