1339 lines
30 KiB
C++
Executable File
1339 lines
30 KiB
C++
Executable File
// $Id: relation.cpp,v 1.30 1995-01-09 16:51:18 guy Exp $
|
|
// relation.cpp
|
|
// fv 12/8/93
|
|
// relation class for isam files
|
|
|
|
#include <ctype.h>
|
|
#include <stdlib.h>
|
|
|
|
#include <applicat.h>
|
|
#include <config.h>
|
|
#include <expr.h>
|
|
#include <extcdecl.h>
|
|
#include <prefix.h>
|
|
#include <relation.h>
|
|
#include <tabutil.h>
|
|
#include <utility.h>
|
|
#include <xvtility.h>
|
|
|
|
// *** 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()
|
|
|
|
///////////////////////////////////////////////////////////
|
|
// 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;
|
|
|
|
protected:
|
|
virtual void print_on(ostream& out) const;
|
|
void print_on(TToken_string& out) const;
|
|
|
|
public:
|
|
// @FPUB
|
|
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);
|
|
|
|
TRelationdef(const TRelation* rel, int file, byte key,
|
|
int linkto, const char* relexprs, int alias,
|
|
bool allow_lock, bool write_enable = FALSE);
|
|
virtual ~TRelationdef() {}
|
|
};
|
|
// @END
|
|
|
|
|
|
TRelationdef::TRelationdef(const TRelation* rel, int idx_file, byte key,
|
|
int idx_to, const char* relexprs, int alias,
|
|
bool allow_lock, bool write_enable)
|
|
: _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(write_enable)
|
|
{
|
|
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);
|
|
out.add(prefhndl->description(f.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, bool linkrecinst)
|
|
: _files(4) , _reldefs(4), _errors(NOERR)
|
|
{
|
|
TLocalisamfile* f = new TLocalisamfile(logicnum, linkrecinst);
|
|
_files.add(f);
|
|
}
|
|
|
|
TRelation::TRelation(const char* tabname, bool linkrecinst)
|
|
: _files(4) , _reldefs(4), _errors(NOERR)
|
|
{
|
|
TTable* t = new TTable(tabname, linkrecinst);
|
|
_files.add(t);
|
|
}
|
|
|
|
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::print_on(TArray& a) const
|
|
{
|
|
const TLocalisamfile& f = file();
|
|
TToken_string s(128);
|
|
print_name(s, f);
|
|
s.add(prefhndl->description(f.name()));
|
|
s.add("");
|
|
s.add(f.getkey());
|
|
s.add(" | ");
|
|
a.destroy();
|
|
a.add(s);
|
|
for (int i = 0; i < _reldefs.items(); i++)
|
|
{
|
|
((TRelationdef&)_reldefs[i]).print_on(s);
|
|
a.add(s);
|
|
}
|
|
}
|
|
|
|
void TRelation::restore_status()
|
|
{
|
|
_status.restart();
|
|
for (int i = 0; i < _files.items(); i++)
|
|
{
|
|
int err = _status.get_int();
|
|
int recno = _status.get_int();
|
|
if (recno >= 0l) file(i).readat(recno);
|
|
else file(i).curr().zero();
|
|
file(i).setstatus(err);
|
|
}
|
|
for (i = 0; i < _reldefs.items(); i++)
|
|
{
|
|
bool first_match = _status.get_int ();
|
|
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();
|
|
_status.add (err);
|
|
_status.add (recno);
|
|
}
|
|
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 == - 1)
|
|
{
|
|
for (int i = 0; i < _reldefs.items(); i++)
|
|
reldef(i).write_enable(on);
|
|
}
|
|
else
|
|
{
|
|
const int idx = log2ind(logicnum);
|
|
CHECKD(idx != NOTFOUND, "File not found n. ", logicnum);
|
|
reldef(idx).write_enable(on);
|
|
}
|
|
}
|
|
|
|
void TRelation::write_enable(const char* name, const bool on)
|
|
|
|
{
|
|
const int idx = name2ind(name);
|
|
CHECKS(idx != NOTFOUND, "File or Table not found:", name);
|
|
reldef(idx).write_enable(on);
|
|
}
|
|
|
|
bool TRelation::add(int logicnum, 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", logicnum, linkto);
|
|
|
|
int idx = log2ind(logicnum);
|
|
TLocalisamfile* f = new TLocalisamfile(logicnum, idx == NOTFOUND);
|
|
idx = _files.add(f);
|
|
|
|
if (relexprs && *relexprs)
|
|
{
|
|
TRelationdef* r = new TRelationdef(this, idx, key, idxto,
|
|
relexprs, alias, allow_lock);
|
|
_reldefs.add(r);
|
|
}
|
|
return _errors;
|
|
}
|
|
|
|
bool TRelation::add(const char* tabname, const char* relexprs, int key,
|
|
int linkto, int alias, bool allow_lock)
|
|
|
|
{
|
|
// look for <to> file
|
|
const int idxto = log2ind(linkto);
|
|
if (idxto == NOTFOUND)
|
|
fatal_box("Can't link to file no. %d", linkto);
|
|
|
|
int idx = name2ind(tabname);
|
|
TTable* f = new TTable(tabname, FALSE);
|
|
idx = _files.add(f);
|
|
|
|
if (relexprs && *relexprs)
|
|
{
|
|
TRelationdef* r = new TRelationdef(this, idx, key, idxto,
|
|
relexprs, alias, allow_lock);
|
|
_reldefs.add(r);
|
|
}
|
|
|
|
return _errors;
|
|
}
|
|
|
|
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
|
|
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 = NOERR;
|
|
|
|
if (file(0).curr().empty())
|
|
return _errors; // *** no error returned **
|
|
// programmer must be aware
|
|
|
|
if ((_errors = file(0).write(atdate)) != NOERR)
|
|
return _errors;
|
|
|
|
|
|
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;
|
|
|
|
int res = file(log).write(atdate);
|
|
if (force && res == _isreinsert)
|
|
res = file(log).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);
|
|
const int log = rd.num();
|
|
|
|
if (!rd.write_enable() ||
|
|
(file(log).curr()).empty()) continue;
|
|
|
|
int res = file(log).rewrite(atdate);
|
|
if (force && res == _iskeynotfound)
|
|
res = file(log).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 TRectype& r,TExpression* cond)
|
|
|
|
{
|
|
for (int i = 0; i < cond->numvar(); i++)
|
|
{
|
|
const char* s = cond->varname(i);
|
|
|
|
cond->setvar(i, r.get(s));
|
|
}
|
|
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 ap = 0, junkl;
|
|
char s[82];
|
|
int junk, l, pagecnt = 0;
|
|
Page p;
|
|
int pos;
|
|
const int kl = file().filehnd()->i.Base[file().filehnd()->i.PN].KeyLen;
|
|
const bool filtered = has_filter();
|
|
|
|
FILE* _f = open_index(TRUE);
|
|
|
|
if (file().filehnd()->i.Base[file().filehnd()->i.PN].PEOD == 0)
|
|
{
|
|
fclose(_f);
|
|
return 0;
|
|
}
|
|
|
|
fseek(_f, 0L, SEEK_SET);
|
|
|
|
l = strlen(to());
|
|
BTrRead(&file().filehnd()->i, (char*)(const char*) from(), s, &junkl, &junk);
|
|
if (junk == _iseof) return 0;
|
|
|
|
TRecnotype* page = new TRecnotype [CMAXELPAGE];
|
|
|
|
if (GetAPage(&file().filehnd()->i, &p, file().filehnd()->i.CurPag, &junk) != NOERR)
|
|
fatal_box("Can't read index n. %d - file n. %d", file().filehnd()->i.PN + 1, file().filehnd()->ln);
|
|
|
|
pos = file().filehnd()->i.Pos - 1;
|
|
_pos = -1;
|
|
while (TRUE)
|
|
{
|
|
if (pos >= p.PA.PageHeader.NKey)
|
|
{
|
|
if (p.PA.PageHeader.Next == -1L) break;
|
|
file().filehnd()->i.CurPag = p.PA.PageHeader.Next;
|
|
if (GetAPage(&file().filehnd()->i, &p, file().filehnd()->i.CurPag, &junk) != NOERR)
|
|
fatal_box("Can't read index n. %d - file n. %d", file().filehnd()->i.PN + 1, file().filehnd()->ln);
|
|
pos = 0;
|
|
}
|
|
const char* s0 = p.PA.AreaForKey + pos++ * (kl + 4);
|
|
if (l && (strncmp(to(), s0, l) < 0)) break;
|
|
|
|
if (pagecnt == CMAXELPAGE)
|
|
{
|
|
if (filtered) pagecnt = filtercursor(pagecnt, page);
|
|
fwrite(page, sizeof(junkl), pagecnt, _f);
|
|
if (pos == -1)
|
|
for (int i = 0; i < pagecnt; i++)
|
|
if (page[i] == rp)
|
|
{
|
|
_pos = ap + i;
|
|
break;
|
|
}
|
|
ap += pagecnt;
|
|
pagecnt = 0;
|
|
}
|
|
page[pagecnt++] = *((long *)(s0 + kl));
|
|
}
|
|
if (pagecnt)
|
|
{
|
|
if (filtered) pagecnt = filtercursor(pagecnt, page);
|
|
fwrite(page, sizeof(junkl), pagecnt, _f);
|
|
if (pos == -1)
|
|
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;
|
|
TRectype& rec = file().curr();
|
|
for (int i = 0; i < pagecnt; i++)
|
|
{
|
|
CRead(&file().filehnd()->f, rec.string(), page[i], _nolock);
|
|
if ((_filterfunction ? _filterfunction(_if) : TRUE ) &&
|
|
(_fexpr ? __evalcondition(rec, _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 ((_filterfunction ? _filterfunction(_if) : TRUE ) &&
|
|
(_fexpr ? __evalcondition(rec, _fexpr) : TRUE))
|
|
return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
bool TCursor::changed()
|
|
|
|
{
|
|
isdef* fh = file().filehnd();
|
|
|
|
if (_frozen && _lastrec != 0L) return _filename != fh->f.name;
|
|
|
|
#if XVT_OS==XVT_OS_SCOUNIX
|
|
const TRecnotype eod = file().eod();
|
|
#else
|
|
int junk = 0;
|
|
const TRecnotype eod = cisgeteod(fh, &junk);
|
|
#endif
|
|
|
|
if (_lastrec != eod ||
|
|
(_lastkrec != fh->i.Base[_nkey -1].PEOD) ||
|
|
(_filename != fh->f.name))
|
|
{
|
|
_lastrec = eod;
|
|
_lastkrec = fh->i.Base[_nkey -1].PEOD;
|
|
_filename = fh->f.name;
|
|
return TRUE;
|
|
}
|
|
else return FALSE;
|
|
}
|
|
|
|
TRecnotype TCursor::update()
|
|
|
|
{
|
|
main_app().begin_wait();
|
|
|
|
file().setkey(_nkey);
|
|
file().read(_isgteq);
|
|
const TRecnotype totrec = buildcursor(file().recno());
|
|
|
|
main_app().end_wait();
|
|
|
|
return totrec;
|
|
}
|
|
|
|
|
|
void TCursor::filter(const char* filter, const TRectype *from,
|
|
const TRectype* to)
|
|
|
|
{
|
|
CHECK(!_frozen, "Impossibile filtrare un cursore congelato");
|
|
|
|
TString kf(_keyfrom), kto(_keyto), kfilter;
|
|
|
|
if (filter)
|
|
kfilter << filter;
|
|
|
|
bool filterchanged = (filter != NULL) && (_filter != kfilter);
|
|
|
|
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 = kfilter;
|
|
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;
|
|
}
|
|
file().setkey(_nkey);
|
|
_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* filter, int nkey, TRectype *from, TRectype* to)
|
|
: _frozen(FALSE), _filterfunction(NULL)
|
|
|
|
{
|
|
_if = r;
|
|
_nkey = nkey;
|
|
CHECKD(_nkey > 0 && _nkey <= file().filehnd()->r->NKeys, "Bad key number : ", _nkey);
|
|
|
|
_pos = 0;
|
|
_totrec = 0;
|
|
_lastrec = 0;
|
|
_lastkrec = 0;
|
|
_filter << filter;
|
|
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;
|
|
file().setkey(_nkey);
|
|
if (file().tab())
|
|
{
|
|
TTable& f = (TTable&)file();
|
|
_keyfrom = _keyto = f.name();
|
|
}
|
|
if (from != NULL)
|
|
{
|
|
_keyfrom = from->key(_nkey);
|
|
int p;
|
|
while ((p = _keyfrom.find('~')) != -1) _keyfrom[p] = ' ';
|
|
}
|
|
if (to != NULL)
|
|
{
|
|
_keyto = to->key(_nkey);
|
|
int p;
|
|
while ((p = _keyto.find('~')) != -1) _keyto[p] = ' ';
|
|
}
|
|
}
|
|
|
|
|
|
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) ||
|
|
(fread(&nrec, sizeof(TRecnotype), 1, _f) != 1))
|
|
fatal_box("Can't read record in file n. %d\n", file().filehnd()->ln);
|
|
|
|
fclose(_f);
|
|
|
|
curr().setdirty();
|
|
CRead(&file().filehnd()->f, curr().string(), nrec, _nolock);
|
|
repos();
|
|
return nrec;
|
|
}
|
|
|
|
int TCursor::lock(TReclock l)
|
|
{
|
|
CLockRec(&file().filehnd()->f, _pos, l);
|
|
return file().filehnd()->f.IOR;
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////
|
|
// TFieldRef
|
|
///////////////////////////////////////////////////////////
|
|
|
|
int name2log(const char* name)
|
|
{
|
|
int log = 0;
|
|
|
|
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 _fileid = pos = 0;
|
|
|
|
int par = s.find('[', pos);
|
|
_name = s.sub(pos, par); _name.strip(" ");
|
|
|
|
if (par > 0)
|
|
{
|
|
pos = par+1;
|
|
_from = atoi(s.mid(pos));
|
|
if (_from > 0) _from--; else _from = 0;
|
|
par = s.find(',', pos);
|
|
if (par > 0) _to = atoi(s.mid(par+1)); else _to = -1;
|
|
}
|
|
else
|
|
{
|
|
_from = 0;
|
|
_to = -1;
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
void TFieldref::print_on(ostream& out) const
|
|
{
|
|
if (_id.not_empty()) out << _id << "->";
|
|
out << _name;
|
|
if (_from > 0 || _to > 0)
|
|
{
|
|
out << '[' << (_from+1);
|
|
if (_to) out << ',' << _to;
|
|
out << ']';
|
|
}
|
|
}
|
|
|
|
const char* TFieldref::read(const TRectype& rec) const
|
|
{
|
|
static TString80 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 (from > 0) buffer.ltrim(from);
|
|
}
|
|
}
|
|
return buffer;
|
|
}
|
|
|
|
const char* TFieldref::read(const TRelation* c) const
|
|
{
|
|
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
|
|
{
|
|
TLocalisamfile* f = NULL;
|
|
TRectype* curr = NULL;
|
|
|
|
if (c == NULL)
|
|
{
|
|
f = new TLocalisamfile(_fileid, TRUE);
|
|
curr = &f->curr();
|
|
}
|
|
else
|
|
curr = &c->lfile(_id).curr();
|
|
|
|
write(val, *curr);
|
|
|
|
if (f != NULL) delete f;
|
|
}
|
|
|
|
|
|
void TFieldref::write(const char* val, TRectype& rec) const
|
|
{
|
|
if (_fileid >= CNF_GENERAL) return;
|
|
if (_from > 0)
|
|
{
|
|
TString80 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;
|
|
|
|
TLocalisamfile f(_fileid);
|
|
const int len = f.curr().length(_name);
|
|
return len - _from;
|
|
}
|
|
|
|
// *** EOF relation.cpp
|
|
|
|
|