#include "../xvaga/incstr.h"

#include <applicat.h>
#include <extcdecl.h>
#include <modaut.h>
#include <progind.h>
#include <recset.h>
#include <relation.h>
#include <utility.h>
#include <xml.h>

#include <statbar.h>

///////////////////////////////////////////////////////////
// TTable name converter
///////////////////////////////////////////////////////////

class TTable_names : public TObject
{
  TAssoc_array _names;
  TArray _ids;
  long _filled;

protected:
  void fill();
  void add_file(int logic, const TString& table);

public:
  const TString& name(int logic_num);
  int logic_num(const TString& name);

  TTable_names() : _filled(-1) { }
} _table_names;

void TTable_names::add_file(int logic, const TString& table)
{
  TString* id = new TString8;
  id->format("%d", logic);
  _names.add(table, id);
  _ids.add(table, logic);
}

void TTable_names::fill()
{
  if (_filled != prefix().get_codditta())
  {
    FileDes dir;
    CGetFile(LF_DIR, &dir, _nolock, NORDIR);
    const int nfiles = (int)dir.EOD;

    TFilename n;
    for (int logic = LF_USER; logic <= nfiles; logic++)
    {     
      const FileDes& fd = prefix().get_filedes(logic);
      n = fd.SysName; n = n.name(); n.upper();
      if (_names.objptr(n) == NULL)
        add_file(logic, n);
    }
    _filled = prefix().get_codditta();
  }
}

int TTable_names::logic_num(const TString& n)
{
  // Non cambiare: n puo' essere temporaneo e pieno di spazi!
  TString80 name = n; name.trim();
  if (isdigit(name[0]))
  {
    int num = atoi(name);
    if (name.ends_with("@"))
      num = -num;
    return num;
  } 
  
  if (name[0] == '%' && name.len() == 4)
    return LF_TABCOM;

  TString* str = (TString*)_names.objptr(name);
  if (str == NULL)
  {
    fill();
    str = (TString*)_names.objptr(name);
  }

  if (str == NULL && name.len() == 3)
    return LF_TAB;

  return str == NULL ? 0 : atoi(*str);
}

const TString& TTable_names::name(int logic_num)
{
  TString* str = (TString*)_ids.objptr(logic_num);
  if (str == NULL)
  {
    fill();
    str = (TString*)_ids.objptr(logic_num);
  }
  return str == NULL ? (const TString&)EMPTY_STRING : *str;
}

const TString& logic2table(int logic_num)
{ return _table_names.name(logic_num); }

int table2logic(const TString& name)
{ return _table_names.logic_num(name); }


///////////////////////////////////////////////////////////
// Utility
///////////////////////////////////////////////////////////

static bool is_numeric(const char* str)
{
  if (str == NULL || *str == '\0' || *str == '0')
    return false;    // Se comincia per zero va preservato!
  if (*str == '-')
  {
    str++;
    if (*str <= ' ')
      return false;
  }
  while (*str)
  {
    if (strchr("0123456789.", *str) == NULL)
      return false;
    str++;
  }
  return true;
}

///////////////////////////////////////////////////////////
// TRecordset
///////////////////////////////////////////////////////////

const TString& TRecordset::query_text() const
{
  return EMPTY_STRING;
}

const TToken_string& TRecordset::sheet_head() const
{
  TToken_string head;
  TToken_string tablefield(32, '.');
  for (unsigned int c = 0; c < columns(); c++)
  {
    const TRecordset_column_info& ci = column_info(c);
    tablefield = ci._name;
    int maxlen = 0;
    FOR_EACH_TOKEN(tablefield, tok)
    {
      if (maxlen == 0)
        head.add(tok);
      else
        head << '\n' << tok;
      const int len = strlen(tok);
      if (len > maxlen)
        maxlen = len;
    }
    head << '@' << max(ci._width, maxlen);
    if (ci._type == _wordfld || ci._type == _intfld || ci._type == _longfld || ci._type == _realfld)
      head << 'R';
  }

  // Creo la stringa temporanea solo ora, altrimenti puo' essere sovrascritta!
  TToken_string& h = get_tmp_string();
  h = head;
  return h;
}

bool TRecordset::save_as_html(const char* path)
{
  TProgind pi(items(), TR("Esportazione in corso..."), true, true);
  ofstream out(path);
  out << "<html>" << endl;
  out << "<body>" << endl;

  TString qry; parsed_text(qry);
  for (int i = qry.find('\n'); i > 0; i = qry.find('\n', i+1))
    qry.insert("<br/>", i+1);

  out << "<p><b>" << qry << "</b></p>" << endl;

  out << "<table border=\"1\">" << endl;
  out << " <thead>";
  for (unsigned int c = 0; c < columns(); c++)
  {
    const TRecordset_column_info& ci = column_info(c);
    TToken_string header(ci._name, '.');
    TString str;
    FOR_EACH_TOKEN(header, tok)
    {
      if (str.not_empty())
        str << "<br/>";
      str << tok;
    }
    out << "  <th>" << str << "</th>" << endl;
  }
  out << " </thead>" << endl;

  TString val;
  for (TRecnotype n = 0; n < items(); n++)
  {
    move_to(n);
    pi.addstatus(1);
    if (pi.iscancelled())
      break;
    out << " <tr>" << endl;
    for (unsigned int c = 0; c < columns(); c++)
    {
      const TRecordset_column_info& ci = column_info(c);
      out << "  <td";
      if (ci._type == _longfld || ci._type == _realfld)
        out << " align=\"right\"";
      out << ">";
      get(c).as_string(val);
      if (!val.blank())
      {
        val.rtrim();
        out << val;
      }
      out << "  </td>" << endl;
    }
    out << " </tr>" << endl;
  }
  
  out << "</table>" << endl;
  out << "</body>" << endl;
  out << "</html>" << endl;

  return !pi.iscancelled();
}

bool TRecordset::save_as_silk(const char* path)
{
  TProgind pi(items(), TR("Esportazione in corso..."), true, true);

  ofstream out(path);
  out << "ID;PWXL;N;E" << endl;

  // Larghezza colonne
  for (unsigned int h = 0; h < columns(); h++)
  {
    const TRecordset_column_info& ci = column_info(h);
    const int w = max(ci._width, ci._name.len());
    out << "F;W" << (h+1) << ' ' << (h+1) << ' ' << w << endl;
  }

  // Intestazioni colonne
  for (unsigned int c = 0; c < columns(); c++)
  {
    const TRecordset_column_info& ci = column_info(c);
    out << "C;Y1;X" << (c+1) << ";K\"" << ci._name << '"' << endl;
  }

  TString val;
  for (TRecnotype n = 0; n < items(); n++)
  {
    move_to(n);
    pi.addstatus(1);
    if (pi.iscancelled())
      break;
    for (unsigned int c = 0; c < columns(); c++)
    {
      const TRecordset_column_info& ci = column_info(c);
      const bool is_alpha = ci._type == _alfafld || ci._type == _charfld || 
                            ci._type == _memofld || ci._type == _datefld;

      out << "C;Y" << (n+2) << ";X" << (c+1) << ";K";
      if (is_alpha) out << '"';
      get(c).as_string(val);
      if (!val.blank())
      {
        val.rtrim();
        val.replace('"', '\'');
        out << val;
      }
      if (is_alpha) out << '"';
      out << endl;
    }
  }
  out << "E" << endl;
 
  return !pi.iscancelled();
}

