#include #include #include #include #include /////////////////////////////////////////////////////////// // TFast_isamfile /////////////////////////////////////////////////////////// class TFast_isamfile : public TIsamfile { public: TFast_isamfile(int logicnum); ~TFast_isamfile() { close(); } }; TFast_isamfile::TFast_isamfile(int logicnum) : TIsamfile(logicnum) { int err = open(_excllock); if (err != NOERR) err = open(_manulock); } /////////////////////////////////////////////////////////// // TRecord_Array /////////////////////////////////////////////////////////// TRecord_array::TRecord_array(const TRectype& r, const char* numfield, int first) : _file(r.num()), _offset(first - 1), _num(numfield) { read(r); } TRecord_array::TRecord_array(int logicnum, const char* numfield, int first) : _file(logicnum), _offset(first - 1), _num(numfield) { set_key(new TRectype(logicnum)); } TRecord_array::TRecord_array(const TRecord_array& a) { copy(a); } // Questo si che e' il costruttore dei miei sogni // Senza cavolate come numfield o necessita' di interi record come chiave! TRecord_array::TRecord_array(const char* keytok, int logicnum, int first) : _file(logicnum), _offset(first - 1) { const RecDes& rd = prefix().get_recdes(logicnum); // Tracciato record del file const KeyDes& kd = rd.Ky[0]; // Tracciato della chiave primaria const int nLast = kd.FieldSeq[kd.NkFields-1] % MaxFields; // Posizione dell'ultimo campo della chiave _num = rd.Fd[nLast].Name; // Nome dell'ultimo campo della chiave TRectype* keyrec = new TRectype(logicnum); // Record chiave (vuoto) if (keytok && *keytok) // La stringa chiave non e' vuota { const TToken_string key(keytok); TString80 val; // Un campo chiave non puo' mai superare i 50 for (int i = 0; i < kd.NkFields-1; i++) // Riempio "quasi" tutta la chiave primaria { const int nPos = kd.FieldSeq[i] % MaxFields; // Posizione del campo i della chiave const char* field = rd.Fd[nPos].Name; // Nome del campo i della chiave key.get(i, val); // Valore del campo i della chiave keyrec->put(field, val); // Riempio il campo chiave corrispondente } } read(keyrec); // Leggo o inizializzo l'array vuoto } TRecord_array::~TRecord_array() { } void TRecord_array::set_key(TRectype* r) { CHECK(r != NULL, "TRecord_array can't have a null key"); CHECK(r->num() == _file, "Bad key record"); if (_data.objptr(0) != r) _data.add(r, 0); // Setta il nuovo campo chiave if (rows() > 0) { const RecDes& recd = r->rec_des(); // Descrizione del record della testata const KeyDes& kd = recd.Ky[0]; // Elenco dei campi della chiave 1 // Copia tutti i campi chiave, tranne l'ultimo, in tutti i records TString val; for (int i = kd.NkFields-2; i >= 0; i--) { const int nf = kd.FieldSeq[i] % MaxFields; const RecFieldDes& rf = recd.Fd[nf]; val = r->get(rf.Name); renum_key(rf.Name, val); CHECKS(i > 0 || val.full(), "First key field can't be empty: ", rf.Name); } } } const TRectype& TRecord_array::key() const { const TRectype* r = (const TRectype*)_data.objptr(0); CHECK(r != NULL, "TRecord_array lost its key"); return *r; } bool TRecord_array::exist(int n) const { const int i = n > 0 ? n - _offset : -1; TObject* r = _data.objptr(i); return r != NULL; } // @doc EXTERNAL // @mfunc Ritorna la riga

-esima; se tale riga non esiste e se

assume il valore true la riga viene creata // // @rdesc Ritorna un TRectype alla riga cercata o eventualemete il reference della nuova creata, // NULL nel caso non esista e non venga creata. TRectype& TRecord_array::row( int n, // @parm Numero della riga da creare bool create) // @parm Indica se creare una nuova riga se non esiste: // // @flag true | Se la riga

non esiste viene creata // @flag false | Se la riga

non esiste non viene creata // @comm Nel caso

