638 lines
15 KiB
C++
Executable File
638 lines
15 KiB
C++
Executable File
#include <sqlset.h>
|
|
|
|
#include <config.h>
|
|
#include <diction.h>
|
|
#include <relation.h>
|
|
#include <progind.h>
|
|
#include <utility.h>
|
|
|
|
///////////////////////////////////////////////////////////
|
|
// Private interface
|
|
///////////////////////////////////////////////////////////
|
|
|
|
#include "../../sqlite3/include/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)
|
|
{
|
|
if (xvt_fsys_is_fixed_drive(_currdb.path()) &&
|
|
ini_get_int(CONFIG_INSTALL, "Main", "Type") == 1) // Single user scenario
|
|
{
|
|
sqlite3_exec(_handle, "PRAGMA locking_mode=EXCLUSIVE", NULL, NULL, NULL);
|
|
sqlite3_exec(_handle, "PRAGMA synchronous=OFF", NULL, NULL, NULL);
|
|
sqlite3_exec(_handle, "PRAGMA journal_mode=WAL", NULL, NULL, NULL);
|
|
}
|
|
else
|
|
{
|
|
sqlite3_exec(_handle, "PRAGMA locking_mode=NORMAL", NULL, NULL, NULL);
|
|
sqlite3_exec(_handle, "PRAGMA synchronous=NORMAL", NULL, NULL, NULL);
|
|
sqlite3_exec(_handle, "PRAGMA journal_mode=DELETE", NULL, NULL, NULL);
|
|
}
|
|
create_dbf_times();
|
|
}
|
|
else
|
|
{
|
|
const char* errmsg = sqlite3_errmsg(_handle); // Stringa di sitema: inutile sqlite3_free(errmsg)
|
|
error_box(errmsg);
|
|
_handle = NULL;
|
|
}
|
|
|
|
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);
|
|
const bool already = exists(table);
|
|
|
|
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) && already)
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
|
|
const RecDes& rd = prefix().get_recdes(logicnum);
|
|
|
|
TString sql;
|
|
if (already)
|
|
{
|
|
// Drop old table (and associated indexes?)
|
|
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();
|
|
TString msg;
|
|
|
|
if (items > 0)
|
|
{
|
|
cur.freeze();
|
|
|
|
msg << TR("Importazione ") << table << ": " << items << ' ' << TR("righe");
|
|
TProgress_monitor pi(items, msg);
|
|
|
|
// 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 << ");";
|
|
|
|
TPerformance_profiler pp(msg);
|
|
exec("BEGIN"); // Inizio transazione
|
|
|
|
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)
|
|
{
|
|
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
|
|
if (!pi.add_status())
|
|
break;
|
|
}
|
|
rc = sqlite3_finalize(pStatement);
|
|
|
|
exec("COMMIT"); // Fine transazione
|
|
}
|
|
|
|
if (rd.NKeys > 0)
|
|
{
|
|
// Creo gli indici DOPO l'importazione per maggiore velocita'
|
|
msg.format("Creazione di %d indici su %s", rd.NKeys, (const char*)table);
|
|
TProgress_monitor pi(rd.NKeys, msg, false);
|
|
exec("BEGIN"); // Inizio transazione
|
|
for (int index = 0; index < rd.NKeys; index++)
|
|
{
|
|
const KeyDes& kd = rd.Ky[index];
|
|
sql = "CREATE ";
|
|
if (!kd.DupKeys) sql << "UNIQUE ";
|
|
sql << "INDEX " << table << '_' << (index+1) << " ON "<< table << "\n(";
|
|
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);
|
|
pi.add_status();
|
|
}
|
|
exec("COMMIT"); // Fine transazione
|
|
}
|
|
|
|
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;
|
|
|
|
TProgress_monitor pi(tables.items(), TR("Importazione tabelle"));
|
|
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)
|
|
{
|
|
pi.set_text(table);
|
|
import(logicnum);
|
|
}
|
|
if (!pi.add_status())
|
|
break;
|
|
}
|
|
|
|
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_same(fldtype, "DATE"))
|
|
{
|
|
info->_type = _datefld;
|
|
info->_width = 10;
|
|
} else
|
|
if (xvt_str_same(fldtype, "NUMERIC"))
|
|
{
|
|
info->_type = _realfld;
|
|
} else
|
|
if (xvt_str_same(fldtype, "BLOB"))
|
|
{
|
|
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);
|
|
}
|
|
|