#include #include #include #include #include /////////////////////////////////////////////////////////// // TODBC_connections /////////////////////////////////////////////////////////// class TODBC_connections { TString _dsn, _usr; XVT_ODBC _odbc; protected: void close(); public: XVT_ODBC get(const char* dsn, const char* usr, const char* pwd, const char* dir); TODBC_connections(); ~TODBC_connections(); }; TODBC_connections _connections; void TODBC_connections::close() { if (_odbc != NULL) { xvt_odbc_free_connection(_odbc); _odbc = NULL; } } XVT_ODBC TODBC_connections::get(const char* dsn, const char* usr, const char* pwd, const char* dir) { if (_odbc == NULL || _dsn != dsn || _usr != usr) { close(); if (dsn && *dsn) _odbc = xvt_odbc_get_connection(_dsn = dsn, _usr = usr, pwd, dir); } return _odbc; } TODBC_connections::TODBC_connections() : _odbc(NULL) {} TODBC_connections::~TODBC_connections() { close(); } /////////////////////////////////////////////////////////// // TODBC_recordset /////////////////////////////////////////////////////////// XVT_ODBC TODBC_recordset::connection() const { return _connections.get(_dsn, _usr, _pwd, _dir); } bool TODBC_recordset::connect(const char* dsn, const char* usr, const char* pwd, const char* dir) { _dsn = dsn; _usr = usr; _pwd = pwd; _dir = dir; bool ok = connection() != NULL; if (ok) { const TString& name = driver_version(); if (name.starts_with("ACEODB")) _driver = ODBC_access; else if (name.starts_with("SQLSRV")) _driver = ODBC_mssql; else if (name.starts_with("MYSQL??")) _driver = ODBC_mysql; else _driver = ODBC_generic; } return ok; } const TString& TODBC_recordset::query_text() const { return _sql; } const TString& TODBC_recordset::driver_version() const { TString& tmp = get_tmp_string(50); xvt_odbc_driver(connection(), tmp.get_buffer(), tmp.size()); return tmp; } void TODBC_recordset::reset() { _first_row = 0; _items = 0; _current_row = -1; _pagesize = 512; _page.destroy(); _column.destroy(); _columns_loaded = false; } int TODBC_recordset::on_get_columns(int argc, char** values, char** columns) { static unsigned long _counter = 0; if (_column.empty()) { _counter = 0; for (int i = 0; i < argc; i++) { const char* fldtype = NULL; TRecordset_column_info* info = new TRecordset_column_info; info->_width = 1; info->_type = _alfafld; if (columns != NULL) { info->_name = columns[i]; fldtype = columns[argc+i]; } else info->_name.format("FIELD%d", i+1); 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); } } const bool processed = _counter++ < 128; if (processed) { 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; } } } return processed ? 0 : -1; } static int query_get_columns(void* jolly, int argc, char** values, char** columns) { TODBC_recordset* q = (TODBC_recordset*)jolly; return q->on_get_columns(argc, values, columns); } void TODBC_recordset::requery() { _items = 0; _current_row = -1; _page.destroy(); } int TODBC_recordset::on_get_items(int argc, char** values, char** columns) { if (!_columns_loaded) on_get_rows(argc, values, columns); return 0; } static int query_get_items(void* jolly, int argc, char** values, char** columns) { TODBC_recordset* q = (TODBC_recordset*)jolly; return q->on_get_items(argc, values, columns); } TRecnotype TODBC_recordset::items() const { if (!_loaded && _items == 0) { TString sql; parsed_text(sql); XVT_ODBC oc = connection(); if (oc != NULL) { TPerformance_profiler prof("ODBC count"); TRecnotype& i = (TRecnotype&)_items; if (!_columns_loaded) { TODBC_recordset* myself = (TODBC_recordset*)this; myself->_page.destroy(); myself->_cursor_pos = 0; i = xvt_odbc_execute(oc, sql, query_get_items, (void*)this); myself->_columns_loaded = true; } else i = xvt_odbc_execute(oc, sql, NULL, NULL); } } return _items; } unsigned int TODBC_recordset::columns() const { if (!_columns_loaded && _column.items() == 0) { XVT_ODBC oc = connection(); if (oc != NULL) { TODBC_recordset* myself = (TODBC_recordset*)this; TString sql; parsed_text(sql); TPerformance_profiler prof("ODBC info"); myself->_cursor_pos = 0; xvt_odbc_execute(oc, sql, query_get_columns, (void*)this); myself->_columns_loaded = true; } } return _column.items(); } const TRecordset_column_info& TODBC_recordset::column_info(unsigned int c) const { if (c >= columns()) // Generare column infos if needed c = 0; return (const TRecordset_column_info&)_column[c]; } // Funzione chiamata per riempire la pagina corrente delle righe della query int TODBC_recordset::on_get_rows(int argc, char** values, char** columns) { if (!_columns_loaded) on_get_columns(argc, values, columns); if (!_freezed && _page.items() >= _pagesize) return -1; if (_cursor_pos++ < _first_row) return 0; // Ignora le righe prima del LIMIT 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 0; } static int query_get_rows(void* jolly, int argc, char** values, char** columns) { TODBC_recordset* rs = (TODBC_recordset*)jolly; return rs->on_get_rows(argc, values, columns); } bool TODBC_recordset::move_to(TRecnotype n) { const TRecnotype tot = items(); _current_row = n; if (_freezed && _loaded) { if (n < 0) _current_row = 0L; if (n > tot) _current_row = tot; return true; } if (n < 0 || n >= tot) { _page.destroy(); // Forza rilettura la prossima volta _first_row = 0; return false; } if ((n < _first_row || n >= _first_row+_page.items()) || _freezed && !_loaded) { TString sql; parsed_text(sql); XVT_ODBC oc = connection(); if (oc == NULL) return false; if (tot > _pagesize && sql.find("LIMIT ") < 0) { const int semicolon = sql.rfind(';'); if (semicolon >= 0) sql.cut(semicolon); sql.trim(); _page.destroy(); if (_freezed) _first_row = 0; else if (n >= _pagesize) _first_row = n-32; // Prendo qualche riga dalla pagina precedente, per velocizzare il pagina su else _first_row = n; } TPerformance_profiler prof("ODBC query"); _cursor_pos = 0; xvt_odbc_execute(oc, sql, query_get_rows, this); _loaded = _freezed; if (!_columns_loaded) _columns_loaded = true; // Brutto posto ma necessario } return true; } long TODBC_recordset::exec(const char* sql) { long err = -1; TString cmd; // Se la stringa "sql" non contiene parametri di connessione ma essi sono in "_sql" // allora cerco di effetture la connessione in base a quella if (_dsn.empty() && strncmp(sql, "ODBC(", 5) != 0 && _sql.starts_with("ODBC(")) parsed_text(cmd); XVT_ODBC oc = connection(); if (oc != NULL) { set(sql); parsed_text(cmd); TPerformance_profiler prof("ODBC command"); err = xvt_odbc_execute(oc, cmd, NULL, NULL); } return err; } long TODBC_recordset::begin() { if (driver() == ODBC_mssql) return exec("BEGIN;"); return 0L; } long TODBC_recordset::commit() { if (driver() == ODBC_mssql) return exec("COMMIT;"); return 0L; } TRecnotype TODBC_recordset::current_row() const { return _current_row; } const TArray* TODBC_recordset::row(TRecnotype n) { const TArray* a = NULL; if (move_to(n)) a = (const TArray*)_page.objptr(n-_first_row); return a; } const TVariant& TODBC_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; } const TVariant& TODBC_recordset::get(const char* name) const { return TRecordset::get(name); } void TODBC_recordset::parsed_text(TString& sql) const { TRecordset::parsed_text(sql); if (sql.starts_with("ODBC(", true)) { const int par = sql.find(')'); if (par > 0) { TToken_string conn(sql.sub(5, par), ','); sql.ltrim(par+1); sql.trim(); TString dsn = conn.get(); dsn.strip("\""); TString usr = conn.get(); usr.strip("\""); TString pwd = conn.get(); pwd.strip("\""); TString dir = conn.get(); dir.strip("\""); if (!((TODBC_recordset*)this)->connect(dsn, usr, pwd, dir)) error_box(FR("Impossibile connettersi al DSN %s"), (const char*)dsn); } } } bool TODBC_recordset::set_log_file(const char* fn) { XVT_ODBC h = connection(); return xvt_odbc_log_file(h, fn) != 0; } int TODBC_recordset::compare_key(const TISAM_recordset& dbfset) { int cmp = 0; const RecDes& recdes = dbfset.cursor()->curr().rec_des(); const int keyno = dbfset.cursor()->key(); const KeyDes& keydes = recdes.Ky[keyno-1]; const int col = columns(); for (int f = 0; f < keydes.NkFields && cmp == 0; f++) { const int nfield = keydes.FieldSeq[f] % MaxFields; const char* fname = recdes.Fd[nfield].Name; const bool upper = keydes.FieldSeq[f] > MaxFields; switch (recdes.Fd[f].TypeF) { case _alfafld: case _memofld: if (upper) cmp = xvt_str_compare_ignoring_case(dbfset.get(fname).as_string(), get(fname).as_string()); else cmp = dbfset.get(fname).compare(get(fname)); break; case _realfld: { const real dbfnum = dbfset.get(fname).as_real(); const real sqlnum = get(fname).as_real(); if (dbfnum != sqlnum) cmp = dbfnum < sqlnum ? -1 : +1; } break; case _datefld: { const TDate dbfdate = dbfset.get(fname).as_date(); const TDate sqldate = get(fname).as_date(); if (dbfdate != sqldate) cmp = dbfdate < sqldate ? -1 : +1; } break; default: cmp = dbfset.get(fname).as_int() - get(fname).as_int(); break; } } return cmp; } int TODBC_recordset::compare_rec(const TISAM_recordset& dbfset) { int cmp = 0; const RecDes& recdes = dbfset.cursor()->curr().rec_des(); const unsigned int nfields = recdes.NFields; const int col = columns(); for (unsigned int f = 0; f < nfields && cmp == 0; f++) { const TVariant& dbffld = dbfset.get(f); const TVariant& sqlfld = get(f); switch (recdes.Fd[f].TypeF) { case _alfafld: case _memofld: cmp = dbffld.compare(sqlfld); break; case _realfld: { const real dbfnum = dbffld.as_real(); const real sqlnum = sqlfld.as_real(); if (dbfnum != sqlnum) cmp = dbfnum < sqlnum ? -1 : +1; } break; case _datefld: { const TDate dbfdate = dbffld.as_date(); const TDate sqldate = sqlfld.as_date(); if (dbfdate != sqldate) cmp = dbfdate < sqldate ? -1 : +1; } break; default: cmp = dbffld.as_int() - sqlfld.as_int(); break; } } return cmp; } int TODBC_recordset::create_rec(const TISAM_recordset& dbfset) { const TRectype& curr = dbfset.cursor()->curr(); const int lf = curr.num(); TString table = prefix().get_filename(lf).name_only(); table.upper(); TString query(255); query << "INSERT INTO " << table << " VALUES("; TODBC_driver drv = driver(); const RecDes& recdes = curr.rec_des(); const unsigned int nfields = recdes.NFields; for (unsigned int f = 0; f < nfields; f++) { const char* fld = recdes.Fd[f].Name; if (f) query << ','; switch (recdes.Fd[f].TypeF) { case _alfafld: case _memofld: quoted_string(query, curr.get(fld)); break; case _datefld: if (drv == ODBC_mssql) { query << "CONVERT(datetime,"; quoted_string(query, curr.get(fld)); query << " ,105)"; } else if (drv == ODBC_mysql) quoted_string(query, format("&ld", curr.get_date(fld))); else if (drv == ODBC_access) { query << "CDATE("; quoted_string(query, curr.get(fld)); query << ")"; } break; case _realfld: query << curr.get_real(fld).string(-1,2); break; case _boolfld: query << (curr.get_bool(fld) ? '1' : '0'); break; default: query << curr.get_long(fld); break; } } query << ");"; TODBC_recordset upd(""); int err = 0; if (upd.connect(dsn())) err = upd.exec(query); return err; } void TODBC_recordset::update_rec(const TISAM_recordset& dbfset) { const TRectype& curr = dbfset.cursor()->curr(); const int lf = curr.num(); TString table = prefix().get_filename(lf).name_only(); table.upper(); TString query; query << "UPDATE " << table << " SET \n"; TODBC_driver drv = driver(); const RecDes& recdes = curr.rec_des(); const unsigned int nfields = recdes.NFields; for (unsigned int f = 0; f < nfields; f++) { const char* fld = recdes.Fd[f].Name; if (f) query << ','; query << fld << '='; switch (recdes.Fd[f].TypeF) { case _alfafld: case _memofld: quoted_string(query, curr.get(fld)); break; case _datefld: if (drv == ODBC_mssql) { query << "CONVERT(datetime,"; quoted_string(query, curr.get(fld)); query << " ,105)"; } else if (drv == ODBC_mysql) quoted_string(query, format("&ld", curr.get_date(fld))); else if (drv == ODBC_access) { query << "CDATE("; quoted_string(query, curr.get(fld)); query << ")"; } break; case _realfld: query << curr.get_real(fld).string(-1,2); break; case _boolfld: query << (curr.get_bool(fld) ? '1' : '0'); break; default: query << curr.get_long(fld); break; } } query << "\nWHERE "; const KeyDes& key = recdes.Ky[0]; for (int k = 0; k < key.NkFields; k++) { if (k > 0) query << " AND "; const int nf = key.FieldSeq[k] % MaxFields; const char* fname = recdes.Fd[nf].Name; query << fname << '='; if (recdes.Fd[nf].TypeF == _alfafld) query << '\'' << curr.get(fname) << '\''; else query << curr.get(fname); } TODBC_recordset upd(""); if (upd.connect(dsn())) upd.exec(query); } void TODBC_recordset::remove_rec(const TISAM_recordset& dbfset) { const TRectype& curr = dbfset.cursor()->curr(); const int lf = curr.num(); TString table = prefix().get_filename(lf).name_only(); table.upper(); TString query; query << "DELETE FROM " << table << " WHERE "; const RecDes& recdes = dbfset.cursor()->curr().rec_des(); const int keyno = dbfset.cursor()->key(); const KeyDes& keydes = recdes.Ky[keyno-1]; for (int f = 0; f < keydes.NkFields; f++) { if (f) query << " AND "; const int nfield = keydes.FieldSeq[f] % MaxFields; const char* fname = recdes.Fd[nfield].Name; query << fname << '='; switch (recdes.Fd[f].TypeF) { case _alfafld: quoted_string(query, get(fname).as_string()); break; case _realfld: case _intfld: case _longfld: case _intzerofld: case _longzerofld: { TString value = get(fname).as_string(); if (value.blank()) value = "0"; query << value; } break; default: query << get(fname); break; break; } } query << ';'; TODBC_recordset upd(""); if (upd.connect(dsn())) upd.exec(query); } void TODBC_recordset::set(const char* sql) { if (!_freezed || !_loaded || _sql != sql) reset(); _sql = sql; if (_sql.find("SELECT") >= 0 || _sql.find("select") >= 0) find_and_reset_vars(); } TODBC_recordset::TODBC_recordset(const char* sql, const bool freezed) : _freezed(freezed), _loaded(false) { set(sql); } TODBC_recordset::~TODBC_recordset() { } /////////////////////////////////////////////////////////// // Creazione "intelligente" del recordset appropriato in base alla query /////////////////////////////////////////////////////////// TRecordset* create_recordset(const TString& sql) { TRecordset* rex = NULL; if (sql.full()) { if (sql.starts_with("US", true)) rex = new TISAM_recordset(sql); else if (sql.starts_with("ODBC", true)) rex = new TODBC_recordset(sql); else if (sql.starts_with("CSV", true)) rex = new TCSV_recordset(sql); else if (sql.starts_with("AS400", true)) rex = new TAS400_recordset(sql); else rex = new TSQL_recordset(sql); } return rex; }