sia true e venga richiesta una riga non esistente si crea un nuovo // record copiando la chiave. { const int i = n >= 0 ? n - _offset : -1; TRectype* r = (TRectype*)_data.objptr(i); if (r == NULL && create) { r = (TRectype*)key().dup(); // Crea nuovo record copiando la chiave n = _data.add(r, i) + _offset; // Riassegna n se era negativo! TString16 str; str << n; r->renum_key(_num, str); // Aggiorna campo numero riga } CHECKD(r && n > 0, "Bad record number ", n); return *r; } // @doc EXTERNAL // @mfunc Rinumera il campo chiave in seguito a reinsert // // @rdesc Ritorna se e' stato rinumerato il campo chiave bool TRecord_array::renum_key( const char* field, // @parm Campo della chiave a cui assegnare un nuovo valore const TString& num) // @parm Nuovo valore da assegnare al campo // @parm long | num | Nuovo valore da assegnare al campo // @syntax bool renum_key(const char* field, const TString& num); // @syntax bool renum_key(const char* field, long num); { CHECKS(_num != field, "You can't renumber field ", field); for (int i = _data.last(); i >= 0; i--) { TRectype* r = (TRectype*)_data.objptr(i); if (r != NULL) r->renum_key(field, num); } return true; } bool TRecord_array::renum_key(const char* field, long num) { CHECKS(num > 0, "Null key value for field: ", field); TString16 n; n << num; return renum_key(field, n); } void TRecord_array::sort(COMPARE_FUNCTION sort_func) { TObject * rec = _data.remove(0); // salva chiave _data.sort(sort_func); // ordina _data.insert(rec,0); // ripristina chiave TString16 n; for (int i = _data.last(); i > 0; i--) row(i, false).renum_key(_num, n.format("%d", i)); // Rinumera } int TRecord_array::rec2row(const TRectype& r) const { CHECK(r.num() == _file, "Incompatible record"); const int n = atoi(r.get(_num)) - _offset; // Non e' detto che sia un int! CHECKD(n >= 0 && n < 30000, "Bad line number in record ", n + _offset); return n; } int TRecord_array::insert_row(TRectype* r) { const int nr = rec2row(*r); CHECK(nr > 0, "You cannot insert a new key"); const bool shift = exist(nr); _data.insert(r, nr); if (shift) { TString16 n; for (int f = _data.last(); f > nr; f = _data.pred(f)) { TRectype& rec = row(f, FALSE); n.format("%ld", (long)_offset + f); rec.renum_key(_num, n); } } return nr; } int TRecord_array::add_row(TRectype* r) { const int nr = rec2row(*r); if (nr==0) set_key(r); else _data.add(r, nr); return nr; } // @doc EXTERNAL // @mfunc Elimina le righe vuote void TRecord_array::pack() { _data.pack(); for (int i = _data.last(); i > 0; i--) { TRectype* r = (TRectype*)_data.objptr(i); if (r != NULL) { TString16 n; n << i+_offset; r->renum_key(_num, n); } } } // @doc EXTERNAL // @mfunc Cancella una riga // // @rdesc Ritorna se e' riuscito ad eliminare la riga bool TRecord_array::destroy_row( int r, // @parm Numero della riga da eliminare bool pack) // @parm Indica se compattare gli elementi dell'array (default false) // @parm const TRectype& | r | Record da eliminare // @syntax bool destroy_row(int n, bool pack); // @syntax bool destroy_row(const TRectype& r, bool pack); { CHECKD(r > _offset, "Can't destroy row ", r); const int index = r - _offset; const bool ok = _data.destroy(index, pack); if (ok && pack) { for (int i = _data.size()-1; i >= index; i--) { TRectype* r = (TRectype*)_data.objptr(i); if (r != NULL) { TString16 n; n << i+_offset; r->renum_key(_num, n); } } } return ok; } void TRecord_array::destroy_rows() { for (int i = _data.last(); i > 0; i = _data.pred(i)) _data.destroy(i); } // @doc EXTERNAL // @mfunc Confronta i campi della chiave 1 scartando l'ultimo // // @rdesc Ritorna il risulato del confronto: // // @flag true | Se i campi sono uguali // @flag false | Se i campi sono diversi bool TRecord_array::good( const TRectype& rec) const // @parm Record di cui confrontare i campi { const bool yes = key().same_key(rec, 1, 1); return yes; } int TRecord_array::read(TRectype* filter) { CHECK(filter != NULL, "You can't specify NULL as filter "); CHECKD(filter->num() == _file, "Bad key record ", filter->num()); CHECKS(filter->get(_num).empty(), "You can't specify in the filter the field ", (const char*)_num); _data.destroy(); set_key(filter); int err = NOERR; if (!filter->empty()) { TLocalisamfile f(_file); TRectype* rec = (TRectype*)filter->dup(); err = rec->read(f, _isgteq); for (int e = err; e == NOERR && good(*rec); e = rec->next(f)) { add_row(rec); rec = (TRectype*)(key().dup()); } delete rec; } else err = _iskeynotfound; return err; } // @doc EXTERNAL // @mfunc Copia un record array // // @rdesc Copia il record array passato in quello corrente TRecord_array& TRecord_array::copy( const TRecord_array& a) // @parm Record_array da copiare { _file = a._file; _data = a._data; _offset = a._offset; _num = a._num; return *this; } int TRecord_array::read(const TRectype& filter) { TRectype* f = (TRectype*)filter.dup(); return read(f); } int TRecord_array::remove_from(TIsamfile& f, int pos) const { int err = NOERR; TRectype* rec = (TRectype*)key().dup(); CHECK(!rec->empty(), "Can't use empty key"); rec->put(_num, pos); for (int e = rec->read(f, _isgteq); e == NOERR && good(*rec); e = rec->next(f)) { const int found = rec->get_int(_num); if (found >= pos) { err = rec->remove(f); if (err != NOERR) break; pos = found+1; } else break; } delete rec; return err; } int TRecord_array::remove_from(int pos) const { TFast_isamfile f(_file); return remove_from(f, pos); } int TRecord_array::write(bool re) const { const int EOR = 32000; // End of records on file int last_on_file = 0; // Last record found on file int err = NOERR; TFast_isamfile f(_file); const int u = _data.last(); CHECK(u<1 || !key().empty(), "Can't write rows using an empty key"); int i; for (i = 1; i <= u; i++) { const TRectype* r = (TRectype*)_data.objptr(i); if (r != NULL) { if (re) { err = r->rewrite(f); if (err == _iskeynotfound || err == _iseof || err == _isemptyfile) err = r->write(f); if (err != NOERR) break; } else { err = r->write(f); if (err == _isreinsert) { err = r->rewrite(f); re = true; } if (err != NOERR) break; } } else { const int pos = i+_offset; // Se pos < last_on_file sicuramente la read fallira' per cui la riga // non puo' esistere su file: non esistendo nemmeno in memoria non devo // fare assolutamente nulla! if (pos >= last_on_file) // Puo' esistere su file? { TRectype* rec = (TRectype*)key().dup(); CHECK(!rec->empty(), "TRecord_array has an empty key"); rec->put(_num, pos); err = rec->read(f, _isgteq); if (err == NOERR && good(*rec)) // Cerca una riga >= pos sul file { last_on_file = atoi(rec->get(_num)); if (last_on_file == pos) // La riga c'era ma ora non piu' { err = (rec->remove(f)); // Cancello il record indesiderato if (err != NOERR) { delete rec; // Orrore! Un errore di cancellazione! break; } } } else last_on_file = EOR; // Sul file non ci sono piu' righe da cancellare delete rec; } } } // Cancella eventuali residui successivi if (err == NOERR && last_on_file != EOR) remove_from(f, i + _offset); return err; } int TRecord_array::remove() const { return remove_from(1 + _offset); } /////////////////////////////////////////////////////////// // TFile_cache /////////////////////////////////////////////////////////// unsigned long TFile_cache::_hits = 0; unsigned long TFile_cache::_misses = 0; void TFile_cache::stats(unsigned long& h, unsigned long& m) { h = _hits; m = _misses; } void TFile_cache::construct(int key) { _file = NULL; _key = key; _last_firm = -883; _limit = 0; _test_changes = false; _changed = false; } TFile_cache::TFile_cache(TLocalisamfile* f , int key) { construct(key); switch (f->num()) { case LF_TABCOM: _filecode << '%' << f->name(); break; case LF_TAB : _filecode = f->name(); break; case LF_TABMOD: _filecode << '&' << f->name(); break; default : _filecode << f->num(); break; } init_file(f); } TFile_cache::TFile_cache(int num, int key) : _key(key) { construct(key); _filecode << num; } TFile_cache::TFile_cache(const char* tab, int key) { construct(key); _filecode = tab; } TFile_cache::~TFile_cache() { kill_file(); } void TFile_cache::kill_file() { if (_file != NULL) { delete _file; _file = NULL; } } void TFile_cache::init_file(TLocalisamfile* f) { if (_file != NULL) { CHECK(_file != f, "Suspicious file pointer"); kill_file(); } if (f == NULL) { int logicnum = atoi(_filecode); if (logicnum == 0) { if (_filecode == '&') _file = new TModule_table(_filecode); else _file = new TTable(_filecode); logicnum = _file->num(); } else { _file = new TLocalisamfile(logicnum); } } else _file = f; if (_file != NULL) { TDir dir; dir.get(_file->num(), _nolock, _nordir, _sysdirop); // Se e' un file comune metti a -1, altrimenti alla ditta corrente _last_firm = dir.is_com() ? -1 : prefix().get_codditta(); _changed = false; } } void TFile_cache::notify_change() { _changed = true; } void TFile_cache::test_firm() { if (_last_firm < -1) init_file(); if (_last_firm >= 0) // Se e' un file di ditta ... { // ... controlla che non sia cambiata const long cur_firm = prefix().get_codditta(); if (cur_firm != _last_firm) { _last_firm = cur_firm; destroy(); } } const bool flush_needed = _test_changes && _changed && _file != NULL; if (flush_needed) { flush(); destroy(); } else { if (_limit > 0 && items() > _limit) { const THash_object* rand = _cache.random_hash_object(); if (rand != NULL) discard(rand->key()); // Butta via un elemento a caso per fare posto } } } void TFile_cache::flush() { // Needed by read/write cache } bool TFile_cache::already_loaded(const char* code) const { return _cache.objptr(code) != NULL; } bool TFile_cache::already_loaded(long code) const { TString16 str; str << code; return already_loaded(str); } const TObject& TFile_cache::query(const char* code) { _error = NOERR; _code = code; test_firm(); TObject* obj = _cache.objptr(_code); if (obj == NULL) { TLocalisamfile& f = file(); TRectype& curr = f.curr(); if (_code.full()) { const RecDes& recd = curr.rec_des(); // Descrizione del record della testata const KeyDes& kd = recd.Ky[_key-1]; // Elenco dei campi della chiave for (int i = 0; i < kd.NkFields; i++) // Riempie la chiave selezionata { const int nf = kd.FieldSeq[i] % MaxFields; const RecFieldDes& rf = recd.Fd[nf]; const char* val = _code.get(); if (val && *val) curr.put(rf.Name, val); else curr.zero(rf.Name); } f.setkey(_key); _error = f.read(); } else _error = _iskeyerr; switch (_error) { case NOERR: break; case _iskeynotfound: case _iseof: case _isemptyfile: default: curr.zero(); break; } obj = rec2obj(curr); _cache.add(_code, obj); _misses++; } else { _hits++; } return *obj; } bool TFile_cache::discard(const char* victim) { if (victim && *victim) return _cache.remove(victim); // destroy(); // ??? return true; } int TFile_cache::io_result() { return _error; } long TFile_cache::fill() { test_firm(); TLocalisamfile& f = file(); TRectype& curr = f.curr(); const RecDes& recd = curr.rec_des(); // Descrizione del record della testata const KeyDes& kd = recd.Ky[_key-1]; // Elenco dei campi della chiave f.setkey(_key); for (int err = f.first(); err == NOERR; err = f.next()) { _code.cut(0); for (int i = f.tab() ? 1 :0; i < kd.NkFields; i++) // Riempie la chiave selezionata { const int nf = kd.FieldSeq[i] % MaxFields; const RecFieldDes& rf = recd.Fd[nf]; const char* val = curr.get(rf.Name); _code.add(val); } TObject* obj = rec2obj(curr); _cache.add(_code, obj); } kill_file(); return _cache.items(); } void TFile_cache::destroy() { _cache.destroy(); _changed = false; } TLocalisamfile & TFile_cache::file() { if (_file == NULL) init_file(); return *_file; } /////////////////////////////////////////////////////////// // TDecoder /////////////////////////////////////////////////////////// TDecoder::TDecoder(int num, const char* outf, int key) : TFile_cache(num, key), _outf(outf) { } TDecoder::TDecoder(const char* tab, const char* outf, int key) : TFile_cache(tab, key), _outf(outf) { } TObject* TDecoder::rec2obj(const TRectype& curr) const { return new TString(curr.get(_outf)); } const TString& TDecoder::decode(const char* code) { if (code && *code) { TString80 key; if (strchr(code, '|') == NULL) { switch (file().num()) { case LF_TABMOD: key << file().get("MOD") << '|' << file().get("CUST") << '|'; // Fall down case LF_TAB: case LF_TABCOM: key << file().name() << '|'; break; default: break; } } key << code; const TString& obj = (const TString&)query(key); return obj; } return EMPTY_STRING; } const TString& TDecoder::decode(long code) { if (code > 0) { TString8 c; c << code; return decode(c); } return EMPTY_STRING; } /////////////////////////////////////////////////////////// // TRecord_cache /////////////////////////////////////////////////////////// TRecord_cache::TRecord_cache(TLocalisamfile *f, int key) : TFile_cache(f, key) { } TRecord_cache::TRecord_cache(int num, int key) : TFile_cache(num, key) { } TRecord_cache::TRecord_cache(const char* tab, int key) : TFile_cache(tab, key) { } TObject* TRecord_cache::rec2obj(const TRectype& curr) const { return new TRectype(curr); } const TRectype& TRecord_cache::get(const char* key) { const TRectype& rec = (const TRectype&)query(key); return rec; } const TRectype& TRecord_cache::get(long key) { TString8 str; str << key; return get(str); } const TString& TRecord_cache::get(const char* key, const char* field) { return get(key).get(field); } const TString& TRecord_cache::get(long key, const char* field) { return get(key).get(field); } /////////////////////////////////////////////////////////// // TDB_cache /////////////////////////////////////////////////////////// TRecord_cache& TDB_cache::rec_cache(int file) { CHECKD(file >= LF_USER && file < LF_EXTERNAL, "Invalid file ", file); TRecord_cache* rc = (TRecord_cache*)objptr(file); if (rc == NULL) { rc = new TRecord_cache(file); const int reclen = prefix().get_reclen(file); int limit = (1024*1024)/reclen; // Al massimo dedico un mega ad ogni cache if (limit > 1024) limit = 1024; rc->set_items_limit(limit); rc->test_file_changes(); add(rc, file); } return *rc; } int TDB_cache::build_table_key(const char* table, const char* key, TToken_string& k) const { CHECK(table && *table, "Invalid Table code"); TString16 tablename = table; // Attenzione posso avere tabelle di modulo come &PC000883BAR int file = LF_TAB; k.cut(0); if (!isalnum(*table)) // gestisco i casi come %IVA e &AUT { tablename.ltrim(1); // toglie carattere speciale switch (*table) { case '%': file = LF_TABCOM; break; case '^': file = LF_TABGEN; break; case '&': file = LF_TABMOD; { const TModule_table mt(table); k = mt.module(); k.add(mt.customer()); tablename = mt.name(); } break; case '$': default : file = LF_TAB; break; } } k.add(tablename); k.add(key); return file; } const TRectype& TDB_cache::get(const char* table, const char* key) { TToken_string tabkey; const int file = build_table_key(table, key, tabkey); return get(file, tabkey); } const TRectype& TDB_cache::get(const TRectype& curr) { const int num = curr.num(); // Numero logico del file (o tabella) const RecDes& recd = curr.rec_des(); // Descrizione del record della testata const KeyDes& kd = recd.Ky[0]; // Elenco dei campi della chiave 1 TToken_string key; for (int i = 0; i < kd.NkFields; i++) // Riempie la chiave { const int nf = kd.FieldSeq[i] % MaxFields; const RecFieldDes& rf = recd.Fd[nf]; key.add(curr.get(rf.Name)); } return get(num, key); } bool TDB_cache::discard(int file, const char* key) { return rec_cache(file).discard(key); } bool TDB_cache::discard(const char *table, const char* key) { TToken_string tabkey; const int file = build_table_key(table, key, tabkey); return rec_cache(file).discard(tabkey); } bool TDB_cache::discard(const TRectype& curr) { const int file = curr.num(); // Numero logico del file (o tabella) const RecDes& recd = curr.rec_des(); // Descrizione del record della testata const KeyDes& kd = recd.Ky[0]; // Elenco dei campi della chiave 1 TToken_string code; for (int i = 0; i < kd.NkFields; i++) // Riempie la chiave { const int nf = kd.FieldSeq[i] % MaxFields; const RecFieldDes& rf = recd.Fd[nf]; const char* val = curr.get(rf.Name); code.add(val); } return discard(file, code); } TDB_cache& cache() { static TDB_cache* _frate_cercone = NULL; if (_frate_cercone == NULL) _frate_cercone = new TDB_cache; return *_frate_cercone; } TRecord_cache& rec_cache(int file) { return cache().rec_cache(file); } // Utente corrente (elemento 0) e suoi gruppi di appartenenza (dall'elemento 1 in poi) const TString_array& user_and_groups() { static TString_array _uag; if (_uag.empty() || _uag.row(0) != user()) { _uag.destroy(); _uag.add(user()); // Inserisco l'utente corrente come primo if (prefix_valid()) // Costruisce lista dei gruppi solo se possibile { TLocalisamfile user(LF_USER); int err = NOERR; while (err == NOERR) { user.put(USR_USERNAME, _uag.row(_uag.last())); err = user.read(); if (err == NOERR) { const TString& group = user.get(USR_GROUPNAME); if (group.full()) _uag.add(group); else err = _iskeynotfound; } } } } return _uag; } void TDB_cache::discard(int file) { rec_cache(file).flush(); rec_cache(file).destroy(); }