bool TRecordset::save_as_text(const char* path)
{
  TProgind pi(items(), TR("Esportazione in corso..."), true, true);

  ofstream out(path);
  TString val;
  for (TRecnotype n = 0; n < items(); n++)
  {
    move_to(n);
    for (unsigned int c = 0; c < columns(); c++)
    {
      if (c > 0)
        out << '\t';
      get(c).as_string(val);
      if (!val.blank())
      {
        val.rtrim();
        out << val;
      }
    }
    out << endl;
    pi.addstatus(1);
    if (pi.iscancelled())
      break;
  }
 
  return !pi.iscancelled();
}

bool TRecordset::save_as_campo(const char* path)
{
  TProgind pi(items(), TR("Esportazione in corso..."), true, true);
  ofstream out(path);

  out << "[Head]" << endl;
  out << "Version=0";

  for (unsigned int c = 0; c < columns(); c++)
  {
    const TRecordset_column_info& ci = column_info(c);
    if ((c % 8) == 0)
      out << endl << "Fields=";
    else
      out << '|';
    out << ci._name;
  }
  out << endl << endl << "[Data]" << endl;

  TString val;
  for (TRecnotype n = 0; n < items(); n++)
  {
    move_to(n);
    for (unsigned int c = 0; c < columns(); c++)
    {
      if (c > 0)
        out << '|';
      get(c).as_string(val);
      if (!val.blank())
      {
        val.rtrim();
        out << val;
      }
    }
    out << endl;
    pi.addstatus(1);
    if (pi.iscancelled())
      break;
  }
  return !pi.iscancelled();
}

bool TRecordset::save_as(const char* path, TRecordsetExportFormat fmt, int mode)
{
  if (fmt == fmt_unknown)
  {
    TString ext;
    xvt_fsys_parse_pathname(path, NULL, NULL, NULL, ext.get_buffer(), NULL);
    ext.lower();
    if (ext.starts_with("htm"))
      fmt = fmt_html; else
    if (ext == "xls" || ext == "slk")
      fmt = fmt_silk;
  }
  bool ok = false;
  switch (fmt)
  {
  case fmt_html : ok = save_as_html(path);  break;
  case fmt_silk : ok = save_as_silk(path);  break;
  case fmt_campo: ok = save_as_campo(path); break;
  case fmt_dbf  : ok = save_as_dbf(path, mode);   break;
  default       : ok = save_as_text(path);  break;
  }

  return ok;
}

int TRecordset::find_column(const char* column_name) const
{
	int i;
  for (i = columns()-1; i >= 0; i--)
  {
    const TRecordset_column_info& info = column_info(i);
    if (info._name == column_name)
      break;
  }
  return i;
}

TVariant& TRecordset::get_tmp_var() const
{
  static TArray _page;      // Variants to be returned by get
  static int _next_var = 0; // Index of next variant to be returned

  if (_next_var >= 32)
    _next_var = 0;
  TVariant* var = (TVariant*)_page.objptr(_next_var);
  if (var == NULL)
  {
    var = new TVariant;
    _page.add(var, _next_var);
  }
  _next_var++;
  return *var;
}

const TVariant& TRecordset::get(const char* column_name) const
{
  if (*column_name == '#')
    return get_var(column_name);

  char* colon = strchr(column_name, ':');
  if (colon != NULL)
  {
    *colon = '\0';
    const int i = find_column(column_name);
    *colon = ':';
    if (i >= 0)
    {
      const TString& str = get(i).as_string();
      TString subfield; subfield << (colon+1) << '=';
      int s = str.find(subfield);
      if (s == 0 || (s > 0 && str[s-1] < ' '))
      {
        static TVariant var;
        s += subfield.len();
        const int e = str.find('\n', s);
        var.set(str.sub(s, e));
        return var;
      }
    }
  }
  else
  {
    const int i = find_column(column_name);
    if (i >= 0)
      return get(i);
  }

  return NULL_VARIANT;
}

const TVariant& TRecordset::get_var(const char* name) const
{
  // Se mi accorgo che posso e voglio accedere ad un campo del recordset padre
  if (_parentset != NULL && strncmp(name, "#PARENT.", 8) == 0) 
  {
    return _parentset->get(name+8); // Attenzione! E' giusto usare get() e non get_var()
  }

  const TVariant* var = (const TVariant*)_var.objptr(name);
  return var != NULL ? *var : NULL_VARIANT;
}

bool TRecordset::set_var(const char* name, const TVariant& var, bool create)
{
  bool ok = false;
  TVariant* old = (TVariant*)_var.objptr(name);
  if (old != NULL)
  {
    *old = var;
    ok = true;
  }
  else
  {
    if (create)
    {
      _var.add(name, var);
      _varnames.add(name);
      ok = true;
    }
  }
  if (ok)
    requery();
  return ok;
}

bool is_var_separator(char c)
{
  if (isspace(c))
    return true;
  return strchr("<=>(,", c) != NULL;
}

// Cerca le variabili nel testo SQL:
// Una variabile comincia per # ed e' composta da soli caratteri alfanumerici.
// Prima del simbolo # e dopo il nome della variabile deve esserci un separatore o blank
void TRecordset::find_and_reset_vars()
{
  _var.destroy();
  _varnames.destroy();
  
  const TString& sql = query_text();
  int diesis = sql.find('#'); // cerco il primo #
  for ( ; diesis > 0; diesis = sql.find('#', diesis+1)) // Cerco tutti i #
  {
    if (is_var_separator(sql[diesis-1])) // Controllo che ci sia un separatore prima del #
    {
      int i = diesis+1;
      for ( ; sql[i] && (isalnum(sql[i]) || strchr("@_.#", sql[i]) != NULL); i++);
      if (i > diesis+1)
      {
        const TString& name = sql.sub(diesis, i);
        set_var(name, NULL_VARIANT, true);
      }
    }
  }
}

