#include "../xvaga/incstr.h" #include #include #include #include #include #include #include #include /////////////////////////////////////////////////////////// // TVariant /////////////////////////////////////////////////////////// const TVariant NULL_VARIANT; void TVariant::set_null() { if (_ptr != NULL) { if (_type != _nullfld && _type != _longfld) delete (char *) _ptr; _ptr = NULL; } _type = _nullfld; } void TVariant::set(const char* str) { if (str != NULL) { if (_type == _alfafld) *((TString*)_ptr) = str; else { set_null(); _type = _alfafld; _ptr = new TString(str); } } else set_null(); } void TVariant::set(const real& r) { if (_type == _realfld) *((real*)_ptr) = r; else { set_null(); _type = _realfld; _ptr = new real(r); } } void TVariant::set(const TDate& d) { if (_type == _datefld) *((TDate*)_ptr) = d; else { set_null(); _type = _datefld; _ptr = new TDate(d); } } void TVariant::set(const long n) { if (_type != _longfld) set_null(); _type = _longfld; _ptr = (void*)n; } bool TVariant::is_zero() const { switch (_type) { case _datefld: return !as_date().ok(); case _longfld: return _ptr == NULL; case _realfld: return as_real().is_zero(); case _alfafld: return real::is_null(as_string()); default: break; } return true; } bool TVariant::is_empty() const { if (_type == _alfafld) return as_string().empty(); return is_zero(); } TDate TVariant::as_date() const { if (_type == _datefld) return *(TDate*)_ptr; if (_type == _intfld || _type == _realfld) return TDate(as_int()); return TDate(as_string()); } long TVariant::as_int() const { long n = 0; switch(_type) { case _datefld: n = as_date().date2ansi(); break; case _longfld: n = (long)_ptr; break; case _realfld: n = as_real().integer(); break; case _alfafld: n = atoi(as_string()); break; default : break; } return n; } bool TVariant::as_bool() const { bool ok = false; if (_type == _alfafld) ok = strchr("1XY", as_string()[0]) != NULL; else ok = as_int() != 0; return ok; } COLOR TVariant::as_color() const { const unsigned long rgb = as_int(); unsigned char r = XVT_COLOR_GET_RED(rgb); unsigned char g = XVT_COLOR_GET_GREEN(rgb); unsigned char b = XVT_COLOR_GET_BLUE(rgb); return MAKE_COLOR(r, g, b); } real TVariant::as_real() const { if (_type == _realfld) return *(real*)_ptr; switch(_type) { case _alfafld: return real(as_string()); break; case _longfld: return real(as_int()); break; default : break; } return ZERO; } bool TVariant::as_string(TString& tmp) const { tmp.cut(0); switch(_type) { case _alfafld: tmp = *(TString*)_ptr; break; case _datefld: tmp = as_date().string(); break; case _longfld: tmp << as_int(); break; case _realfld: tmp = as_real().string(); break; default: break; } return !is_null(); } const TString& TVariant::as_string() const { if (_type == _alfafld) return *(TString*)_ptr; TString& tmp = get_tmp_string(); as_string(tmp); return tmp; } void TVariant::convert_to(TFieldtypes ft) { if (_type != ft) { switch (ft) { case _alfafld: set(as_string()); break; case _datefld: set(as_date()); break; case _longfld: set(as_int()); break; case _realfld: set(as_real()); break; default : set_null(); break; } } } void TVariant::copy(const TVariant& var) { switch (var._type) { case _datefld: set(var.as_date()); break; case _longfld: set(var.as_int()); break; case _realfld: set(var.as_real()); break; case _alfafld: set(var.as_string()); break; default : set_null(); break; } } int TVariant::compare(const TSortable& s) const { const TVariant& var = (const TVariant&)s; int cmp = 0; switch (_type) { case _datefld: cmp = as_date() - var.as_date(); break; case _longfld: cmp = as_int() - var.as_int(); break; case _realfld: { const real n = as_real() - var.as_real(); cmp = n.sign(); } break; case _alfafld: cmp = as_string().compare(var.as_string()); break; default : cmp = var.is_null() ? 0 : -1; } return cmp; } TVariant& TVariant::add(const TVariant& var) { switch (_type) { case _datefld: set(as_date() + var.as_int()); break; case _longfld: set(as_int() + var.as_int()); break; case _alfafld: *(TString*)_ptr << var.as_string(); break; case _realfld: *(real*)_ptr += var.as_real(); break; default: copy(var); break; } return *this; } TVariant& TVariant::sub(const TVariant& var) { switch (_type) { case _datefld: set(as_date() - var.as_int()); break; case _longfld: if (var.type() == _longfld) { set(as_int() - var.as_int()); break; } // Fall down default: { real n = as_real(); n -= var.as_real(); set(n); } break; } return *this; } /////////////////////////////////////////////////////////// // 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) { TString8* 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& name) { if (isdigit(name[0])) { int num = atoi(name); if (name[name.len()-1] == '@') 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 << "" << endl; out << "" << endl; out << "" << endl; out << " " << endl; out << " "; 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 << "
"; str << tok; } out << " " << endl; } out << " " << endl; TString val; for (TRecnotype n = 0; n < items(); n++) { move_to(n); pi.addstatus(1); if (pi.iscancelled()) break; out << " " << endl; for (unsigned int c = 0; c < columns(); c++) { const TRecordset_column_info& ci = column_info(c); out << " "; get(c).as_string(val); if (!val.blank()) { val.rtrim(); out << val; } out << " " << endl; } out << " " << endl; } out << "

