/* 
 * 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 "recordset_be.h"
#include "recordset_data_storage.h"
#include "sqlide_generics_private.h"
#include "grtpp.h"
#include "cppdbc.h"
#include "grtui/binary_data_editor.h"

#include "mforms/utilities.h"
#include "mforms/filechooser.h"

#include "base/string_utilities.h"
#include "base/boost_smart_ptr_helpers.h"
#include "sqlite/command.hpp"
#include <boost/foreach.hpp>
#include <fstream>

#include "recordset_text_storage.h"

using namespace bec;
using namespace base;

#define CATCH_AND_DISPATCH_EXCEPTION(rethrow, res) \
catch (sql::SQLException &e)\
{\
  rethrow ? throw : task->send_msg(grt::ErrorMsg, strfmt("Error Code: %i\n%s", e.getErrorCode(), e.what()));\
  res= false;\
}\
catch (sqlite::database_exception &e)\
{\
  rethrow ? throw : task->send_msg(grt::ErrorMsg, e.what());\
  res= false;\
}\
catch (std::exception &e)\
{\
  rethrow ? throw : task->send_msg(grt::ErrorMsg, e.what());\
  res= false;\
}


const std::string ERRMSG_PENDING_CHANGES= _("There are pending changes. Please commit or rollback first.");
std::string Recordset::_add_change_record_statement= "insert into `changes` (`record`, `action`, `column`) values (?, ?, ?)";


Recordset::Ref Recordset::create(bec::GRTManager *grtm)
{
  Ref instance(new Recordset(grtm));
  return instance;
}


Recordset::Ref Recordset::create(GrtThreadedTask::Ref parent_task)
{
  Ref instance(new Recordset(parent_task));
  return instance;
}

static gint next_id = 0;

Recordset::Recordset(GRTManager *grtm)
  : VarGridModel(grtm), _show_apply_buttons(false), task(GrtThreadedTask::create(grtm))
{
  _id = g_atomic_int_get(&next_id);
  g_atomic_int_inc(&next_id);

  task->send_task_res_msg(false);
  apply_changes= boost::bind(&Recordset::apply_changes_, this);
  register_default_actions();
  reset();
}


Recordset::Recordset(GrtThreadedTask::Ref parent_task)
  : VarGridModel(parent_task->grtm()), _show_apply_buttons(false), task(GrtThreadedTask::create(parent_task))
{
  _id = g_atomic_int_get(&next_id);
  g_atomic_int_inc(&next_id);

  task->send_task_res_msg(false);
  register_default_actions();
  reset();
}


Recordset::~Recordset()
{
}


bool Recordset::reset(Recordset_data_storage::Ptr data_storage_ptr, bool rethrow)
{
  VarGridModel::reset();

  boost::shared_ptr<sqlite::connection> data_swap_db= this->data_swap_db();

  bool res= true;

  _aux_column_count= 0;
  _rowid_column= 0;
  _real_row_count= 0;
  _min_new_rowid= 0;
  _next_new_rowid= 0;
  _sort_columns.clear();
  _column_filter_expr_map.clear();
  _data_search_string.clear();

  RETAIN_WEAK_PTR (Recordset_data_storage, data_storage_ptr, data_storage)
  if (data_storage)
  {
    try
    {
      data_storage->do_unserialize(this, data_swap_db.get());
      rebuild_data_index(data_swap_db.get(), false, false);

      _column_count= _column_names.size();
      _aux_column_count= data_storage->aux_column_count();

      // add aux `id` column required by 2-level caching
      ++_aux_column_count;
      ++_column_count;
      _rowid_column= _column_count - 1;
      _column_names.push_back("id");
      _column_types.push_back(int());
      _real_column_types.push_back(int());
      _column_quoting.push_back(false);

      {
        sqlite::query q(*data_swap_db, "select coalesce(max(id)+1, 0) from `data`");
        if (q.emit())
        {
          boost::shared_ptr<sqlite::result> rs= q.get_result();
          _min_new_rowid= rs->get_int(0);
        }
        else
        {
          _min_new_rowid= 0;
        }
        _next_new_rowid= _min_new_rowid;
      }

      recalc_row_count(data_swap_db.get());

      _readonly= data_storage->readonly();

      _readonly_reason = data_storage->readonly_reason();
    }
    CATCH_AND_DISPATCH_EXCEPTION(rethrow, res)
  }

  refresh_ui_status_bar();
  refresh_ui();

  return res;
}


void Recordset::reset()
{
  reset(false);
}


bool Recordset::reset(bool rethrow)
{
  return reset(_data_storage, rethrow);
}


bool Recordset::can_close()
{
  return can_close(true);
}


bool Recordset::can_close(bool interactive)
{
  bool res= !has_pending_changes();
  if (!res && interactive)
  {
    int r= mforms::Utilities::show_warning(_("Close Recordset"),
      strfmt(_("There are unsaved changed to the recordset data: %s. Do you want to apply them before closing?"), _caption.c_str()),
      _("Apply"), _("Cancel"), _("Don't Apply"));
    switch (r)
    {
    case mforms::ResultOk: // Apply
      apply_changes();
      res= !has_pending_changes();
      break;
    case mforms::ResultCancel:
      res= false;
      break;
    case mforms::ResultOther:
      res= true;
      break;
    }
  }
  return res;
}


bool Recordset::close()
{
  RETVAL_IF_FAIL_TO_RETAIN_RAW_PTR (Recordset, this, false)
  on_close(weak_ptr_from(this));
  return true;
}


void Recordset::refresh()
{
  if (has_pending_changes())
  {
    task->send_msg(grt::ErrorMsg, ERRMSG_PENDING_CHANGES);
    return;
  }

  VarGridModel::refresh();
  reset();
}


void Recordset::rollback()
{
  if (!reset(false))
    task->send_msg(grt::ErrorMsg, "Rollback failed");
}


void Recordset::refresh_ui_status_bar()
{
  if (_grtm->in_main_thread())
    refresh_ui_status_bar_signal();
}


RowId Recordset::real_row_count() const
{
  return _real_row_count;
}


void Recordset::recalc_row_count(sqlite::connection *data_swap_db)
{
  // row count (visible rows only, some can be filtered out by applied column filters)
  {
    sqlite::query q(*data_swap_db, "select count(*) from `data_index`");
    if (q.emit())
    {
      boost::shared_ptr<sqlite::result> rs= q.get_result();
      _row_count= rs->get_int(0);
    }
    else
    {
      _row_count= 0;
    }
  }

  // real row count (as if no column filters applied)
  {
    sqlite::query q(*data_swap_db, "select count(*) from `data`");
    if (q.emit())
    {
      boost::shared_ptr<sqlite::result> rs= q.get_result();
      _real_row_count= rs->get_int(0);
    }
    else
    {
      _real_row_count= 0;
    }
  }
}


Recordset::Cell Recordset::cell(RowId row, ColumnId column)
{
  if (_row_count == row)
  {
    RowId rowid= _next_new_rowid++; // rowid of the new record
    {
      boost::shared_ptr<sqlite::connection> data_swap_db= this->data_swap_db();
      sqlide::Sqlite_transaction_guarder transaction_guarder(data_swap_db.get());

      // insert new empty data record
      {
        std::list<sqlite::Variant> bind_vars;
        bind_vars.push_back((int)rowid);
        emit_partition_commands(data_swap_db.get(), data_swap_db_partition_count(), "insert into `data%s` (id) values (?)", bind_vars);
      }

      // insert new data index record
      {
        sqlite::command insert_data_index_record_statement(*data_swap_db, "insert into `data_index` (id) values (?)");
        insert_data_index_record_statement % (int)rowid;	
        insert_data_index_record_statement.emit();
      }

      // log insert action
      {
        sqlite::command add_change_record_statement(*data_swap_db, _add_change_record_statement);
        add_change_record_statement % (int)rowid;
        add_change_record_statement % 1;
        static sqlite::null_type null_obj;
        add_change_record_statement % null_obj;
        add_change_record_statement.emit();
      }

      transaction_guarder.commit();
    }

    _data.resize(_data.size() + _column_count);
    ++_row_count;

    // init new row fields with null-values
    Cell new_cell= _data.begin() + (_data.size() - _column_count);
    for (ColumnId col= 0; _column_count > col; ++col, ++new_cell)
      *(new_cell)= sqlite::Null();
    _data[_data.size() - _column_count + _rowid_column]= (int)rowid;
  }

  return VarGridModel::cell(row, column);
}


void Recordset::after_set_field(const NodeId &node, int column, const sqlite::Variant &value)
{
  VarGridModel::after_set_field(node, column, value);
  mark_dirty(node[0], column, value);
  refresh_ui_status_bar();
  tree_changed();
}


void Recordset::mark_dirty(RowId row, ColumnId column, const sqlite::Variant &new_value)
{
  GStaticRecMutexLock data_mutex(_data_mutex);

  RowId rowid;
  NodeId node(row);
  if (get_field_(node, _rowid_column, (int&)rowid))
  {
    boost::shared_ptr<sqlite::connection> data_swap_db= this->data_swap_db();
    sqlide::Sqlite_transaction_guarder transaction_guarder(data_swap_db.get());

    // update record
    {
      size_t partition= data_swap_db_column_partition(column);
      std::string partition_suffix= data_swap_db_partition_suffix(partition);
      std::string sql= strfmt("update `data%s` set `_%u`=? where `id`=?", partition_suffix.c_str(), (unsigned int) column);
      sqlite::command update_data_record_statement(*data_swap_db, sql);
      sqlide::BindSqlCommandVar bind_sql_command_var(&update_data_record_statement);
      boost::apply_visitor(bind_sql_command_var, new_value);
      update_data_record_statement % (int)rowid;
      update_data_record_statement.emit();
    }

    // log update action
    {
      sqlite::command add_data_change_record_statement(*data_swap_db, _add_change_record_statement);
      add_data_change_record_statement % (int)rowid;
      add_data_change_record_statement % 0;
      add_data_change_record_statement % (int)column;
      add_data_change_record_statement.emit();
    }

    transaction_guarder.commit();
  }
}

std::string Recordset::caption()
{
  return base::strfmt("%s%s", _caption.c_str(), has_pending_changes()?"*":"");
}

bool Recordset::delete_node(const bec::NodeId &node)
{
  std::vector<bec::NodeId> nodes(1, node);
  return delete_nodes(nodes);
}


bool Recordset::delete_nodes(std::vector<bec::NodeId> &nodes)
{
  {
    GStaticRecMutexLock data_mutex(_data_mutex);

    {
      std::sort(nodes.begin(), nodes.end());
      std::vector<bec::NodeId>::iterator i= std::unique(nodes.begin(), nodes.end());
      nodes.erase(i, nodes.end());
    }
    RowId processed_node_count= 0;

    BOOST_FOREACH (const NodeId &node, nodes)
    {
      RowId row= node[0] - processed_node_count;
      if (!node.is_valid() || (row >= _row_count))
        return false;
    }

    BOOST_FOREACH (const NodeId &node, nodes)
    {
      node[0]-= processed_node_count;
      RowId row= node[0];

      RowId rowid;
      if (get_field_(node, _rowid_column, (int&)rowid))
      {
        boost::shared_ptr<sqlite::connection> data_swap_db= this->data_swap_db();
        sqlide::Sqlite_transaction_guarder transaction_guarder(data_swap_db.get());

        // save copy of the record being deleted
        for (size_t partition= 0, partition_count= data_swap_db_partition_count(); partition < partition_count; ++partition)
        {
          std::string partition_suffix= data_swap_db_partition_suffix(partition);
          sqlite::command save_deleted_data_record_statement(*data_swap_db,
            strfmt("insert into `deleted_rows%s` select * from `data%s` where id=?", partition_suffix.c_str(), partition_suffix.c_str()));
          save_deleted_data_record_statement % (int)rowid;
          save_deleted_data_record_statement.emit();
        }

        // delete data record
        {
          std::list<sqlite::Variant> bind_vars;
          bind_vars.push_back((int)rowid);
          emit_partition_commands(data_swap_db.get(), data_swap_db_partition_count(), "delete from `data%s` where id=?", bind_vars);
        }

        // delete data index record
        {
          sqlite::command delete_data_index_record_statement(*data_swap_db, "delete from `data_index` where id=?");
          delete_data_index_record_statement % (int)rowid;
          delete_data_index_record_statement.emit();
        }

        // log delete action
        {
          sqlite::command add_change_record_statement(*data_swap_db, _add_change_record_statement);
          add_change_record_statement % (int)rowid;
          add_change_record_statement % -1;
          static sqlite::null_type null_obj;
          add_change_record_statement % null_obj;
          add_change_record_statement.emit();
        }

        transaction_guarder.commit();

        --_row_count;
        --_data_frame_end;

        // delete record from cached data frame
        {
          Cell row_begin= _data.begin() + (row - _data_frame_begin) * _column_count;
          _data.erase(row_begin, row_begin + _column_count);
        }

        ++processed_node_count;
      }
    }

    nodes.clear();
  }

  refresh_ui_status_bar();

  return true;
}


bool Recordset::has_pending_changes()
{
  boost::shared_ptr<sqlite::connection> data_swap_db= this->data_swap_db();
  if (data_swap_db)
  {
    sqlite::query check_pending_changes_statement(*data_swap_db, "select exists(select 1 from `changes`)");
    boost::shared_ptr<sqlite::result> rs= check_pending_changes_statement.emit_result();
    return (rs->get_int(0) == 1);
  }
  else
  {
    return false;
  }
}


void Recordset::pending_changes(int &upd_count, int &ins_count, int &del_count) const
{
  boost::shared_ptr<sqlite::connection> data_swap_db= this->data_swap_db();

  std::string count_pending_changes_statement_sql=
    "select 1, (select count(*) from `data` where id>=?)\n"
    "union all\n"
    "select -1, (select count(*) from `deleted_rows` where id<?)\n"
    "union all\n"
    "select 0, (select count(1) from\n"
    "(select `record` from `changes` where `action`=0 and `record`<? group by `record`\n"
    "except\n"
    "select id from `deleted_rows`))";
  sqlite::query count_pending_changes_statement(*data_swap_db, count_pending_changes_statement_sql);
  count_pending_changes_statement % (int)_min_new_rowid;
  count_pending_changes_statement % (int)_min_new_rowid;
  count_pending_changes_statement % (int)_min_new_rowid;
  boost::shared_ptr<sqlite::result> rs= count_pending_changes_statement.emit_result();
  do
  {
    switch (rs->get_int(0))
    {
    case 0:
      upd_count= rs->get_int(1);
      break;
    case 1:
      ins_count= rs->get_int(1);
      break;
    case -1:
      del_count= rs->get_int(1);
      break;
    }
  }
  while (rs->next_row());
}


grt::StringRef Recordset::do_apply_changes(grt::GRT *grt, Ptr self_ptr, Recordset_data_storage::Ptr data_storage_ptr)
{
  RETVAL_IF_FAIL_TO_RETAIN_WEAK_PTR (Recordset, self_ptr, self, grt::StringRef(""))
  RETVAL_IF_FAIL_TO_RETAIN_WEAK_PTR (Recordset_data_storage, data_storage_ptr, data_storage, grt::StringRef(""))

  bool res= true;
  try
  {
    data_storage->apply_changes(self_ptr);
    task->send_msg(grt::InfoMsg, "Commit complete");
    reset(data_storage_ptr, false);
  }
  CATCH_AND_DISPATCH_EXCEPTION(false, res)

  return grt::StringRef("");
}


void Recordset::apply_changes_(Recordset_data_storage::Ptr data_storage_ptr)
{
  task->finish_cb(boost::bind(&Recordset::on_apply_changes_finished, this));
  task->exec(true,
    boost::bind(&Recordset::do_apply_changes, this, _1, weak_ptr_from(this), data_storage_ptr));
}



static int process_task_msg(int msgType, const std::string &message, const std::string &detail,
                            int &error_count, std::string &messages_out)
{
  if (msgType == grt::ErrorMsg)
    error_count++;
  
  if (!message.empty())
  {
    if (!messages_out.empty())
      messages_out.append("\n");
    messages_out.append(message);
  }  
  return 0;
}


bool Recordset::apply_changes_and_gather_messages(std::string &messages)
{
  int error_count = 0;
  GrtThreadedTask::Msg_cb cb(task->msg_cb());

  task->msg_cb(boost::bind(process_task_msg, _1, _2, _3, boost::ref(error_count), boost::ref(messages)));
  apply_changes();
  task->msg_cb(cb);

  return error_count == 0;
}


void Recordset::rollback_and_gather_messages(std::string &messages)
{
  int error_count = 0;
  GrtThreadedTask::Msg_cb cb(task->msg_cb());
  
  task->msg_cb(boost::bind(process_task_msg, _1, _2, _3, boost::ref(error_count), boost::ref(messages)));
  rollback();
  task->msg_cb(cb);
}


int Recordset::on_apply_changes_finished()
{
  task->finish_cb(GrtThreadedTask::Finish_cb());
  refresh_ui_status_bar();
  return refresh_ui();
}


void Recordset::apply_changes_()
{
  apply_changes_(_data_storage);
}


bool Recordset::limit_rows()
{
  return (_data_storage ? _data_storage->limit_rows() : false);
}


void Recordset::limit_rows(bool value)
{
  if (has_pending_changes())
  {
    task->send_msg(grt::ErrorMsg, ERRMSG_PENDING_CHANGES);
    return;
  }

  if (_data_storage)
  {
    if (_data_storage->limit_rows() != value)
    {
      _data_storage->limit_rows(value);
      refresh();
    }
  }
}


void Recordset::toggle_limit_rows()
{
  limit_rows(!limit_rows());
}


void Recordset::scroll_rows_frame_forward()
{
  if (_data_storage)
  {
    _data_storage->scroll_rows_frame_forward();
    refresh();
  }
}


void Recordset::scroll_rows_frame_backward()
{
  if (_data_storage && (_data_storage->limit_rows_offset() != 0))
  {
    _data_storage->scroll_rows_frame_backward();
    refresh();
  }
}


int Recordset::limit_rows_count()
{
  return (_data_storage ? _data_storage->limit_rows_count() : 0);
}


void Recordset::limit_rows_count(int value)
{
  if (_data_storage)
    _data_storage->limit_rows_count(value);
}


bool Recordset::limit_rows_applicable()
{
  if (_data_storage && !_data_storage->limit_rows_applicable())
    return false;

  bool limit_rows_= limit_rows();
  int limit_rows_count_= limit_rows_count();
  int row_count_= real_row_count();
  return (limit_rows_ && (row_count_ == limit_rows_count_))
    || (!limit_rows_ && (row_count_ > limit_rows_count_))
    || (0 < _data_storage->limit_rows_offset());
}


Recordset_data_storage::Ref Recordset::data_storage_for_export(const std::string &format)
{
  _data_storage_for_export.reset();

  {
    std::vector<Recordset_storage_info> storage_types(Recordset_text_storage::storage_types(_grtm));
    for (std::vector<Recordset_storage_info>::const_iterator i = storage_types.begin(); 
         i != storage_types.end(); ++i)
    {
      if (i->name == format)
      {
        Recordset_text_storage::Ref ds(Recordset_text_storage::create(_grtm));
        ds->data_format(format);
        _data_storage_for_export = ds;
        break;
      }
    }
  }
  
  if (_data_storage_for_export)
    return _data_storage_for_export;
  throw std::runtime_error(strfmt("Data storage format is not supported: %s", format.c_str()));
}


std::vector<Recordset_storage_info> Recordset::data_storages_for_export()
{
  std::vector<Recordset_storage_info> storage_types;
  
  storage_types = Recordset_text_storage::storage_types(_grtm);
  
  return storage_types;
}


void Recordset::sort_by(ColumnId column, int direction, bool retaining)
{
  if (!retaining)
  {
    _sort_columns.clear();
    if (!(direction))
    {
      boost::shared_ptr<sqlite::connection> data_swap_db= this->data_swap_db();
      rebuild_data_index(data_swap_db.get(), true, true);
      //refresh_ui();
      return;
    }
  }

  bool sort_column_exists= false;
  bool is_resort_needed= true;
  for (SortColumns::iterator sort_column= _sort_columns.begin(), end= _sort_columns.end(); sort_column != end; ++sort_column)
  {
    if (sort_column->first == column)
    {
      if ((direction))
      {
        sort_column->second= direction;
        sort_column_exists= true;
      }
      else
      {
        if (_sort_columns.rbegin()->first == column)
          is_resort_needed= false;
        _sort_columns.erase(sort_column);
      }
      break;
    }
  }
  if (!sort_column_exists && (direction))
    _sort_columns.push_back(std::make_pair(column, direction));

  if (!is_resort_needed || _sort_columns.empty())
    return;

  boost::shared_ptr<sqlite::connection> data_swap_db= this->data_swap_db();
  rebuild_data_index(data_swap_db.get(), true, true);
}


std::string Recordset::get_column_filter_expr(ColumnId column) const
{
  Column_filter_expr_map::const_iterator i= _column_filter_expr_map.find(column);
  if (i != _column_filter_expr_map.end())
    return i->second;
  return "";
}


bool Recordset::has_column_filters() const
{
  return !_column_filter_expr_map.empty();
}


bool Recordset::has_column_filter(ColumnId column) const
{
  Column_filter_expr_map::const_iterator i= _column_filter_expr_map.find(column);
  return (i != _column_filter_expr_map.end());
}


void Recordset::reset_column_filters()
{
  _column_filter_expr_map.clear();

  boost::shared_ptr<sqlite::connection> data_swap_db= this->data_swap_db();
  rebuild_data_index(data_swap_db.get(), true, true);
}


void Recordset::reset_column_filter(ColumnId column)
{
  Column_filter_expr_map::iterator i= _column_filter_expr_map.find(column);
  if (i == _column_filter_expr_map.end())
    return;
  _column_filter_expr_map.erase(i);

  boost::shared_ptr<sqlite::connection> data_swap_db= this->data_swap_db();
  rebuild_data_index(data_swap_db.get(), true, true);
}


void Recordset::set_column_filter(ColumnId column, const std::string &filter_expr)
{
  if ((int)column >= get_column_count())
    return;
  Column_filter_expr_map::const_iterator i= _column_filter_expr_map.find(column);
  if ((i != _column_filter_expr_map.end()) && (i->second == filter_expr))
    return;
  _column_filter_expr_map[column]= filter_expr;

  boost::shared_ptr<sqlite::connection> data_swap_db= this->data_swap_db();
  rebuild_data_index(data_swap_db.get(), true, true);
}


int Recordset::column_filter_icon_id() const
{
  IconManager *icon_man= IconManager::get_instance();
  return icon_man->get_icon_id("tiny_search.png");
}


const std::string & Recordset::data_search_string() const
{
  return _data_search_string;
}


void Recordset::set_data_search_string(const std::string &value)
{
  if (value == _data_search_string)
    return;
  _data_search_string= value;

  boost::shared_ptr<sqlite::connection> data_swap_db= this->data_swap_db();
  rebuild_data_index(data_swap_db.get(), true, true);
}


void Recordset::reset_data_search_string()
{
  if (_data_search_string.empty())
    return;
  _data_search_string.clear();

  boost::shared_ptr<sqlite::connection> data_swap_db= this->data_swap_db();
  rebuild_data_index(data_swap_db.get(), true, true);
}


void Recordset::rebuild_data_index(sqlite::connection *data_swap_db, bool do_cache_data_frame, bool do_refresh_ui)
{
  {
    GStaticRecMutexLock data_mutex(_data_mutex);

    std::string where_clause;
    {
      sqlide::QuoteVar qv;
      {
        qv.escape_string= boost::bind(sqlide::QuoteVar::escape_ansi_sql_string, _1);
        qv.store_unknown_as_string= true;
        qv.allow_func_escaping= false;
      }
      sqlite::Variant var_string_type= std::string();
      sqlite::Variant var_string;
      std::string sql_string;

      // column filters subclause
      std::string where_subclause1;
      {
        BOOST_FOREACH (Column_filter_expr_map::value_type &column_filter_expr, _column_filter_expr_map)
        {
          var_string= column_filter_expr.second;
          sql_string= boost::apply_visitor(qv, var_string_type, var_string);
          where_subclause1+= strfmt("_%u like %s and ", (unsigned int) column_filter_expr.first, sql_string.c_str());
        }
        if (!where_subclause1.empty())
        {
          where_subclause1.resize(where_subclause1.size()-std::string(" and ").size());
          where_subclause1.insert(0, "(");
          where_subclause1.append(")");
        }
      }

      // data search subclause
      std::string where_subclause2;
      if (!_data_search_string.empty())
      {
        var_string= "%" + _data_search_string + "%";
        sql_string= boost::apply_visitor(qv, var_string_type, var_string);
        for (int column= 0, column_count= get_column_count(); column < column_count; ++column)
        {
          where_subclause2+= strfmt("_%u like %s or ", (unsigned int) column, sql_string.c_str());
        }
        if (!where_subclause2.empty())
        {
          where_subclause2.resize(where_subclause2.size()-std::string(" or ").size());
          where_subclause2.insert(0, "(");
          where_subclause2.append(")");
        }
      }

      if (!where_subclause1.empty() || !where_subclause2.empty())
      {
        std::string subclauses_mediator= (!where_subclause1.empty() && !where_subclause2.empty()) ? " and " : "";
        where_clause= strfmt("where %s%s%s", where_subclause1.c_str(), subclauses_mediator.c_str(), where_subclause2.c_str());
      }
    }

    std::string orderby_clause;
    {
      BOOST_FOREACH (SortColumns::value_type &sort_column, _sort_columns)
      {
        std::string column_expr;
        switch (get_real_column_type(sort_column.first))
        {
        case NumericType:
        case FloatType:
        case DatetimeType:
          column_expr= strfmt("cast(_%u as numeric)", (unsigned int) sort_column.first);
          break;
        default:
          column_expr= strfmt("_%u", (unsigned int) sort_column.first);
          break;
        }
        const char *dir;
        switch (sort_column.second)
        {
        case 1: dir= "ASC"; break;
        case -1: dir= "DESC"; break;
        default: dir= ""; break;
        }
        orderby_clause+= strfmt("%s %s, ", column_expr.c_str(), dir);
      }
      if (!orderby_clause.empty())
      {
        orderby_clause.resize(orderby_clause.size()-std::string(", ").size());
        orderby_clause.insert(0, "order by ");
      }
    }

    std::string tables_join= "`data`";
    {
      for (size_t partition= 1, partition_count= data_swap_db_partition_count(); partition < partition_count; ++partition)
      {
        std::string partition_suffix= data_swap_db_partition_suffix(partition);
        tables_join+= strfmt(" inner join `data%s` on (`data`.id=`data%s`.id)", partition_suffix.c_str(), partition_suffix.c_str());
      }
    }

    {
      sqlide::Sqlite_transaction_guarder transaction_guarder(data_swap_db);

      std::string temp_table_name= "`data_index_" + grt::get_guid() + "`";

      sqlite::execute(*data_swap_db, strfmt("create table if not exists %s (`id` integer)", temp_table_name.c_str()), true);
      sqlite::execute(*data_swap_db, strfmt("insert into %s select `data`.`id` from %s %s %s", temp_table_name.c_str(), tables_join.c_str(), where_clause.c_str(), orderby_clause.c_str()), true);
      sqlite::execute(*data_swap_db, "drop table if exists `data_index`", true);
      sqlite::execute(*data_swap_db, strfmt("alter table %s rename to `data_index`", temp_table_name.c_str()), true);

      transaction_guarder.commit();
    }

    recalc_row_count(data_swap_db);

    if (do_cache_data_frame)
      cache_data_frame(0, true);
  }

  if (do_refresh_ui)
    refresh_ui();
}


void Recordset::paste_rows_from_clipboard(int dest_row)
{
  std::string text = mforms::Utilities::get_clipboard_text();
  std::vector<std::string> rows;
  
  if (text.find("\r\n") != std::string::npos)
    rows = base::split(text, "\r\n");
  else
    rows = base::split(text, "\n");

  if (rows.empty())
    return;
  
  if (rows.back().empty())
    rows.pop_back();
  
  if (dest_row < 0 || dest_row == count()-1)
    dest_row = count()-1;
  else
  {
    if (rows.size() > 1)
    {
      if (mforms::Utilities::show_message_and_remember("Paste Rows",
                                                       "Cannot paste more than one row into an existing row, would you like to append them?",
                                                       "Append", "Cancel", "",
                                                       "Recordset.appendMultipleRowsOnPaste", "") != mforms::ResultOk)
        return;
      dest_row = count()-1;
    }
  }
  
  int separator = ',';
  if (text.find('\t') != std::string::npos)
    separator = '\t';
  
  for (std::vector<std::string>::const_iterator row = rows.begin(); row != rows.end(); ++row)
  {
    if (!row->empty())
    {
      std::vector<std::string> parts = base::split_token_list(*row, separator);
      
      if ((int)parts.size() != get_column_count())
      {
        mforms::Utilities::show_error("Cannot Paste Row",
                                      strfmt("Number of fields in pasted data doesn't match the columns in the table (%i vs %i).\n"
                                             "Data must be in the same format used by the Copy Row Content command.",
                                             (int)parts.size(), (int)get_column_count()),
                                      "OK");
        return;
      }
      size_t i = 0;
      for (std::vector<std::string>::const_iterator p = parts.begin(); p != parts.end(); ++p, ++i)
      {
        std::string token = base::trim(*p);
        if (token == "NULL")
          set_field_null(dest_row, i);
        else
        {
          if (!token.empty() && token[0] == '\'' && token[token.size()-1] == '\'')
            token = token.substr(1, token.size()-2);
          set_field(dest_row, i, base::unescape_sql_string(token, '\''));
        }
      }
      dest_row++;
    }
  }
}


bec::MenuItemList Recordset::get_popup_menu_items(const std::vector<int> &rows, int clicked_column)
{
  bec::MenuItemList items;
  bec::MenuItem item;
  bool ro = is_readonly();

  bec::MenuItemList plugin_items;
  if (popup_menu_items_slot)
    plugin_items= popup_menu_items_slot(rows, clicked_column);

  item.name = "edit_cell";
  if (ro)
    item.caption = "Open Value in Viewer";
  else
    item.caption = "Open Value in Editor";
  item.enabled = (rows.size() > 0) && (clicked_column >= 0);
  if (item.enabled)
  {
    switch (get_real_column_type(clicked_column))
    {
    case StringType:
    case BlobType:
      break;
    default:
      item.enabled= false;
      break;
    }
  }
  items.push_back(item);
  
  item.name = "";
  item.type = bec::MenuSeparator;
  item.caption = "";
  items.push_back(item);
  item.type = bec::MenuAction;
  
  item.name = "set_to_null";
  item.caption = "Set Field(s) to NULL";
#ifdef _WIN32
  item.enabled = clicked_column >= 0 && !ro;
#else
  item.enabled = clicked_column >= 0 && rows.size() == 1 && !ro;
#endif
  items.push_back(item);

  item.name = "set_to_function";
  item.caption = "Mark Field Value as a Function";
#ifdef _WIN32
  item.enabled = clicked_column >= 0 && !ro;
#else
  item.enabled = clicked_column >= 0 && rows.size() == 1 && !ro;
#endif
  items.push_back(item);
  
  item.name = "delete_row";
  item.caption = "Delete Row(s)";
  item.enabled = rows.size() > 0 && !ro;
  items.push_back(item);
  
  item.name = "";
  item.type = bec::MenuSeparator;
  item.caption = "";
  items.push_back(item);
  item.type = bec::MenuAction;
  
  item.name = "load_from_file";
  item.caption = "Load Value From File";
  item.enabled = clicked_column >= 0 && rows.size() == 1 && !ro;
  items.push_back(item);

  item.name = "save_to_file";
  item.caption = "Save Value To File";
  item.enabled = clicked_column >= 0 && rows.size() == 1 && !ro;
  items.push_back(item);
  
  item.name = "";
  item.type = bec::MenuSeparator;
  item.caption = "";
  items.push_back(item);
  item.type = bec::MenuAction;

  item.name = "copy_row";
  item.caption = "Copy Row Content";
  item.enabled = rows.size() > 0;
  items.push_back(item);

  item.name = "copy_row_unquoted";
  item.caption = "Copy Row Content (unquoted)";
  item.enabled = rows.size() > 0;
  items.push_back(item);

  item.name = "copy_row_tabsep";
  item.caption = "Copy Row Content (tab separated)";
  item.enabled = rows.size() > 0;
  items.push_back(item);
  
  item.name = "copy_field";
  item.caption = "Copy Field Content";
  item.enabled = clicked_column >= 0 && rows.size() == 1;
  items.push_back(item);

  item.name = "copy_field_unquoted";
  item.caption = "Copy Field Content (unquoted)";
  item.enabled = clicked_column >= 0 && rows.size() == 1;
  items.push_back(item);

  item.name = "paste_row";
  item.caption = "Paste Row";
  item.enabled = rows.size() <= 1 && !mforms::Utilities::get_clipboard_text().empty() && !ro;
  items.push_back(item);
    
  if (!plugin_items.empty())
  {
    item.name = "";
    item.type = bec::MenuSeparator;
    item.caption = "";
    items.push_back(item);
    
    items.insert(items.end(), plugin_items.begin(), plugin_items.end());
  }
  return items;
}


bool Recordset::activate_popup_menu_item(const std::string &action, const std::vector<int> &rows, int clicked_column)
{
  bool result = false;
  bool need_ui_refresh = false;

  if (action == "edit_cell")
  {
    if (rows.size() == 1 && clicked_column >= 0)
    {
      open_field_data_editor(rows[0], clicked_column);
      result = true;
    }
  }
  else if (action == "set_to_null")
  {
    if (rows.size() == 1 && clicked_column >= 0)
    {
      bec::NodeId node;
      node.append(rows[0]);
      set_field_null(node, clicked_column);
      result = true;
    }
  }
  else if (action == "set_to_function")
  {
    if (rows.size() == 1 && clicked_column >= 0)
    {
      bec::NodeId node;
      Cell cell;
      
      node.append(rows[0]);
      std::string function;
      if (!get_cell(cell, node, clicked_column, false))
        function = "";
      else
        function = boost::apply_visitor(_var_to_str, *cell);
      if (!g_str_has_prefix(function.c_str(), "\\func"))
        set_field(node, clicked_column, std::string("\\func ")+function);
      result = true;
    }
  }  
  else if (action == "delete_row")
  {
    if (rows.size() > 0)
    {
      std::vector<int> sorted_rows(rows);
      std::sort(sorted_rows.begin(), sorted_rows.end());
      std::vector<bec::NodeId> nodes;
      for (std::vector<int>::reverse_iterator iter = sorted_rows.rbegin(); iter != sorted_rows.rend(); ++iter)
        nodes.push_back(bec::NodeId(*iter));
      delete_nodes(nodes);
      need_ui_refresh = true;
      result = true;
    }
  }
  else if (action == "save_to_file")
  {
    if (rows.size() == 1 && clicked_column >= 0)
    {
      bec::NodeId node;
      node.append(rows[0]);
      save_to_file(node, clicked_column);
      result = true;
    }
  }
  else if (action == "load_from_file")
  {
    if (rows.size() == 1 && clicked_column >= 0)
    {
      bec::NodeId node;
      node.append(rows[0]);
      load_from_file(node, clicked_column);
      result = true;
    }
  }  
  else if (action == "copy_row")
  {
    if (rows.size() > 0)
    {
      copy_rows_to_clipboard(rows, ", ");
      result = true;
    }
  }
  else if (action == "copy_row_unquoted")
  {
    if (rows.size() > 0)
    {
      copy_rows_to_clipboard(rows, ", ", false);
      result = true;
    }
  }
  else if (action == "copy_row_tabsep")
  {
    if (rows.size() > 0)
    {
      copy_rows_to_clipboard(rows, "\t", false);
      result = true;
    }
  }  
  else if (action == "copy_field")
  {
    if (rows.size() == 1 && clicked_column >= 0)
    {
      copy_field_to_clipboard(rows[0], clicked_column);
      result = true;
    }
  }
  else if (action == "copy_field_unquoted")
  {
    if (rows.size() == 1 && clicked_column >= 0)
    {
      copy_field_to_clipboard(rows[0], clicked_column, false);
      result = true;
    }
  }
  else if (action == "paste_row")
  {
    paste_rows_from_clipboard(rows.empty() ? -1 : rows[0]);
    result = true;
  }
  else
    if (call_popup_menu_item_slot(action, rows, clicked_column))
      result = true;

  if (need_ui_refresh)
    refresh_ui();

  return result;
}


void Recordset::copy_rows_to_clipboard(const std::vector<int> &indeces, std::string sep, bool quoted)
{
  ColumnId editable_col_count= get_column_count();
  if (!editable_col_count)
    return;

  sqlide::QuoteVar qv;
  {
    qv.escape_string= boost::bind(base::escape_sql_string, _1, false);
    qv.store_unknown_as_string= true;
    qv.allow_func_escaping= true;
  }

  Cell cell;
  std::string text;
  BOOST_FOREACH (RowId row, indeces)
  {
    std::string line;
    for (ColumnId col= 0; editable_col_count > col; ++col)
    {
      bec::NodeId node(row);
      if (!get_cell(cell, node, col, false))
        continue;
      if (col > 0)
        line+= sep;
      if (quoted)
        line+= boost::apply_visitor(qv, _column_types[col], *cell);
      else
        line+= boost::apply_visitor(_var_to_str, *cell);
    }
    if (!line.empty())
      text+= line+"\n";
  }
  mforms::Utilities::set_clipboard_text(text);
}


void Recordset::copy_field_to_clipboard(int row, int column, bool quoted)
{
  sqlide::QuoteVar qv;
  {
    qv.escape_string= boost::bind(sqlide::QuoteVar::escape_ansi_sql_string, _1);
    qv.store_unknown_as_string= true;
    qv.allow_func_escaping= true;
  }
  std::string text;
  bec::NodeId node(row);
  Cell cell;
  if (get_cell(cell, node, column, false))
  {
    if (quoted)
      text= boost::apply_visitor(qv, _column_types[column], *cell);
    else
      text= boost::apply_visitor(_var_to_str, *cell);
  }
  mforms::Utilities::set_clipboard_text(text);
}

static void add_toolbar_action_item(bec::ToolbarItemList &items, bec::IconManager *im, const std::string &item_icon, const std::string &item_name, const std::string &item_tooltip)
{
  bec::ToolbarItem item;
  item.name = item_name;
  item.icon = im->get_icon_id(item_icon);
  item.caption = item_tooltip;
  item.command = item.name;
  item.tooltip = item.caption;
  item.type = bec::ToolbarAction;
  item.enabled = true;
  items.push_back(item);
}

static void add_toolbar_action_item(bec::ToolbarItemList &items, bec::IconManager *im, const std::string &item_name, const std::string &item_tooltip)
{
  add_toolbar_action_item(items, im, item_name + ".png", item_name, item_tooltip);
}

static void add_toolbar_separator_item(bec::ToolbarItemList &items)
{
  bec::ToolbarItem item;
  item.type = bec::ToolbarSeparator;
  items.push_back(item);
}

static void add_toolbar_label_item(bec::ToolbarItemList &items, const std::string &label)
{
  bec::ToolbarItem item;
  item.caption = label;
  item.type = bec::ToolbarLabel;
  items.push_back(item);
}

std::string Recordset::status_text()
{
  std::string limit_text;

  if (limit_rows_applicable() && limit_rows())
    limit_text = ", more available";
  else
    limit_text = "";
  
  std::string skipped_row_count_text;
  if (_data_storage && _data_storage->limit_rows())
  {
    int limit_rows_offset= _data_storage->limit_rows_offset();
    if (limit_rows_offset > 0)
      skipped_row_count_text= strfmt(" after %i skipped", limit_rows_offset);
  }

  std::string status_text = strfmt("Fetched %i records%s%s", (int)real_row_count(), skipped_row_count_text.c_str(), limit_text.c_str());
  {
    int upd_count = 0, ins_count = 0, del_count = 0;
    pending_changes(upd_count, ins_count, del_count);
    if (upd_count > 0)
      status_text += strfmt(", updated %i", upd_count);
    if (ins_count > 0)
      status_text += strfmt(", inserted %i", ins_count);
    if (del_count > 0)
      status_text += strfmt(", deleted %i", del_count);
  }
  status_text.append(".");
  if (!status_text_trailer.empty())
    status_text.append(" ").append(status_text_trailer);
  
  return status_text;
}

bec::ToolbarItemList Recordset::get_toolbar_items()
{
  bec::ToolbarItemList items;
  bec::ToolbarItem item;
  bec::IconManager *im = bec::IconManager::get_instance();

  add_toolbar_label_item(items, "Filter:");
  {
    bec::ToolbarItem item;
    item.command = "filter";
    item.type = bec::ToolbarSearch;
    items.push_back(item);
  }
  if (!_data_storage || _data_storage->reloadable())
    add_toolbar_action_item(items, im, "tiny_refresh.png", "record_refresh", "Refresh data from data source");

  if (!is_readonly())
  {
    add_toolbar_separator_item(items);
    add_toolbar_label_item(items, "Edit:");
    add_toolbar_action_item(items, im, "record_edit", "Edit current row");
    add_toolbar_action_item(items, im, "record_add", "Insert new row");
    add_toolbar_action_item(items, im, "record_del", "Delete selected rows");
   
    if (_show_apply_buttons)
    {
      add_toolbar_separator_item(items);
      add_toolbar_action_item(items, im, "record_save", "Apply changes to data");
      add_toolbar_action_item(items, im, "record_discard", "Discard changes to data");
    }
  }
  add_toolbar_separator_item(items);
  add_toolbar_label_item(items, "File:");
  add_toolbar_action_item(items, im, "record_export", "Export recordset to an external file");
  if (!is_readonly())
    add_toolbar_action_item(items, im, "record_import", "Import records from an external file");

#ifndef __APPLE__
  add_toolbar_separator_item(items);
  add_toolbar_label_item(items, "Autosize:");
  //  add_toolbar_action_item(items, im, "record_autosize", "Resize columns to fit contents");  
  add_toolbar_action_item(items, im, "record_wrap_vertical", "Toggle wrapping of cell contents");
#endif
  
  if (limit_rows_applicable())
  {
    add_toolbar_separator_item(items);
    add_toolbar_label_item(items, "Fetch rows:");
    add_toolbar_action_item(items, im, "record_fetch_next.png", "scroll_rows_frame_forward", "Fetch next frame of records from the data source");
    add_toolbar_action_item(items, im, "record_fetch_prev.png", "scroll_rows_frame_backward", "Fetch previous frame of records from the data source");
    add_toolbar_action_item(items, im, "record_fetch_all", "Toggle limitation of the records number");
  }
  return items;
}


ActionList & Recordset::action_list()
{
  return _action_list;
}


void Recordset::register_default_actions()
{
  _action_list.register_action("record_sort_reset",
    boost::bind(&Recordset::sort_by, this, 0, 0, false));

  _action_list.register_action("scroll_rows_frame_forward",
    boost::bind(&Recordset::scroll_rows_frame_forward, this));

  _action_list.register_action("scroll_rows_frame_backward",
    boost::bind(&Recordset::scroll_rows_frame_backward, this));

  _action_list.register_action("record_fetch_all",
    boost::bind(&Recordset::toggle_limit_rows, this));

  _action_list.register_action("record_refresh",
    boost::bind(&Recordset::refresh, this));
}


class DataEditorSelector : public boost::static_visitor<BinaryDataEditor*>
{
public:
  DataEditorSelector(bec::GRTManager *grtm, bool read_only) : _grtm(grtm), _read_only(read_only) {}
  DataEditorSelector(bec::GRTManager *grtm, bool read_only, const std::string &encoding) : _grtm(grtm), _encoding(encoding), _read_only(read_only) {}
  const std::string & encoding() const { return _encoding; }
  void encoding(const std::string &value) { _encoding= value; }
private:
  bec::GRTManager *_grtm;
  std::string _encoding;
  bool _read_only;
public:
  result_type operator()(const sqlite::Null &v) { return new BinaryDataEditor(_grtm, NULL, 0, _encoding, _read_only); }
  result_type operator()(const std::string &v) { return new BinaryDataEditor(_grtm, v.c_str(), v.length(), _encoding, _read_only); }
  result_type operator()(const sqlite::BlobRef &v) { return new BinaryDataEditor(_grtm, ((!v || v->empty()) ? NULL : (const char*)&(*v)[0]), v->size(), _encoding, _read_only); }
  template<typename V> result_type operator()(const V &v) { return NULL; }
};
class DataEditorSelector2 : public boost::static_visitor<BinaryDataEditor*>
{
public:
  DataEditorSelector2(bec::GRTManager *grtm, bool read_only) : _grtm(grtm), _read_only(read_only) {}
private:
  bec::GRTManager *_grtm;
  bool _read_only;
public:
  template<typename V> result_type operator()(const std::string &t, const V &v) { return DataEditorSelector(_grtm, _read_only, "UTF-8")(v); }
  template<typename V> result_type operator()(const sqlite::BlobRef &t, const V &v) { return DataEditorSelector(_grtm, _read_only, "LATIN1")(v); }
  template<typename T, typename V> result_type operator()(const T &r, const V &v) {
    //return NULL;
    // For unknown types treat them for now as string values. Since we have a binary editor pane there that should work
    // all the time well enough.
    return DataEditorSelector(_grtm, _read_only, "UTF-8")(v);
  }
};
void Recordset::open_field_data_editor(RowId row, ColumnId column)
{
  GStaticRecMutexLock data_mutex(_data_mutex);

  bool res= true;
  try
  {
    sqlite::Variant blob_value;
    sqlite::Variant *value;

    if (sqlide::is_var_blob(_real_column_types[column]))
    {
      if (!_data_storage)
        return;
      RowId rowid;
      NodeId node(row);
      if (!get_field_(node, _rowid_column, (int&)rowid))
        return;
      boost::shared_ptr<sqlite::connection> data_swap_db= this->data_swap_db();
      _data_storage->fetch_blob_value(this, data_swap_db.get(), rowid, column, blob_value);
      value= &blob_value;
    }
    else
    {
      Cell cell;
      bec::NodeId node(row);
      if (!get_cell(cell, node, column, false))
        return;
      value= &(*cell);
    }

    DataEditorSelector2 data_editor_selector2(_grtm, is_readonly());
    BinaryDataEditor *data_editor=
      boost::apply_visitor(data_editor_selector2, _real_column_types[column], *value);
    if (!data_editor)
      return;
    data_editor->set_title(base::strfmt("Edit Data for %s", _column_names[column].c_str()));
    data_editor->set_release_on_close(true);
    data_editor->signal_saved.connect(boost::bind(&Recordset::set_field_value,this, row, column, data_editor));
    data_editor->show(true);
  }
  CATCH_AND_DISPATCH_EXCEPTION(false, res)
}


class DataValueConv : public boost::static_visitor<sqlite::Variant>
{
public:
  DataValueConv() : _data(NULL), _length(0) {}
  DataValueConv(const char *data, size_t length) { set_data(data, length); }
  void set_data(const char *data, size_t length) { _data= data; _length= length; }
private:
  const char *_data;
  size_t _length;
public:
  result_type operator()(const sqlite::BlobRef &t)
  {
    sqlite::BlobRef val= sqlite::BlobRef(new sqlite::Blob());
    val->resize(_length);
    memcpy(&(*val)[0], _data, _length);
    return val;
  }
  result_type operator()(const std::string &t) { return std::string(_data, _length); }
  template<typename T> result_type operator()(const T &t) { return sqlite::Unknown(); }
};


void Recordset::set_field_value(RowId row, ColumnId column, BinaryDataEditor *data_editor)
{
  set_field_raw_data(row, column, data_editor->data(), data_editor->length());
}


void Recordset::set_field_raw_data(RowId row, ColumnId column, const char *data, size_t data_length)
{
  DataValueConv data_value_conv(data, data_length);
  sqlite::Variant value= boost::apply_visitor(data_value_conv, _real_column_types[column]);
  if (sqlide::is_var_unknown(value))
    throw std::logic_error("Can't save value of this data type.");
  bec::NodeId node(row);
  set_field(node, column, value);
}


void Recordset::load_from_file(const bec::NodeId &node, int column, const std::string &file)
{
  char *data;
  gsize length;
  GError *error = 0;
  
  if (!g_file_get_contents(file.c_str(), &data, &length, &error))
  {
    mforms::Utilities::show_error("Cannot Load Field Value",
                                  error ? error->message : "Error loading file data", 
                                  "OK");
    return;
  }
  
  try
  {
    set_field_raw_data(node[0], column, data, length);  
  }
  catch (const std::exception &exc)
  {
    mforms::Utilities::show_error("Cannot Load Field Value", exc.what(), "OK", "", "");
  }
}


void Recordset::load_from_file(const bec::NodeId &node, int column)
{
  mforms::FileChooser chooser(mforms::OpenFile);
  
  chooser.set_title("Load Field Value");
  
  if (chooser.run_modal())
    load_from_file(node, column, chooser.get_path());
}


class DataValueDump : public boost::static_visitor<void>
{
public:
  DataValueDump(const char *filename) : os(filename, std::ios_base::out|std::ios_base::binary) {}
  std::ofstream os;
public:
  result_type operator()(const sqlite::BlobRef &v)
  {
    std::copy(v->begin(), v->end(), std::ostreambuf_iterator<char>(os));
  }
  result_type operator()(const std::string &v) { os << v; }
  template<typename T> result_type operator()(const T &) {}
};


void Recordset::save_to_file(const bec::NodeId &node, int column, const std::string &file)
{
  GStaticRecMutexLock data_mutex(_data_mutex);
      
  sqlite::Variant blob_value;
  sqlite::Variant *value;
  
  if (sqlide::is_var_blob(_real_column_types[column]))
  {
    if (!_data_storage)
      return;
    RowId rowid;
    if (!get_field_(node, _rowid_column, (int&)rowid))
      return;
    boost::shared_ptr<sqlite::connection> data_swap_db= this->data_swap_db();
    _data_storage->fetch_blob_value(this, data_swap_db.get(), rowid, column, blob_value);
    value= &blob_value;
  }
  else
  {
    Cell cell;
    if (!get_cell(cell, node, column, false))
      return;
    value= &(*cell);
  }
  
  DataValueDump data_value_dump(file.c_str());
  if (data_value_dump.os)
  {
    boost::apply_visitor(data_value_dump, *value);
  }
}


void Recordset::save_to_file(const bec::NodeId &node, int column)
{
  mforms::FileChooser chooser(mforms::SaveFile);
  
  chooser.set_title("Save Field Value");
  
  if (chooser.run_modal())
  {
    bool res = false;
    try
    {      
      save_to_file(node, column, chooser.get_path());
    }
    CATCH_AND_DISPATCH_EXCEPTION(false, res)
  }
}