void TRecordset::parsed_text(TString& sql) const
{
  sql = query_text();
  const bool is_isam = sql.starts_with("US");
  const bool is_sql = !is_isam;
  const char* apici = is_isam ? "\"" : "'";

  const bool vars = ((TSQL_recordset*)this)->ask_variables(false);
  if (vars) // Se ci sono variabili faccio le sostituzioni
  {
    const TString_array& names = variables();
    TString s;
    FOR_EACH_ARRAY_ROW(names, i, name) // Scandisco tutte le variabili
    {
      TVariant var = get_var(*name);
      int pos = sql.find(*name);
      for ( ; pos > 0; pos = sql.find(*name, pos+1))
      {
        const TString& after = sql.mid(pos+name->len());
        sql.cut(pos);
        
        if (var.type() == _datefld)
          s.format("%ld", var.as_date().date2ansi());
        else
        {
          s = var.as_string();
          if (is_sql) // Raddoppia gli apici in SQL
          {
            for (int i = 0; s[i]; i++)
            {
              if (s[i] == '\'')
                s.insert("'", i++);
            }
          }
        }
        if ((var.is_string() && s[0] != *apici && sql.right(1) != apici) || var.is_null())
        {
          s.insert(apici);
          s << apici;
        }
        sql << s << after;
      }
    }
  }
}

bool ask_variable(const char* name, TVariant& var)
{
  TMask m(TR("Richiesta variabile"), 1, 52, 4);
  m.add_static(-1, 0, name, 1, 0);
  m.add_string(101, 0, "", 1, 1, 80, "", 50);
  m.add_button(DLG_OK, 0, "", -12, -1, 10, 2);
  m.add_button(DLG_CANCEL, 0, "", -22, -1, 10, 2);
  m.set(101, var.as_string());
  const bool ok = m.run() == K_ENTER;
  if (ok)
  {
    const TString& str = m.get(101);
    if (is_numeric(str))
      var = real(str);
    else
      var = str;
  }
  return ok;
}

bool TRecordset::ask_variables(bool all)
{
  const bool ok = variables().items() > 0;
  if (ok) // Se ci sono variabili faccio le sostituzioni
  {
    FOR_EACH_ARRAY_ROW(_varnames, i, name)
    {
      TVariant var = get_var(*name);
      if (var.is_null() || all) 
      {
        ask_variable(*name, var);
        if (var.is_null())
          var.set(""); // Mi serve assolutamente un valore!
        set_var(*name, var);
      }
    }
  }
  return ok;
}

TRecordset::TRecordset() : _parentset(NULL)
{ }

///////////////////////////////////////////////////////////
// Utility
///////////////////////////////////////////////////////////

static void sort_files(TString_array& files)
{
  TFilename path;

  // Trasforma i path completi in nomi senza estensione
  FOR_EACH_ARRAY_ROW(files, i, row)
  {
    path = *row; 
    path = path.name(); 
    path.ext("");
    path.lower();
    *row = path;
  }
  files.sort();  // Ordina alfabeticamente

  // Rimuove i files doppi proveninenti da Campo e Custom
  for (int j = files.last(); j > 0; j--)
  {
    if (files.row(j) == files.row(j-1))
      files.destroy(j);
  }
}

static bool get_xml_attr(const TString& line, const char* attr, TString& value)
{
  TString80 str; str << ' ' << attr << "=\"";
  const int pos = line.find(str);
  if (pos >= 0)
  {
    const int apicia = line.find('"', pos)+1;
    const int apicic = line.find('"', apicia);
    if (apicic > apicia)
    {
      value = line.sub(apicia, apicic);
      return true;
    }
  }
	value.cut(0);
  return false;
}

static bool get_xml_child(const TString& line, const char* tag, TString& value)
{
  TString80 str; str << '<' << tag << '>';
  const int pos = line.find(str);
  if (pos >= 0)
  {
    const int apicia = line.find('>', pos)+1;
    const int apicic = line.find('<', apicia);
    if (apicic > apicia)
    {
      value = line.sub(apicia, apicic);
      return true;
    }
  }
	value.cut(0);
  return false;
}

bool list_custom_files(const char* ext, const char* classe, TString_array& files)
{
  TString_array lista;
  TFilename path;

  {
    TWait_cursor hourglass;

    TString_array files;
    // Leggo i files in custom
    if (main_app().has_module(RSAUT))
    {
      TFilename custom = firm2dir(-1);
      custom.add("custom");
      if (!custom.exist())
        xvt_fsys_mkdir(custom);

      path = custom;
      path.add("*");
      path.ext(ext);
		}
    list_files(path, lista);
  }
  path = "*"; path.ext(ext);  // Leggo i files in campo
  list_files(path, lista);
  sort_files(lista);           // Ordino i files e rimuovo i doppioni


  TString8 acqua;
  TString stringona, desc;

  FOR_EACH_ARRAY_ROW(lista, i, row)
  {
    path = *row; path.ext(ext);
    bool ok = path.custom_path();
    if (ok)
    { 
      TScanner scan(path);
      stringona.cut(0);
      for (int i = 0; i < 3 && scan.good(); i++) // Leggo solo le prime righe
        stringona << scan.line();
  
      get_xml_attr(stringona, "class", acqua);
      if (classe && *classe)
        ok = acqua == classe;

    
      if (ok)
      {
        get_xml_child(stringona, "description", desc);
        TToken_string* riga = new TToken_string;
        riga->add(*row);
        riga->add(acqua);
        riga->add(desc);   
        files.add(riga);

      }
    }
  }
  return !files.empty();
}

bool select_custom_file(TFilename& path, const char* ext, const char* library)
{
  TArray_sheet sheet(-1, -1, 80, 20, TR("Selezione"), HR("Nome@8|Classe|Descrizione@50|Custom"));
  TString_array& files = sheet.rows_array();
  bool ok = list_custom_files(ext, library, files);

  if (ok)
  {
    TFilename name;
    FOR_EACH_ARRAY_ROW(files, i, row)
    {
      name = row->get(0); 
      name.ext(ext);
      name.custom_path();
      if (name.find("custom") > 0)
        row->add("X");
    }

    ok = sheet.run() == K_ENTER;
    if (ok)
    {
      path = sheet.row(-1).get(0);
      path.ext(ext);
      ok = path.custom_path();
    }
  }

  return ok;
}


///////////////////////////////////////////////////////////
// Private interface
///////////////////////////////////////////////////////////
#include "../sqlite/sqlite3.h"

class TSQLite : public TObject
{
  sqlite3* _handle;
  TFilename _currdb;

protected:
  TVariant& get_sql_value(const TRectype& curr, const RecFieldDes& fd, TVariant& tmp) const;

  void build_curr_path(TFilename& name) const;
  void test_path();

  bool bind_record(const TRectype& rec, const RecDes& rd, sqlite3_stmt* pStatement) const;