" << path << "

" << str << "
" << endl; out << "" << endl; out << "" << 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; 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++) { out << "C;Y" << (n+2) << ";X" << (c+1) << ";K\""; get(c).as_string(val); if (!val.blank()) { val.rtrim(); val.replace('"', '\''); out << val; } 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) { 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; 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; } 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 { 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; } } 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]) || sql[i] == '_'); 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 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(); for (int i = 0; s[i]; i++) { if (s[i] == '\'') s.insert("'", i++); } } if ((var.is_string() && s[0] != '\'') || var.is_null()) { s.insert("'"); s << '\''; } sql << s << after; } } } } bool ask_variable(const char* name, TVariant& var) { TMask m("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; } /////////////////////////////////////////////////////////// // Utility /////////////////////////////////////////////////////////// bool select_custom_file(TFilename& path, const char* ext) { TFilename custom = firm2dir(-1); custom.add("custom"); if (!custom.exist()) xvt_fsys_mkdir(custom); path = custom; path.add("*"); path.ext(ext); TString_array files; list_files(path, files); TArray_sheet sheet(-1, -1, 78, 20, TR("Selezione"), TR("Nome@20|Descrizione@50")); TString str; FOR_EACH_ARRAY_ROW(files, i, row) { TXmlItem item; if (item.Load(*row)) { TToken_string* riga = new TToken_string; path = *row; path = path.name(); path.ext(""); riga->add(path); const TXmlItem* desc = item.FindFirst("description"); str = *row; if (desc != NULL) desc->GetEnclosedText(str); riga->add(str); sheet.add(riga); } } const bool ok = sheet.run() == K_ENTER; if (ok) { path = custom; path.add(sheet.row(-1).get(0)); path.ext(ext); } return ok; } /////////////////////////////////////////////////////////// // Private interface /////////////////////////////////////////////////////////// #include "../sqlite/sqlite.h" class TSQLite : public TObject { sqlite* _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 esporta(const TRectype& rec, ostream& sql) 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: sqlite* open(const char* fname = NULL); bool exec(const char* sql, sqlite_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); } sqlite* TSQLite::open(const char* fname) { close(); _currdb = fname; char* errmsg = NULL; _handle = sqlite_open(_currdb, 0, &errmsg); if (errmsg != NULL) { error_box(errmsg); sqlite_freemem(errmsg); } create_dbf_times(); return _handle; } void TSQLite::test_path() { TFilename n; build_curr_path(n); if (n != _currdb) open(n); } bool TSQLite::exec(const char* sql, sqlite_callback callback, void* jolly, bool show_error) { if (_handle == NULL) test_path(); TWait_cursor hourglass; char* errmsg = NULL; const int rc = sqlite_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); } sqlite_freemem(errmsg); } return rc == SQLITE_OK; } void TSQLite::close() { if (_handle != NULL) { sqlite_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::esporta(const TRectype& rec, ostream& sql) const { const RecDes& rd = *rec.rec_des(); TVariant tmp; for (int i = 0; i < rd.NFields; i++) { if (i > 0) sql << '\t'; get_sql_value(rec, rd.Fd[i], tmp); sql << tmp.as_string(); } sql << '\n'; return true; } 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("; for (int 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; // Creazione indici for (int index = 0; index < rd.NKeys; index++) { 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); } TRelation rel(logicnum); TCursor cur(&rel); const TRecnotype items = cur.items(); cur.freeze(); const TRectype& curr = rel.curr(); TString msg; msg << TR("Esportazione tabella") << ' ' << table; msg << ": " << items << ' ' << TR("righe"); TProgind pi(items, msg, true, true); TFilename tmp; tmp.tempdir(); tmp.add("sql.txt"); ofstream txt(tmp, ios::binary); for (cur = 0; cur.pos() < items; ++cur) { esporta(curr, txt); pi.addstatus(1); if (pi.iscancelled()) break; } txt.close(); msg << '\n' << TR("Importazione tabella") << ' ' << table; msg << ": " << items << ' ' << TR("righe"); pi.set_text(msg); sql.cut(0) << "COPY " << table << " FROM '" << tmp << "';"; if (exec(sql)) set_dbf_time(table, last); // Aggiorna ora di ultima modifica ::remove(tmp); 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; _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); } bool TSQL_recordset::set_var(const char* name, const TVariant& var, bool create) { const bool ok = TRecordset::set_var(name, var, create); if (ok) { _items = 0; _page.destroy(); } return ok; } 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, char** columns) { 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: { 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, columns); } 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-_pagesize/8; // 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(); } } 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(); 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_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 c; _instr.get(c); while (!isspace(c) && c != EOF) { _token << c; _instr.get(c); } } 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_d_spaces(); se.replace(' ', sep); se.separator(sep); } 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_region(TRectype& rec) { TString16 field; TString 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_filter(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; const int logicnum = table2logic(tok); if (logicnum != LF_TAB && logicnum != LF_TABCOM) _relation = new TRelation(logicnum); else _relation = new TRelation(tok); add_column_info(tok, _relation->curr()); int key = 1; // key number pop(); if (tok.starts_with("KE")) { pop(); key = atoi(tok); } 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(); pop(); if (tok.starts_with("FI") || tok.starts_with("SE")) { TToken_string filter; parse_filter(filter); if (_cursor == NULL) _cursor = new TCursor(_relation, filter, key); else _cursor->setfilter(filter); } else push(); if (_cursor == NULL) _cursor = new TCursor(_relation, "", key); TRectype rec_start(_relation->curr()); TRectype rec_stop(_relation->curr()); pop(); if (tok.starts_with("FR")) parse_region(rec_start); else push(); pop(); if (tok.starts_with("TO")) parse_region(rec_stop); else push(); if (!rec_start.empty() || !rec_stop.empty()) _cursor->setregion(rec_start, rec_stop, 0x2); while (true) { pop(); if (tok.starts_with("JO")) parse_join(); else if (tok.starts_with("SO")) parse_sortedjoin(); else break; } push(); 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 /////////////////////////////////////////////////////////// TVariant& TISAM_recordset::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& TISAM_recordset::get(int logic, const char* fldname) const { 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()->curr(logic); 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) { logic = table2logic(fldname.left(table_end)); 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) { _cursor->items(); _cursor->freeze(); } } 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() : -1; } unsigned int TISAM_recordset::columns() const { cursor(); return _column.items(); } bool TISAM_recordset::move_to(TRecnotype pos) { TCursor* c = cursor(); bool ok = c != NULL; 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; } } bool TISAM_recordset::set_var(const char* name, const TVariant& var, bool create) { const bool ok = TRecordset::set_var(name, var, create); if (ok) reset(); return ok; } 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) { } /////////////////////////////////////////////////////////// // TPerformance_profiler /////////////////////////////////////////////////////////// TPerformance_profiler::TPerformance_profiler(const char* desc) : _desc(desc) { #ifdef DBG _start = clock(); TString80 msg; msg << "Profiling " << desc << "..."; statbar_set_title(TASK_WIN, msg); while (true) { const clock_t clk = clock(); if (clk != _start) { _start = clk; break; } } #endif } void TPerformance_profiler::show() const { #ifdef DBG const double s = double(clock() - _start) / CLOCKS_PER_SEC; int hour = 0, min = 0; int sec = int(s); const int cent = int((s - sec)*100); if (sec >= 3600) { hour = sec / 3600; sec -= hour * 3600; } if (sec >= 60) { min = sec / 60; sec -= min * 60; } TString80 msg = _desc; msg.format("%s %02d:%02d:%02d.%02d", (const char*)_desc, hour, min, sec, cent); statbar_set_title(TASK_WIN, msg); #endif } TPerformance_profiler::~TPerformance_profiler() { show(); }