mirror of
https://github.com/MariaDB/server.git
synced 2025-01-15 19:42:28 +01:00
2926 lines
96 KiB
C++
2926 lines
96 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"
|
|
#include "m_string.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) {
|
|
safe_strcpy(g->Message, sizeof(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);
|
|
safe_strcat(fname, sizeof(fname), "_");
|
|
safe_strcat(fname, sizeof(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());
|
|
safe_strcat(PlugRemoveType(filename, filename), sizeof(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 {
|
|
safe_strcpy(g->Message, sizeof(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 {
|
|
safe_strcpy(g->Message, sizeof(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 {
|
|
safe_strcpy(g->Message, sizeof(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
|
|
|
|
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);
|
|
//void *memp = cat->GetDescp();
|
|
(void) defp->GetCat(); // XXX Should be removed?
|
|
|
|
|
|
if ((nrec = defp->GetElemt()) < 2) {
|
|
if (!To_Def->Partitioned()) {
|
|
// This may be wrong to do in some cases
|
|
safe_strcpy(g->Message, sizeof(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);
|
|
safe_strcpy(g->Message, sizeof(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) {
|
|
safe_strcpy(g->Message, sizeof(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));
|
|
|
|
snprintf(p, 24 + strlen(Name), "%s%s", MSG(OPTIMIZING), Name);
|
|
dup->Step = p;
|
|
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) {
|
|
safe_strcpy(g->Message, sizeof(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) {
|
|
safe_strcpy(g->Message, sizeof(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);
|
|
safe_strcat(g->Message, sizeof(g->Message), ": ");
|
|
safe_strcat(g->Message, sizeof(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;
|
|
int len;
|
|
bool newblk = false;
|
|
size_t ndv, nbm, nbk, blk;
|
|
FILE *opfile;
|
|
PCOLDEF cdp;
|
|
PDOSDEF defp = (PDOSDEF)To_Def;
|
|
PDBUSER dup = PlgGetUser(g);
|
|
|
|
(void) defp->GetCat(); // XXX Should be removed?
|
|
|
|
|
|
#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));
|
|
snprintf(p, 48 + strlen(Name), "%s%s", MSG(GET_DIST_VALS), Name);
|
|
dup->Step = p;
|
|
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)) {
|
|
safe_strcpy(g->Message, sizeof(g->Message), MSG(OPT_CANCELLED));
|
|
return true;
|
|
} else
|
|
#elif defined(THREAD)
|
|
if (!dup->Step) {
|
|
safe_strcpy(g->Message, sizeof(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();
|
|
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;
|
|
PXOB xp[2];
|
|
PCOL colp;
|
|
PBF 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
|
|
safe_strcpy(g->Message, sizeof(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;
|
|
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()) {
|
|
safe_strcpy(g->Message, sizeof(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) {
|
|
safe_strcpy(g->Message, sizeof(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)) {
|
|
safe_strcpy(g->Message, sizeof(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) {
|
|
safe_strcpy(g->Message, sizeof(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:
|
|
safe_strcpy(fmt, sizeof(fmt), (Ldz) ? "%0*hd" : "%*.hd");
|
|
i = 0;
|
|
|
|
if (Nod)
|
|
for (; i < Dcm; i++)
|
|
safe_strcat(fmt, sizeof(fmt), "0");
|
|
|
|
len = sprintf(Buf, fmt, field - i, Value->GetShortValue());
|
|
break;
|
|
case TYPE_INT:
|
|
safe_strcpy(fmt, sizeof(fmt), (Ldz) ? "%0*d" : "%*.d");
|
|
i = 0;
|
|
|
|
if (Nod)
|
|
for (; i < Dcm; i++)
|
|
safe_strcat(fmt,sizeof(fmt), "0");
|
|
|
|
len = sprintf(Buf, fmt, field - i, Value->GetIntValue());
|
|
break;
|
|
case TYPE_TINY:
|
|
safe_strcpy(fmt, sizeof(fmt), (Ldz) ? "%0*d" : "%*.d");
|
|
i = 0;
|
|
|
|
if (Nod)
|
|
for (; i < Dcm; i++)
|
|
safe_strcat(fmt, sizeof(fmt), "0");
|
|
|
|
len = sprintf(Buf, fmt, field - i, Value->GetTinyValue());
|
|
break;
|
|
case TYPE_DOUBLE:
|
|
case TYPE_DECIM:
|
|
safe_strcpy(fmt, sizeof(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
|
|
|
|
/* ------------------------------------------------------------------- */
|