  bool create_dbf_times();
  long get_dbf_time(const TString& table);
  bool set_dbf_time(const TString& table, long last);
  bool import(int logicnum);

public:
  sqlite3* open(const char* fname = NULL);
  bool exec(const char* sql, sqlite3_callback callback = NULL, void* jolly = NULL, bool show_error = true);

  void close();

  bool exists(const char* table);
  bool parse_select_from(const char* szSql);

  TSQLite();
  virtual ~TSQLite();
} _TheDataBase;

void get_sql_directory(TFilename& name)
{
  name = firm2dir(-1); 
  name.add("sql");
  if (!name.exist())
    make_dir(name);
}

void TSQLite::build_curr_path(TFilename& name) const
{
  TString16 firm; firm.format("%05ldA.sql", prefix().get_codditta());
  get_sql_directory(name);
  name.add(firm);
}

sqlite3* TSQLite::open(const char* fname)
{
  close();
  _currdb = fname;
  int err = sqlite3_open(_currdb, &_handle);

  if (err = SQLITE_CORRUPT)
  {
    close();
    xvt_fsys_removefile(_currdb);
    err = sqlite3_open(_currdb, &_handle);
  }
  
  if (err == SQLITE_OK)
  {
    create_dbf_times();
  }
  else
  {
    const char* errmsg = sqlite3_errmsg(_handle); // Stringa di sitema: inutile sqlite3_free(errmsg)
    error_box(errmsg);
	}

  return _handle; 
}

void TSQLite::test_path()
{
  TFilename n;
  build_curr_path(n);
  if (n != _currdb)
    open(n);
}

bool TSQLite::exec(const char* sql, sqlite3_callback callback, void* jolly, bool show_error)
{
  if (_handle == NULL)
    test_path();

  TWait_cursor hourglass;
  char* errmsg = NULL;
  const int rc = sqlite3_exec(_handle, sql, callback, jolly, &errmsg);

  if (errmsg != NULL)
  {
    if (show_error)
    {
      TString msg;
      msg << sql;
      msg.cut(128);
      msg << '\n' << errmsg;
      error_box(msg);
    }
    sqlite3_free(errmsg);
  }
  return rc == SQLITE_OK;
}

void TSQLite::close()
{
  if (_handle != NULL)
  {
    sqlite3_close(_handle);
    _handle = NULL;
  }
}

const char* const DBF_TIMES_TABLE = "DBF_TIMES";

bool TSQLite::create_dbf_times()
{
  bool ok = exists(DBF_TIMES_TABLE);
  if (!ok)
  {
    TString sql;
    sql << "CREATE TABLE " << DBF_TIMES_TABLE << " (name TEXT,time NUMERIC);\n"
        << "CREATE UNIQUE INDEX " << DBF_TIMES_TABLE << "_1 ON " << DBF_TIMES_TABLE << " (name);";
    ok = exec(sql);
  }
  return ok;
}

bool TSQLite::set_dbf_time(const TString& table, long last)
{
  TString sql;
  sql << "REPLACE INTO " << DBF_TIMES_TABLE << " VALUES("
      << '\'' << table << "','" << last << "');";
  return exec(sql);
}

static int dbf_time_callback(void* jolly, int argc, char** argv, char** columns)
{
  long& last = *(long*)jolly;
  last = atol(argv[0]);
  return SQLITE_OK;
}

long TSQLite::get_dbf_time(const TString& table)
{
  TString sql;
  sql << "SELECT time FROM " << DBF_TIMES_TABLE << " WHERE name='" << table << "';";
  long last = 0;
  exec(sql, dbf_time_callback, &last);
  return last;
}

TVariant& TSQLite::get_sql_value(const TRectype& curr, const RecFieldDes& fd, TVariant& tmp) const
{
  switch (fd.TypeF)
  {
  case _realfld    : tmp.set(curr.get_real(fd.Name));  break;
  case _intfld     :
  case _longfld    :
  case _wordfld    :
  case _intzerofld :
  case _longzerofld: tmp.set(curr.get_long(fd.Name)); break;
  case _datefld    : 
    {
      const TDate date = curr.get_date(fd.Name);
      tmp.set(date.date2ansi()); 
    }
    break;
  case _boolfld    : tmp.set(curr.get_bool(fd.Name)); break;
  case _memofld:
    {
      TString memo = curr.get(fd.Name);
      memo.replace('\n', char(0xB6)); // Simbolo di paragrafo
      tmp.set(memo);
    }
    break;
  default          : tmp.set(curr.get(fd.Name)); break;
  }
  return tmp;
}

static int exists_callback(void *jolly, int argc, char **argv, char **azColName)
{
  bool& yes = *(bool*)jolly;
  yes = argc > 0;
  return SQLITE_OK;
}

bool TSQLite::exists(const char* table)
{
  TString sql; 
  sql << "SELECT name FROM sqlite_master WHERE (type='table')AND(name='" << table << "');";
  bool yes = false;
  exec(sql, exists_callback, &yes, false);
  return yes;
}

bool TSQLite::bind_record(const TRectype& rec, const RecDes& rd, sqlite3_stmt* pStatement) const
{
  int rc = SQLITE_OK;
  TVariant tmp;
  for (int i = 0; i < rd.NFields && rc==SQLITE_OK ; i++)
  {
    get_sql_value(rec, rd.Fd[i], tmp);
    const TString& val = tmp.as_string();
    rc = sqlite3_bind_text(pStatement, i+1, val, val.len(), SQLITE_TRANSIENT);
  }
  return rc == SQLITE_OK;
}

