// $Id: relation.cpp,v 1.55 1995-08-03 14:55:26 angelo Exp $ // relation.cpp // fv 12/8/93 // relation class for isam files #include #include #include #include #include #include #include #include #include #include #include #include #include // *** check if not already defined #define NOTFOUND (-1) // *** maximum number of elements in a cursor working page #define CMAXELPAGE 8000 #define print_name(out, f) out << (f.num() == LF_TABCOM ? "%" : "") << f.name() extern int get_error(int); HIDDEN const char* field_type_str(TFieldtypes f) { const char* c = ""; switch(f) { case _charfld: c = "Carattere"; break; case _realfld: c = "Reale"; break; case _datefld: c = "Data"; break; case _boolfld: c = "Logico"; break; // case _wordfld: // case _intzerofld: // case _longzerofld: // case _intfld: // case _longfld: default: c = "Intero"; break; } return c; } /////////////////////////////////////////////////////////// // TRelationdef /////////////////////////////////////////////////////////// class TRelationdef : public TObject { // @DPRIV friend class TRelation; const TRelation* _rel; // Relazione padre int _num; // Posizione file int _numto; // Posizione padre int _alias; // Alias byte _key; // Chiave TArray _fields; // Campi di join TArray _exprs; // Condizioni di uguaglianza TArray _altexprs; // Condizioni di uguaglianza alternative TBit_array _forced; bool _first_match : 1; // primo match (ed esiste) bool _allow_lock : 1; // ????? bool _write_enable : 1; public: // TObject virtual void print_on(ostream& out) const; public: int num() const { return _num; } int link() const { return _numto; } int alias() const { return _alias; } bool write_enable() const { return _write_enable; } void write_enable(bool we) { _write_enable = we; } TRectype& load_rec(TRectype& r, const TBaseisamfile& from) const; const char* evaluate_expr(int j, const TLocalisamfile& to); void print_on(TToken_string& out) const; TRelationdef(const TRelation* rel, int file, byte key, int linkto, const char* relexprs, int alias, bool allow_lock); virtual ~TRelationdef() {} }; TRelationdef::TRelationdef(const TRelation* rel, int idx_file, byte key, int idx_to, const char* relexprs, int alias, bool allow_lock) : _num(idx_file), _key(key), _numto(idx_to), _rel(rel), _fields(4), _exprs(4), _alias(alias), _first_match(FALSE), _allow_lock(allow_lock), _write_enable(FALSE) { TToken_string rels(relexprs); int i = 0; TString80 r,s; for (const char* g = rels.get(); g; g = rels.get(), i++) { r = g; int eq = r.find('='); CHECKS(eq > 0, "Ahoo! E l'uguale 'ndo sta? ", (const char*)g); s = r.left(eq); // Parte a sinistra dell' = #ifdef DBG const char* n = s; const int p = s.find('['); if (p > 0) n = s.left(p); if (rel->file(_num).curr().exist(n) == FALSE) { yesnofatal_box("Errore di JOIN: '%s' non e' un campo del file %d", n, rel->file(_num).num()); continue; } #endif if (r[eq+1] == '=') { _forced.set(i); eq++; } _fields.add(new TFieldref(s, 0)); s = r.mid(eq+1); const int par = s.find('('); if (par > 0) { _exprs.add(new TExpression(s.left(par), _strexpr), i); _altexprs.add(new TExpression(s.sub(par+1, s.len()-1), _strexpr), i); } else _exprs.add(new TExpression(s, _strexpr), i); } } void TRelationdef::print_on(ostream& out) const { const TLocalisamfile& f = _rel->file(_num); out << "JOIN "; print_name(out, f); if (_numto > 0) { out << " TO "; const int alias = _rel->reldef(_numto-1).alias(); if (alias > 0) out << alias << '@'; else { const TLocalisamfile& t = _rel->file(_numto); print_name(out, t); } } if (_key > 1) out << " KEY " << (int)_key; if (_alias > 0) out << " ALIAS " << _alias; for (int i = 0; i < _fields.items(); i++) { if (i == 0) out << " INTO"; out << ' ' << _fields[i] << '='; if (_forced[i]) out << '='; out << _exprs[i]; if (_altexprs.objptr(i)) out << '(' << _altexprs[i] << ')'; } } void TRelationdef::print_on(TToken_string& out) const { const TLocalisamfile& f = _rel->file(_num); out = ""; print_name(out, f); // add description const char* name = f.name(); if (f.tab()) name = TDir::tab_des(name); else name = prefix().description(name); out.add(name); out << '|'; if (_numto > 0) { const int alias = _rel->reldef(_numto-1).alias(); if (alias > 0) out << alias << '@'; else { const TLocalisamfile& t = _rel->file(_numto); print_name(out, t); } } else out << ' '; out.add(_key); out.add(_alias); out << '|'; for (int i = 0; i < _fields.items(); i++) { if (i) out << ' '; out << _fields[i] << '='; if (_forced[i]) out << '='; out << _exprs[i]; if (_altexprs.objptr(i)) out << '(' << _altexprs[i] << ')'; } } const char* TRelationdef::evaluate_expr(int j, const TLocalisamfile& to) { TExpression& expr = (TExpression&)_exprs[j]; for (int k = 0; k < expr.numvar(); k++) expr.setvar(k, to.get(expr.varname(k))); const char* val = expr; if (*val == '\0' && _altexprs.objptr(j)) { TExpression& altexpr = (TExpression&)_altexprs[j]; for (int k = 0; k < expr.numvar(); k++) altexpr.setvar(k, to.get(altexpr.varname(k))); val = altexpr; } return val; } /////////////////////////////////////////////////////////// // TRelation /////////////////////////////////////////////////////////// TRelation::TRelation(int logicnum) : _files(4), _reldefs(4), _errors(NOERR) { TLocalisamfile* f = new TLocalisamfile(logicnum, FALSE); _files.add(f); } TRelation::TRelation(const char* tabname) : _files(4), _reldefs(4), _errors(NOERR) { TTable* t = new TTable(tabname, FALSE); _files.add(t); } TRelation::TRelation(TLocalisamfile* l) : _files(4), _reldefs(4), _errors(NOERR) { _files.add(l); } TRelation::~TRelation() {} void TRelation::print_on(ostream& out) const { const TLocalisamfile& f = file(); out << "USE "; print_name(out, f); const int k = f.getkey(); if (k > 1) out << " KEY " << k; out << endl; for (int r = 0; r < _reldefs.items(); r++) out << _reldefs[r] << endl; } void TRelation::restore_status() { _status.restart(); for (int i = 0; i < _files.items(); i++) { const int err = _status.get_int(); const TRecnotype recno = _status.get_long(); const int key = _status.get_int(); file(i).setkey(key); if (recno >= 0l) file(i).readat(recno); else file(i).curr().zero(); file(i).setstatus(err); } for (i = 0; i < _reldefs.items(); i++) { const bool first_match = _status.get_int() ? TRUE : FALSE; reldef(i)._first_match = first_match; } } void TRelation::save_status() { _status.cut(0); for (int i = 0; i < _files.items(); i++) { const int err = file(i).status(); const TRecnotype recno = file(i).eof() ? -1l : file(i).recno(); const int key = file(i).getkey(); _status.add (err); _status.add (recno); _status.add (key); } for (i = 0; i < _reldefs.items(); i++) { const bool first_match = reldef(i)._first_match; _status.add(format("%d",first_match)); } } int TRelation::log2ind(int log) const { // returns _files index of logical number or // NOTFOUND if not present // sets error status if (log < 0) return alias2ind(-log); const int num = _files.items(); if (log > 0) { for (int i = 0; i < num; i++) if (file(i).num() == log) return i; } return num ? 0 : NOTFOUND; } int TRelation::alias2ind(int alias) const { if (alias < 1) return 0; for (int i = 0; i < _reldefs.items(); i++) { const TRelationdef& r = (const TRelationdef&)_reldefs[i]; if (r.alias() == alias) return r.num(); } return NOTFOUND; } int TRelation::name2ind(const char* name) const { const int num = name2log(name); const int ind = log2ind(num); return ind; } TLocalisamfile& TRelation::lfile(int logicnum) const { const int idx = log2ind(logicnum); CHECKD(idx != NOTFOUND, "File not found n. ", logicnum); return (TLocalisamfile&)_files[idx]; } TLocalisamfile& TRelation::lfile(const char* name) const { const int idx = name2ind(name); CHECKS(idx != NOTFOUND, "File or Table not found:", name); return (TLocalisamfile&)_files[idx]; } void TRelation::write_enable(int logicnum, const bool on) { if (logicnum == 0) { for (int i = 0; i < _reldefs.items(); i++) reldef(i).write_enable(on); } else { const int idx = log2ind(logicnum); CHECKD(idx > 0, "File not found n. ", logicnum); reldef(idx-1).write_enable(on); } } void TRelation::write_enable(const char* name, const bool on) { const int idx = name2ind(name); CHECKS(idx > 0, "File or Table not found:", name); reldef(idx-1).write_enable(on); } bool TRelation::add(TLocalisamfile* f, const char* relexprs, int key, int linkto, int alias, bool allow_lock) { const int idxto = log2ind(linkto); if (idxto == NOTFOUND) fatal_box("Can't join file %d to %d", f->num(), linkto); const int idx = _files.add(f); CHECK(relexprs && *relexprs, "Mancano le espressioni di collegamento"); TRelationdef* r = new TRelationdef(this, idx, key, idxto, relexprs, alias, allow_lock); _reldefs.add(r); return _errors; } bool TRelation::add(int logicnum, const char* relexprs, int key, int linkto, int alias, bool allow_lock) { TLocalisamfile* f = new TLocalisamfile(logicnum, FALSE); return add(f, relexprs, key, linkto, alias, allow_lock); } bool TRelation::add(const char* tabname, const char* relexprs, int key, int linkto, int alias, bool allow_lock) { TTable* t = new TTable(tabname, FALSE); return add(t, relexprs, key, linkto, alias, allow_lock); } void TRelation::replace(TLocalisamfile* f, int index) { const TLocalisamfile* p = (const TLocalisamfile*)_files.objptr(index); CHECK(p && p->num() == f->num(), "Can't replace a file with different logic number"); _files.add(f, index); } TRectype& TRelationdef::load_rec(TRectype& r, const TBaseisamfile& from) const { r.zero(); for (int j = 0 ; j < _fields.items(); j++) // for each field { TFieldref& s = (TFieldref&) _fields[j]; s.write(s.read(from.curr()),r); } return r; } void TRelation::zero() { for (int i = 0; i < _files.items(); i++) file(i).zero(); } int TRelation::position_rels(TIsamop op, TReclock lockop, TDate& atdate, int first) { _errors = NOERR; // workhorse: position files for each active relation for (int i = first; i < _reldefs.items(); i++) { TRelationdef& rd = reldef(i); TLocalisamfile& from = file(rd.num()); TLocalisamfile& to = file(rd.link()); TReclock lck = rd._allow_lock ? lockop : _nolock; if (to.curr().empty()) { from.zero(); continue; } from.setkey(rd._key); from.curr().zero(); // build record if (rd._fields.items() && rd._exprs.items()) { for (int j = 0 ; j < rd._fields.items(); j++) // for each field { /* TExpression& expr = (TExpression&)rd._exprs[j]; for (int k = 0; k < expr.numvar(); k++) expr.setvar(k, to.get(expr.varname(k))); */ const char* expr = rd.evaluate_expr(j, to); TFieldref& s = (TFieldref&) rd._fields[j]; s.write(expr, from.curr()); } // for each field } // read record: if not found, zero current record const TRectype rec(from.curr()); from.read(op, lck, atdate); if (from.bad()) { rd._first_match = (from.curr() == rec); if (rd._first_match) { bool eq = TRUE; for (int kk = 0; eq && kk < rd._fields.items(); kk++) { if (rd._forced[kk]) { TFieldref& fl = (TFieldref&)rd._fields[kk]; const TString f_fr(fl.read(from.curr())); eq = (f_fr == rd.evaluate_expr(kk, to)); } } if (eq) from.setstatus(NOERR); else { rd._first_match = FALSE; from.curr().zero(); } } else from.curr().zero(); } else rd._first_match = TRUE; } // for each relation return _errors; } bool TRelation::next_match(int logicnum, const char* fieldlist, int nkey) { if (logicnum == file().num()) { next(); return file().good(); } const int i = log2ind(logicnum); CHECKD(i != NOTFOUND,"Nonexistent file referenced in relation ",logicnum); for (int j = 0; j < _reldefs.items(); j++) if (reldef(j).num() == i) break; TLocalisamfile& from = file(i); TLocalisamfile& to = file(reldef(j).link()); reldef(j)._first_match = FALSE; if (from.bad()) return FALSE; // && vaffanculo(); const TRecnotype last = from.recno(); bool ok = TRUE; // Corrispondenza trovata ? if (fieldlist == NULL) { TRectype rec(from.curr()); reldef(j).load_rec(rec, from); from.setkey(reldef(j)._key); from.next(); ok = (from.good() && from.curr() == rec); for (int kk = 0; ok && kk < reldef(j)._fields.items(); kk++) { if (reldef(j)._forced[kk]) { TFieldref& fl = (TFieldref&)reldef(j)._fields[kk]; const TString f_fr(fl.read(from.curr())); const TString f_to(fl.read(to.curr())); ok = (f_fr == f_to); } } if (ok) from.setstatus(NOERR); } else { if (nkey > 0) from.setkey(nkey); TToken_string fields(fieldlist); TToken_string old(80); for (const char* f = fields.get(0); f; f = fields.get()) old.add(from.curr().get(f)); from.next(); old.restart(); for (f = fields.get(0); f && ok; f = fields.get()) { const TFixed_string v(from.curr().get(f)); const char* o = old.get(); if (v != o) ok = FALSE; } } if (!ok) { from.readat(last); return FALSE; } // Riposiziona gli eventuali files successivi position_rels(_isequal, _nolock, (TDate&)botime, j+1); reldef(j)._first_match = FALSE; return ok; } bool TRelation::is_first_match(int logicnum) // TRUE se c'e' un record ed e' il primo match (non si e' mai fatta // position_rels) { const int i = log2ind(logicnum); CHECKD(i != NOTFOUND,"Nonexistent file referenced in relation ",logicnum); for (int j = 0; j < _reldefs.items(); j++) if (reldef(j).num() == i) return reldef(j)._first_match; const int err = file().status(); return err == NOERR; } #ifdef DBG bool TRelation::isconsistent(bool reset) { const int MAXREL = 24; int bad = 0; TRecnotype recnos[MAXREL]; for (int i = 0; i < _files.items() && i < MAXREL; i++) { // must be file OK, non-empty record bad |= file(i).good() || file(i).curr().empty(); recnos[i] = file(i).recno(); } // if the above hold, check consistency if (bad) return _errors = bad; position_rels(_isequal, _nolock, (TDate&)botime); for (i = 0; i < _files.items(); i++) if (file(i).recno() != recnos[i]) { bad = -1; break; } if (reset == FALSE) // leave as before for(i = 0; i < _files.items(); i++) file(i).readat(recnos[i]); return _errors = bad; } #endif int TRelation::write(bool force, TDate& atdate) { _errors = file(0).write(atdate); if (_errors != NOERR) return _errors; for (int i = 0; i < _reldefs.items(); i++) { TRelationdef& rd = reldef(i); TLocalisamfile& lf = file(rd.num()); if (!rd.write_enable() || lf.curr().empty()) continue; int res = lf.write(atdate); if (force && res == _isreinsert) res = lf.rewrite(atdate); if (_errors == NOERR) _errors = res; } return _errors; } int TRelation::rewrite(bool force, TDate& atdate) { _errors = file(0).rewrite(atdate); // Riscrive testata if (force && _errors == _iskeynotfound) // Se non la trova ... _errors = file(0).write(atdate); // ... forza la scrittura for (int i = 0; i < _reldefs.items(); i++) { TRelationdef& rd = reldef(i); TLocalisamfile& lf = file(rd.num()); if (!rd.write_enable() || lf.curr().empty()) continue; int res = lf.rewrite(atdate); if (force && res == _iskeynotfound) res = lf.write(atdate); if (_errors == NOERR) _errors = res; } return _errors; } int TRelation::remove(TDate& atdate) { const int res = file(0).remove(atdate); if (_errors == NOERR && res != _iskeynotfound) _errors = res; for (int i = 0; i < _reldefs.items(); i++) { TRelationdef& rd = reldef(i); const int log = rd.num(); if (!rd.write_enable() || (file(log).curr()).empty()) continue; const int res = file(log).remove(atdate); if (_errors == NOERR && res != _iskeynotfound) _errors = res; } return _errors; } /////////////////////////////////////////////////////////// // TCursor /////////////////////////////////////////////////////////// HIDDEN bool __evalcondition(const TRelation* r,TExpression* cond) { for (int i = 0; i < cond->numvar(); i++) { const char* s = cond->varname(i); TFieldref f(s,0); cond->setvar(i, f.read(r)); } return (bool) *cond; } FILE* TCursor::open_index(bool create) { #if XVT_OS == XVT_OS_SCOUNIX const char* const r = "r"; const char* const w = "w"; #else const char* const r = "rb"; const char* const w = "wb"; #endif if (create && _indexname.empty()) _indexname.temp("ci$$"); FILE* f = fopen(_indexname, create ? w : r); if (f == NULL) fatal_box("Can't use cursor index for file %d: '%s'\n", file().filehnd()->ln, (const char*)_indexname); return f; } TRecnotype TCursor::buildcursor(TRecnotype rp) { TRecnotype oldrecno=0,pos,ap = 0; int junk, l, pagecnt = 0; const bool filtered = has_filter(); FILE* _f = open_index(TRUE); if (DB_reccount(file().filehnd()->fhnd) == 0) { fclose(_f); return 0; } fseek(_f, 0L, SEEK_SET); l = strlen(to()); junk=DB_index_seek(file().filehnd()->fhnd, (char*)(const char*) from()); if (junk < 0) junk=get_error(junk); if (junk == _iseof) return 0; TRecnotype* page = new TRecnotype [CMAXELPAGE]; pos = DB_index_recno(file().filehnd()->fhnd); _pos=-1; while (TRUE) { if (DB_index_eof(file().filehnd()->fhnd)) break; const char* s0 = DB_index_getkey(file().filehnd()->fhnd); const TRecnotype recno = DB_index_recno(file().filehnd()->fhnd); if (l && (strncmp(to(), s0, l) < 0)) break; if (recno == oldrecno) break; // means that no more keys are available oldrecno=recno; if (pagecnt == CMAXELPAGE) { if (filtered) pagecnt = filtercursor(pagecnt,page); fwrite(page,sizeof(TRecnotype),pagecnt,_f); for (int i= 0; i< pagecnt; i++) if (page[i] == rp) { _pos = ap + i; break; } ap += pagecnt; pagecnt = 0; } page[pagecnt++] = recno; DB_index_next(file().filehnd()->fhnd); int rt=get_error(-1); if (rt != NOERR) fatal_box("Can't read index n. %d - file n. %d",DB_tagget(file().filehnd()->fhnd),file().filehnd()->ln); } // while if (pagecnt) { if (filtered) pagecnt = filtercursor(pagecnt, page); fwrite(page, sizeof(TRecnotype), pagecnt, _f); for (int i = 0; i < pagecnt; i++) if (page[i] == rp) { _pos = ap + i; break; } ap += pagecnt; } if (_pos == -1) pos=0; delete page; fclose (_f); return ap; } int TCursor::filtercursor(int pagecnt, TRecnotype* page) { int np=0, handle=file().filehnd()->fhnd; TRectype& rec=file().curr(); for (int i=0; i< pagecnt; i++) { file().readat(rec,page[i]); if (update_relation()) _if->update(); if ((_filterfunction ? _filterfunction(_if) : TRUE ) && (_fexpr ? __evalcondition(_if, _fexpr) : TRUE)) { if (np < i) page[np] = page[i]; np++; } } return np; } bool TCursor::ok() const { if (file().bad()) return FALSE; const TRectype& rec = file().curr(); TString key(rec.key(_nkey)), kf(from()), kt(to()); if (file().tab()) { key.ltrim(3); kf.ltrim(3); kt.ltrim(3); } if (key < kf || (kt.not_empty() && kt < key.left(kt.len()))) return FALSE; if (_filter_update || _filterfunction_update) _if->update(); if ((_filterfunction ? _filterfunction(_if) : TRUE ) && (_fexpr ? __evalcondition(_if, _fexpr) : TRUE)) return TRUE; return FALSE; } bool TCursor::changed() { isdef* fh = file().filehnd(); if (_frozen && _lastrec > 0L) return _filename != fh->d->SysName; const TRecnotype eod = DB_reccount(fh->fhnd); if (_lastrec != eod || (_lastkrec != DB_changed(fh->fhnd)) || (_filename != fh->d->SysName)) { // _lastrec = eod; // _filename = fh->d->SysName; // _lastkrec = DB_changed(fh->fhnd); return TRUE; } else return FALSE; } TRecnotype TCursor::update() { main_app().begin_wait(); file().setkey(_nkey); if (file().curr().empty()) file().curr().zero(); file().read(_isgteq); const TRecnotype totrec = buildcursor(file().recno()); main_app().end_wait(); isdef* fh = file().filehnd(); const TRecnotype eod = DB_reccount(fh->fhnd); _lastrec = eod; _filename = fh->d->SysName; _lastkrec = DB_changed(fh->fhnd); return totrec; } void TCursor::filter(const char* fil, const TRectype *from, const TRectype* to) { CHECK(!_frozen, "Impossibile filtrare un cursore congelato"); TString kf(_keyfrom), kto(_keyto); const bool filterchanged = (fil != NULL) && (_filter != fil); if (file().tab()) { TTable& f = (TTable&) file(); kf = kto = f.name(); } if (from != NULL) { kf = from->key(_nkey); int p; while ((p = kf.find('~')) != -1) kf[p] = ' '; } if (to != NULL) { kto = to->key(_nkey); int p; while ((p = kto.find('~')) != -1) kto[p] = ' '; } if (filterchanged || (_keyfrom != kf) || (_keyto != kto)) { _pos = 0; _totrec = 0; _lastrec = 0; if (filterchanged) { _filter = fil; if (_fexpr) delete _fexpr; TTypeexp type = (_filter.find('"') != -1) ? _strexpr : _numexpr; if (_filter.not_empty()) { _fexpr = new TExpression(_filter, type); if (_fexpr->type() == _numexpr) for (int i = 0 ; i < _fexpr->numvar(); i++) if (file().curr().type(_fexpr->varname(i)) == _alfafld) { _fexpr->set_type(_strexpr); break; } } else _fexpr = NULL; } _keyfrom = kf; _keyto = kto; } } void TCursor::setkey(int nkey) { if (nkey != _nkey) { _lastrec = 0L; _nkey = nkey; file().setkey(_nkey); filter(NULL); } } TRecnotype TCursor::read(TIsamop op, TReclock lockop, TDate& atdate) { TRecnotype *page; int pagecnt; file().setkey(_nkey); const bool approx = (op == _isgteq); _if->file().read(op, lockop, atdate); if (approx) { while (_if->file().good() && !ok()) _if->file().next(); } const TRecnotype curpos = file().recno(); if (changed()) _totrec = update(); if (approx && _if->file().status() == _iseof) { _pos = _totrec - 1; return _pos; } FILE* _f = open_index(); if (fseek(_f, 0L, SEEK_SET) != 0) fatal_box("Can't repos cursor : File %d\n", file().filehnd()->ln); page = new TRecnotype [CMAXELPAGE]; _pos = -1; for (TRecnotype max = _totrec; _pos == -1 && max > 0; max -= pagecnt) { pagecnt = (max < CMAXELPAGE) ? (int)max : CMAXELPAGE; fread(page, sizeof(TRecnotype), pagecnt, _f); for (int i = 0; i < pagecnt; i++) if (page[i] == curpos) { _pos = _totrec - max + i; break; } } if (_pos == -1) _pos = 0; delete page; fclose(_f); readrec(); return _pos; } TCursor::TCursor(TRelation* r, const char* fil, int nkey, const TRectype *from, const TRectype* to) : _if(r), _nkey(nkey), _frozen(FALSE), _filterfunction(NULL), _fexpr(NULL), _filter_update(FALSE), _filterfunction_update(FALSE) { file().setkey(_nkey); _pos = 0; _totrec = 0; _lastrec = 0; _lastkrec = 0; filter(fil, from, to); } TCursor::~TCursor() { if (_indexname.not_empty()) ::remove(_indexname); if (_fexpr) delete _fexpr; } TRecnotype TCursor::readrec() { TRecnotype& nrec = file().filehnd()->RecNo; if (_pos == items()) { file().setstatus(_iseof); curr().zero(); return nrec = 0L; } file().setstatus(NOERR); FILE* _f = open_index(); if (fseek(_f, _pos * sizeof(TRecnotype), SEEK_SET) != 0) fatal_box("Can't seek position %ld in cursor n. %d\n", _pos, file().filehnd()->ln); if (fread(&nrec, sizeof(TRecnotype), 1, _f) != 1) fatal_box("Can't read position %ld in cursor n. %d\n", _pos, file().filehnd()->ln); fclose(_f); curr().setdirty(); file().readat(nrec); repos(); return nrec; } int TCursor::lock(TReclock l) { int rt=NOERR; switch(l) { case _lock: rt=DB_lock_rec(file().filehnd()->fhnd,_pos); break; case _unlock: rt=DB_unlock(file().filehnd()->fhnd); break; default: break; } if (rt != NOERR) rt=get_error(rt); return(rt); } TRecnotype TCursor::operator =(const TRecnotype pos) { if (changed()) _totrec = update(); CHECKD(pos >= 0 && pos <= _totrec, "Bad cursor position : ", pos); _pos = pos; readrec(); return _pos; } TRecnotype TCursor::operator +=(const TRecnotype npos) { if (changed()) _totrec = update(); _pos += npos; if (_pos > _totrec) _pos = _totrec; else if (_pos < 0) _pos = 0; readrec(); return _pos; } TRecnotype TCursor::items() { if (changed()) _totrec = update(); return _totrec; } bool TCursor::next_match(int lognum, const char* fl, int nk) { if (lognum == 0 || lognum == file().num()) {++(*this); return file().good(); } else return _if->next_match(lognum, fl, nk); } bool TCursor::is_first_match(int ln) { return (ln == 0 || ln == file().num()) ? (_pos == 0 && file().good()) : (_if->is_first_match(ln)); } /////////////////////////////////////////////////////////// // TSorted_cursor /////////////////////////////////////////////////////////// typedef struct { char f[512]; TRecnotype p; } El_To_Sort; bool TSorted_cursor::check_expr(TString& s) // Controlla la validita' dell'espressione ritornando un bool // Modifica l'espressione in modo da avere una valido parametro per TFieldref! // Es. UPPER(20->RAGSOC[1,40]+) ==> 20->RAGSOC[1,40]+ // N.B. : la direzione dell'ordinamento va messa indicata sempre dopo la definizione del campo, mai // dopo l'operatore UPPER(). E' a cura delle altre funzioni esterne che utilizzano la check_expr() // discriminare il segno + o - che sia { int p=s.find('('); int p1=s.find(')'); int rt=TRUE; if (p>-1 && p1>-1) if (s.find("UPPER") == -1) { error_box("Only UPPER() operator is allowed"); rt=FALSE; } else { s=s.mid(6,s.len()-7); p=s.find('('); p1=s.find(')'); if (p>-1 || p1 >-1) { error_box("Too many parentheses!"); rt=FALSE; } } else if ((p>-1 && p1==-1) || (p==-1 && p1>-1)) { error_box("Unbalanced parentheses in expression"); rt=FALSE; } return rt; } bool TSorted_cursor::is_upper(TString& s) // Controlla se nell'espressione esiste l'operatore UPPER() { bool rt=TRUE; if (s.find("UPPER")>-1) rt=check_expr(s); else rt=FALSE; return rt; } TRecnotype TSorted_cursor::buildcursor(TRecnotype rp) { TRecnotype oldrecno=0,pos,ap = 0; int abspos=0,junk, l, pagecnt = 0; const bool filtered = has_filter(); TString s; El_To_Sort * Element; _order_expr.restart(); while ((s=_order_expr.get()) && s.not_empty()) { check_expr(s); // Toglie l'eventuale operatore UPPER(), in modo che l'ultimo carattere // indichi eventualmente la direzione dell'ordinamento char versus = (s.right(1)=="-") ? 'd' : 'a'; if (s.right(1) == "-" || s.right(1) == "+") s.cut(s.len()-1); TFieldref f(s,0); // Il controllo del file e' automatico in f.len() _sort->addsortkey(abspos+f.from(),f.len(relation()->curr()),versus); CHECKS(f.len(relation()->curr())!=0,"Field can not have null length: ",(const char *) s); int lf = (f.file()!=0 ? f.file() : file().num()); TRectype r(lf); abspos+=r.length(f.name()); CHECKD(abspos<=512,"Cannot exceed 512 bytes-key %d",abspos); } _sort->init(); FILE* _f = open_index(TRUE); if (DB_reccount(file().filehnd()->fhnd) == 0) { fclose(_f); return 0; } fseek(_f, 0L, SEEK_SET); l = strlen(to()); junk=DB_index_seek(file().filehnd()->fhnd, (char*)(const char*) from()); if (junk < 0) junk=get_error(junk); if (junk == _iseof) return 0; TRecnotype* page = new TRecnotype [CMAXELPAGE]; pos = DB_index_recno(file().filehnd()->fhnd); TCursor::pos()=-1; while (TRUE) { if (DB_index_eof(file().filehnd()->fhnd)) break; const char* s0 = DB_index_getkey(file().filehnd()->fhnd); const TRecnotype recno = DB_index_recno(file().filehnd()->fhnd); if (l && (strncmp(to(), s0, l) < 0)) break; if (recno == oldrecno) break; // means that no more keys are available oldrecno=recno; if (pagecnt == CMAXELPAGE) { pagecnt = filtercursor(pagecnt,page); for (int i= 0; i< pagecnt; i++) if (page[i] == rp) { TCursor::pos() = ap + i; break; } ap += pagecnt; pagecnt = 0; } page[pagecnt++] = recno; DB_index_next(file().filehnd()->fhnd); int rt=get_error(-1); if (rt != NOERR) fatal_box("Can't read index n. %d - file n. %d",DB_tagget(file().filehnd()->fhnd),file().filehnd()->ln); } // while if (pagecnt) { pagecnt = filtercursor(pagecnt, page); for (int i = 0; i < pagecnt; i++) if (page[i] == rp) { TCursor::pos() = ap + i; break; } ap += pagecnt; } _sort->endsort(); pagecnt=0; while ((Element=(El_To_Sort *)_sort->retrieve()) != NULL) { page[pagecnt++]=Element->p; if (pagecnt==CMAXELPAGE) { fwrite(page,sizeof(TRecnotype),pagecnt,_f); pagecnt=0; } } if (pagecnt) fwrite(page,sizeof(TRecnotype),pagecnt,_f); if (TCursor::pos() == -1) pos=0; delete page; fclose (_f); return ap; } int TSorted_cursor::filtercursor(int pagecnt, TRecnotype* page) { int np=0, handle=file().filehnd()->fhnd; TRectype& rec=file().curr(); TString s; El_To_Sort Element; for (int i=0; i< pagecnt; i++) { file().readat(rec,page[i]); if (update_relation()) relation()->update(); if ((filterfunction() ? filterfunction()(relation()) : TRUE ) && (expression() ? __evalcondition(relation(), expression()) : TRUE)) { if (np < i) page[np] = page[i]; np++; _order_expr.restart(); strcpy(Element.f,""); while ((s=_order_expr.get()) && s.not_empty()) { bool is_up=is_upper(s); int p=s.find('[');// Qui estrae il nome del campo: if (p>-1) // rimane solo l'indicazione del file (eventuale) s.cut(p); // ed il nome del campo: else // UPPER(20->RAGSOC[1,30]+) ===>> 20->RAGSOC if (s.right(1) == "-" || s.right(1) == "+") s.cut(s.len()-1); TFieldref f(s,0); TString sf=f.read(relation()); bool is_date = file(f.file()).curr().type(s) ==_datefld; if (is_date) // Se il campo e' di tipo data, la converte in ANSI! { TDate d(sf); sf=d.string(ANSI); } if (is_up) sf.upper(); TString fmt; fmt.format("%%-%ds",f.len(rec)); strcat(Element.f,(const char*)sf.format((const char *)fmt,(const char *)sf)); } Element.p=page[i]; _sort->sort((char *) &Element); } } return np; } bool TSorted_cursor::changed() { bool rt; if (_is_valid_expr) { rt = TCursor::changed(); if (!rt) rt=_is_changed_expr; _is_changed_expr = FALSE; } else { error_box("Can't perform changed() while expression ain't valid!"); rt=FALSE; } return rt; } void TSorted_cursor::change_order(const char* order_expr) { if (order_expr && *order_expr && order_expr != _order_expr) { TString s; _order_expr = order_expr; _order_expr.restart(); while ((s=_order_expr.get()) && s.not_empty() && (_is_valid_expr=check_expr(s))) ; if (_is_valid_expr) _is_changed_expr=TRUE; } } TSorted_cursor::TSorted_cursor(TRelation *f, const char * order_expr, const char * filter, int key, const TRectype* from, const TRectype* to) : TCursor(f,filter,key,from,to) { change_order(order_expr); _sort = new TSort(sizeof(El_To_Sort)); } TSorted_cursor::~TSorted_cursor() { if (_sort) delete _sort; } /////////////////////////////////////////////////////////// // TFieldRef /////////////////////////////////////////////////////////// int name2log(const char* name) { int log = 0; if (name) while (*name == ' ') name++; if (name && *name) { if (isdigit(*name) || *name == '-') { log = atoi(name); if (strchr(name, '@')) log = -log; } else { const int len = strlen(name); if (len == 3 || len == 4) log = TTable::name2log(name); } CHECKS(log != 0, "Non e' un file ne' una tabella: ", name); } return log; } TFieldref::TFieldref() : _fileid(0), _name(0) {} TFieldref::TFieldref(const TString& s, short defid) { operator=(s); if (_fileid == 0) _fileid = defid; } // A Fieldref should have the following format (only NAME is mandatory): // FILE->NAME[FROM,TO] TFieldref& TFieldref::operator =(const TString& s) { int pos = s.find("->"); if (pos > 0) { _id = s.left(pos); _id.strip(" "); _fileid = name2log(_id); pos += 2; } else { _id.cut(0); _fileid = pos = 0; } int par = s.find('[', pos); _name = s.sub(pos, par); _name.strip(" "); if (par > 0) { pos = par+1; set_from(atoi(s.mid(pos))); par = s.find(',', pos); if (par > 0) set_to(atoi(s.mid(par+1))); else set_to(from()+1); } else { set_from(0); set_to(-1); } return *this; } void TFieldref::set_file(int f) { _id.cut(0); if (f != 0) { _fileid = f; _id << abs(f); if (f < 0) _id << '@'; } else _fileid = 0; } void TFieldref::print_on(ostream& out) const { if (_id.not_empty()) out << _id << "->"; out << _name; if (_from > 0 || _to > 0) { out << '[' << (_from+1); out << ',' << _to << ']'; } } const char* TFieldref::read(const TRectype& rec) const { static TString256 buffer; if (_fileid >= CNF_GENERAL) { TToken_string s(_name, '.'); TConfig c(_fileid - CNF_STUDIO, s.get()); buffer = c.get(s.get()); } else { buffer = rec.get(_name); if (_from > 0 || _to > 0) { const int l = buffer.len(); // const int from = (_from > l) ? l : _from; // const int to = (_to > l || _to < 1) ? l : _to; // if (to < l) buffer.cut(to); if (_to < l && _to > 0) buffer.cut(_to); // if (from > 0) buffer.ltrim(from); if (_from > 0) buffer.ltrim(_from); } } return buffer; } const char* TFieldref::read(const TRelation* c) const { CHECK(c != NULL, "Can't read field from NULL relation"); const char * s; /* if (c == NULL) { TLocalisamfile f(_fileid, TRUE); s = read(f.curr()); } else */ s = read(c->lfile(_id).curr()); return s; } void TFieldref::write(const char* val, TRelation* c) const { CHECK(c != NULL, "Can't write field on NULL relation"); /* if (c == NULL) { TLocalisamfile f(_fileid, TRUE); write(val, f.curr()); } else */ { TRectype &curr = c->lfile(_id).curr(); write(val, curr); } } void TFieldref::write(const char* val, TRectype& rec) const { if (_fileid >= CNF_GENERAL) return; if (_from > 0) { TString256 campo(rec.get(_name)); campo.overwrite(val, _from); rec.put(_name, campo); } else rec.put(_name, val); } int TFieldref::len(TRectype &rec) const { if (_to >= 0) return _to - _from; if (_fileid == 0) return rec.length(_name) - _from; const TRectype r(_fileid); const int len = r.length(_name); return len - _from; } /////////////////////////////////////////////////////////// // TRecord_Array /////////////////////////////////////////////////////////// TRecord_array::TRecord_array(const TRectype& r, const char* numfield, int first) : _file(r.num()), _num(numfield), _offset(first - 1) { read(r); } TRecord_array::TRecord_array(int logicnum, const char* numfield, int first) : _file(logicnum), _num(numfield), _offset(first - 1) {} const TRectype& TRecord_array::key() const { TRectype* r = (TRectype*)objptr(0); CHECK(r, "A TRecord_array lost its key"); return *r; } TRectype& TRecord_array::row(int n, bool create) { const int i = n > 0 ? n - _offset : -1; TRectype* r = (TRectype*)objptr(i); if (r == NULL && create) { r = new TRectype(key()); n = add(r, i) + _offset; // Riassegna n se era negativo! r->put(_num, n); // Aggiorna campo numero riga } CHECKD(r && n > 0, "Bad record number ", n); return *r; } bool TRecord_array::renum_key(const char* field, const TString& num) { CHECKS(!num.blank(), "Blank key value for field: ", field); TRectype* r = (TRectype*)objptr(0); if (r == NULL) { r = new TRectype(_file); r->zero(); add(r, 0); } const TString& curr = key().get(field); if (curr == num) return FALSE; for (int i = last(); i >= 0; i--) { r = (TRectype*)objptr(i); if (r) r->put(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); } int TRecord_array::rec2row(const TRectype& r) const { CHECK(r == key(), "Incompatible record"); const int n = atoi(r.get(_num)) - _offset; // Non e' detto che sia un int! CHECKD(n > 0 && n < 32000, "Bad line number in record ", n + _offset); return n; } int TRecord_array::add_row(const TRectype& r) { const int nr = rec2row(r); TRectype* o = (TRectype*)objptr(nr); if (o) *o = r; else add(r, nr); return nr; } bool TRecord_array::destroy_row(int r, bool pack) { CHECKD(r > _offset, "Can't destroy row ", r); const int index = r - _offset; const bool ok = destroy(index, pack); if (ok && pack) { for (int i = index; i < items(); i++) row(i + _offset, FALSE).put(_num, i + _offset); } return ok; } void TRecord_array::destroy_rows() { for (int i = last(); i > 0; i--) destroy(i); } int TRecord_array::read(const TRectype& 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); destroy(); add(filter, 0); // Store filter record for later use TLocalisamfile f(_file); f.curr() = key(); int err = f.read(_isgteq); for (int e = err; e == NOERR && f.curr() == key(); e = f.next()) add_row(f.curr()); return err; } int TRecord_array::write(bool re) { int err = NOERR; TLocalisamfile f(_file); const int u = last(); for (int i = 1; i <= u; i++) { const TRectype* r = (TRectype*)objptr(i); if (r != NULL) { if (re) { err = f.rewrite(*r); if (err == _iskeynotfound || err == _iseof || err == _isemptyfile) err = f.write(*r); if (err != NOERR) break; } else { err = f.write(*r); if (err == _isreinsert) err = f.rewrite(*r); if (err != NOERR) break; } } else { f.curr() = key(); f.put(_num, i + _offset); err = f.read(); if (err == NOERR) // La riga c'era ma ora non piu' { err = f.remove(); if (err != NOERR) break; } } } if (re && err == NOERR) { // Cancella eventuali residui successivi f.curr() = key(); f.put(_num, i + _offset); for (int e = f.read(_isgteq); e == NOERR && f.curr() == key(); e = f.next()) { err = f.remove(); if (err != NOERR) break; } } return err; } int TRecord_array::remove() { destroy_rows(); return rewrite(); } /////////////////////////////////////////////////////////// // TRelation_description /////////////////////////////////////////////////////////// void TRelation_description::init_files_array() { const TLocalisamfile& f = _rel->file(); TToken_string s(128); print_name(s, f); // Logic number const char* name = f.name(); if (f.tab()) name = TDir::tab_des(name); else name = prefix().description(name); s.add(name); // Description s.add(" "); // No join s.add(f.getkey()); // Key s.add(" | "); // No Alias nor expression _files.destroy(); _files.add(s); // Main file for (int i = 0; i < _rel->items(); i++) { _rel->reldef(i).print_on(s); _files.add(s); } } void TRelation_description::read_rel() { // scan files and build description arrays init_files_array(); _fields.destroy(); for (int i = 0; i < _files.items(); i++) { TToken_string& tt = (TToken_string&)_files[i]; TFilename descfname; descfname << DESCDIR << "/d"; const char* tn = tt.get(0); // Codice del file o tabella int which_file = atoi(tn); // Non spostare questa riga (tn puo' cambiare!) if (which_file == 0) which_file = TTable::name2log(tn); // Numero logico del file if (tn[0] == '%' || tn[0] == '$') descfname << (tn+1); else descfname << tn; descfname << ".des"; TConfig conf(descfname, DESCPAR); // new record descriptor for _fields array TString_array* rdesc = new TString_array; TTrec trec; trec.get(which_file); TToken_string ttmp(64); for (int f = 0; f < trec.fields(); f++) { ttmp = trec.fielddef(f); const TString16 name(ttmp.get(0)); if (!name.blank()) { TString80 dfld(conf.get(name, NULL, -1, "Missing description")); if (!dfld.blank() && dfld[0] != '#') { ttmp.add(dfld,4); // contiene: nome campo, tipo, lunghezza, decimali, descrizione rdesc->add(ttmp); } } } _fields.add(rdesc, i); } } void TRelation_description::change_relation(TRelation& r, TString_array& a) { _rel = &r; read_rel(); if (a.items() > 0) _files = a; } void TRelation_description::print_on(ostream& out) const { for (int i = 0; i < _files.items(); i++) { TToken_string& r = ((TRelation_description*)this)->_files.row(i); TString16 cod(r.get(4)); if (atoi(cod) == 0) cod = r.get(0); else cod << '@'; out << " " << cod << "->* "; out << "\"" << r.get(1) << "\"\n"; } } bool TRelation_description::choose_file(int file) { TArray_sheet sht(-1,-1,0,0,"Selezione archivio", "Codice|Descrizione archivio@70"); TToken_string tt(80); int sel = 0; for (int i = 0; i < _files.items(); i++) { TToken_string& tf = _files.row(i); tt = tf.get(4); int num = atoi(tt); if (num == 0) { tt = tf.get(0); num = atoi(tt); if (sel == 0 && file > 0 && num == file) sel = i; } else { if (sel == 0 && file < 0 && num == -file) sel = i; tt << '@'; } tt.add(tf.get(1)); sht.add(tt); } sht.select(sel); if (sht.run() == K_ENTER) { _cur_file = (int)sht.selected(); _cfile = _files.row(_cur_file); return TRUE; } return FALSE; } bool TRelation_description::choose_field(const char* fld) { TArray_sheet sht(-1,-1,0,0,"Selezione campo", "Campo@10|Descrizione@50|Tipo@10|Dim."); TString_array& fd = (TString_array&)_fields[_cur_file]; TToken_string tt(80); int sel = 0; for (int i = 0; i < fd.items(); i++) { TToken_string& tf = fd.row(i); tt = tf.get(0); if (sel == 0 && tt == fld) sel = i; tt.add(tf.get(4)); tt.add(field_type_str((TFieldtypes)tf.get_int(1))); tt.add(tf.get(2)); sht.add(tt); } sht.select(sel); if (sht.run() == K_ENTER) { _cur_field = (int)sht.selected(); _cfield = fd.row(_cur_field); return TRUE; } return FALSE; } bool TRelation_description::build_menu(const char* title) { return FALSE; } bool TRelation_description::remove_menu() { return FALSE; } bool TRelation_description::set_cur_file(int id) { const int n = _rel->log2ind(id); if (n >= 0 && n != _cur_file) { _cur_file = n; _cfile = _files.row(n); } return n >= 0; } int TRelation_description::file_num() { int n = -_cfile.get_int(4); if (n == 0) n = _cfile.get_int(0); return n; } const char* TRelation_description::file_desc() { return _cfile.get(1); } void TRelation_description::file_desc(const char* desc) { _cfile.add(desc, 1); _files.row(_cur_file) = _cfile; } const char* TRelation_description::field_desc() { return _cfield.get(4); } const char* TRelation_description::field_name() { return _cfield.get(0); } int TRelation_description::field_len() { return _cfield.get_int(2); } TFieldtypes TRelation_description::field_type() { return (TFieldtypes)_cfield.get_int(1); } bool TRelation_description::set_field_description(const char* field, const char* des) { TString_array& fld = (TString_array&)_fields[_cur_file]; for (int i = 0; i < fld.items(); i++) { TToken_string& tt = fld.row(i); if (strcmp(tt.get(0),field) == 0) { if (des && *des) tt.add(des, 4); else { fld.destroy(i); fld.pack(); } return TRUE; } } if (des && *des) { TToken_string tt(80); tt.add(field); tt.add(des, 4); fld.add(tt); return TRUE; } return FALSE; } const char* TRelation_description::get_field_description(const char* field) { TString_array& fld = (TString_array&)_fields[_cur_file]; for (int i = 0; i < fld.items(); i++) { TToken_string& tt = fld.row(i); if (strcmp(tt.get(0),field) == 0) return tt.get(4); } return ""; } TRelation_description::TRelation_description(TRelation& r) : _rel(&r), _menu(FALSE), _cur_file(0), _cur_field(0), _cfile(80), _cfield(80) { read_rel(); if (_files.items() > 0) { _cfile = _files.row(0); _cfield = ""; } } TRelation_description::~TRelation_description() { if (_menu) remove_menu(); } // *** EOF relation.cpp