#include #include #include #include #include /////////////////////////////////////////////////////////// // 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; } } } _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.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); }