bool TSQLite::import(int logicnum)
{
  const TString& table = logic2table(logicnum);

  long last = get_dbf_time(table);
  if (logicnum >= LF_USER) // Dummy test
  {
    TLocalisamfile file(logicnum);
    if (!file.is_changed_since(last))
      return true;
  }

  const RecDes& rd = prefix().get_recdes(logicnum);

  TString sql; 
  if (exists(table))
  {
    // Drop old table
    sql.cut(0) << "DROP TABLE "<< table << ';'; 
    exec(sql);
  }

  // Create new table
  sql.cut(0) << "CREATE TABLE "<< table << "\n("; 
  int i;
  for (i = 0; i < rd.NFields; i++)
  {
    if (i > 0) sql << ',';
    sql << rd.Fd[i].Name << ' ';
    switch (rd.Fd[i].TypeF)
    {
    case _alfafld: sql << "TEXT"; break;
    case _memofld: sql << "BLOB"; break;
    case _datefld: sql << "DATE"; break;
    default      : sql << "NUMERIC"; break;
    }
  }
  sql << ");";
  if (!exec(sql))
    return false;

  TRelation rel(logicnum);
  TCursor cur(&rel);
  const TRecnotype items = cur.items();
  if (items > 0)
  {
    cur.freeze();

    TString msg;
    msg << TR("Importazione tabella") << ' ' << table;
    msg << ": " << items << ' ' << TR("righe");
    TProgind pi(items, msg, true, true);

    exec("BEGIN"); // Inizio transazione

    // Creo il comando INSERT INTO table VALUES(?,?,?,?,?,?);
    sql.cut(0) << "INSERT INTO " << table << " VALUES(";
    for (i = 0; i < rd.NFields; i++)
    {
      if (i != 0) sql << ',';
      sql << '?';
    }
    sql << ");";

    sqlite3_stmt* pStatement = NULL;
    int rc = sqlite3_prepare(_handle, sql, sql.len(), &pStatement, NULL);
  
    const TRectype& curr = rel.curr();
    for (cur = 0; cur.pos() < items; ++cur)
    {
      pi.addstatus(1);
      if (pi.iscancelled())
        break;
      bind_record(curr, rd, pStatement); // Sostituisce i ? coi veri valori
      rc = sqlite3_step(pStatement);     // Ritorna sempre 101 (SQLITE_DONE)
      rc = sqlite3_reset(pStatement);    // Azzero lo statement per ricominciare
    }
    rc = sqlite3_finalize(pStatement);
    exec("COMMIT");  // Fine transazione
  }
  
  // Creo gli indici DOPO l'importazione per maggiore velocita'
  TProgind pi(rd.NKeys, TR("Creazione indici"), false, true);
  for (int index = 0; index < rd.NKeys; index++)
  {
    pi.addstatus(1);
    sql.cut(0) << "CREATE INDEX " << table << '_' << (index+1) << " ON "<< table << "\n("; 
    const KeyDes& kd = rd.Ky[index];
    for (int k = 0; k < kd.NkFields; k++)
    {
      if (k > 0) sql << ',';
      const int ndx = kd.FieldSeq[k] % MaxFields;
      sql << rd.Fd[ndx].Name;
    }
    sql << ");";
    exec(sql);
  }

  set_dbf_time(table, last);  // Aggiorna ora di ultima modifica
 
  return true;
}

bool TSQLite::parse_select_from(const char* szSql)
{
  test_path();

  TString sql(szSql);
  sql.trim(); sql.upper();
  if (!sql.starts_with("SELECT"))
    return false;
  
  const int from = sql.find("FROM");
  if (from < 0)
    return false;

  const int where = sql.find("WHERE", from);
  TToken_string tables(sql.sub(from+5, where), ',');
  TString table;
  FOR_EACH_TOKEN(tables, tok)
  {
    table = tok;
    table.trim();
    for (int i = 0; table[i]; i++)
    {
      if (table[i] <= ' ' || table[i] == ';')
      { table.cut(i); break; }
    }
    const int logicnum = table2logic(table);
    if (logicnum > 0)
      import(logicnum);
  }

  return true;
}

TSQLite::TSQLite() : _handle(NULL)
{ }

TSQLite::~TSQLite()
{
  close();
}

///////////////////////////////////////////////////////////
// TSQL_recordset
///////////////////////////////////////////////////////////

void TSQL_recordset::reset()
{
  _items = 0;
  _first_row = 0;
  _current_row = -1;
  _pagesize = 512;
  _page.destroy();
  _column.destroy();
}

int TSQL_recordset::on_get_items(int argc, char** values, char** columns)
{
  if (_column.items() == 0)
  {
    for (int i = 0; i < argc; i++)
    {
      TRecordset_column_info* info = new TRecordset_column_info;
      info->_name = columns[i];
      info->_width = 1;
      info->_type = _alfafld;

      const char* fldtype = columns[argc+i];
      if (fldtype != NULL)
      {
        if (xvt_str_compare_ignoring_case(fldtype, "DATE") == 0)
        {
          info->_type = _datefld; 
          info->_width = 10;
        } else
        if (xvt_str_compare_ignoring_case(fldtype, "NUMERIC") == 0)
        {
          info->_type = _realfld;
        } else
        if (xvt_str_compare_ignoring_case(fldtype, "BLOB") == 0)
        {
          info->_type = _memofld;
          info->_width = 50;
        }
      }
      _column.add(info);
    }
  }
  if (_items < _pagesize)
  {
    for (int i = 0; i < argc; i++) if (values[i] && *values[i])
    {
      TRecordset_column_info& info = (TRecordset_column_info&)_column[i];
      if (info._type == _alfafld || info._type == _realfld)
      {
        const int len = strlen(values[i]);
        if (len > info._width)
          info._width = len;
      }
    }
  }

  _items++;
  return SQLITE_OK;
}

static int query_get_items(void* jolly, int argc, char** values, char** columns)
{
  TSQL_recordset* q = (TSQL_recordset*)jolly;
  return q->on_get_items(argc, values, columns);
}

void TSQL_recordset::requery()
{
  _items = 0;
  _page.destroy();
}

TRecnotype TSQL_recordset::items() const
{
  if (_items == 0)
  {
    TString sql; parsed_text(sql);
    TPerformance_profiler prof("SQL query");
    _TheDataBase.exec("PRAGMA show_datatypes = ON;", NULL, NULL);
    _TheDataBase.exec(sql, query_get_items, (TSQL_recordset*)this);
    _TheDataBase.exec("PRAGMA show_datatypes = OFF;", NULL, NULL);
  }
  return _items;
}

unsigned int TSQL_recordset::columns() const
{
  if (_column.items() == 0)
    items();
  return _column.items();
}
 
const TRecordset_column_info& TSQL_recordset::column_info(unsigned int c) const
{
  return (const TRecordset_column_info&)_column[c];
}

// Funzione chiamata per riempire la pagina corrente delle righe della query
int TSQL_recordset::on_get_rows(int argc, char** values)
{
  TArray* a = new TArray;
  for (int c = 0; c < argc; c++)
  {
    TVariant* var = new TVariant;
    switch (column_info(c)._type)
    {
    case _alfafld: 
      var->set(values[c]);
      break;
    case _memofld: 
      if (values[c])
      {
        TFixed_string memo(values[c]);
        memo.replace(char(0xB6), '\n');
        var->set(memo); 
      }
      break;
    case _datefld: 
      var->set(TDate(values[c])); 
      break;
    default: 
      var->set(real(values[c]));
      break;
    }      
    a->add(var, c);
  }
  _page.add(a);
  return SQLITE_OK;
}

static int query_get_rows(void* jolly, int argc, char** values, char** /*columns*/)
{
  TSQL_recordset* rs = (TSQL_recordset*)jolly;
  return rs->on_get_rows(argc, values);
}

