mariadb/storage/connect/tabdos.cpp
Mikhail Chalov 19af1890b5 Use memory safe snprintf() in Connect Engine
This commit replaces sprintf(buf, ...) with
snprintf(buf, sizeof(buf), ...),
specifically in the "easy" cases where buf is allocated with a size
known at compile time.

The changes make sure we are not write outside array/string bounds which
will lead to undefined behaviour. In case the code is trying to write
outside bounds - safe version of functions simply cut the string
messages so we process this gracefully.

All new code of the whole pull request, including one or several files
that are either new files or modified ones, are contributed under the BSD-new
license.  I am contributing on behalf of my employer Amazon Web Services,
Inc.

bsonudf.cpp warnings cleanup by Daniel Black

Reviewer: Daniel Black
2022-07-26 16:28:59 +10:00

2920 lines
95 KiB
C++

/************* TabDos C++ Program Source Code File (.CPP) **************/
/* PROGRAM NAME: TABDOS */
/* ------------- */
/* Version 4.9.5 */
/* */
/* COPYRIGHT: */
/* ---------- */
/* (C) Copyright to the author Olivier BERTRAND 1998-2020 */
/* */
/* WHAT THIS PROGRAM DOES: */
/* ----------------------- */
/* This program are the DOS tables classes. */
/* */
/***********************************************************************/
/***********************************************************************/
/* Include relevant sections of the System header files. */
/***********************************************************************/
#include "my_global.h"
#if defined(_WIN32)
#include <io.h>
#include <sys\timeb.h> // For testing only
#include <fcntl.h>
#include <errno.h>
#if defined(__BORLANDC__)
#define __MFC_COMPAT__ // To define min/max as macro
#endif // __BORLANDC__
//#include <windows.h>
#else // !_WIN32
#if defined(UNIX)
#include <errno.h>
#include <unistd.h>
#else // !UNIX
#include <io.h>
#endif // !UNIX
#include <fcntl.h>
#endif // !_WIN32
/***********************************************************************/
/* Include application header files: */
/* global.h is header containing all global declarations. */
/* plgdbsem.h is header containing the DB application declarations. */
/* filamtxt.h is header containing the file AM classes declarations. */
/***********************************************************************/
#include "global.h"
#include "osutil.h"
#include "plgdbsem.h"
//#include "catalog.h"
#include "mycat.h"
#include "xindex.h"
#include "filamap.h"
#include "filamfix.h"
#include "filamdbf.h"
#if defined(GZ_SUPPORT)
#include "filamgz.h"
#endif // GZ_SUPPORT
#if defined(ZIP_SUPPORT)
#include "filamzip.h"
#endif // ZIP_SUPPORT
#include "tabdos.h"
#include "tabfix.h"
#include "tabmul.h"
#include "array.h"
#include "blkfil.h"
/***********************************************************************/
/* DB static variables. */
/***********************************************************************/
int num_read, num_there, num_eq[2]; // Statistics
/***********************************************************************/
/* Size of optimize file header. */
/***********************************************************************/
#define NZ 4
/***********************************************************************/
/* External function. */
/***********************************************************************/
bool ExactInfo(void);
USETEMP UseTemp(void);
/***********************************************************************/
/* Min and Max blocks contains zero ended fields (blank = false). */
/* No conversion of block values (check = true). */
/***********************************************************************/
PVBLK AllocValBlock(PGLOBAL, void *, int, int, int len= 0, int prec= 0,
bool check= true, bool blank= false, bool un= false);
/* --------------------------- Class DOSDEF -------------------------- */
/***********************************************************************/
/* Constructor. */
/***********************************************************************/
DOSDEF::DOSDEF(void)
{
Pseudo = 3;
Fn = NULL;
Ofn = NULL;
Entry = NULL;
To_Indx = NULL;
Pwd = NULL;
Recfm = RECFM_VAR;
Mapped = false;
Zipped = false;
Mulentries = false;
Append = false;
Padded = false;
Huge = false;
Accept = false;
Eof = false;
To_Pos = NULL;
Optimized = 0;
AllocBlks = 0;
Compressed = 0;
Lrecl = 0;
AvgLen = 0;
Block = 0;
Last = 0;
Blksize = 0;
Maxerr = 0;
ReadMode = 0;
Ending = 0;
Teds = 0;
} // end of DOSDEF constructor
/***********************************************************************/
/* DefineAM: define specific AM block values from XDB file. */
/***********************************************************************/
bool DOSDEF::DefineAM(PGLOBAL g, LPCSTR am, int)
{
char buf[8];
bool map = (am && (*am == 'M' || *am == 'm'));
LPCSTR dfm = (am && (*am == 'F' || *am == 'f')) ? "F"
: (am && (*am == 'B' || *am == 'b')) ? "B"
: (am && (*am == 'X' || *am == 'x')) ? "X"
: (am && !stricmp(am, "DBF")) ? "D" : "V";
if ((Zipped = GetBoolCatInfo("Zipped", false))) {
Entry = GetStringCatInfo(g, "Entry", NULL);
Mulentries = (Entry && *Entry) ? strchr(Entry, '*') || strchr(Entry, '?')
: false;
Mulentries = GetBoolCatInfo("Mulentries", Mulentries);
Append = GetBoolCatInfo("Append", false);
Pwd = GetStringCatInfo(g, "Password", NULL);
} // endif Zipped
Desc = Fn = GetStringCatInfo(g, "Filename", NULL);
Ofn = GetStringCatInfo(g, "Optname", Fn);
GetCharCatInfo("Recfm", (PSZ)dfm, buf, sizeof(buf));
Recfm = (toupper(*buf) == 'F') ? RECFM_FIX :
(toupper(*buf) == 'B') ? RECFM_BIN :
(toupper(*buf) == 'X') ? RECFM_NAF : // MGO
(toupper(*buf) == 'D') ? RECFM_DBF : RECFM_VAR;
Lrecl = GetIntCatInfo("Lrecl", 0);
if (Recfm != RECFM_DBF)
Compressed = GetIntCatInfo("Compressed", 0);
Mapped = GetBoolCatInfo("Mapped", map);
//Block = GetIntCatInfo("Blocks", 0);
//Last = GetIntCatInfo("Last", 0);
Ending = GetIntCatInfo("Ending", CRLF);
if (Ending <= 0) {
Ending = (Recfm == RECFM_BIN || Recfm == RECFM_VCT) ? 0 : CRLF;
SetIntCatInfo("Ending", Ending);
} // endif ending
if (Recfm == RECFM_FIX || Recfm == RECFM_BIN) {
Huge = GetBoolCatInfo("Huge", Cat->GetDefHuge());
Padded = GetBoolCatInfo("Padded", false);
Blksize = GetIntCatInfo("Blksize", 0);
Eof = (GetIntCatInfo("EOF", 0) != 0);
Teds = toupper(*GetStringCatInfo(g, "Endian", ""));
} else if (Recfm == RECFM_DBF) {
Maxerr = GetIntCatInfo("Maxerr", 0);
Accept = GetBoolCatInfo("Accept", false);
ReadMode = GetIntCatInfo("Readmode", 0);
} else // (Recfm == RECFM_VAR)
AvgLen = GetIntCatInfo("Avglen", 0);
// Ignore wrong Index definitions for catalog commands
SetIndexInfo();
return false;
} // end of DefineAM
/***********************************************************************/
/* Get the full path/name of the optization file. */
/***********************************************************************/
bool DOSDEF::GetOptFileName(PGLOBAL g, char *filename)
{
PCSZ ftype;
switch (Recfm) {
case RECFM_VAR: ftype = ".dop"; break;
case RECFM_FIX: ftype = ".fop"; break;
case RECFM_BIN: ftype = ".bop"; break;
case RECFM_VCT: ftype = ".vop"; break;
case RECFM_CSV: ftype = ".cop"; break;
case RECFM_DBF: ftype = ".dbp"; break;
default:
snprintf(g->Message, sizeof(g->Message), MSG(INVALID_FTYPE), Recfm);
return true;
} // endswitch Ftype
PlugSetPath(filename, Ofn, GetPath());
strcat(PlugRemoveType(filename, filename), ftype);
return false;
} // end of GetOptFileName
/***********************************************************************/
/* After an optimize error occurred, remove all set optimize values. */
/***********************************************************************/
void DOSDEF::RemoveOptValues(PGLOBAL g)
{
char filename[_MAX_PATH];
PCOLDEF cdp;
// Delete settings of optimized columns
for (cdp = To_Cols; cdp; cdp = cdp->GetNext())
if (cdp->GetOpt()) {
cdp->SetMin(NULL);
cdp->SetMax(NULL);
cdp->SetNdv(0);
cdp->SetNbm(0);
cdp->SetDval(NULL);
cdp->SetBmap(NULL);
} // endif Opt
// Delete block position setting for not fixed tables
To_Pos = NULL;
AllocBlks = 0;
// Delete any eventually ill formed non matching optimization file
if (!GetOptFileName(g, filename))
#if defined(_WIN32)
DeleteFile(filename);
#else // UNIX
remove(filename);
#endif // _WIN32
Optimized = 0;
} // end of RemoveOptValues
/***********************************************************************/
/* DeleteIndexFile: Delete DOS/UNIX index file(s) using platform API. */
/***********************************************************************/
bool DOSDEF::DeleteIndexFile(PGLOBAL g, PIXDEF pxdf)
{
PCSZ ftype;
char filename[_MAX_PATH];
bool sep, rc = false;
if (!To_Indx)
return false; // No index
// If true indexes are in separate files
sep = GetBoolCatInfo("SepIndex", false);
if (!sep && pxdf) {
strcpy(g->Message, MSG(NO_RECOV_SPACE));
return true;
} // endif sep
switch (Recfm) {
case RECFM_VAR: ftype = ".dnx"; break;
case RECFM_FIX: ftype = ".fnx"; break;
case RECFM_BIN: ftype = ".bnx"; break;
case RECFM_VCT: ftype = ".vnx"; break;
case RECFM_CSV: ftype = ".cnx"; break;
case RECFM_DBF: ftype = ".dbx"; break;
default:
snprintf(g->Message, sizeof(g->Message), MSG(BAD_RECFM_VAL), Recfm);
return true;
} // endswitch Ftype
/*********************************************************************/
/* Check for existence of an index file. */
/*********************************************************************/
if (sep) {
// Indexes are save in separate files
#if defined(_WIN32)
char drive[_MAX_DRIVE];
#else
char *drive = NULL;
#endif
char direc[_MAX_DIR];
char fname[_MAX_FNAME];
bool all = !pxdf;
if (all)
pxdf = To_Indx;
for (; pxdf; pxdf = pxdf->GetNext()) {
_splitpath(Ofn, drive, direc, fname, NULL);
strcat(strcat(fname, "_"), pxdf->GetName());
_makepath(filename, drive, direc, fname, ftype);
PlugSetPath(filename, filename, GetPath());
#if defined(_WIN32)
if (!DeleteFile(filename))
rc |= (GetLastError() != ERROR_FILE_NOT_FOUND);
#else // UNIX
if (remove(filename))
rc |= (errno != ENOENT);
#endif // UNIX
if (!all)
break;
} // endfor pxdf
} else { // !sep
// Drop all indexes, delete the common file
PlugSetPath(filename, Ofn, GetPath());
strcat(PlugRemoveType(filename, filename), ftype);
#if defined(_WIN32)
if (!DeleteFile(filename))
rc = (GetLastError() != ERROR_FILE_NOT_FOUND);
#else // UNIX
if (remove(filename))
rc = (errno != ENOENT);
#endif // UNIX
} // endif sep
if (rc)
snprintf(g->Message, sizeof(g->Message), MSG(DEL_FILE_ERR), filename);
return rc; // Return true if error
} // end of DeleteIndexFile
/***********************************************************************/
/* InvalidateIndex: mark all indexes as invalid. */
/***********************************************************************/
bool DOSDEF::InvalidateIndex(PGLOBAL)
{
if (To_Indx)
for (PIXDEF xp = To_Indx; xp; xp = xp->Next)
xp->Invalid = true;
return false;
} // end of InvalidateIndex
/***********************************************************************/
/* GetTable: makes a new Table Description Block. */
/***********************************************************************/
PTDB DOSDEF::GetTable(PGLOBAL g, MODE mode)
{
// Mapping not used for insert
USETEMP tmp = UseTemp();
bool map = Mapped && mode != MODE_INSERT &&
!(tmp != TMP_NO && Recfm == RECFM_VAR
&& mode == MODE_UPDATE) &&
!(tmp == TMP_FORCE &&
(mode == MODE_UPDATE || mode == MODE_DELETE));
PTXF txfp = NULL;
PTDBASE tdbp;
/*********************************************************************/
/* Allocate table and file processing class of the proper type. */
/* Column blocks will be allocated only when needed. */
/*********************************************************************/
if (Recfm == RECFM_DBF) {
if (Catfunc == FNC_NO) {
if (Zipped) {
if (mode == MODE_READ || mode == MODE_ANY || mode == MODE_ALTER) {
txfp = new(g) UZDFAM(this);
} else {
strcpy(g->Message, "Zipped DBF tables are read only");
return NULL;
} // endif's mode
} else if (map)
txfp = new(g) DBMFAM(this);
else
txfp = new(g) DBFFAM(this);
tdbp = new(g) TDBFIX(this, txfp);
} else
tdbp = new(g) TDBDCL(this); // Catfunc should be 'C'
} else if (Zipped) {
#if defined(ZIP_SUPPORT)
if (Recfm == RECFM_VAR) {
if (mode == MODE_READ || mode == MODE_ANY || mode == MODE_ALTER) {
txfp = new(g) UNZFAM(this);
} else if (mode == MODE_INSERT) {
txfp = new(g) ZIPFAM(this);
} else {
strcpy(g->Message, "UPDATE/DELETE not supported for ZIP");
return NULL;
} // endif's mode
tdbp = new(g) TDBDOS(this, txfp);
} else {
if (mode == MODE_READ || mode == MODE_ANY || mode == MODE_ALTER) {
txfp = new(g) UZXFAM(this);
} else if (mode == MODE_INSERT) {
txfp = new(g) ZPXFAM(this);
} else {
strcpy(g->Message, "UPDATE/DELETE not supported for ZIP");
return NULL;
} // endif's mode
tdbp = new(g)TDBFIX(this, txfp);
} // endif Recfm
#else // !ZIP_SUPPORT
snprintf(g->Message, sizeof(g->Message), MSG(NO_FEAT_SUPPORT), "ZIP");
return NULL;
#endif // !ZIP_SUPPORT
} else if (Recfm != RECFM_VAR && Compressed < 2) {
if (Huge)
txfp = new(g) BGXFAM(this);
else if (map)
txfp = new(g) MPXFAM(this);
else if (Compressed) {
#if defined(GZ_SUPPORT)
txfp = new(g) GZXFAM(this);
#else // !GZ_SUPPORT
snprintf(g->Message, sizeof(g->Message), MSG(NO_FEAT_SUPPORT), "GZ");
return NULL;
#endif // !GZ_SUPPORT
} else
txfp = new(g) FIXFAM(this);
tdbp = new(g) TDBFIX(this, txfp);
} else {
if (Compressed) {
#if defined(GZ_SUPPORT)
if (Compressed == 1)
txfp = new(g) GZFAM(this);
else
txfp = new(g) ZLBFAM(this);
#else // !GZ_SUPPORT
snprintf(g->Message, sizeof(g->Message), MSG(NO_FEAT_SUPPORT), "GZ");
return NULL;
#endif // !GZ_SUPPORT
} else if (map)
txfp = new(g) MAPFAM(this);
else
txfp = new(g) DOSFAM(this);
// Txfp must be set even for not multiple tables because
// it is needed when calling Cardinality in GetBlockValues.
tdbp = new(g) TDBDOS(this, txfp);
} // endif Recfm
if (Multiple)
tdbp = new(g) TDBMUL(tdbp);
else
/*******************************************************************/
/* For block tables, get eventually saved optimization values. */
/*******************************************************************/
if (tdbp->GetBlockValues(g)) {
PushWarning(g, tdbp);
// return NULL; // causes a crash when deleting index
} else if (Recfm == RECFM_VAR || Compressed > 1) {
if (IsOptimized()) {
if (map) {
txfp = new(g) MBKFAM(this);
} else if (Compressed) {
#if defined(GZ_SUPPORT)
if (Compressed == 1)
txfp = new(g) ZBKFAM(this);
else {
txfp->SetBlkPos(To_Pos);
((PZLBFAM)txfp)->SetOptimized(To_Pos != NULL);
} // endelse
#else
snprintf(g->Message, sizeof(g->Message), MSG(NO_FEAT_SUPPORT), "GZ");
return NULL;
#endif
} else
txfp = new(g) BLKFAM(this);
((PTDBDOS)tdbp)->SetTxfp(txfp);
} // endif Optimized
} // endif Recfm
return tdbp;
} // end of GetTable
/* ------------------------ Class TDBDOS ----------------------------- */
/***********************************************************************/
/* Implementation of the TDBDOS class. This is the common class that */
/* contain all that is common between the TDBDOS and TDBMAP classes. */
/***********************************************************************/
TDBDOS::TDBDOS(PDOSDEF tdp, PTXF txfp) : TDBASE(tdp)
{
if ((Txfp = txfp))
Txfp->SetTdbp(this);
Lrecl = tdp->Lrecl;
AvgLen = tdp->AvgLen;
Ftype = tdp->Recfm;
To_Line = NULL;
//To_BlkIdx = NULL;
To_BlkFil = NULL;
SavFil = NULL;
//Xeval = 0;
Beval = 0;
Abort = false;
Indxd = false;
} // end of TDBDOS standard constructor
TDBDOS::TDBDOS(PGLOBAL g, PTDBDOS tdbp) : TDBASE(tdbp)
{
Txfp = (g) ? tdbp->Txfp->Duplicate(g) : tdbp->Txfp;
Lrecl = tdbp->Lrecl;
AvgLen = tdbp->AvgLen;
Ftype = tdbp->Ftype;
To_Line = tdbp->To_Line;
//To_BlkIdx = tdbp->To_BlkIdx;
To_BlkFil = tdbp->To_BlkFil;
SavFil = tdbp->SavFil;
//Xeval = tdbp->Xeval;
Beval = tdbp->Beval;
Abort = tdbp->Abort;
Indxd = tdbp->Indxd;
} // end of TDBDOS copy constructor
// Method
PTDB TDBDOS::Clone(PTABS t)
{
PTDB tp;
PDOSCOL cp1, cp2;
PGLOBAL g = t->G;
tp = new(g) TDBDOS(g, this);
for (cp1 = (PDOSCOL)Columns; cp1; cp1 = (PDOSCOL)cp1->GetNext()) {
cp2 = new(g) DOSCOL(cp1, tp); // Make a copy
NewPointer(t, cp1, cp2);
} // endfor cp1
return tp;
} // end of Clone
/***********************************************************************/
/* Allocate DOS column description block. */
/***********************************************************************/
PCOL TDBDOS::MakeCol(PGLOBAL g, PCOLDEF cdp, PCOL cprec, int n)
{
return new(g) DOSCOL(g, cdp, this, cprec, n);
} // end of MakeCol
/***********************************************************************/
/* Print debug information. */
/***********************************************************************/
void TDBDOS::PrintAM(FILE *f, char *m)
{
fprintf(f, "%s AM(%d): mode=%d\n", m, GetAmType(), Mode);
if (Txfp->To_File)
fprintf(f, "%s File: %s\n", m, Txfp->To_File);
} // end of PrintAM
/***********************************************************************/
/* Remake the indexes after the table was modified. */
/***********************************************************************/
int TDBDOS::ResetTableOpt(PGLOBAL g, bool dop, bool dox)
{
int prc = RC_OK, rc = RC_OK;
if (!GetFileLength(g)) {
// Void table, delete all opt and index files
PDOSDEF defp = (PDOSDEF)To_Def;
defp->RemoveOptValues(g);
return (defp->DeleteIndexFile(g, NULL)) ? RC_INFO : RC_OK;
} // endif GetFileLength
MaxSize = -1; // Size must be recalculated
Cardinal = -1; // as well as Cardinality
PTXF xp = Txfp;
To_Filter = NULL; // Disable filtering
//To_BlkIdx = NULL; // and index filtering
To_BlkFil = NULL; // and block filtering
// After the table was modified the indexes
// are invalid and we should mark them as such...
(void)((PDOSDEF)To_Def)->InvalidateIndex(g);
if (dop) {
Columns = NULL; // Not used anymore
if (Txfp->Blocked) {
// MakeBlockValues must be executed in non blocked mode
// except for ZLIB access method.
if (Txfp->GetAmType() == TYPE_AM_MAP) {
Txfp = new(g) MAPFAM((PDOSDEF)To_Def);
#if defined(GZ_SUPPORT)
} else if (Txfp->GetAmType() == TYPE_AM_GZ) {
Txfp = new(g) GZFAM((PDOSDEF)To_Def);
} else if (Txfp->GetAmType() == TYPE_AM_ZLIB) {
Txfp->Reset();
((PZLBFAM)Txfp)->SetOptimized(false);
#endif // GZ_SUPPORT
} else if (Txfp->GetAmType() == TYPE_AM_BLK)
Txfp = new(g) DOSFAM((PDOSDEF)To_Def);
Txfp->SetTdbp(this);
} else
Txfp->Reset();
Use = USE_READY; // So the table can be reopened
Mode = MODE_ANY; // Just to be clean
rc = MakeBlockValues(g); // Redo optimization
} // endif dop
if (dox && (rc == RC_OK || rc == RC_INFO)) {
// Remake eventual indexes
// if (Mode != MODE_UPDATE)
To_SetCols = NULL; // Positions are changed
Columns = NULL; // Not used anymore
Txfp->Reset(); // New start
Use = USE_READY; // So the table can be reopened
Mode = MODE_READ; // New mode
prc = rc;
if (PlgGetUser(g)->Check & CHK_OPT)
// We must remake all indexes.
rc = MakeIndex(g, NULL, false);
rc = (rc == RC_INFO) ? prc : rc;
} // endif dox
return rc;
} // end of ResetTableOpt
/***********************************************************************/
/* Calculate the block sizes so block I/O can be used and also the */
/* Min/Max values for clustered/sorted table columns. */
/***********************************************************************/
int TDBDOS::MakeBlockValues(PGLOBAL g)
{
int i, lg, nrec, rc, n = 0;
int curnum, curblk, block, savndv, savnbm;
void *savmin, *savmax;
bool blocked, xdb2 = false;
//POOLHEADER save;
PCOLDEF cdp;
PDOSDEF defp = (PDOSDEF)To_Def;
PDOSCOL colp = NULL;
PDBUSER dup = PlgGetUser(g);
PCATLG cat = defp->GetCat();
//void *memp = cat->GetDescp();
if ((nrec = defp->GetElemt()) < 2) {
if (!To_Def->Partitioned()) {
// This may be wrong to do in some cases
strcpy(g->Message, MSG(TABLE_NOT_OPT));
return RC_INFO; // Not to be optimized
} else
return RC_OK;
} else if (GetMaxSize(g) == 0 || !(dup->Check & CHK_OPT)) {
// Suppress the opt file firstly if the table is void,
// secondly when it was modified with OPTIMIZATION unchecked
// because it is no more valid.
defp->RemoveOptValues(g); // Erase opt file
return RC_OK; // void table
} else if (MaxSize < 0)
return RC_FX;
defp->SetOptimized(0);
// Estimate the number of needed blocks
if ((block = (int)((MaxSize + (int)nrec - 1) / (int)nrec)) < 2) {
// This may be wrong to do in some cases
defp->RemoveOptValues(g);
strcpy(g->Message, MSG(TABLE_NOT_OPT));
return RC_INFO; // Not to be optimized
} // endif block
// We have to use local variables because Txfp->CurBlk is set
// to Rows+1 by unblocked variable length table access methods.
curblk = -1;
curnum = nrec - 1;
//last = 0;
Txfp->Block = block; // This is useful mainly for
Txfp->CurBlk = curblk; // blocked tables (ZLBFAM), for
Txfp->CurNum = curnum; // others it is just to be clean.
/*********************************************************************/
/* Allocate the array of block starting positions. */
/*********************************************************************/
//if (memp)
// save = *(PPOOLHEADER)memp;
Txfp->BlkPos = (int*)PlugSubAlloc(g, NULL, (block + 1) * sizeof(int));
/*********************************************************************/
/* Allocate the blocks for clustered columns. */
/*********************************************************************/
blocked = Txfp->Blocked; // Save
Txfp->Blocked = true; // So column block can be allocated
for (cdp = defp->GetCols(), i = 1; cdp; cdp = cdp->GetNext(), i++)
if (cdp->GetOpt()) {
lg = cdp->GetClen();
if (cdp->GetFreq() && cdp->GetFreq() <= dup->Maxbmp) {
cdp->SetXdb2(true);
savndv = cdp->GetNdv();
cdp->SetNdv(0); // Reset Dval number of values
xdb2 = true;
savmax = cdp->GetDval();
cdp->SetDval(PlugSubAlloc(g, NULL, cdp->GetFreq() * lg));
savnbm = cdp->GetNbm();
cdp->SetNbm(0); // Prevent Bmap allocation
// savmin = cdp->GetBmap();
// cdp->SetBmap(PlugSubAlloc(g, NULL, block * sizeof(int)));
if (trace(1))
htrc("Dval(%p) Bmap(%p) col(%d) %s Block=%d lg=%d\n",
cdp->GetDval(), cdp->GetBmap(), i, cdp->GetName(), block, lg);
// colp will be initialized with proper Dval VALBLK
colp = (PDOSCOL)MakeCol(g, cdp, colp, i);
colp->InitValue(g); // Allocate column value buffer
cdp->SetNbm(savnbm);
// cdp->SetBmap(savmin); // Can be reused if the new size
cdp->SetDval(savmax); // is not greater than this one.
cdp->SetNdv(savndv);
} else {
cdp->SetXdb2(false); // Maxbmp may have been reset
savmin = cdp->GetMin();
savmax = cdp->GetMax();
cdp->SetMin(PlugSubAlloc(g, NULL, block * lg));
cdp->SetMax(PlugSubAlloc(g, NULL, block * lg));
// Valgrind complains if there are uninitialised bytes
// after the null character ending
if (IsTypeChar(cdp->GetType())) {
memset(cdp->GetMin(), 0, block * lg);
memset(cdp->GetMax(), 0, block * lg);
} // endif Type
if (trace(1))
htrc("min(%p) max(%p) col(%d) %s Block=%d lg=%d\n",
cdp->GetMin(), cdp->GetMax(), i, cdp->GetName(), block, lg);
// colp will be initialized with proper opt VALBLK's
colp = (PDOSCOL)MakeCol(g, cdp, colp, i);
colp->InitValue(g); // Allocate column value buffer
cdp->SetMin(savmin); // Can be reused if the number
cdp->SetMax(savmax); // of blocks does not change.
} // endif Freq
} // endif Clustered
// No optimised columns. Still useful for blocked variable tables.
if (!colp && defp->Recfm != RECFM_VAR) {
strcpy(g->Message, "No optimised columns");
return RC_INFO;
} // endif colp
Txfp->Blocked = blocked;
/*********************************************************************/
/* Now do calculate the optimization values. */
/*********************************************************************/
Mode = MODE_READ;
if (OpenDB(g))
return RC_FX;
if (xdb2) {
/*********************************************************************/
/* Retrieve the distinct values of XDB2 columns. */
/*********************************************************************/
if (GetDistinctColumnValues(g, nrec))
return RC_FX;
OpenDB(g); // Rewind the table file
} // endif xdb2
#if defined(PROG_INFO)
/*********************************************************************/
/* Initialize progress information */
/*********************************************************************/
char *p = (char *)PlugSubAlloc(g, NULL, 24 + strlen(Name));
dup->Step = strcat(strcpy(p, MSG(OPTIMIZING)), Name);
dup->ProgMax = GetProgMax(g);
dup->ProgCur = 0;
#endif // SOCKET_MODE || THREAD
/*********************************************************************/
/* Make block starting pos and min/max values of cluster columns. */
/*********************************************************************/
while ((rc = ReadDB(g)) == RC_OK) {
if (blocked) {
// A blocked FAM class handles CurNum and CurBlk (ZLBFAM)
if (!Txfp->CurNum)
Txfp->BlkPos[Txfp->CurBlk] = Txfp->GetPos();
} else {
if (++curnum >= nrec) {
if (++curblk >= block) {
strcpy(g->Message, MSG(BAD_BLK_ESTIM));
goto err;
} else
curnum = 0;
// Get block starting position
Txfp->BlkPos[curblk] = Txfp->GetPos();
} // endif CurNum
// last = curnum + 1; // curnum is zero based
Txfp->CurBlk = curblk; // Used in COLDOS::SetMinMax
Txfp->CurNum = curnum; // Used in COLDOS::SetMinMax
} // endif blocked
/*******************************************************************/
/* Now calculate the min and max values for the cluster columns. */
/*******************************************************************/
for (colp = (PDOSCOL)Columns; colp; colp = (PDOSCOL)colp->GetNext())
if (colp->Clustered == 2) {
if (colp->SetBitMap(g))
goto err;
} else
if (colp->SetMinMax(g))
goto err; // Currently: column is not sorted
#if defined(PROG_INFO)
if (!dup->Step) {
strcpy(g->Message, MSG(OPT_CANCELLED));
goto err;
} else
dup->ProgCur = GetProgCur();
#endif // PROG_INFO
n++; // Used to calculate block and last
} // endwhile
if (rc == RC_EF) {
Txfp->Nrec = nrec;
#if 0 // No good because Curblk and CurNum after EOF are different
// depending on whether the file is mapped or not mapped.
if (blocked) {
// Txfp->Block = Txfp->CurBlk + 1;
Txfp->Last = (Txfp->CurNum) ? Txfp->CurNum : nrec;
// Txfp->Last = (Txfp->CurNum) ? Txfp->CurNum + 1 : nrec;
Txfp->Block = Txfp->CurBlk + (Txfp->Last == nrec ? 0 : 1);
} else {
Txfp->Block = curblk + 1;
Txfp->Last = last;
} // endif blocked
#endif // 0
// New values of Block and Last
Txfp->Block = (n + nrec - 1) / nrec;
Txfp->Last = (n % nrec) ? (n % nrec) : nrec;
// This is needed to be able to calculate the last block size
Txfp->BlkPos[Txfp->Block] = Txfp->GetNextPos();
} else
goto err;
/*********************************************************************/
/* Save the optimization values for this table. */
/*********************************************************************/
if (!SaveBlockValues(g)) {
defp->Block = Txfp->Block;
defp->Last = Txfp->Last;
CloseDB(g);
defp->SetIntCatInfo("Blocks", Txfp->Block);
defp->SetIntCatInfo("Last", Txfp->Last);
return RC_OK;
} // endif SaveBlockValues
err:
// Restore Desc memory suballocation
//if (memp)
// *(PPOOLHEADER)memp = save;
defp->RemoveOptValues(g);
CloseDB(g);
return RC_FX;
} // end of MakeBlockValues
/***********************************************************************/
/* Save the block and Min/Max values for this table. */
/* The problem here is to avoid name duplication, because more than */
/* one data file can have the same name (but different types) and/or */
/* the same data file can be used with different block sizes. This is */
/* why we use Ofn that defaults to the file name but can be set to a */
/* different name if necessary. */
/***********************************************************************/
bool TDBDOS::SaveBlockValues(PGLOBAL g)
{
char filename[_MAX_PATH];
int lg, n[NZ + 2];
size_t nbk, ndv, nbm, block = Txfp->Block;
bool rc = false;
FILE *opfile;
PDOSCOL colp;
PDOSDEF defp = (PDOSDEF)To_Def;
if (defp->GetOptFileName(g, filename))
return true;
if (!(opfile = fopen(filename, "wb"))) {
snprintf(g->Message, sizeof(g->Message), MSG(OPEN_MODE_ERROR),
"wb", (int)errno, filename);
strcat(strcat(g->Message, ": "), strerror(errno));
if (trace(1))
htrc("%s\n", g->Message);
return true;
} // endif opfile
memset(n, 0, sizeof(n)); // To avoid valgrind warning
if (Ftype == RECFM_VAR || defp->Compressed == 2) {
/*******************************************************************/
/* Write block starting positions into the opt file. */
/*******************************************************************/
block++;
lg = sizeof(int);
n[0] = Txfp->Last; n[1] = lg; n[2] = Txfp->Nrec; n[3] = Txfp->Block;
if (fwrite(n, sizeof(int), NZ, opfile) != NZ) {
snprintf(g->Message, sizeof(g->Message), MSG(OPT_HEAD_WR_ERR), strerror(errno));
rc = true;
} // endif size
if (fwrite(Txfp->BlkPos, lg, block, opfile) != block) {
snprintf(g->Message, sizeof(g->Message), MSG(OPTBLK_WR_ERR), strerror(errno));
rc = true;
} // endif size
block--; // = Txfp->Block;
} // endif Ftype
/*********************************************************************/
/* Write the Min/Max values into the opt file. */
/*********************************************************************/
for (colp = (PDOSCOL)Columns; colp; colp = (PDOSCOL)colp->Next) {
lg = colp->Value->GetClen();
// Now start the writing process
if (colp->Clustered == 2) {
// New XDB2 block optimization. Will be recognized when reading
// because the column index is negated.
ndv = colp->Ndv; nbm = colp->Nbm;
nbk = nbm * block;
n[0] = -colp->Index; n[1] = lg; n[2] = Txfp->Nrec; n[3] = block;
n[4] = ndv; n[5] = nbm;
if (fwrite(n, sizeof(int), NZ + 2, opfile) != NZ + 2) {
snprintf(g->Message, sizeof(g->Message), MSG(OPT_HEAD_WR_ERR), strerror(errno));
rc = true;
} // endif size
if (fwrite(colp->Dval->GetValPointer(), lg, ndv, opfile) != ndv) {
snprintf(g->Message, sizeof(g->Message), MSG(OPT_DVAL_WR_ERR), strerror(errno));
rc = true;
} // endif size
if (fwrite(colp->Bmap->GetValPointer(), sizeof(int), nbk, opfile) != nbk) {
snprintf(g->Message, sizeof(g->Message), MSG(OPT_BMAP_WR_ERR), strerror(errno));
rc = true;
} // endif size
} else {
n[0] = colp->Index; n[1] = lg; n[2] = Txfp->Nrec; n[3] = block;
if (fwrite(n, sizeof(int), NZ, opfile) != NZ) {
snprintf(g->Message, sizeof(g->Message), MSG(OPT_HEAD_WR_ERR), strerror(errno));
rc = true;
} // endif size
if (fwrite(colp->Min->GetValPointer(), lg, block, opfile) != block) {
snprintf(g->Message, sizeof(g->Message), MSG(OPT_MIN_WR_ERR), strerror(errno));
rc = true;
} // endif size
if (fwrite(colp->Max->GetValPointer(), lg, block, opfile) != block) {
snprintf(g->Message, sizeof(g->Message), MSG(OPT_MAX_WR_ERR), strerror(errno));
rc = true;
} // endif size
} // endif Clustered
} // endfor colp
fclose(opfile);
return rc;
} // end of SaveBlockValues
/***********************************************************************/
/* Read the Min/Max values for this table. */
/* The problem here is to avoid name duplication, because more than */
/* one data file can have the same name (but different types) and/or */
/* the same data file can be used with different block sizes. This is */
/* why we use Ofn that defaults to the file name but can be set to a */
/* different name if necessary. */
/***********************************************************************/
bool TDBDOS::GetBlockValues(PGLOBAL g)
{
char filename[_MAX_PATH];
int i, lg, n[NZ];
int nrec, block = 0, last = 0, allocblk = 0;
int len;
bool newblk = false;
size_t ndv, nbm, nbk, blk;
FILE *opfile;
PCOLDEF cdp;
PDOSDEF defp = (PDOSDEF)To_Def;
PCATLG cat = defp->GetCat();
PDBUSER dup = PlgGetUser(g);
#if 0
if (Mode == MODE_INSERT && Txfp->GetAmType() == TYPE_AM_DOS)
return false;
#endif // _WIN32
if (defp->Optimized || !(dup->Check & CHK_OPT))
return false; // Already done or to be redone
if (Ftype == RECFM_VAR || defp->Compressed == 2) {
/*******************************************************************/
/* Variable length file that can be read by block. */
/*******************************************************************/
nrec = (defp->GetElemt()) ? defp->GetElemt() : 1;
if (nrec > 1) {
// The table can be declared optimized if it is void.
// This is useful to handle Insert in optimized mode.
char filename[_MAX_PATH];
int h;
int flen = -1;
PlugSetPath(filename, defp->Fn, GetPath());
h = open(filename, O_RDONLY);
flen = (h == -1 && errno == ENOENT) ? 0 : _filelength(h);
if (h != -1)
close(h);
if (!flen) {
defp->SetOptimized(1);
return false;
} // endif flen
} else
return false; // Not optimisable
cdp = defp->GetCols();
i = 1;
} else {
/*******************************************************************/
/* Fixed length file. Opt file exists only for clustered columns. */
/*******************************************************************/
// Check for existence of clustered columns
for (cdp = defp->GetCols(), i = 1; cdp; cdp = cdp->GetNext(), i++)
if (cdp->GetOpt())
break;
if (!cdp)
return false; // No optimization needed
if ((len = Cardinality(g)) < 0)
return true; // Table error
else if (!len)
return false; // File does not exist yet
block = Txfp->Block; // Was set in Cardinality
nrec = Txfp->Nrec;
} // endif Ftype
if (defp->GetOptFileName(g, filename))
return true;
if (!(opfile = fopen(filename, "rb")))
return false; // No saved values
if (Ftype == RECFM_VAR || defp->Compressed == 2) {
/*******************************************************************/
/* Read block starting positions from the opt file. */
/*******************************************************************/
lg = sizeof(int);
if (fread(n, sizeof(int), NZ, opfile) != NZ) {
snprintf(g->Message, sizeof(g->Message), MSG(OPT_HEAD_RD_ERR), strerror(errno));
goto err;
} // endif size
if (n[1] != lg || n[2] != nrec) {
snprintf(g->Message, sizeof(g->Message), MSG(OPT_NOT_MATCH), filename);
goto err;
} // endif
last = n[0];
block = n[3];
blk = block + 1;
defp->To_Pos = (int*)PlugSubAlloc(g, NULL, blk * lg);
if (fread(defp->To_Pos, lg, blk, opfile) != blk) {
snprintf(g->Message, sizeof(g->Message), MSG(OPTBLK_RD_ERR), strerror(errno));
goto err;
} // endif size
} // endif Ftype
/*********************************************************************/
/* Read the Min/Max values from the opt file. */
/*********************************************************************/
for (; cdp; cdp = cdp->GetNext(), i++)
if (cdp->GetOpt()) {
lg = cdp->GetClen();
blk = block;
// Now start the reading process.
if (fread(n, sizeof(int), NZ, opfile) != NZ) {
snprintf(g->Message, sizeof(g->Message), MSG(OPT_HEAD_RD_ERR), strerror(errno));
goto err;
} // endif size
if (n[0] == -i) {
// Read the XDB2 opt values from the opt file
if (n[1] != lg || n[2] != nrec || n[3] != block) {
snprintf(g->Message, sizeof(g->Message), MSG(OPT_NOT_MATCH), filename);
goto err;
} // endif
if (fread(n, sizeof(int), 2, opfile) != 2) {
snprintf(g->Message, sizeof(g->Message), MSG(OPT_HEAD_RD_ERR), strerror(errno));
goto err;
} // endif fread
ndv = n[0]; nbm = n[1]; nbk = nbm * blk;
if (cdp->GetNdv() < (int)ndv || !cdp->GetDval())
cdp->SetDval(PlugSubAlloc(g, NULL, ndv * lg));
cdp->SetNdv((int)ndv);
if (fread(cdp->GetDval(), lg, ndv, opfile) != ndv) {
snprintf(g->Message, sizeof(g->Message), MSG(OPT_DVAL_RD_ERR), strerror(errno));
goto err;
} // endif size
if (newblk || cdp->GetNbm() < (int)nbm || !cdp->GetBmap())
cdp->SetBmap(PlugSubAlloc(g, NULL, nbk * sizeof(int)));
cdp->SetNbm((int)nbm);
if (fread(cdp->GetBmap(), sizeof(int), nbk, opfile) != nbk) {
snprintf(g->Message, sizeof(g->Message), MSG(OPT_BMAP_RD_ERR), strerror(errno));
goto err;
} // endif size
cdp->SetXdb2(true);
} else {
// Read the Min/Max values from the opt file
if (n[0] != i || n[1] != lg || n[2] != nrec || n[3] != block) {
snprintf(g->Message, sizeof(g->Message), MSG(OPT_NOT_MATCH), filename);
goto err;
} // endif
if (newblk || !cdp->GetMin())
cdp->SetMin(PlugSubAlloc(g, NULL, blk * lg));
if (fread(cdp->GetMin(), lg, blk, opfile) != blk) {
snprintf(g->Message, sizeof(g->Message), MSG(OPT_MIN_RD_ERR), strerror(errno));
goto err;
} // endif size
if (newblk || !cdp->GetMax())
cdp->SetMax(PlugSubAlloc(g, NULL, blk * lg));
if (fread(cdp->GetMax(), lg, blk, opfile) != blk) {
snprintf(g->Message, sizeof(g->Message), MSG(OPT_MAX_RD_ERR), strerror(errno));
goto err;
} // endif size
cdp->SetXdb2(false);
} // endif n[0] (XDB2)
} // endif Clustered
defp->SetBlock(block);
defp->Last = last; // For Cardinality
defp->SetAllocBlks(block);
defp->SetOptimized(1);
fclose(opfile);
MaxSize = -1; // Can be refined later
return false;
err:
defp->RemoveOptValues(g);
fclose(opfile);
// Ignore error if not in mode CHK_OPT
return (PlgGetUser(g)->Check & CHK_OPT) != 0;
} // end of GetBlockValues
/***********************************************************************/
/* This fonction is used while making XDB2 block optimization. */
/* It constructs for each elligible columns, the sorted list of the */
/* distinct values existing in the column. This function uses an */
/* algorithm that permit to get several sets of distinct values by */
/* reading the table only once, which cannot be done using a standard */
/* SQL query. */
/***********************************************************************/
bool TDBDOS::GetDistinctColumnValues(PGLOBAL g, int nrec)
{
char *p;
int rc, blk, n = 0;
PDOSCOL colp;
PDBUSER dup = PlgGetUser(g);
/*********************************************************************/
/* Initialize progress information */
/*********************************************************************/
p = (char *)PlugSubAlloc(g, NULL, 48 + strlen(Name));
dup->Step = strcat(strcpy(p, MSG(GET_DIST_VALS)), Name);
dup->ProgMax = GetProgMax(g);
dup->ProgCur = 0;
while ((rc = ReadDB(g)) == RC_OK) {
for (colp = (PDOSCOL)Columns; colp; colp = (PDOSCOL)colp->Next)
if (colp->Clustered == 2)
if (colp->AddDistinctValue(g))
return true; // Too many distinct values
#if defined(SOCKET_MODE)
if (SendProgress(dup)) {
strcpy(g->Message, MSG(OPT_CANCELLED));
return true;
} else
#elif defined(THREAD)
if (!dup->Step) {
strcpy(g->Message, MSG(OPT_CANCELLED));
return true;
} else
#endif // THREAD
dup->ProgCur = GetProgCur();
n++;
} // endwhile
if (rc != RC_EF)
return true;
// Reset the number of table blocks
//nrec = ((PDOSDEF)To_Def)->GetElemt(); (or default value)
blk = (n + nrec - 1) / nrec;
Txfp->Block = blk; // Useful mainly for ZLBFAM ???
// Set Nbm, Bmap for XDB2 columns
for (colp = (PDOSCOL)Columns; colp; colp = (PDOSCOL)colp->Next)
if (colp->Clustered == 2) {
// colp->Cdp->SetNdv(colp->Ndv);
colp->Nbm = (colp->Ndv + MAXBMP - 1) / MAXBMP;
colp->Bmap = AllocValBlock(g, NULL, TYPE_INT, colp->Nbm * blk);
} // endif Clustered
return false;
} // end of GetDistinctColumnValues
/***********************************************************************/
/* Analyze the filter and construct the Block Evaluation Filter. */
/* This is possible when a filter contains predicates implying a */
/* column marked as "clustered" or "sorted" matched to a constant */
/* argument. It is then possible by comparison against the smallest */
/* and largest column values in each block to determine whether the */
/* filter condition will be always true or always false for the block.*/
/***********************************************************************/
PBF TDBDOS::InitBlockFilter(PGLOBAL g, PFIL filp)
{
bool blk = Txfp->Blocked;
if (To_BlkFil)
return To_BlkFil; // Already done
else if (!filp)
return NULL;
else if (blk) {
if (Txfp->GetAmType() == TYPE_AM_DBF)
/*****************************************************************/
/* If RowID is used in this query, block optimization cannot be */
/* used because currently the file must be read sequentially. */
/*****************************************************************/
for (PCOL cp = Columns; cp; cp = cp->GetNext())
if (cp->GetAmType() == TYPE_AM_ROWID && !((RIDBLK*)cp)->GetRnm())
return NULL;
} // endif blk
int i, op = filp->GetOpc(), opm = filp->GetOpm(), n = 0;
bool cnv[2];
PCOL colp;
PXOB arg[2] = {NULL,NULL};
PBF *fp = NULL, bfp = NULL;
switch (op) {
case OP_EQ:
case OP_NE:
case OP_GT:
case OP_GE:
case OP_LT:
case OP_LE:
if (! opm) {
for (i = 0; i < 2; i++) {
arg[i] = filp->Arg(i);
cnv[i] = filp->Conv(i);
} // endfor i
bfp = CheckBlockFilari(g, arg, op, cnv);
break;
} // endif !opm
// if opm, pass thru
// fall through
case OP_IN:
if (filp->GetArgType(0) == TYPE_COLBLK &&
filp->GetArgType(1) == TYPE_ARRAY) {
arg[0] = filp->Arg(0);
arg[1] = filp->Arg(1);
colp = (PCOL)arg[0];
if (colp->GetTo_Tdb() == this) {
// Block evaluation is possible for...
if (colp->GetAmType() == TYPE_AM_ROWID) {
// Special column ROWID and constant array, but
// currently we don't know how to retrieve a RowID
// from a DBF table that is not sequentially read.
// if (Txfp->GetAmType() != TYPE_AM_DBF ||
// ((RIDBLK*)arg[0])->GetRnm())
bfp = new(g) BLKSPCIN(g, this, op, opm, arg, Txfp->Nrec);
} else if (blk && Txfp->Nrec > 1 && colp->IsClustered())
// Clustered column and constant array
if (colp->GetClustered() == 2)
bfp = new(g) BLKFILIN2(g, this, op, opm, arg);
else
bfp = new(g) BLKFILIN(g, this, op, opm, arg);
} // endif this
#if 0
} else if (filp->GetArgType(0) == TYPE_SCALF &&
filp->GetArgType(1) == TYPE_ARRAY) {
arg[0] = filp->Arg(0);
arg[1] = filp->Arg(1);
if (((PSCALF)arg[0])->GetOp() == OP_ROW &&
arg[1]->GetResultType() == TYPE_LIST) {
PARRAY par = (PARRAY)arg[1];
LSTVAL *vlp = (LSTVAL*)par->GetValue();
((SFROW*)arg[0])->GetParms(n);
if (n != vlp->GetN())
return NULL;
else
n = par->GetNval();
arg[1] = new(g) CONSTANT(vlp);
fp = (PBF*)PlugSubAlloc(g, NULL, n * sizeof(PBF));
cnv[0] = cnv[1] = false;
if (op == OP_IN)
op = OP_EQ;
for (i = 0; i < n; i++) {
par->GetNthValue(vlp, i);
if (!(fp[i] = CheckBlockFilari(g, arg, op, cnv)))
return NULL;
} // endfor i
bfp = new(g) BLKFILLOG(this, (opm == 2 ? OP_AND : OP_OR), fp, n);
} // endif ROW
#endif // 0
} // endif Type
break;
case OP_AND:
case OP_OR:
fp = (PBF*)PlugSubAlloc(g, NULL, 2 * sizeof(PBF));
fp[0] = InitBlockFilter(g, (PFIL)(filp->Arg(0)));
fp[1] = InitBlockFilter(g, (PFIL)(filp->Arg(1)));
if (fp[0] || fp[1])
bfp = new(g) BLKFILLOG(this, op, fp, 2);
break;
case OP_NOT:
fp = (PBF*)PlugSubAlloc(g, NULL, sizeof(PBF));
if ((*fp = InitBlockFilter(g, (PFIL)(filp->Arg(0)))))
bfp = new(g) BLKFILLOG(this, op, fp, 1);
break;
case OP_LIKE:
default:
break;
} // endswitch op
return bfp;
} // end of InitBlockFilter
/***********************************************************************/
/* Analyze the passed arguments and construct the Block Filter. */
/***********************************************************************/
PBF TDBDOS::CheckBlockFilari(PGLOBAL g, PXOB *arg, int op, bool *cnv)
{
//int i, n1, n2, ctype = TYPE_ERROR, n = 0, type[2] = {0,0};
//bool conv = false, xdb2 = false, ok = false, b[2];
//PXOB *xarg1, *xarg2 = NULL, xp[2];
int i, n = 0, type[2] = {0,0};
bool conv = false, xdb2 = false, ok = false;
PXOB *xarg2 = NULL, xp[2];
PCOL colp;
//LSTVAL *vlp = NULL;
//SFROW *sfr[2];
PBF *fp = NULL, bfp = NULL;
for (i = 0; i < 2; i++) {
switch (arg[i]->GetType()) {
case TYPE_CONST:
type[i] = 1;
// ctype = arg[i]->GetResultType();
break;
case TYPE_COLBLK:
conv = cnv[i];
colp = (PCOL)arg[i];
if (colp->GetTo_Tdb() == this) {
if (colp->GetAmType() == TYPE_AM_ROWID) {
// Currently we don't know how to retrieve a RowID
// from a DBF table that is not sequentially read.
// if (Txfp->GetAmType() != TYPE_AM_DBF ||
// ((RIDBLK*)arg[i])->GetRnm())
type[i] = 5;
} else if (Txfp->Blocked && Txfp->Nrec > 1 &&
colp->IsClustered()) {
type[i] = 2;
xdb2 = colp->GetClustered() == 2;
} // endif Clustered
} else if (colp->GetColUse(U_CORREL)) {
// This is a column pointing to the outer query of a
// correlated subquery, it has a constant value during
// each execution of the subquery.
type[i] = 1;
// ctype = arg[i]->GetResultType();
} // endif this
break;
// case TYPE_SCALF:
// if (((PSCALF)arg[i])->GetOp() == OP_ROW) {
// sfr[i] = (SFROW*)arg[i];
// type[i] = 7;
// } // endif Op
// break;
default:
break;
} // endswitch ArgType
if (!type[i])
break;
n += type[i];
} // endfor i
if (n == 3 || n == 6) {
if (conv) {
// The constant has not the good type and will not match
// the block min/max values. Warn and abort.
snprintf(g->Message, sizeof(g->Message), "Block opt: %s", MSG(VALTYPE_NOMATCH));
PushWarning(g, this);
return NULL;
} // endif Conv
if (type[0] == 1) {
// Make it always as Column-op-Value
*xp = arg[0];
arg[0] = arg[1];
arg[1] = *xp;
switch (op) {
case OP_GT: op = OP_LT; break;
case OP_GE: op = OP_LE; break;
case OP_LT: op = OP_GT; break;
case OP_LE: op = OP_GE; break;
} // endswitch op
} // endif
#if defined(_DEBUG)
// assert(arg[0]->GetResultType() == ctype);
#endif
if (n == 3) {
if (xdb2) {
if (((PDOSCOL)arg[0])->GetNbm() == 1)
bfp = new(g) BLKFILAR2(g, this, op, arg);
else // Multiple bitmap made of several ULONG's
bfp = new(g) BLKFILMR2(g, this, op, arg);
} else
bfp = new(g) BLKFILARI(g, this, op, arg);
} else // n = 6
bfp = new(g) BLKSPCARI(this, op, arg, Txfp->Nrec);
#if 0
} else if (n == 8 || n == 14) {
if (n == 8 && ctype != TYPE_LIST) {
// Should never happen
strcpy(g->Message, "Block opt: bad constant");
throw 99;
} // endif Conv
if (type[0] == 1) {
// Make it always as Column-op-Value
sfr[0] = sfr[1];
arg[1] = arg[0];
switch (op) {
case OP_GT: op = OP_LT; break;
case OP_GE: op = OP_LE; break;
case OP_LT: op = OP_GT; break;
case OP_LE: op = OP_GE; break;
} // endswitch op
} // endif
xarg1 = sfr[0]->GetParms(n1);
if (n == 8) {
vlp = (LSTVAL*)arg[1]->GetValue();
n2 = vlp->GetN();
xp[1] = new(g) CONSTANT((PVAL)NULL);
} else
xarg2 = sfr[1]->GetParms(n2);
if (n1 != n2)
return NULL; // Should we flag an error ?
fp = (PBF*)PlugSubAlloc(g, NULL, n1 * sizeof(PBF));
for (i = 0; i < n1; i++) {
xp[0] = xarg1[i];
if (n == 8)
((CONSTANT*)xp[1])->SetValue(vlp->GetSubVal(i));
else
xp[1] = xarg2[i];
b[0] = b[1] = (xp[0]->GetResultType() != xp[1]->GetResultType());
ok |= ((fp[i] = CheckBlockFilari(g, xp, op, b)) != NULL);
} // endfor i
if (ok)
bfp = new(g) BLKFILLOG(this, OP_AND, fp, n1);
#endif // 0
} // endif n
return bfp;
} // end of CheckBlockFilari
/***********************************************************************/
/* ResetBlkFil: reset the block filter and restore filtering, or make */
/* the block filter if To_Filter was not set when opening the table. */
/***********************************************************************/
void TDBDOS::ResetBlockFilter(PGLOBAL g)
{
if (!To_BlkFil) {
if (To_Filter)
if ((To_BlkFil = InitBlockFilter(g, To_Filter))) {
htrc("BlkFil=%p\n", To_BlkFil);
MaxSize = -1; // To be recalculated
} // endif To_BlkFil
return;
} // endif To_BlkFil
To_BlkFil->Reset(g);
if (SavFil && !To_Filter) {
// Restore filter if it was disabled by optimization
To_Filter = SavFil;
SavFil = NULL;
} // endif
Beval = 0;
} // end of ResetBlockFilter
/***********************************************************************/
/* Block optimization: evaluate the block index filter against */
/* the min and max values of this block and return: */
/* RC_OK: if some records in the block can meet filter criteria. */
/* RC_NF: if no record in the block can meet filter criteria. */
/* RC_EF: if no record in the remaining file can meet filter criteria.*/
/* In addition, temporarily supress filtering if all the records in */
/* the block meet filter criteria. */
/***********************************************************************/
int TDBDOS::TestBlock(PGLOBAL g)
{
int rc = RC_OK;
if (To_BlkFil && Beval != 2) {
// Check for block filtering evaluation
if (Beval == 1) {
// Filter was removed for last block, restore it
To_Filter = SavFil;
SavFil = NULL;
} // endif Beval
// Check for valid records in new block
switch (Beval = To_BlkFil->BlockEval(g)) {
case -2: // No more valid values in file
rc = RC_EF;
break;
case -1: // No valid values in block
rc = RC_NF;
break;
case 1: // All block values are valid
case 2: // All subsequent file values are Ok
// Before suppressing the filter for the block(s) it is
// necessary to reset the filtered columns to NOT_READ
// so their new values are retrieved by the SELECT list.
if (To_Filter) // Can be NULL when externally called (XDB)
To_Filter->Reset();
SavFil = To_Filter;
To_Filter = NULL; // So remove filter
} // endswitch Beval
if (trace(1))
htrc("BF Eval Beval=%d\n", Beval);
} // endif To_BlkFil
return rc;
} // end of TestBlock
/***********************************************************************/
/* Check whether we have to create/update permanent indexes. */
/***********************************************************************/
int TDBDOS::MakeIndex(PGLOBAL g, PIXDEF pxdf, bool add)
{
int k, n, rc = RC_OK;
bool fixed, doit, sep, b = (pxdf != NULL);
PCOL *keycols, colp;
PIXDEF xdp, sxp = NULL;
PKPDEF kdp;
PDOSDEF dfp;
//PCOLDEF cdp;
PXINDEX x;
PXLOAD pxp;
Mode = MODE_READ;
Use = USE_READY;
dfp = (PDOSDEF)To_Def;
if (!Cardinality(g)) {
// Void table erase eventual index file(s)
(void)dfp->DeleteIndexFile(g, NULL);
return RC_OK;
} else
fixed = Ftype != RECFM_VAR;
// Are we are called from CreateTable or CreateIndex?
if (pxdf) {
if (!add && dfp->GetIndx()) {
strcpy(g->Message, MSG(INDX_EXIST_YET));
return RC_FX;
} // endif To_Indx
if (add && dfp->GetIndx()) {
for (sxp = dfp->GetIndx(); sxp; sxp = sxp->GetNext())
if (!stricmp(sxp->GetName(), pxdf->GetName())) {
snprintf(g->Message, sizeof(g->Message), MSG(INDEX_YET_ON), pxdf->GetName(), Name);
return RC_FX;
} else if (!sxp->GetNext())
break;
sxp->SetNext(pxdf);
// first = false;
} else
dfp->SetIndx(pxdf);
// pxdf->SetDef(dfp);
} else if (!(pxdf = dfp->GetIndx()))
return RC_INFO; // No index to make
try {
// Allocate all columns that will be used by indexes.
// This must be done before opening the table so specific
// column initialization can be done (in particular by TDBVCT)
for (n = 0, xdp = pxdf; xdp; xdp = xdp->GetNext())
for (kdp = xdp->GetToKeyParts(); kdp; kdp = kdp->GetNext()) {
if (!(colp = ColDB(g, kdp->GetName(), 0))) {
snprintf(g->Message, sizeof(g->Message), MSG(INDX_COL_NOTIN), kdp->GetName(), Name);
goto err;
} else if (colp->GetResultType() == TYPE_DECIM) {
snprintf(g->Message, sizeof(g->Message), "Decimal columns are not indexable yet");
goto err;
} // endif Type
colp->InitValue(g);
n = MY_MAX(n, xdp->GetNparts());
} // endfor kdp
keycols = (PCOL*)PlugSubAlloc(g, NULL, n * sizeof(PCOL));
sep = dfp->GetBoolCatInfo("SepIndex", false);
/*********************************************************************/
/* Construct and save the defined indexes. */
/*********************************************************************/
for (xdp = pxdf; xdp; xdp = xdp->GetNext())
if (!OpenDB(g)) {
if (xdp->IsAuto() && fixed)
// Auto increment key and fixed file: use an XXROW index
continue; // XXROW index doesn't need to be made
// On Update, redo only indexes that are modified
doit = !To_SetCols;
n = 0;
if (sxp)
xdp->SetID(sxp->GetID() + 1);
for (kdp = xdp->GetToKeyParts(); kdp; kdp = kdp->GetNext()) {
// Check whether this column was updated
for (colp = To_SetCols; !doit && colp; colp = colp->GetNext())
if (!stricmp(kdp->GetName(), colp->GetName()))
doit = true;
keycols[n++] = ColDB(g, kdp->GetName(), 0);
} // endfor kdp
// If no indexed columns were updated, don't remake the index
// if indexes are in separate files.
if (!doit && sep)
continue;
k = xdp->GetNparts();
// Make the index and save it
if (dfp->Huge)
pxp = new(g) XHUGE;
else
pxp = new(g) XFILE;
if (k == 1) // Simple index
x = new(g) XINDXS(this, xdp, pxp, keycols);
else // Multi-Column index
x = new(g) XINDEX(this, xdp, pxp, keycols);
if (!x->Make(g, sxp)) {
// Retreive define values from the index
xdp->SetMaxSame(x->GetMaxSame());
// xdp->SetSize(x->GetSize());
// store KXYCOL Mxs in KPARTDEF Mxsame
xdp->SetMxsame(x);
#if defined(TRACE)
printf("Make done...\n");
#endif // TRACE
// if (x->GetSize() > 0)
sxp = xdp;
xdp->SetInvalid(false);
} else
goto err;
} else
return RC_INFO; // Error or Physical table does not exist
} catch (int n) {
if (trace(1))
htrc("Exception %d: %s\n", n, g->Message);
rc = RC_FX;
} catch (const char *msg) {
strcpy(g->Message, msg);
rc = RC_FX;
} // end catch
if (Use == USE_OPEN)
CloseDB(g);
return rc;
err:
if (sxp)
sxp->SetNext(NULL);
else
dfp->SetIndx(NULL);
return RC_FX;
} // end of MakeIndex
/***********************************************************************/
/* Make a dynamic index. */
/***********************************************************************/
bool TDBDOS::InitialyzeIndex(PGLOBAL g, volatile PIXDEF xdp, bool sorted)
{
int k;
volatile bool dynamic;
bool brc;
PCOL colp;
PCOLDEF cdp;
PVAL valp;
PXLOAD pxp;
volatile PKXBASE kxp;
PKPDEF kdp;
if (!xdp && !(xdp = To_Xdp)) {
strcpy(g->Message, "NULL dynamic index");
return true;
} else
dynamic = To_Filter && xdp->IsUnique() && xdp->IsDynamic();
// dynamic = To_Filter && xdp->IsDynamic(); NIY
// Allocate the key columns definition block
Knum = xdp->GetNparts();
To_Key_Col = (PCOL*)PlugSubAlloc(g, NULL, Knum * sizeof(PCOL));
// Get the key column description list
for (k = 0, kdp = xdp->GetToKeyParts(); kdp; kdp = kdp->GetNext())
if (!(colp = ColDB(g, kdp->GetName(), 0)) || colp->InitValue(g)) {
snprintf(g->Message, sizeof(g->Message), "Wrong column %s", kdp->GetName());
return true;
} else
To_Key_Col[k++] = colp;
#if defined(_DEBUG)
if (k != Knum) {
snprintf(g->Message, sizeof(g->Message), "Key part number mismatch for %s",
xdp->GetName());
return 0;
} // endif k
#endif // _DEBUG
// Allocate the pseudo constants that will contain the key values
To_Link = (PXOB*)PlugSubAlloc(g, NULL, Knum * sizeof(PXOB));
for (k = 0, kdp = xdp->GetToKeyParts(); kdp; k++, kdp = kdp->GetNext()) {
if ((cdp = Key(k)->GetCdp()))
valp = AllocateValue(g, cdp->GetType(), cdp->GetLength());
else { // Special column ?
colp = Key(k);
valp = AllocateValue(g, colp->GetResultType(), colp->GetLength());
} // endif cdp
To_Link[k]= new(g) CONSTANT(valp);
} // endfor k
// Make the index on xdp
if (!xdp->IsAuto()) {
if (!dynamic) {
if (((PDOSDEF)To_Def)->Huge)
pxp = new(g) XHUGE;
else
pxp = new(g) XFILE;
} else
pxp = NULL;
if (Knum == 1) // Single index
kxp = new(g) XINDXS(this, xdp, pxp, To_Key_Col, To_Link);
else // Multi-Column index
kxp = new(g) XINDEX(this, xdp, pxp, To_Key_Col, To_Link);
} else // Column contains same values as ROWID
kxp = new(g) XXROW(this);
try {
if (dynamic) {
ResetBlockFilter(g);
kxp->SetDynamic(dynamic);
brc = kxp->Make(g, xdp);
} else
brc = kxp->Init(g);
if (!brc) {
if (Txfp->GetAmType() == TYPE_AM_BLK) {
// Cannot use indexing in DOS block mode
Txfp = new(g) DOSFAM((PBLKFAM)Txfp, (PDOSDEF)To_Def);
Txfp->AllocateBuffer(g);
To_BlkFil = NULL;
} // endif AmType
To_Kindex= kxp;
if (!(sorted && To_Kindex->IsSorted()) &&
((Mode == MODE_UPDATE && IsUsingTemp(g)) ||
(Mode == MODE_DELETE && Txfp->GetAmType() != TYPE_AM_DBF)))
Indxd = true;
} // endif brc
} catch (int n) {
if (trace(1))
htrc("Exception %d: %s\n", n, g->Message);
brc = true;
} catch (const char *msg) {
strcpy(g->Message, msg);
brc = true;
} // end catch
return brc;
} // end of InitialyzeIndex
/***********************************************************************/
/* DOS GetProgMax: get the max value for progress information. */
/***********************************************************************/
int TDBDOS::GetProgMax(PGLOBAL g)
{
return (To_Kindex) ? GetMaxSize(g) : GetFileLength(g);
} // end of GetProgMax
/***********************************************************************/
/* DOS GetProgCur: get the current value for progress information. */
/***********************************************************************/
int TDBDOS::GetProgCur(void)
{
return (To_Kindex) ? To_Kindex->GetCur_K() + 1 : GetRecpos();
} // end of GetProgCur
/***********************************************************************/
/* RowNumber: return the ordinal number of the current row. */
/***********************************************************************/
int TDBDOS::RowNumber(PGLOBAL g, bool)
{
if (To_Kindex) {
/*******************************************************************/
/* Don't know how to retrieve RowID from file address. */
/*******************************************************************/
snprintf(g->Message, sizeof(g->Message), MSG(NO_ROWID_FOR_AM),
GetAmName(g, Txfp->GetAmType()));
return 0;
} else
return Txfp->GetRowID();
} // end of RowNumber
/***********************************************************************/
/* DOS Cardinality: returns table cardinality in number of rows. */
/* This function can be called with a null argument to test the */
/* availability of Cardinality implementation (1 yes, 0 no). */
/***********************************************************************/
int TDBDOS::Cardinality(PGLOBAL g)
{
int n = Txfp->Cardinality(NULL);
if (!g)
return (Mode == MODE_ANY) ? 1 : n;
if (Cardinal < 0) {
if (!Txfp->Blocked && n == 0) {
// Info command, we try to return exact row number
PDOSDEF dfp = (PDOSDEF)To_Def;
PIXDEF xdp = dfp->To_Indx;
if (xdp && xdp->IsValid()) {
// Cardinality can be retreived from one index
PXLOAD pxp;
if (dfp->Huge)
pxp = new(g) XHUGE;
else
pxp = new(g) XFILE;
PXINDEX kxp = new(g) XINDEX(this, xdp, pxp, NULL, NULL);
if (!(kxp->GetAllSizes(g, Cardinal)))
return Cardinal;
} // endif Mode
if (Mode == MODE_ANY && ExactInfo()) {
// Using index impossible or failed, do it the hard way
Mode = MODE_READ;
To_Line = (char*)PlugSubAlloc(g, NULL, (size_t)Lrecl + 1);
if (Txfp->OpenTableFile(g))
return (Cardinal = Txfp->Cardinality(g));
for (Cardinal = 0; n != RC_EF;)
if (!(n = Txfp->ReadBuffer(g)))
Cardinal++;
Txfp->CloseTableFile(g, false);
Mode = MODE_ANY;
} else {
// Return the best estimate
int len = GetFileLength(g);
if (len >= 0) {
int rec;
if (trace(1))
htrc("Estimating lines len=%d ending=%d/n",
len, ((PDOSDEF)To_Def)->Ending);
/*************************************************************/
/* Estimate the number of lines in the table (if not known) */
/* by dividing the file length by the average record length. */
/*************************************************************/
rec = ((PDOSDEF)To_Def)->Ending;
if (AvgLen <= 0) // No given average estimate
rec += EstimatedLength();
else // An estimate was given for the average record length
rec += AvgLen;
Cardinal = (len + rec - 1) / rec;
if (trace(1))
htrc("avglen=%d MaxSize%d\n", rec, Cardinal);
} // endif len
} // endif Mode
} else
Cardinal = Txfp->Cardinality(g);
} // endif Cardinal
return Cardinal;
} // end of Cardinality
/***********************************************************************/
/* DOS GetMaxSize: returns file size estimate in number of lines. */
/* This function covers variable record length files. */
/***********************************************************************/
int TDBDOS::GetMaxSize(PGLOBAL g)
{
if (MaxSize >= 0)
return MaxSize;
if (!Cardinality(NULL)) {
int len = GetFileLength(g);
if (len >= 0) {
int rec;
if (trace(1))
htrc("Estimating lines len=%d ending=%d/n",
len, ((PDOSDEF)To_Def)->Ending);
/*****************************************************************/
/* Estimate the number of lines in the table (if not known) by */
/* dividing the file length by minimum record length. */
/*****************************************************************/
rec = EstimatedLength() + ((PDOSDEF)To_Def)->Ending;
MaxSize = (len + rec - 1) / rec;
if (trace(1))
htrc("avglen=%d MaxSize%d\n", rec, MaxSize);
} // endif len
} else
MaxSize = Cardinality(g);
return MaxSize;
} // end of GetMaxSize
/***********************************************************************/
/* DOS EstimatedLength. Returns an estimated minimum line length. */
/***********************************************************************/
int TDBDOS::EstimatedLength(void)
{
int dep = 0;
PCOLDEF cdp = To_Def->GetCols();
if (!cdp->GetNext()) {
// One column table, we are going to return a ridiculous
// result if we set dep to 1
dep = 1 + cdp->GetLong() / 20; // Why 20 ?????
} else for (; cdp; cdp = cdp->GetNext())
if (!(cdp->Flags & (U_VIRTUAL|U_SPECIAL)))
dep = MY_MAX(dep, cdp->GetOffset());
return (int)dep;
} // end of Estimated Length
/***********************************************************************/
/* DOS tables favor the use temporary files for Update. */
/***********************************************************************/
bool TDBDOS::IsUsingTemp(PGLOBAL)
{
USETEMP utp = UseTemp();
return (utp == TMP_YES || utp == TMP_FORCE ||
(utp == TMP_AUTO && Mode == MODE_UPDATE));
} // end of IsUsingTemp
/***********************************************************************/
/* DOS Access Method opening routine. */
/* New method now that this routine is called recursively (last table */
/* first in reverse order): index blocks are immediately linked to */
/* join block of next table if it exists or else are discarted. */
/***********************************************************************/
bool TDBDOS::OpenDB(PGLOBAL g)
{
if (trace(1))
htrc("DOS OpenDB: tdbp=%p tdb=R%d use=%d mode=%d\n",
this, Tdb_No, Use, Mode);
if (Use == USE_OPEN) {
/*******************************************************************/
/* Table already open, just replace it at its beginning. */
/*******************************************************************/
if (!To_Kindex) {
Txfp->Rewind(); // see comment in Work.log
if (SkipHeader(g))
return true;
} else
/*****************************************************************/
/* Table is to be accessed through a sorted index table. */
/*****************************************************************/
To_Kindex->Reset();
ResetBlockFilter(g);
return false;
} // endif use
if (Mode == MODE_DELETE && !Next && Txfp->GetAmType() != TYPE_AM_DOS
#if defined(BSON_SUPPORT)
&& Txfp->GetAmType() != TYPE_AM_BIN
#endif // BSON_SUPPORT
&& Txfp->GetAmType() != TYPE_AM_MGO) {
// Delete all lines. Not handled in MAP or block mode
Txfp = new(g) DOSFAM((PDOSDEF)To_Def);
Txfp->SetTdbp(this);
} else if (Txfp->Blocked && (Mode == MODE_DELETE ||
(Mode == MODE_UPDATE && UseTemp() != TMP_NO))) {
/*******************************************************************/
/* Delete is not currently handled in block mode neither Update */
/* when using a temporary file. */
/*******************************************************************/
if (Txfp->GetAmType() == TYPE_AM_MAP && Mode == MODE_DELETE)
Txfp = new(g) MAPFAM((PDOSDEF)To_Def);
#if defined(GZ_SUPPORT)
else if (Txfp->GetAmType() == TYPE_AM_GZ)
Txfp = new(g) GZFAM((PDOSDEF)To_Def);
#endif // GZ_SUPPORT
else // if (Txfp->GetAmType() != TYPE_AM_DOS) ???
Txfp = new(g) DOSFAM((PDOSDEF)To_Def);
Txfp->SetTdbp(this);
} // endif Mode
/*********************************************************************/
/* Open according to logical input/output mode required. */
/* Use conventionnal input/output functions. */
/* Treat files as binary in Delete mode (for line moving) */
/*********************************************************************/
if (Txfp->OpenTableFile(g))
return true;
Use = USE_OPEN; // Do it now in case we are recursively called
/*********************************************************************/
/* Allocate the block filter tree if evaluation is possible. */
/*********************************************************************/
To_BlkFil = InitBlockFilter(g, To_Filter);
/*********************************************************************/
/* Lrecl does not include line ending */
/*********************************************************************/
size_t linelen = Lrecl + ((PDOSDEF)To_Def)->Ending + 1;
To_Line = (char*)PlugSubAlloc(g, NULL, linelen);
if (Mode == MODE_INSERT) {
// Spaces between fields must be filled with blanks
memset(To_Line, ' ', Lrecl);
To_Line[Lrecl] = '\0';
} else
memset(To_Line, 0, linelen);
if (trace(1))
htrc("OpenDos: R%hd mode=%d To_Line=%p\n", Tdb_No, Mode, To_Line);
if (SkipHeader(g)) // When called from CSV/FMT files
return true;
/*********************************************************************/
/* Reset statistics values. */
/*********************************************************************/
num_read = num_there = num_eq[0] = num_eq[1] = 0;
return false;
} // end of OpenDB
/***********************************************************************/
/* ReadDB: Data Base read routine for DOS access method. */
/***********************************************************************/
int TDBDOS::ReadDB(PGLOBAL g)
{
if (trace(2))
htrc("DOS ReadDB: R%d Mode=%d key=%p link=%p Kindex=%p To_Line=%p\n",
GetTdb_No(), Mode, To_Key_Col, To_Link, To_Kindex, To_Line);
if (To_Kindex) {
/*******************************************************************/
/* Reading is by an index table. */
/*******************************************************************/
int recpos = To_Kindex->Fetch(g);
switch (recpos) {
case -1: // End of file reached
return RC_EF;
case -2: // No match for join
return RC_NF;
case -3: // Same record as non null last one
num_there++;
return RC_OK;
default:
/***************************************************************/
/* Set the file position according to record to read. */
/***************************************************************/
if (SetRecpos(g, recpos))
return RC_FX;
if (trace(2))
htrc("File position is now %d\n", GetRecpos());
if (Mode == MODE_READ)
/*************************************************************/
/* Defer physical reading until one column setting needs it */
/* as it can be a big saving on joins where no other column */
/* than the keys are used, so reading is unnecessary. */
/*************************************************************/
if (Txfp->DeferReading())
return RC_OK;
} // endswitch recpos
} // endif To_Kindex
if (trace(2))
htrc(" ReadDB: this=%p To_Line=%p\n", this, To_Line);
/*********************************************************************/
/* Now start the reading process. */
/*********************************************************************/
return ReadBuffer(g);
} // end of ReadDB
/***********************************************************************/
/* PrepareWriting: Prepare the line to write. */
/***********************************************************************/
bool TDBDOS::PrepareWriting(PGLOBAL)
{
if (Ftype == RECFM_VAR && (Mode == MODE_INSERT || Txfp->GetUseTemp())) {
char *p;
/*******************************************************************/
/* Suppress trailing blanks. */
/* Also suppress eventual null from last line. */
/*******************************************************************/
for (p = To_Line + Lrecl -1; p >= To_Line; p--)
if (*p && *p != ' ')
break;
*(++p) = '\0';
} // endif Mode
return false;
} // end of PrepareWriting
/***********************************************************************/
/* WriteDB: Data Base write routine for DOS access method. */
/***********************************************************************/
int TDBDOS::WriteDB(PGLOBAL g)
{
if (trace(2))
htrc("DOS WriteDB: R%d Mode=%d \n", Tdb_No, Mode);
// Make the line to write
if (PrepareWriting(g))
return RC_FX;
if (trace(2))
htrc("Write: line is='%s'\n", To_Line);
// Now start the writing process
return Txfp->WriteBuffer(g);
} // end of WriteDB
/***********************************************************************/
/* Data Base delete line routine for DOS (and FIX) access method. */
/* RC_FX means delete all. Nothing to do here (was done at open). */
/***********************************************************************/
int TDBDOS::DeleteDB(PGLOBAL g, int irc)
{
return (irc == RC_FX) ? RC_OK : Txfp->DeleteRecords(g, irc);
} // end of DeleteDB
/***********************************************************************/
/* Data Base close routine for DOS access method. */
/***********************************************************************/
void TDBDOS::CloseDB(PGLOBAL g)
{
if (To_Kindex) {
To_Kindex->Close();
To_Kindex = NULL;
} // endif
Txfp->CloseTableFile(g, Abort);
RestoreNrec();
} // end of CloseDB
// ------------------------ DOSCOL functions ----------------------------
/***********************************************************************/
/* DOSCOL public constructor (also called by MAPCOL). */
/***********************************************************************/
DOSCOL::DOSCOL(PGLOBAL g, PCOLDEF cdp, PTDB tp, PCOL cp, int i, PCSZ am)
: COLBLK(cdp, tp, i)
{
char *p;
int prec = Format.Prec;
PTXF txfp = ((PTDBDOS)tp)->Txfp;
assert(cdp);
if (cp) {
Next = cp->GetNext();
cp->SetNext(this);
} else {
Next = tp->GetColumns();
tp->SetColumns(this);
} // endif cprec
// Set additional Dos access method information for column.
Deplac = cdp->GetOffset();
Long = cdp->GetLong();
To_Val = NULL;
Clustered = cdp->GetOpt();
Sorted = (cdp->GetOpt() == 2) ? 1 : 0;
Ndv = 0; // Currently used only for XDB2
Nbm = 0; // Currently used only for XDB2
Min = NULL;
Max = NULL;
Bmap = NULL;
Dval = NULL;
Buf = NULL;
if (txfp && txfp->Blocked && Opt && (cdp->GetMin() || cdp->GetDval())) {
int nblk = txfp->GetBlock();
Clustered = (cdp->GetXdb2()) ? 2 : 1;
Sorted = (cdp->GetOpt() > 1) ? 1 : 0; // Currently ascending only
if (Clustered == 1) {
Min = AllocValBlock(g, cdp->GetMin(), Buf_Type, nblk, Long, prec);
Max = AllocValBlock(g, cdp->GetMax(), Buf_Type, nblk, Long, prec);
} else { // Clustered == 2
// Ndv is the number of distinct values in Dval. Ndv and Nbm
// may be 0 when optimizing because Ndval is not filled yet,
// but the size of the passed Dval memory block is Ok.
Ndv = cdp->GetNdv();
Dval = AllocValBlock(g, cdp->GetDval(), Buf_Type, Ndv, Long, prec);
// Bmap cannot be allocated when optimizing, we must know Nbm first
if ((Nbm = cdp->GetNbm()))
Bmap = AllocValBlock(g, cdp->GetBmap(), TYPE_INT, Nbm * nblk);
} // endif Clustered
} // endif Opt
OldVal = NULL; // Currently used only in MinMax
Dsp = 0;
Ldz = false;
Nod = false;
Dcm = -1;
p = cdp->GetFmt();
Buf = NULL;
if (p && IsTypeNum(Buf_Type)) {
// Formatted numeric value
for (; p && *p && isalpha(*p); p++)
switch (toupper(*p)) {
case 'Z': // Have leading zeros
Ldz = true;
break;
case 'N': // Have no decimal point
Nod = true;
break;
case 'D': // Decimal separator
Dsp = *(++p);
break;
} // endswitch p
// Set number of decimal digits
Dcm = (*p) ? atoi(p) : GetScale();
} // endif fmt
if (trace(1))
htrc(" making new %sCOL C%d %s at %p\n", am, Index, Name, this);
} // end of DOSCOL constructor
/***********************************************************************/
/* DOSCOL constructor used for copying columns. */
/* tdbp is the pointer to the new table descriptor. */
/***********************************************************************/
DOSCOL::DOSCOL(DOSCOL *col1, PTDB tdbp) : COLBLK(col1, tdbp)
{
Deplac = col1->Deplac;
Long = col1->Long;
To_Val = col1->To_Val;
Ldz = col1->Ldz;
Dsp = col1->Dsp;
Nod = col1->Nod;
Dcm = col1->Dcm;
OldVal = col1->OldVal;
Buf = col1->Buf;
Clustered = col1->Clustered;
Sorted = col1->Sorted;
Min = col1->Min;
Max = col1->Max;
Bmap = col1->Bmap;
Dval = col1->Dval;
Ndv = col1->Ndv;
Nbm = col1->Nbm;
} // end of DOSCOL copy constructor
/***********************************************************************/
/* VarSize: This function tells UpdateDB whether or not the block */
/* optimization file must be redone if this column is updated, even */
/* it is not sorted or clustered. This applies to the last column of */
/* a variable length table that is blocked, because if it is updated */
/* using a temporary file, the block size may be modified. */
/***********************************************************************/
bool DOSCOL::VarSize(void)
{
PTDBDOS tdbp = (PTDBDOS)To_Tdb;
PTXF txfp = tdbp->Txfp;
if (Cdp && !Cdp->GetNext() // Must be the last column
&& tdbp->Ftype == RECFM_VAR // of a DOS variable length
&& txfp->Blocked // blocked table
&& txfp->GetUseTemp()) // using a temporary file.
return true;
else
return false;
} // end VarSize
/***********************************************************************/
/* SetBuffer: prepare a column block for write operation. */
/***********************************************************************/
bool DOSCOL::SetBuffer(PGLOBAL g, PVAL value, bool ok, bool check)
{
if (!(To_Val = value)) {
snprintf(g->Message, sizeof(g->Message), MSG(VALUE_ERROR), Name);
return true;
} else if (Buf_Type == value->GetType()) {
// Values are of the (good) column type
if (Buf_Type == TYPE_DATE) {
// If any of the date values is formatted
// output format must be set for the receiving table
if (GetDomain() || ((DTVAL *)value)->IsFormatted())
goto newval; // This will make a new value;
} else if (Buf_Type == TYPE_DOUBLE)
// Float values must be written with the correct (column) precision
// Note: maybe this should be forced by ShowValue instead of this ?
value->SetPrec(GetScale());
Value = value; // Directly access the external value
} else {
// Values are not of the (good) column type
if (check) {
snprintf(g->Message, sizeof(g->Message), MSG(TYPE_VALUE_ERR), Name,
GetTypeName(Buf_Type), GetTypeName(value->GetType()));
return true;
} // endif check
newval:
if (InitValue(g)) // Allocate the matching value block
return true;
} // endif's Value, Buf_Type
// Allocate the buffer used in WriteColumn for numeric columns
if (!Buf && IsTypeNum(Buf_Type))
Buf = (char*)PlugSubAlloc(g, NULL, MY_MAX(64, Long + 1));
else // Text columns do not need additional buffer
Buf = (char*)Value->GetTo_Val();
// Because Colblk's have been made from a copy of the original TDB in
// case of Update, we must reset them to point to the original one.
if (To_Tdb->GetOrig())
To_Tdb = (PTDB)To_Tdb->GetOrig();
// Set the Column
Status = (ok) ? BUF_EMPTY : BUF_NO;
return false;
} // end of SetBuffer
/***********************************************************************/
/* ReadColumn: what this routine does is to access the last line */
/* read from the corresponding table, extract from it the field */
/* corresponding to this column and convert it to buffer type. */
/***********************************************************************/
void DOSCOL::ReadColumn(PGLOBAL g)
{
char *p = NULL;
int i, rc;
int field;
bool err = false;
double dval;
PTDBDOS tdbp = (PTDBDOS)To_Tdb;
if (trace(2))
htrc(
"DOS ReadColumn: col %s R%d coluse=%.4X status=%.4X buf_type=%d\n",
Name, tdbp->GetTdb_No(), ColUse, Status, Buf_Type);
/*********************************************************************/
/* If physical reading of the line was deferred, do it now. */
/*********************************************************************/
if (!tdbp->IsRead())
if ((rc = tdbp->ReadBuffer(g)) != RC_OK) {
if (rc == RC_EF)
snprintf(g->Message, sizeof(g->Message), MSG(INV_DEF_READ), rc);
throw 11;
} // endif
p = tdbp->To_Line + Deplac;
field = Long;
/*********************************************************************/
/* For a variable length file, check if the field exists. */
/*********************************************************************/
if ((tdbp->Ftype == RECFM_VAR || tdbp->Ftype == RECFM_CSV)
&& strlen(tdbp->To_Line) < (unsigned)Deplac)
field = 0;
else if (Dsp)
for(i = 0; i < field; i++)
if (p[i] == Dsp)
p[i] = '.';
switch (tdbp->Ftype) {
case RECFM_VAR:
case RECFM_FIX: // Fixed length text file
case RECFM_CSV: // Variable length CSV or FMT file
case RECFM_DBF: // Fixed length DBase file
if (Nod) switch (Buf_Type) {
case TYPE_INT:
case TYPE_SHORT:
case TYPE_TINY:
case TYPE_BIGINT:
err = Value->SetValue_char(p, field - Dcm);
break;
case TYPE_DOUBLE:
if (!(err = Value->SetValue_char(p, field))) {
dval = Value->GetFloatValue();
for (i = 0; i < Dcm; i++)
dval /= 10.0;
Value->SetValue(dval);
} // endif err
break;
default:
err = Value->SetValue_char(p, field);
if (!err && Buf_Type == TYPE_DECIM) {
char* s = Value->GetCharValue();
if (!(err = ((i = strlen(s)) >= Value->GetClen()))) {
for (int d = Dcm + 1; d; i--, d--)
s[i + 1] = s[i];
s[i + 1] = '.';
} // endif err
} // endif DECIM
break;
} // endswitch Buf_Type
else
err = Value->SetValue_char(p, field);
break;
default:
snprintf(g->Message, sizeof(g->Message), MSG(BAD_RECFM), tdbp->Ftype);
throw 34;
} // endswitch Ftype
if (err) {
snprintf(g->Message, sizeof(g->Message), "Out of range value for column %s at row %d",
Name, tdbp->RowNumber(g));
PushWarning(g, tdbp);
} // endif err
// Set null when applicable
if (Nullable)
Value->SetNull(Value->IsZero());
} // end of ReadColumn
/***********************************************************************/
/* WriteColumn: what this routine does is to access the last line */
/* read from the corresponding table, and rewrite the field */
/* corresponding to this column from the column buffer and type. */
/***********************************************************************/
void DOSCOL::WriteColumn(PGLOBAL g)
{
char *p, fmt[32];
int i, k, n, len, field;
PTDBDOS tdbp = (PTDBDOS)To_Tdb;
if (trace(2))
htrc("DOS WriteColumn: col %s R%d coluse=%.4X status=%.4X\n",
Name, tdbp->GetTdb_No(), ColUse, Status);
p = tdbp->To_Line + Deplac;
if (trace(2))
htrc("Lrecl=%d deplac=%d int=%d\n", tdbp->Lrecl, Deplac, Long);
field = Long;
if (tdbp->Ftype == RECFM_VAR && tdbp->Mode == MODE_UPDATE) {
len = (signed)strlen(tdbp->To_Line);
if (tdbp->IsUsingTemp(g))
// Because of eventual missing field(s) the buffer must be reset
memset(tdbp->To_Line + len, ' ', tdbp->Lrecl - len);
else
// The size actually available must be recalculated
field = MY_MIN(len - Deplac, Long);
} // endif Ftype
if (trace(2))
htrc("Long=%d field=%d coltype=%d colval=%p\n",
Long, field, Buf_Type, Value);
/*********************************************************************/
/* Get the string representation of Value according to column type. */
/*********************************************************************/
if (Value != To_Val)
Value->SetValue_pval(To_Val, false); // Convert the updated value
/*********************************************************************/
/* This test is only useful for compressed(2) tables. */
/*********************************************************************/
if (tdbp->Ftype != RECFM_BIN) {
if (Ldz || Nod || Dcm >= 0) {
switch (Buf_Type) {
case TYPE_SHORT:
strcpy(fmt, (Ldz) ? "%0*hd" : "%*.hd");
i = 0;
if (Nod)
for (; i < Dcm; i++)
strcat(fmt, "0");
len = sprintf(Buf, fmt, field - i, Value->GetShortValue());
break;
case TYPE_INT:
strcpy(fmt, (Ldz) ? "%0*d" : "%*.d");
i = 0;
if (Nod)
for (; i < Dcm; i++)
strcat(fmt, "0");
len = sprintf(Buf, fmt, field - i, Value->GetIntValue());
break;
case TYPE_TINY:
strcpy(fmt, (Ldz) ? "%0*d" : "%*.d");
i = 0;
if (Nod)
for (; i < Dcm; i++)
strcat(fmt, "0");
len = sprintf(Buf, fmt, field - i, Value->GetTinyValue());
break;
case TYPE_DOUBLE:
case TYPE_DECIM:
strcpy(fmt, (Ldz) ? "%0*.*lf" : "%*.*lf");
len = field + ((Nod && Dcm) ? 1 : 0);
snprintf(Buf, len + 1, fmt, len, Dcm, Value->GetFloatValue());
len = strlen(Buf);
if (Nod && Dcm)
for (i = k = 0; i < len; i++, k++)
if (Buf[i] != ' ') {
if (Buf[i] == '.')
k++;
Buf[i] = Buf[k];
} // endif Buf(i)
len = strlen(Buf);
break;
default:
snprintf(g->Message, sizeof(g->Message), "Invalid field format for column %s", Name);
throw 31;
} // endswitch BufType
n = strlen(Buf);
} else // Standard CONNECT format
n = Value->ShowValue(Buf, field);
if (trace(1))
htrc("new length(%p)=%d\n", Buf, n);
if ((len = n) > field) {
char *p = Value->GetCharString(Buf);
snprintf(g->Message, sizeof(g->Message), MSG(VALUE_TOO_LONG), p, Name, field);
throw 31;
} else if (Dsp)
for (i = 0; i < len; i++)
if (Buf[i] == '.')
Buf[i] = Dsp;
if (trace(2))
htrc("buffer=%s\n", Buf);
/*******************************************************************/
/* Updating must be done only when not in checking pass. */
/*******************************************************************/
if (Status) {
memset(p, ' ', field);
memcpy(p, Buf, len);
if (trace(2))
htrc(" col write: '%.*s'\n", len, p);
} // endif Status
} else // BIN compressed table
/*******************************************************************/
/* Check if updating is Ok, meaning col value is not too long. */
/* Updating to be done only during the second pass (Status=true) */
/*******************************************************************/
if (Value->GetBinValue(p, Long, Status)) {
snprintf(g->Message, sizeof(g->Message), MSG(BIN_F_TOO_LONG),
Name, Value->GetSize(), Long);
throw 31;
} // endif
} // end of WriteColumn
/***********************************************************************/
/* SetMinMax: Calculate minimum and maximum values for one block. */
/* Note: TYPE_STRING is stored and processed with zero ended strings */
/* to be matching the way the FILTER Eval function processes them. */
/***********************************************************************/
bool DOSCOL::SetMinMax(PGLOBAL g)
{
PTDBDOS tp = (PTDBDOS)To_Tdb;
ReadColumn(g); // Extract column value from current line
if (CheckSorted(g))
return true;
if (!tp->Txfp->CurNum) {
Min->SetValue(Value, tp->Txfp->CurBlk);
Max->SetValue(Value, tp->Txfp->CurBlk);
} else {
Min->SetMin(Value, tp->Txfp->CurBlk);
Max->SetMax(Value, tp->Txfp->CurBlk);
} // endif CurNum
return false;
} // end of SetMinMax
/***********************************************************************/
/* SetBitMap: Calculate the bit map of existing values in one block. */
/* Note: TYPE_STRING is processed with zero ended strings */
/* to be matching the way the FILTER Eval function processes them. */
/***********************************************************************/
bool DOSCOL::SetBitMap(PGLOBAL g)
{
int i, m, n;
uint *bmp;
PTDBDOS tp = (PTDBDOS)To_Tdb;
PDBUSER dup = PlgGetUser(g);
n = tp->Txfp->CurNum;
bmp = (uint*)Bmap->GetValPtr(Nbm * tp->Txfp->CurBlk);
// Extract column value from current line
ReadColumn(g);
if (CheckSorted(g))
return true;
if (!n) // New block
for (m = 0; m < Nbm; m++)
bmp[m] = 0; // Reset the new bit map
if ((i = Dval->Find(Value)) < 0) {
char buf[32];
snprintf(g->Message, sizeof(g->Message), MSG(DVAL_NOTIN_LIST),
Value->GetCharString(buf), Name);
return true;
} else if (i >= dup->Maxbmp) {
snprintf(g->Message, sizeof(g->Message), MSG(OPT_LOGIC_ERR), i);
return true;
} else {
m = i / MAXBMP;
#if defined(_DEBUG)
assert (m < Nbm);
#endif // _DEBUG
bmp[m] |= (1 << (i % MAXBMP));
} // endif's i
return false;
} // end of SetBitMap
/***********************************************************************/
/* Checks whether a column declared as sorted is sorted indeed. */
/***********************************************************************/
bool DOSCOL::CheckSorted(PGLOBAL g)
{
if (Sorted)
if (OldVal) {
// Verify whether this column is sorted all right
if (OldVal->CompareValue(Value) > 0) {
// Column is no more in ascending order
snprintf(g->Message, sizeof(g->Message), MSG(COL_NOT_SORTED), Name, To_Tdb->GetName());
Sorted = false;
return true;
} else
OldVal->SetValue_pval(Value);
} else
OldVal = AllocateValue(g, Value);
return false;
} // end of CheckSorted
/***********************************************************************/
/* AddDistinctValue: Check whether this value already exist in the */
/* list and if not add it to the distinct values list. */
/***********************************************************************/
bool DOSCOL::AddDistinctValue(PGLOBAL g)
{
bool found = false;
int i, m, n;
ReadColumn(g); // Extract column value from current line
// Perhaps a better algorithm can be used when Ndv gets bigger
// Here we cannot use Find because we must get the index of where
// to insert a new value if it is not found in the array.
for (n = 0; n < Ndv; n++) {
m = Dval->CompVal(Value, n);
if (m > 0)
continue;
else if (!m)
found = true; // Already there
break;
} // endfor n
if (!found) {
// Check whether we have room for an additional value
if (Ndv == Freq) {
// Too many values because of wrong Freq setting
snprintf(g->Message, sizeof(g->Message), MSG(BAD_FREQ_SET), Name);
return true;
} // endif Ndv
// New value, add it to the list before the nth value
Dval->SetNval(Ndv + 1);
for (i = Ndv; i > n; i--)
Dval->Move(i - 1, i);
Dval->SetValue(Value, n);
Ndv++;
} // endif found
return false;
} // end of AddDistinctValue
/* ------------------------------------------------------------------- */