git-svn-id: svn://10.65.10.50/branches/R_10_00@22742 c028cbd2-c16b-5b4b-a496-9718f37d4682
		
			
				
	
	
		
			604 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			604 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
		
			Executable File
		
	
	
	
	
| #include <sqlset.h>
 | |
| 
 | |
| #include <diction.h>
 | |
| #include <relation.h>
 | |
| #include <progind.h>
 | |
| #include <utility.h>
 | |
| 
 | |
| ///////////////////////////////////////////////////////////
 | |
| // 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();
 | |
|     _currdb.fremove();
 | |
|     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, 'last' should be updated ALWAYS!
 | |
|   {
 | |
|     TBaseisamfile 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_pos = sql.find("WHERE", from);
 | |
|   TToken_string tables(sql.sub(from+5, where_pos), ',');
 | |
|   TString table;
 | |
|   FOR_EACH_TOKEN(tables, tok)
 | |
|   {
 | |
|     table = tok;
 | |
|     const int join_pos = table.find("JOIN ");
 | |
|     if (join_pos > 0)
 | |
|     {
 | |
|       const TString& joined = table.mid(join_pos+5).before(' ');
 | |
|       tables.add(joined);
 | |
|       table = table.before(' ');
 | |
|     }
 | |
|     table.trim();
 | |
|     for (int i = 0; table[i]; i++)
 | |
|     {
 | |
|       if (table[i] <= ' ' || table[i] == ';')
 | |
|       { table.cut(i); break; }
 | |
|     }
 | |
| 
 | |
| 		const int logicnum = (table == "MAG") ? LF_MAG : table2logic(table);
 | |
|     if (logicnum >= LF_USER)
 | |
|       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)
 | |
|   {
 | |
|     // Aggiusta le lunghezze dei campi testo e numerici
 | |
|     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;
 | |
|           if (info._type == _alfafld)
 | |
|           {
 | |
|             if (len == 8 && atol(values[i]) > 19000000)
 | |
|             {
 | |
|               info._type = _datefld; 
 | |
|               info._width = 10;
 | |
|             } else
 | |
|             if (real::is_real(values[i]))
 | |
|               info._type = _realfld;
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   _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 prossima volta
 | |
|     _first_row = 0;
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   if (n < _first_row || n >= _first_row+_page.items())
 | |
|   {
 | |
|     TString sql; parsed_text(sql);
 | |
|     if (sql.starts_with("SELECT ") && 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;
 | |
| }
 | |
| 
 | |
| 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();
 | |
|   }
 | |
| }
 | |
| 
 | |
| const TString& TSQL_recordset::driver_version() const
 | |
| {
 | |
|   TString& tmp = get_tmp_string();
 | |
|   tmp << "SQLite " << sqlite3_libversion();
 | |
|   return tmp;
 | |
| }
 | |
| 
 | |
| TSQL_recordset::TSQL_recordset(const char* sql) 
 | |
| { 
 | |
|   set(sql); 
 | |
| }
 | |
| 
 |