bool TSQL_recordset::move_to(TRecnotype n)
{
  _current_row = n;
  if (n < 0 || n >= items())
  {
    _page.destroy(); // Forza rilettura la prossiva volta
    _first_row = 0;
    return false;
  }

  if (n < _first_row || n >= _first_row+_page.items())
  {
    TString sql; parsed_text(sql);
    if (sql.find("LIMIT ") < 0)
    {
      const int semicolon = sql.rfind(';');
      if (semicolon >= 0)
        sql.cut(semicolon);
      sql.trim();
      _page.destroy();
      if (n >= _pagesize)
        _first_row = n-32; // Prendo qualche riga dalla pagina precedente, per velocizzare il pagina su
      else
        _first_row = n;
      sql << "\nLIMIT " << _pagesize << " OFFSET " << _first_row << ';';
    }
    _TheDataBase.exec(sql, query_get_rows, this);
  }

  return true;
}

const TArray* TSQL_recordset::row(TRecnotype n)
{
  const TArray* a = NULL;
  if (move_to(n))
    a = (const TArray*)_page.objptr(n-_first_row);
  return a;
}

const TVariant& TSQL_recordset::get(unsigned int c) const
{
  const TArray* a = (const TArray*)_page.objptr(_current_row-_first_row);
  if (a != NULL)
  {
    const TVariant* s = (const TVariant*)a->objptr(c);
    if (s != NULL)
      return *s;
  }
  return NULL_VARIANT;
}

bool TRecordset::save_as_dbf(const char* table, int mode)
{
  char volume[_MAX_DRIVE];
  char dirname[_MAX_PATH];
  char name[_MAX_FNAME];
  char ext[_MAX_EXT];
  xvt_fsys_parse_pathname (table, volume, dirname, name, ext, NULL);
  
  const int logicnum = table2logic(name);
 
  if (mode == 0x0)
    mode = 0x1;
  if (mode & 0x4)
  {
    if (logicnum >= LF_USER && *dirname == '\0')
    {
      TSystemisamfile isam(logicnum);
      if (isam.zap() != NOERR)
        return error_box(TR("Impossibile cancellare il file '%s'"), table);
    }
    else
    {
      TFilename n;
      n = volume; n.add(dirname); n.add(name); n.ext("*");
      TString_array files; list_files(n, files);
      FOR_EACH_ARRAY_ROW(files, f, row)
        xvt_fsys_removefile(*row);
    }
    mode = 0x1;
  }

  TLocalisamfile* pisam = NULL;
  if (logicnum >= LF_USER)
  {
    if (*dirname)
      pisam = new TIsamtempfile(logicnum, table);
    else
      pisam = new TLocalisamfile(logicnum);
  }
  if (pisam == NULL)
    return error_box("Impossibile creare il file %s", table);

  TLocalisamfile& isam = *pisam;

  TProgind pi(items(), TR("Esportazione in corso..."));
  TRectype& rec = isam.curr();

  TString_array names;
  int valid = 0;
  for (unsigned int j = 0; j < columns(); j++)
  {
    const TVariant& var = get(j);
    const TString& name = column_info(j)._name;
    if (rec.exist(name))
    {
      names.add(name);
      valid++;
    }
    else
      names.add(EMPTY_STRING);
  }

  bool ok = true;
  for (bool go = move_first(); go; go = move_next())
  {
    pi.addstatus(1);
    rec.zero();
    FOR_EACH_ARRAY_ROW(names, j, name) if (name->not_empty())
      rec.put(*name, get(j).as_string());

    int err = NOERR;
    bool to_be_written = true;

    // Devo solo aggiornare parte dei campi?
    if ((mode & 0x2) && valid < rec.items())
    {
      err = isam.read(_isequal, _lock);
      if (err != NOERR)
        rec.zero();
      FOR_EACH_ARRAY_ROW(names, j, name) if (name->not_empty())
        rec.put(*name, get(j).as_string());
      if (err == NOERR)
        to_be_written = isam.rewrite() != NOERR;
    }
    if (to_be_written)
    {
      err = (mode & 0x1) ? isam.write() : isam.rewrite();
      if (err != NOERR && (mode & 0x3) != 0)
        err = (mode & 0x1) ? isam.rewrite() : isam.write();
    }
    if (err != NOERR)
    {
      ok = error_box(FR("Errore %d durante la scrittura del record %s"), err, rec.build_key());
      break;
    }
  }

  return ok;
}


void TSQL_recordset::set(const char* sql) 
{ 
  reset(); 
  _sql = sql;
  if (_sql.find("SELECT") >= 0 || _sql.find("select") >= 0)
  {
    _TheDataBase.parse_select_from(_sql);
    find_and_reset_vars();
  }
}

TSQL_recordset::TSQL_recordset(const char* sql) 
{ 
  set(sql); 
}

///////////////////////////////////////////////////////////
// TCursor_parser
///////////////////////////////////////////////////////////

class TCursor_parser
{
  istream& _instr;
  TArray& _column;

  TString _pushed;
  TString _token;
  
  TRelation* _relation;
  TCursor* _cursor;

protected:
  const TString& pop();
  const TString& line();
  void push();
  void add_column_info(const char* table, const TRectype& rec);

  void parse_sortexpr(TToken_string& se);
  void parse_filter(TToken_string& filter);
  void parse_select(TToken_string& filter);
  void parse_region(TRectype& rec);
  void parse_join_param(TRelation* rel, const TString& j, int to);
  void parse_join();
  void parse_sortedjoin();

public:
  TRelation* get_relation() { return _relation; }
  TCursor* get_cursor() { return _cursor; }

  TCursor_parser(istream& instr, TArray& column);
};

const TString& TCursor_parser::pop()
{
  if (_pushed.not_empty())
  {
    _token = _pushed;
    _pushed.cut(0);
  }
  else
  {
    _token.cut(0);
   
    eatwhite(_instr);
    if (_instr.eof())
      return _token;
  
    char instring = '\0';
    while (true)
    {
      char c; 
      _instr.get(c);
      if (c == EOF)
        break;
      if (instring > ' ') // Sono dentro ad una stringa
      {
        if (c == '\n')
          break;
        if (c == instring)
          instring = '\0';
        _token << c;
      }
      else
      {
        if (c == '"' || c == '\'')
        {
          instring = c;
          _token << c;
        }
        else
        {
          if (isspace(c))
            break;
          _token << c;
        }
      }
    }
  }
  return _token;
}

const TString& TCursor_parser::line()
{
  if (_pushed.not_empty())
    return pop();
  char* buff = _token.get_buffer(256);
  _instr.getline(buff, _token.size());
  return _token;
}

void TCursor_parser::push()
{
  CHECK(_pushed.empty(), "Repushing?");
  _pushed = _token;
}

