nella filtercursor() dei TSorted_cursor. git-svn-id: svn://10.65.10.50/trunk@3744 c028cbd2-c16b-5b4b-a496-9718f37d4682
2536 lines
63 KiB
C++
Executable File
2536 lines
63 KiB
C++
Executable File
#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 <sheet.h>
|
|
#include <sort.h>
|
|
#include <tabutil.h>
|
|
#include <utility.h>
|
|
#include <xvtility.h>
|
|
|
|
#include <codeb.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()
|
|
|
|
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
|
|
{
|
|
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];
|
|
TString16 name;
|
|
for (int k = 0; k < expr.numvar(); k++)
|
|
{
|
|
name = expr.varname(k); name.upper();
|
|
expr.setvar(k, to.get(name));
|
|
}
|
|
|
|
const char* val = expr;
|
|
|
|
if (*val == '\0' && _altexprs.objptr(j))
|
|
{
|
|
TExpression& altexpr = (TExpression&)_altexprs[j];
|
|
for (int k = 0; k < expr.numvar(); k++)
|
|
{
|
|
name = altexpr.varname(k); name.upper();
|
|
altexpr.setvar(k, to.get(name));
|
|
}
|
|
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));
|
|
}
|
|
}
|
|
|
|
// @doc EXTERNAL
|
|
|
|
// @mfunc Ritorna l'indice di <p _files> del numero logico passato
|
|
//
|
|
// @rdesc Ritorna l'indice oppure NOTFOUND nel caso non sia presente
|
|
int TRelation::log2ind(
|
|
int log) const // @parm Numero logico del file di cui conoscere l'indice
|
|
|
|
// @comm Nel caso <p log> sia minore di 0 chiama la <mf TRelation::alias2ind>
|
|
//
|
|
// @xref <mf TRelation::alias2ind> <mf TRelation::name2ind>
|
|
{
|
|
// returns _files index of logical number or NOTFOUND if not present
|
|
|
|
if (log <= 0)
|
|
return alias2ind(-log);
|
|
|
|
const int nf = _files.items();
|
|
for (int i = 0; i < nf; i++)
|
|
if (file(i).num() == log)
|
|
return i;
|
|
|
|
return NOTFOUND;
|
|
}
|
|
|
|
// @doc EXTERNAL
|
|
|
|
// @mfunc Ritorna l'indice di <p _files> del alias del file passato
|
|
//
|
|
// @rdesc Ritorna l'indice oppure NOTFOUND nel caso non sia presente
|
|
int TRelation::alias2ind(
|
|
int alias) const // @parm Alias del file di cui conoscere l'indice
|
|
|
|
|
|
// @xref <mf TRelation::log2ind> <mf TRelation::name2ind>
|
|
{
|
|
if (alias <= 0) 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;
|
|
}
|
|
|
|
// @doc EXTERNAL
|
|
|
|
// @mfunc Ritorna l'indice di <p _files> del nome del file passato
|
|
//
|
|
// @rdesc Ritorna l'indice oppure NOTFOUND nel caso non sia presente
|
|
int TRelation::name2ind(
|
|
const char* name) const // @parm Nome del file di cui conoscere l'indice
|
|
|
|
// @xref <mf TRelation::alias2ind> <mf TRelation::alias2ind>
|
|
{
|
|
const int num = name2log(name);
|
|
const int ind = log2ind(num);
|
|
return ind;
|
|
}
|
|
|
|
// @doc EXTERNAL
|
|
|
|
// @mfunc Ritorna il descrittore del file
|
|
//
|
|
// @rdesc Ritorna il reference ad un <c TLocalisamfile> che indica il descrittore del
|
|
// file della relazione
|
|
TLocalisamfile& TRelation::lfile(
|
|
int logicnum) const // @parm Numero logico del file da ritornare (default 0)
|
|
// @parm const char* | name | Nome del file da ritornare
|
|
// @syntax TLocalisamfile& lfile(int logicnum)
|
|
// @syntax TLocalisamfile& lfile(const char* name)
|
|
//
|
|
// @comm E' comodo utilizzare anche l'operatore [] che richiama questa funzione con
|
|
// la prima sintassi.
|
|
|
|
{
|
|
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];
|
|
}
|
|
|
|
// @doc EXTERNAL
|
|
|
|
// @mfunc Abilita/disabilita la scrittura sul file
|
|
void TRelation::write_enable(
|
|
int logicnum, // @parm Numero logico del file da abilitare/disabilitare (default 0)
|
|
// @parm cont char* | name | Nome del file da abilitare/disabilitare
|
|
const bool on) // @parm Indica l'operazione da effettuare sul file:
|
|
//
|
|
// @flag TRUE | Abilita la scrittura sul file (default)
|
|
// @flag FALSE | Disabilita la scrittura sul file
|
|
|
|
// @syntax void write_enable(int logicnum, const bool on)
|
|
// @syntax void write_enable(const char* name, const bool on)
|
|
//
|
|
// @comm Nel caso venga passato un numero logico uguale a 0 vengono abilitati/disabilitati tutti
|
|
// i file dell relazione
|
|
|
|
|
|
{
|
|
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);
|
|
}
|
|
|
|
|
|
// @doc EXTERNAL
|
|
|
|
// @mfunc Aggiunge una nuovo file nella relazione
|
|
//
|
|
// @rdesc Ritorna se e' riuscito ad aggiungere il file alla relazione
|
|
bool TRelation::add(
|
|
TLocalisamfile* f, // @parm Descrittore del file da aggiungere
|
|
const char* relexprs, // @parm Espressione della relazione
|
|
int key, // @parm Chiave del file principale
|
|
int linkto, // @parm Posizione a cui aggiungere il file
|
|
int alias, // @parm Alias da dare al file da aggiungere
|
|
bool allow_lock) // @parm Indica se fare il lock sul file oppure ignorarli
|
|
// @parm int | logicnum | Numero logico del file da aggiungere
|
|
// @parm const char* | tabname | Nome della tabella da aggiungere
|
|
|
|
// @syntax bool add(TLocalisamfile* f, const char* relexprs, int key, int linkto, int alias, bool allow_lock);
|
|
// @syntax bool add(int logicnum, const char* relexprs, int key, int linkto, int alias, bool allow_lock);
|
|
// @syntax bool add(const char* tabname, const char* relexprs, int key, int linkto, int alias, bool allow_lock);
|
|
|
|
// @comm Il parametro <p linkto> puo' assumere i valori:
|
|
//
|
|
// @flag 0 | Per il file principale
|
|
// @flag <gt>0 | Indica il numero logico del file
|
|
// @flag <lt>0 | Indica l'alias del file
|
|
|
|
|
|
{
|
|
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);
|
|
}
|
|
|
|
// @doc EXTERNAL
|
|
|
|
// @mfunc Sostituisce nella relazione un file
|
|
void TRelation::replace(
|
|
TLocalisamfile* f, // @parm Descrittore del file sostituto
|
|
int index) // @parm Posizione nel quale sostituire il file (default 0)
|
|
{
|
|
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();
|
|
}
|
|
|
|
// @doc EXTERNAL
|
|
|
|
// @mfunc Permette di posizionare l'albero di relazioni tra i file
|
|
//
|
|
// @rdesc Ritorna il numero di errore avvenuto nell'opeerazione, NOERR nel caso
|
|
// venga eseguito tutto correttamente
|
|
int TRelation::position_rels(
|
|
TIsamop op, // @parm Operatore che esegue la relazione (default _isequal)
|
|
TReclock lockop, // @parm Tipo di lock sul file (default _nolock)
|
|
TDate& atdate, // @parm UNUSED
|
|
int first) // @parm Numero del file da cui costruire la relazione (default
|
|
// 0, cioe' dal file principale)
|
|
|
|
// @comm Viene eseguito tutto il lavoro di creazione delle relazione: se non
|
|
// trova un record adatto su un file, svuota il record corrente e non
|
|
// ritorna errore.
|
|
// <nl>Le altre funzioni sul record procedono normalmente
|
|
|
|
{
|
|
_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
|
|
{
|
|
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;
|
|
}
|
|
|
|
|
|
// @doc EXTERNAL
|
|
|
|
// @mfunc Posiziona il numero logico sul record successivo
|
|
//
|
|
// @rdesc Ritorna se ha trovato la corrispondenza:
|
|
//
|
|
// @flag TRUE | Corrispondenza trovata
|
|
// @flag FALSE | Corrispondenza non trovata
|
|
bool TRelation::next_match(
|
|
int logicnum, // @parm Numero logico del file da fare avanzare
|
|
const char* fieldlist, // @parm Lista dei campi che devono rimanere costanti (default NULL)
|
|
int nkey) // @parm Numero della chiave (default 0)
|
|
|
|
// @comm Il posizionamento del numero logico non deve essere riferita al file
|
|
// principale; in ogni caso viene considerata consistente ad eccezione dei
|
|
// casi di inconsistenza nella prima posizione.
|
|
|
|
{
|
|
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;
|
|
}
|
|
|
|
|
|
// @doc EXTERNAL
|
|
|
|
// @mfunc Controlla se c'e' un record ed e' il primo match (non si e' mai fatta
|
|
// <mf TRelation::position_rels>)
|
|
//
|
|
// @rdesc Ritorna TRUE se c'e' un record ed e' il primo match
|
|
bool TRelation::is_first_match(
|
|
int logicnum) // @parm Numero logico del file da controllare
|
|
{
|
|
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
|
|
|
|
// @doc EXTERNAL
|
|
|
|
// @mfunc Controlla se la relazione e' corretta
|
|
//
|
|
// @rdesc Ritorna i seguent valori
|
|
//
|
|
// @flag TRUE | La relazione soddisfa i criteri di correttezza
|
|
// @flag FALSE | La relazione manca di almeno uno di criteri di correttezza
|
|
bool TRelation::isconsistent(
|
|
bool reset) // @parm Indica se cercare di riportare la relazione ad uno stato
|
|
// consistente (default FALSE)
|
|
|
|
// @comm Per essere corretta una relazione necessita che tutti i file siano
|
|
// corretti, che il record corrente non sia vuoto e che la relazione sia
|
|
// consistente
|
|
// <nl>Se <p reset> e' TRUE si cerca di riportare la relazione in uno stato
|
|
// consistente (basato sul record principale). Non viene fatto nessun altro
|
|
// controllo
|
|
// <nl>Questa funzione e' chiamata internamente da <mf TRelation::update>
|
|
// e <mf TRelation::remove>.
|
|
|
|
{
|
|
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;
|
|
}
|
|
|
|
bool TRelation::exist(int logicnum) const
|
|
{
|
|
const bool lucky = log2ind(logicnum) >= 0;
|
|
return lucky;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////
|
|
// TCursor
|
|
///////////////////////////////////////////////////////////
|
|
|
|
|
|
HIDDEN bool __evalcondition(const TRelation& r,TExpression* cond)
|
|
{
|
|
for (int i = cond->numvar() - 1; i >= 0; i--)
|
|
{
|
|
const char* s = cond->varname(i);
|
|
TFieldref f(s,0);
|
|
cond->setvar(i, f.read(r));
|
|
}
|
|
return (bool) *cond;
|
|
}
|
|
|
|
// @doc EXTERNAL
|
|
|
|
// @mfunc Apre il file di indice
|
|
//
|
|
// @rdesc Ritorna l'handle del file aperto
|
|
FILE* TCursor::open_index(
|
|
bool create) // @parm Indica se creare l'indice nel caso manchi (default FALSE)
|
|
{
|
|
#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 < (const char *) kf || (kt.not_empty() && kt < (const char *) key.left(kt.len())))
|
|
return FALSE;
|
|
if (update_relation()) _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;
|
|
}
|
|
|
|
// @doc EXTERNAL
|
|
|
|
// @mfunc Setta il filtro sul cursore
|
|
void TCursor::filter(
|
|
const char* fil, // @parm Espressione del filtro
|
|
const TRectype *from, // @parm Primo record del pacchetto da esaminare
|
|
const TRectype* to) // @parm Ultimo record del pacchetto da esaminare
|
|
|
|
// @comm ATTENZIONE: non e' possibile 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))
|
|
{
|
|
CHECK(!frozen(), "Impossibile filtrare un cursore congelato");
|
|
|
|
_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;
|
|
if (_pos < 0)
|
|
{
|
|
file().zero();
|
|
file().setstatus(_isemptyfile);
|
|
}
|
|
else
|
|
{
|
|
readrec();
|
|
file().setstatus(_iseof);
|
|
}
|
|
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;
|
|
}
|
|
|
|
// @doc EXTERNAL
|
|
|
|
// @mfunc Mette un lock sul record
|
|
//
|
|
// @rdesc Ritorna il numero di errore che si verifica nel porre il lock (NOERR) se
|
|
// non si verificano errori
|
|
int TCursor::lock(
|
|
TReclock l) // @parm Tipo di lock da porre sul record (vedi <t TReclock>)
|
|
{
|
|
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[256];
|
|
TRecnotype p;
|
|
} El_To_Sort;
|
|
|
|
// @doc EXTERNAL
|
|
|
|
// @mfunc Controlla la validita' dell'espressione
|
|
//
|
|
// @rdesc Ritorna se si tratta di una espressione valida
|
|
bool TSorted_cursor::check_expr(
|
|
TString& s) // @parm Espressione da controllare
|
|
|
|
// @comm Modifica l'espressione in modo da avere una valido parametro per TFieldref!
|
|
// <nl>Es. UPPER(20->RAGSOC[1,40]+) ==> 20->RAGSOC[1,40]+
|
|
// <nl>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;
|
|
|
|
_sort = new TSort(sizeof(El_To_Sort));
|
|
_order_expr.restart();
|
|
while ((s=_order_expr.get()).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()
|
|
int n = f.file();
|
|
int flen = f.len(n<0 ? relation()->lfile(n).curr() : relation()->curr());
|
|
_sort->addsortkey(abspos+f.from(),flen,versus);
|
|
CHECKS(flen!=0,"Field can not have null length: ",(const char *) s);
|
|
int lf = (relation()->lfile(n).num());
|
|
TRectype r(lf);
|
|
abspos+=r.length(f.name());
|
|
CHECKD(abspos<=256,"Cannot exceed 256 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; // lasciare cosi' altrimenti la readat legge due volte lo stesso record
|
|
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();
|
|
|
|
ap = 0;
|
|
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;
|
|
}
|
|
ap++;
|
|
}
|
|
if (pagecnt)
|
|
fwrite(page,sizeof(TRecnotype),pagecnt,_f);
|
|
if (TCursor::pos() == -1) pos=0;
|
|
delete page;
|
|
if (_sort) delete _sort;
|
|
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()).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());
|
|
TRectype& frec = file(f.file()).curr();
|
|
TFieldtypes fld_type = frec.type(f.name());
|
|
if (fld_type == _datefld) // 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;
|
|
if (fld_type == _alfafld || fld_type == _datefld)
|
|
fmt.format("%%-%ds",f.len(frec));
|
|
else
|
|
fmt.format("%%%ds",f.len(frec));
|
|
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()).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);
|
|
}
|
|
|
|
TSorted_cursor::~TSorted_cursor()
|
|
{
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////
|
|
// TFieldRef
|
|
///////////////////////////////////////////////////////////
|
|
|
|
// @doc EXTERNAL
|
|
|
|
// @func Converte una stringa in numero logico o numero tabella
|
|
//
|
|
// @rdesc Ritorna il numero logico del file o il numero della tabella
|
|
int name2log(
|
|
const char* name) // @parm Stringa da convertire
|
|
{
|
|
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;
|
|
}
|
|
|
|
TObject* TFieldref::dup() const
|
|
{
|
|
TFieldref* f = new TFieldref();
|
|
f->_fileid = _fileid;
|
|
f->_id = _id;
|
|
f->_name = _name;
|
|
f->_from = _from;
|
|
f->_to = _to;
|
|
return f;
|
|
}
|
|
|
|
// @doc EXTERNAL
|
|
|
|
// @mfunc Operatore di assegnamento
|
|
//
|
|
// @rdesc Ritorna l'oggetto assegnato
|
|
TFieldref& TFieldref::operator =(
|
|
const TString& s) // @parm Stringa da assegnare all'oggetto
|
|
|
|
// @comm Un <c TFildref> deve avere il seguente formato (solamente NAME e il mandante):
|
|
// <nl>FILE->NAME[FROM,TO]
|
|
|
|
{
|
|
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); // Cerca la fine del nome del campo
|
|
_name = s.sub(pos, par); // Estrae il nome del campo
|
|
_name.strip(" "); // Elimina eventuali spazi superflui
|
|
|
|
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(0));
|
|
buffer = c.get(s.get());
|
|
}
|
|
else
|
|
{
|
|
buffer = _name; buffer.upper();
|
|
buffer = rec.get(buffer);
|
|
if (_from > 0 || _to > 0)
|
|
{
|
|
const int l = buffer.len();
|
|
if (_to < l && _to > 0) buffer.cut(_to);
|
|
if (_from > 0) buffer.ltrim(_from);
|
|
}
|
|
}
|
|
return buffer;
|
|
}
|
|
|
|
const char* TFieldref::read(const TRelation& r) const
|
|
{
|
|
const TRectype& rec = _id.empty() ? r.curr() : r.lfile(_id).curr();
|
|
const char *s = read(rec);
|
|
return s;
|
|
}
|
|
|
|
void TFieldref::write(const char* val, TRelation& r) const
|
|
{
|
|
TRectype &rec = _id.empty() ? r.curr() : r.lfile(_id).curr();
|
|
write(val, rec);
|
|
}
|
|
|
|
|
|
void TFieldref::write(const char* val, TRectype& rec) const
|
|
{
|
|
if (_fileid >= CNF_GENERAL) return;
|
|
if (_from > 0)
|
|
{
|
|
TString 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)
|
|
{
|
|
set_key(new TRectype(logicnum));
|
|
}
|
|
|
|
TRecord_array::TRecord_array(const TRecord_array& a)
|
|
: TArray(a), _file(a._file), _offset(a._offset), _num(a._num)
|
|
{}
|
|
|
|
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");
|
|
|
|
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
|
|
for (int i = kd.NkFields-2; i >= 0; i--)
|
|
{
|
|
const int nf = kd.FieldSeq[i] % MaxFields;
|
|
const RecFieldDes& rf = recd->Fd[nf];
|
|
const TString& val = r->get(rf.Name);
|
|
renum_key(rf.Name, val);
|
|
}
|
|
}
|
|
}
|
|
|
|
const TRectype& TRecord_array::key() const
|
|
{
|
|
TRectype* r = (TRectype*)objptr(0);
|
|
CHECK(r, "TRecord_array lost its key");
|
|
return *r;
|
|
}
|
|
|
|
bool TRecord_array::exist(int n) const
|
|
{
|
|
const int i = n > 0 ? n - _offset : -1;
|
|
TObject* r = objptr(i);
|
|
return r != NULL;
|
|
}
|
|
|
|
// @doc EXTERNAL
|
|
|
|
// @mfunc Ritorna la riga <p r>-esima; se tale riga non esiste e se <p create> 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 <p n> non esiste viene creata
|
|
// @flag FALSE | Se la riga <p n> non esiste non viene creata
|
|
|
|
// @comm Nel caso <p create> 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*)objptr(i);
|
|
if (r == NULL && create)
|
|
{
|
|
r = (TRectype*)key().dup(); // Crea nuovo record copiando la chiave
|
|
n = add(r, i) + _offset; // Riassegna n se era negativo!
|
|
char str[16]; sprintf(str, "%d", 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);
|
|
const TString& curr = key().get(field);
|
|
if (curr == num)
|
|
return FALSE;
|
|
|
|
for (int i = last(); i >= 0; i--)
|
|
{
|
|
TRectype* r = (TRectype*)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);
|
|
}
|
|
|
|
|
|
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);
|
|
insert(r, nr);
|
|
if (shift)
|
|
{
|
|
for (int f = last(); f > nr; f = pred(f))
|
|
{
|
|
char n[16];
|
|
TRectype & rec = row(f, FALSE);
|
|
|
|
sprintf(n, "%ld", (long)_offset + f);
|
|
rec.renum_key(_num, n);
|
|
}
|
|
}
|
|
return nr;
|
|
}
|
|
|
|
int TRecord_array::add_row(TRectype* r)
|
|
{
|
|
const int nr = rec2row(*r);
|
|
add(r, nr);
|
|
if (nr == 0 && rows() > 0) // Se ho cambiato il record campione
|
|
{ // e ci sono altre righe ...
|
|
for (int f = r->items()-1; f >= 0; f--)
|
|
{
|
|
const char* fn = r->fieldname(f);
|
|
const TString& v = r->get(fn);
|
|
if (v.not_empty()) renum_key(fn, v); // ... aggiorna tutte le righe in base
|
|
} // ai campi non vuoti del campione
|
|
}
|
|
return nr;
|
|
}
|
|
|
|
|
|
// @doc EXTERNAL
|
|
|
|
// @mfunc Elimina le righe vuote
|
|
void TRecord_array::pack()
|
|
{
|
|
TArray::pack();
|
|
|
|
for (int i = size()-1; i > 0; i--)
|
|
{
|
|
TRectype* r = (TRectype*)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 = destroy(index, pack);
|
|
|
|
if (ok && pack)
|
|
{
|
|
for (int i = size()-1; i >= index; i--)
|
|
{
|
|
TRectype* r = (TRectype*)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 = last(); i > 0; i = pred(i))
|
|
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)
|
|
{
|
|
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();
|
|
int err = NOERR;
|
|
if (filter != NULL && !filter->empty())
|
|
{
|
|
TLocalisamfile f(_file);
|
|
|
|
set_key(filter);
|
|
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;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
int TRecord_array::read(const TRectype& filter)
|
|
{
|
|
TRectype* f = (TRectype*)filter.dup();
|
|
return read(f);
|
|
}
|
|
|
|
int TRecord_array::remove_from(int pos) const
|
|
{
|
|
int err = NOERR;
|
|
|
|
TRectype* rec = (TRectype*)key().dup();
|
|
CHECK(!rec->empty(), "Can't use empty key");
|
|
|
|
TLocalisamfile f(_file);
|
|
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::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;
|
|
|
|
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 = 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(i + _offset);
|
|
|
|
return err;
|
|
}
|
|
|
|
int TRecord_array::remove() const
|
|
{
|
|
return remove_from(1 + _offset);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////
|
|
// 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);
|
|
}
|
|
}
|
|
|
|
// @doc INTERNAL
|
|
|
|
// @mfunc Cambia la relazione descritta
|
|
void TRelation_description::change_relation(
|
|
TRelation& r, // @parm Nuova relazione da assegnare
|
|
TString_array& a) // @parm Array di descrittore dei file
|
|
{
|
|
_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";
|
|
}
|
|
}
|
|
|
|
// @doc INTERNAL
|
|
|
|
// @mfunc Seleziona un file
|
|
//
|
|
// @rdesc Ritorna il risultato dell'operazione:
|
|
//
|
|
// @flag TRUE | E' stato selzionato il file
|
|
// @flag FALSE | Non e' riuscito a selezionare il file
|
|
bool TRelation_description::choose_file(
|
|
int file) // @parm Numero logico del file da selezionare (default 0)
|
|
|
|
// @comm Dopo aver scelto un file (occorre che ritorni TRUE) i metodi della classe
|
|
// permettono di conoscere tutti i dati riguardanti i parametri del file selezionato
|
|
|
|
{
|
|
TArray_sheet sht(-1,-1,-4,-4,"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;
|
|
}
|
|
|
|
// @doc INTERNAL
|
|
|
|
// @mfunc Seleziona un campo <p fld> del file selezionato
|
|
//
|
|
// @rdesc Ritorna il risultato dell'operazione:
|
|
//
|
|
// @flag TRUE | E' stato selzionato il campo
|
|
// @flag FALSE | Non e' riuscito a selezionare il campo
|
|
bool TRelation_description::choose_field(
|
|
const char* fld) // @parm Nome del campo da selezionare (default "")
|
|
|
|
// @comm Occorre che sia selezionato un file, diversamente viene considerato
|
|
// selezionato il primo file
|
|
{
|
|
TArray_sheet sht(-1,-1,76,20,"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;
|
|
}
|
|
|
|
// @doc INTERNAL
|
|
|
|
// @mfunc Costruisce un menu
|
|
//
|
|
// @rdesc Ritorna solamente FALSE
|
|
bool TRelation_description::build_menu(
|
|
const char* title) // @parm Titolo del menu'
|
|
|
|
// @comm Costruisce un albero di menu' e setta il campo corrente quando viene
|
|
// selezionato (deve essere popup)
|
|
//
|
|
// @devnote Funzione non implementata.
|
|
{
|
|
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);
|
|
}
|
|
|
|
// @doc INTERNAL
|
|
|
|
// @mfunc Setta la descrizione del campo
|
|
//
|
|
// @rdesc Ritorna se ha trovato il campo da settare
|
|
bool TRelation_description::set_field_description(
|
|
const char* field, // @parm Campo a cui assegnare la descrizione
|
|
const char* des) // @parm Descrizione da assgnare al campo
|
|
{
|
|
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
|
|
|