void TCursor_parser::add_column_info(const char* table, const TRectype& rec)
{
  for (int i = 0; i < rec.items(); i++)
  {
    TRecordset_column_info* info = new TRecordset_column_info;
    const char* name = rec.fieldname(i);
    info->_name << table << '.' << name;
    info->_type = rec.type(name);
    switch (info->_type)
    {
    case _datefld: info->_width = 10; break;
    case _memofld: info->_width = 50; break;
    default      : info->_width = rec.length(name); break;
    }
    _column.add(info);
  }
}

void TCursor_parser::parse_sortexpr(TToken_string& se)
{
  const char sep = se.separator();
  se.separator(' ');
  _instr.getline(se.get_buffer(), se.size());
  se.strip_double_spaces();
  se.replace(' ', sep);
  se.separator(sep);

  // Trasforma i nomi dei files in numeri se necessario
  if (se.find('.') > 0)
  {
    TToken_string fld(16, '.');
    TString16 name;
    for (int i = 0; se.get(i, fld); i++)
    {
      if (!isdigit(fld[0]) && fld.find('.') > 0)
      {
        fld.get(0, name);
        const int num = ::table2logic(name);
        if (num != 0)
        {
          fld.add(num, 0);
          se.add(fld, i);
        }
      }
    }
  }
}

void TCursor_parser::parse_filter(TToken_string& filter)
{
  const TString& str = pop();
  while (str.find('=') > 0)
  {
    filter.add(str);
    pop();
  }
  push();
}

void TCursor_parser::parse_select(TToken_string& filter)
{
  filter = line();
  filter.trim();
}


void TCursor_parser::parse_region(TRectype& rec)
{
  TString16 field;
  TString80 value;
  while (true)
  {
    const TString& ass = pop();
    const int equal = ass.find('=');
    if (equal > 0)
    {
      field = ass.left(equal);
      value = ass.mid(equal+1);
      if (value[0] == '"' || value[0] == '\'')
      {
        value.rtrim(1);
        value.ltrim(1);
      }
      rec.put(field, value);       
    }
    else
      break;
  }
  push();
}


void TCursor_parser::parse_join_param(TRelation* rel, const TString& j, int to)
{
  int key = 1;
  const TString& tok = pop();
  if (tok.starts_with("KE"))
  {
    pop();
    key = atoi(tok);
  }
  else 
    push();

  int alias = 0;
  pop();
  if (tok.starts_with("AL"))
  {
    pop();
    alias = atoi(tok);
  }
  else 
    push();

  TToken_string exp(80);
  pop();
  if (tok == "INTO")
  {
    parse_filter(exp);
  }
  if (exp.empty())
    yesnofatal_box("JOIN senza espressioni INTO");

  const int logicnum = table2logic(j);
  if (logicnum != LF_TAB && logicnum != LF_TABCOM)
    rel->add(logicnum, exp, key, to, alias);   // join file
  else
    rel->add(j, exp, key, to, alias);         // join table

  TString16 tabname;
  if (alias > 0)
    tabname << alias << '@'; 
  else
    tabname = j;
  const TRectype& rec = rel->curr(logicnum);
  add_column_info(tabname, rec);    
}

void TCursor_parser::parse_join()
{
  const TString j = pop();           // File or table

  int to = 0;
  const TString& tok = pop();
  if (tok == "TO")         // TO keyword
  {
    pop();
    to = table2logic(tok);
  }
  else 
    push();
  
  parse_join_param(_relation, j, to);
}

void TCursor_parser::parse_sortedjoin()
{
  TToken_string filter,sortexp;
  const TString j = pop();           // File or table
  const TString& tok = pop(); 
  if (tok == "BY" )
  {
    parse_sortexpr(sortexp);
  } 
  else 
    push();
 
  pop();
  if (tok.starts_with("FI") || tok.starts_with("SE")) 
  {
    parse_select(filter);
  } 
  else 
    push();
  
  TRelation* sortrel = new TRelation(table2logic(j)); 
  while (true)
  {
    pop();
    if (tok.empty() || tok.starts_with("JO"))
    {
      push();
      break;
    }

    if (tok.starts_with("US"))         // USING keyword
    {
      const TString subj = pop();      // File or table
      parse_join_param(sortrel, subj, table2logic(j));
    } 
  } 

  int to = 0;
  pop();
  if (tok == "TO")         // TO keyword
  {
    pop();
    to = table2logic(tok);
  }
  else 
    push();

  int key = 1;
  pop();
  if (tok.starts_with("KE"))
  {
    pop();
    key = atoi(tok);
  }
  else 
    push();

  int alias = 0;
  pop();
  if (tok.starts_with("AL"))
  {
    pop();
    alias = atoi(tok);
  }
  else 
    push();

  TToken_string exp(80);
  if (pop() == "INTO")
  {
    pop();
    while (tok.find('=') > 0)
    {
      exp.add(tok);
      pop();
    }
  }
  push();

  TSortedfile *sf= new TSortedfile(atoi(j),sortrel,sortexp,filter,key);
  _relation->add((TLocalisamfile *)sf, exp, key, to, alias, false);         // join table

  TString16 tabname = j;
  if (alias > 0)
    tabname.cut(0) << alias << '@'; 
  add_column_info(tabname, sf->curr());    
}

TCursor_parser::TCursor_parser(istream& instr, TArray& col) 
                : _instr(instr), _column(col), _relation(NULL), _cursor(NULL)
{
  _column.destroy();
  const TString& tok = pop();
  if (!tok.starts_with("US"))
    push();

  pop();
  if (tok.blank())
    return;

  int logicnum = table2logic(tok);
  if (logicnum == LF_MAG && tok == "MAG")  // Faccio prevalere la tabella MAG sul file MAG
    logicnum = LF_TAB;

  if (logicnum == LF_TAB || logicnum == LF_TABCOM)
    _relation = new TRelation(tok);
  else
    _relation = new TRelation(logicnum);
  add_column_info(tok, _relation->curr());

  int key = 1;  // key number
  pop();
  if (tok.starts_with("KE"))
  {
    pop();
    key = atoi(tok);
  }
  else
    push();

  pop();
  
	TToken_string filter;

	if (tok.starts_with("FI") || tok.starts_with("SE"))
    parse_select(filter);
  else
    push();

  pop();
  if (tok.starts_with("BY"))  // "sort BY": user-defined sort
  { 
    TToken_string ordexpr(256);
    parse_sortexpr(ordexpr);
    _cursor = new TSorted_cursor(_relation, ordexpr, "", key);
  }
  else
    push();


  if (_cursor == NULL)
    _cursor = new TCursor(_relation, "", key);

  TRectype rec_start(_relation->curr());
  TRectype rec_stop(rec_start);

  while (true)
  {
    pop();
    if (tok.starts_with("FR"))
      parse_region(rec_start); else
    if (tok.starts_with("TO"))
      parse_region(rec_stop); else
    if (tok.starts_with("JO"))
      parse_join(); else
    if (tok.starts_with("SO"))
      parse_sortedjoin(); 
    else
      break;
  }
  push(); 

  if (!rec_start.empty() || !rec_stop.empty())
    _cursor->setregion(rec_start, rec_stop);
	if (!filter.empty())
		_cursor->setfilter(filter);
  if (_relation->items() == 0) // Non ci sono anche tabelle collegate
  {
    FOR_EACH_ARRAY_ITEM(_column, i, obj)
    {
      TRecordset_column_info* info = (TRecordset_column_info*)obj;
      const int arrow = info->_name.find('.');
      if (arrow > 0)
        info->_name = info->_name.mid(arrow+1);
    }
  }
}

///////////////////////////////////////////////////////////
// TISAM_recordset
///////////////////////////////////////////////////////////

const TVariant& TISAM_recordset::get(int logic, const char* fldname) const
{
  const int idx = relation()->log2ind(logic);
  if (idx < 0)
    return NULL_VARIANT;

  TString80 name = fldname;
  TString16 subfield;
  int from = 1, to = 0;

  const int open_bracket = name.find('[');
  if (open_bracket > 0)
  {
    sscanf((const char*)name + open_bracket, "[%d,%d]", &from, &to);
    name.cut(open_bracket);
  }

  const int colon = name.find(':');

  if (colon > 0)
  {
    subfield = name.mid(colon+1);
    name.cut(colon);
  }

  const TRectype& rec = relation()->file(idx).curr();
  const TFieldtypes ft = rec.type(name);

  if (ft == _nullfld)
  {
    if (logic == LF_DOC) // Proviamo la magia
    {
      subfield = name;
      name = "G1";
    }
    else
      return NULL_VARIANT;
  }

  TVariant& var = get_tmp_var();
  switch (ft)
  {
  case _datefld: var.set(rec.get_date(name)); break;
  case _realfld: var.set(rec.get_real(name)); break;
  case _intfld : 
  case _longfld:
  case _wordfld: var.set(rec.get_long(name)); break;
  default      : var.set(rec.get(name)); break;
  }

  if (subfield.not_empty())
  {
    subfield << '=';
    const TString& str = var.as_string();
    int s = str.find(subfield);
    if (s == 0 || (s > 0 && str[s-1] < ' '))
    {
      s += subfield.len();
      const int e = str.find('\n', s);
      var.set(str.sub(s, e));
    }
  }

  if (to >= from)
    var.set(var.as_string().sub(from-1, to));

  return var;
}

const TVariant& TISAM_recordset::get(size_t c) const
{
  const TRecordset_column_info* info = (const TRecordset_column_info*)_column.objptr(c);
  if (info != NULL)
  {
    int logic = 0;
    const char* field = info->_name;
    const int dot = info->_name.find('.');
    if (dot > 0)
    {
      logic = table2logic(info->_name.left(dot));
      field += dot+1;
    }
    return get(logic, field);
  }
  return NULL_VARIANT;
}

const TVariant& TISAM_recordset::get(const char* name) const
{
  if (*name == '#')
    return get_var(name);

  const TFixed_string fldname(name);
  
  int table_end = fldname.find('.');
  int field_start = table_end+1;
  if (table_end < 0)
  {
    table_end = fldname.find('-');
    if (table_end > 0)
      field_start = table_end+2;
  }
  int logic = 0;
  const char* field = name;
  if (table_end > 0)
  {
    const TString& table = fldname.left(table_end);
    logic = table2logic(table);
    field += field_start;
  }
  return get(logic, field);
}

const TRecordset_column_info& TISAM_recordset::column_info(size_t i) const
{
  return (const TRecordset_column_info&)_column[i];
}

TCursor* TISAM_recordset::cursor() const
{
  if (_cursor == NULL && !_use.blank())
  {
    TString use; parsed_text(use);
    TPerformance_profiler prof("ISAM query");
    TISAM_recordset* my = (TISAM_recordset*)this;
#ifdef LINUX
		string s(use.get_buffer());
    istringstream instr(s);
#else
    istrstream instr(use.get_buffer(), use.len());
#endif
    TCursor_parser parser(instr, my->_column);

    my->_relation = parser.get_relation();
    my->_cursor = parser.get_cursor();

    if (_cursor != NULL)
    {
      set_custom_filter(*_cursor);
      const TRecnotype items = _cursor->items();

      _cursor->freeze();
			if (items > 0)
				*_cursor = 0L;
    }
  }
  return _cursor;
}

TRelation* TISAM_recordset::relation() const
{
  cursor();
  return _relation;
}

TRecnotype TISAM_recordset::current_row() const
{
  TCursor* c = cursor();
  return c != NULL ? c->pos() : -1;
}

TRecnotype TISAM_recordset::items() const 
{
  TCursor* c = cursor();
  return c != NULL ? c->items() : 0;
}

unsigned int TISAM_recordset::columns() const 
{ 
  cursor();
  return _column.items(); 
}


bool TISAM_recordset::move_to(TRecnotype pos)
{
  TCursor* c = cursor();
  bool ok = c != NULL && pos >= 0;
  if (ok)
  {
    *c = pos;
    ok = pos >= 0 && pos < items();
  }
  return ok;
}

void TISAM_recordset::reset()
{
  _column.destroy();
  if (_relation != NULL)
  {
    delete _relation; 
    _relation = NULL;
  }
  if (_cursor != NULL)
  {
    delete _cursor; 
    _cursor = NULL;
  }
}

void TISAM_recordset::requery()
{
  if (_cursor != NULL)
  {
    delete _cursor; 
    _cursor = NULL;
  }
}

void TISAM_recordset::set(const char* use)
{
  reset();
  _use = use;
  find_and_reset_vars(); 
}

TISAM_recordset::TISAM_recordset(const char* use) 
               : _relation(NULL), _cursor(NULL)
{ 
  set(use); 
}

TISAM_recordset::~TISAM_recordset()
{
  reset();
}

///////////////////////////////////////////////////////////
// TRecordset_sheet
///////////////////////////////////////////////////////////

void TRecordset_sheet::get_row(long r, TToken_string& row)
{
  row.separator('\t');
  row.cut(0);
  if (_query.move_to(r))
  {
    TString str;
    for (unsigned int c = 0; c < _query.columns(); c++)
    {
      _query.get(c).as_string(str);
      row.add(str);
    }
  }
}

long TRecordset_sheet::get_items() const
{
  return _query.items();
}

TRecordset_sheet::TRecordset_sheet(TRecordset& query) 
                : TSheet(-1, -1, -2, -4, "Query", query.sheet_head()), _query(query)
{
}