/***************** Xindex C++ Class Xindex Code (.CPP) *****************/ /* Name: XINDEX.CPP Version 3.0 */ /* */ /* (C) Copyright to the author Olivier BERTRAND 2004-2017 */ /* */ /* This file contains the class XINDEX implementation code. */ /***********************************************************************/ /***********************************************************************/ /* Include relevant sections of the System header files. */ /***********************************************************************/ #include "my_global.h" #if defined(_WIN32) #include #include #include //#include #else // !_WIN32 #if defined(UNIX) #include #include #include #include #else // !UNIX #include #endif // !UNIX #include #endif // !_WIN32 /***********************************************************************/ /* Include required application header files */ /* global.h is header containing all global Plug declarations. */ /* plgdbsem.h is header containing the DB applic. declarations. */ /* kindex.h is header containing the KINDEX class definition. */ /***********************************************************************/ #include "global.h" #include "plgdbsem.h" #include "osutil.h" #include "maputil.h" //nclude "filter.h" #include "tabcol.h" #include "xindex.h" #include "xobject.h" //nclude "scalfnc.h" //nclude "array.h" #include "filamtxt.h" #include "tabdos.h" #if defined(VCT_SUPPORT) #include "tabvct.h" #endif // VCT_SUPPORT /***********************************************************************/ /* Macro or external routine definition */ /***********************************************************************/ #define NZ 8 #define NW 5 #define MAX_INDX 10 #ifndef INVALID_SET_FILE_POINTER #define INVALID_SET_FILE_POINTER 0xFFFFFFFF #endif /***********************************************************************/ /* DB external variables. */ /***********************************************************************/ extern MBLOCK Nmblk; /* Used to initialize MBLOCK's */ #if defined(XMAP) extern my_bool xmap; #endif // XMAP /***********************************************************************/ /* Last two parameters are true to enable type checking, and last one */ /* to have rows filled by blanks to be compatible with QRY blocks. */ /***********************************************************************/ PVBLK AllocValBlock(PGLOBAL, void *, int, int, int, int, bool check = true, bool blank = true, bool un = false); /***********************************************************************/ /* Check whether we have to create/update permanent indexes. */ /***********************************************************************/ int PlgMakeIndex(PGLOBAL g, PSZ name, PIXDEF pxdf, bool add) { int rc; PTABLE tablep; PTDB tdbp; PCATLG cat = PlgGetCatalog(g, true); /*********************************************************************/ /* Open a new table in mode read and with only the keys columns. */ /*********************************************************************/ tablep = new(g) XTAB(name); if (!(tdbp = cat->GetTable(g, tablep))) rc = RC_NF; else if (!tdbp->GetDef()->Indexable()) { sprintf(g->Message, MSG(TABLE_NO_INDEX), name); rc = RC_NF; } else if ((rc = ((PTDBASE)tdbp)->MakeIndex(g, pxdf, add)) == RC_INFO) rc = RC_OK; // No or remote index return rc; } // end of PlgMakeIndex /* -------------------------- Class INDEXDEF ------------------------- */ /***********************************************************************/ /* INDEXDEF Constructor. */ /***********************************************************************/ INDEXDEF::INDEXDEF(char *name, bool uniq, int n) { //To_Def = NULL; Next = NULL; ToKeyParts = NULL; Name = name; Unique = uniq; Invalid = false; AutoInc = false; Dynamic = false; Mapped = false; Nparts = 0; ID = n; //Offset = 0; //Offhigh = 0; //Size = 0; MaxSame = 1; } // end of INDEXDEF constructor /***********************************************************************/ /* Set the max same values for each colum after making the index. */ /***********************************************************************/ void INDEXDEF::SetMxsame(PXINDEX x) { PKPDEF kdp; PXCOL xcp; for (kdp = ToKeyParts, xcp = x->To_KeyCol; kdp && xcp; kdp = kdp->Next, xcp = xcp->Next) kdp->Mxsame = xcp->Mxs; } // end of SetMxsame /* -------------------------- Class KPARTDEF ------------------------- */ /***********************************************************************/ /* KPARTDEF Constructor. */ /***********************************************************************/ KPARTDEF::KPARTDEF(PSZ name, int n) { Next = NULL; Name = name; Mxsame = 0; Ncol = n; Klen = 0; } // end of KPARTDEF constructor /* -------------------------- XXBASE Class --------------------------- */ /***********************************************************************/ /* XXBASE public constructor. */ /***********************************************************************/ XXBASE::XXBASE(PTDBDOS tbxp, bool b) : CSORT(b), To_Rec((int*&)Record.Memp) { Tbxp = tbxp; Record = Nmblk; Cur_K = -1; Old_K = -1; Num_K = 0; Ndif = 0; Bot = Top = Inf = Sup = 0; Op = OP_EQ; To_KeyCol = NULL; Mul = false; Srtd = false; Dynamic = false; Val_K = -1; Nblk = Sblk = 0; Thresh = 7; ID = -1; Nth = 0; } // end of XXBASE constructor /***********************************************************************/ /* Make file output of XINDEX contents. */ /***********************************************************************/ void XXBASE::Printf(PGLOBAL, FILE *f, uint n) { char m[64]; memset(m, ' ', n); // Make margin string m[n] = '\0'; fprintf(f, "%sXINDEX: Tbxp=%p Num=%d\n", m, Tbxp, Num_K); } // end of Printf /***********************************************************************/ /* Make string output of XINDEX contents. */ /***********************************************************************/ void XXBASE::Prints(PGLOBAL, char *ps, uint z) { *ps = '\0'; strncat(ps, "Xindex", z); } // end of Prints /* -------------------------- XINDEX Class --------------------------- */ /***********************************************************************/ /* XINDEX public constructor. */ /***********************************************************************/ XINDEX::XINDEX(PTDBDOS tdbp, PIXDEF xdp, PXLOAD pxp, PCOL *cp, PXOB *xp, int k) : XXBASE(tdbp, !xdp->IsUnique()) { Xdp = xdp; ID = xdp->GetID(); Tdbp = tdbp; X = pxp; To_LastCol = NULL; To_LastVal = NULL; To_Cols = cp; To_Vals = xp; Mul = !xdp->IsUnique(); Srtd = false; Nk = xdp->GetNparts(); Nval = (k) ? k : Nk; Incr = 0; //Defoff = xdp->GetOffset(); //Defhigh = xdp->GetOffhigh(); //Size = xdp->GetSize(); MaxSame = xdp->GetMaxSame(); } // end of XINDEX constructor /***********************************************************************/ /* XINDEX Reset: re-initialize a Xindex block. */ /***********************************************************************/ void XINDEX::Reset(void) { for (PXCOL kp = To_KeyCol; kp; kp = kp->Next) kp->Val_K = kp->Ndf; Cur_K = Num_K; Old_K = -1; // Needed to avoid not setting CurBlk for Update Op = (Op == OP_FIRST || Op == OP_NEXT) ? OP_FIRST : (Op == OP_FSTDIF || Op == OP_NXTDIF) ? OP_FSTDIF : OP_EQ; Nth = 0; } // end of Reset /***********************************************************************/ /* XINDEX Close: terminate index and free all allocated data. */ /* Do not reset values that are used at return to make. */ /***********************************************************************/ void XINDEX::Close(void) { // Close file or view of file if (X) X->Close(); // De-allocate data PlgDBfree(Record); PlgDBfree(Index); PlgDBfree(Offset); for (PXCOL kcp = To_KeyCol; kcp; kcp = kcp->Next) { // Column values cannot be retrieved from key anymore if (kcp->Colp) kcp->Colp->SetKcol(NULL); // De-allocate Key data kcp->FreeData(); } // endfor kcp } // end of Close /***********************************************************************/ /* XINDEX compare routine for C Quick/Insertion sort. */ /***********************************************************************/ int XINDEX::Qcompare(int *i1, int *i2) { int k; PXCOL kcp; for (kcp = To_KeyCol, k = 0; kcp; kcp = kcp->Next) if ((k = kcp->Compare(*i1, *i2))) break; //num_comp++; return k; } // end of Qcompare /***********************************************************************/ /* AddColumns: here we try to determine whether it is worthwhile to */ /* add to the keys the values of the columns selected for this table. */ /* Sure enough, it is done while records are read and permit to avoid */ /* reading the table while doing the join (Dynamic index only) */ /***********************************************************************/ bool XINDEX::AddColumns(void) { if (!Dynamic) return false; // Not applying to static index else if (IsMul()) return false; // Not done yet for multiple index #if defined(VCT_SUPPORT) else if (Tbxp->GetAmType() == TYPE_AM_VCT && ((PTDBVCT)Tbxp)->IsSplit()) return false; // This would require to read additional files #endif // VCT_SUPPORT else return true; } // end of AddColumns /***********************************************************************/ /* Make: Make and index on key column(s). */ /***********************************************************************/ bool XINDEX::Make(PGLOBAL g, PIXDEF sxp) { /*********************************************************************/ /* Table can be accessed through an index. */ /*********************************************************************/ int k, nk = Nk, rc = RC_OK; int *bof, i, j, n, ndf, nkey; PKPDEF kdfp = Xdp->GetToKeyParts(); bool brc = false; PCOL colp; PFIL filp = Tdbp->GetFilter(); PXCOL kp, addcolp, prev = NULL, kcp = NULL; //PDBUSER dup = (PDBUSER)g->Activityp->Aptr; #if defined(_DEBUG) assert(X || Nk == 1); #endif // _DEBUG /*********************************************************************/ /* Allocate the storage that will contain the keys and the file */ /* positions corresponding to them. */ /*********************************************************************/ if ((n = Tdbp->GetMaxSize(g)) < 0) return true; else if (!n) { Num_K = Ndif = 0; MaxSame = 1; // The if condition was suppressed because this may be an existing // index that is now void because all table lines were deleted. // if (sxp) goto nox; // Truncate eventually existing index file // else // return false; } // endif n if (trace(1)) htrc("XINDEX Make: n=%d\n", n); // File position must be stored Record.Size = n * sizeof(int); if (!PlgDBalloc(g, NULL, Record)) { sprintf(g->Message, MSG(MEM_ALLOC_ERR), "index", n); goto err; // Error } // endif /*********************************************************************/ /* Allocate the KXYCOL blocks used to store column values. */ /*********************************************************************/ for (k = 0; k < Nk; k++) { colp = To_Cols[k]; if (!kdfp) { sprintf(g->Message, MSG(INT_COL_ERROR), (colp) ? colp->GetName() : "???"); goto err; // Error } // endif kdfp kcp = new(g) KXYCOL(this); if (kcp->Init(g, colp, n, true, kdfp->Klen)) goto err; // Error if (prev) { kcp->Previous = prev; prev->Next = kcp; } else To_KeyCol = kcp; prev = kcp; kdfp = kdfp->Next; } // endfor k To_LastCol = prev; if (AddColumns()) { PCOL kolp = To_Cols[0]; // Temporary while imposing Nk = 1 i = 0; // Allocate the accompanying for (colp = Tbxp->GetColumns(); colp; colp = colp->GetNext()) { // Count how many columns to add // for (k = 0; k < Nk; k++) // if (colp == To_Cols[k]) // break; // if (k == nk) if (colp != kolp) i++; } // endfor colp if (i && i < 10) // Should be a parameter for (colp = Tbxp->GetColumns(); colp; colp = colp->GetNext()) { // for (k = 0; k < Nk; k++) // if (colp == To_Cols[k]) // break; // if (k < nk) if (colp == kolp) continue; // This is a key column kcp = new(g) KXYCOL(this); if (kcp->Init(g, colp, n, true, 0)) return true; if (trace(1)) htrc("Adding colp=%p Buf_Type=%d size=%d\n", colp, colp->GetResultType(), n); nk++; prev->Next = kcp; prev = kcp; } // endfor colp } // endif AddColumns #if 0 /*********************************************************************/ /* Get the starting information for progress. */ /*********************************************************************/ dup->Step = (char*)PlugSubAlloc(g, NULL, 128); sprintf((char*)dup->Step, MSG(BUILD_INDEX), Xdp->GetName(), Tdbp->Name); dup->ProgMax = Tdbp->GetProgMax(g); dup->ProgCur = 0; #endif // 0 /*********************************************************************/ /* Standard init: read the file and construct the index table. */ /* Note: reading will be sequential as To_Kindex is not set. */ /*********************************************************************/ for (i = nkey = 0; rc != RC_EF; i++) { #if 0 if (!dup->Step) { strcpy(g->Message, MSG(QUERY_CANCELLED)); throw 99; } // endif Step #endif // 0 /*******************************************************************/ /* Read a valid record from table file. */ /*******************************************************************/ rc = Tdbp->ReadDB(g); // Update progress information // dup->ProgCur = Tdbp->GetProgCur(); // Check return code and do whatever must be done according to it switch (rc) { case RC_OK: if (ApplyFilter(g, filp)) break; // fall through case RC_NF: continue; case RC_EF: goto end_of_file; default: sprintf(g->Message, MSG(RC_READING), rc, Tdbp->Name); goto err; } // endswitch rc /*******************************************************************/ /* Get and Store the file position of the last read record for */ /* future direct access. */ /*******************************************************************/ if (nkey == n) { sprintf(g->Message, MSG(TOO_MANY_KEYS), nkey); return true; } else To_Rec[nkey] = Tdbp->GetRecpos(); if (trace(2)) htrc("Make: To_Rec[%d]=%d\n", nkey, To_Rec[nkey]); /*******************************************************************/ /* Get the keys and place them in the key blocks. */ /*******************************************************************/ for (k = 0, kcp = To_KeyCol; k < nk && kcp; k++, kcp = kcp->Next) { // colp = To_Cols[k]; colp = kcp->Colp; if (!colp->GetStatus(BUF_READ)) colp->ReadColumn(g); else colp->Reset(); kcp->SetValue(colp, nkey); } // endfor k nkey++; // A new valid key was found } // endfor i end_of_file: // Update progress information //dup->ProgCur = Tdbp->GetProgMax(g); /*********************************************************************/ /* Record the Index size and eventually resize memory allocation. */ /*********************************************************************/ if ((Num_K = nkey) < n) { PlgDBrealloc(g, NULL, Record, Num_K * sizeof(int)); for (kcp = To_KeyCol; kcp; kcp = kcp->Next) kcp->ReAlloc(g, Num_K); } // endif Num_K /*********************************************************************/ /* Sort the index so we can use an optimized Find algorithm. */ /* Note: for a unique index we use the non conservative sort */ /* version because normally all index values are different. */ /* This was set at CSORT class construction. */ /* For all indexes, an offset array is made so we can check the */ /* uniqueness of unique indexes. */ /*********************************************************************/ Index.Size = Num_K * sizeof(int); if (!PlgDBalloc(g, NULL, Index)) { sprintf(g->Message, MSG(MEM_ALLOC_ERR), "index", Num_K); goto err; // Error } // endif alloc Offset.Size = (Num_K + 1) * sizeof(int); if (!PlgDBalloc(g, NULL, Offset)) { sprintf(g->Message, MSG(MEM_ALLOC_ERR), "offset", Num_K + 1); goto err; // Error } // endif alloc // We must separate keys and added columns before sorting addcolp = To_LastCol->Next; To_LastCol->Next = NULL; // Call the sort program, it returns the number of distinct values if ((Ndif = Qsort(g, Num_K)) < 0) goto err; // Error during sort if (trace(1)) htrc("Make: Nk=%d n=%d Num_K=%d Ndif=%d addcolp=%p BlkFil=%p X=%p\n", Nk, n, Num_K, Ndif, addcolp, Tdbp->To_BlkFil, X); // Check whether the unique index is unique indeed if (!Mul) { if (Ndif < Num_K) { strcpy(g->Message, MSG(INDEX_NOT_UNIQ)); brc = true; goto err; } else PlgDBfree(Offset); // Not used anymore } // Restore kcp list To_LastCol->Next = addcolp; // Use the index to physically reorder the xindex Srtd = Reorder(g); if (Ndif < Num_K) { // Resize the offset array PlgDBrealloc(g, NULL, Offset, (Ndif + 1) * sizeof(int)); // Initial value of MaxSame MaxSame = Pof[1] - Pof[0]; // Resize the Key array by only keeping the distinct values for (i = 1; i < Ndif; i++) { for (kcp = To_KeyCol; kcp; kcp = kcp->Next) kcp->Move(i, Pof[i]); MaxSame = MY_MAX(MaxSame, Pof[i + 1] - Pof[i]); } // endfor i for (kcp = To_KeyCol; kcp; kcp = kcp->Next) kcp->ReAlloc(g, Ndif); } else { Mul = false; // Current index is unique PlgDBfree(Offset); // Not used anymore MaxSame = 1; // Reset it when remaking an index } // endif Ndif /*********************************************************************/ /* Now do the reduction of the index. Indeed a multi-column index */ /* can be used for only some of the first columns. For instance if */ /* an index is defined for column A, B, C PlugDB can use it for */ /* only the column A or the columns A, B. */ /* What we do here is to reduce the data so column A will contain */ /* only the sorted distinct values of A, B will contain data such */ /* as only distinct values of A,B are stored etc. */ /* This implies that for each column set an offset array is made */ /* except if the subset originally contains unique values. */ /*********************************************************************/ // Update progress information //dup->Step = STEP(REDUCE_INDEX); ndf = Ndif; To_LastCol->Mxs = MaxSame; for (kcp = To_LastCol->Previous; kcp; kcp = kcp->Previous) { if (!(bof = kcp->MakeOffset(g, ndf))) goto err; else *bof = 0; for (n = 0, i = j = 1; i < ndf; i++) for (kp = kcp; kp; kp = kp->Previous) if (kp->Compare(n, i)) { // Values are not equal to last ones bof[j++] = n = i; break; } // endif Compare if (j < ndf) { // Sub-index is multiple bof[j] = ndf; ndf = j; // New number of distinct values // Resize the Key array by only keeping the distinct values for (kp = kcp; kp; kp = kp->Previous) { for (i = 1; i < ndf; i++) kp->Move(i, bof[i]); kp->ReAlloc(g, ndf); } // endif kcp // Resize the offset array kcp->MakeOffset(g, ndf); // Calculate the max same value for this column kcp->Mxs = ColMaxSame(kcp); } else { // Current sub-index is unique kcp->MakeOffset(g, 0); // The offset is not used anymore kcp->Mxs = 1; // Unique } // endif j } // endfor kcp /*********************************************************************/ /* For sorted columns and fixed record size, file position can be */ /* calculated, so the Record array can be discarted. */ /* Not true for DBF tables because of eventual soft deleted lines. */ /* Note: for Num_K = 1 any non null value is Ok. */ /*********************************************************************/ if (Srtd && !filp && Tdbp->Ftype != RECFM_VAR && Tdbp->Ftype != RECFM_CSV && Tdbp->Txfp->GetAmType() != TYPE_AM_DBF) { Incr = (Num_K > 1) ? To_Rec[1] : Num_K; PlgDBfree(Record); } // endif Srtd /*********************************************************************/ /* Check whether a two-tier find algorithm can be implemented. */ /* It is currently implemented only for single key indexes. */ /*********************************************************************/ if (Nk == 1 && ndf >= 65536) { // Implement a two-tier find algorithm for (Sblk = 256; (Sblk * Sblk * 4) < ndf; Sblk *= 2) ; Nblk = (ndf -1) / Sblk + 1; if (To_KeyCol->MakeBlockArray(g, Nblk, Sblk)) goto err; // Error } // endif Num_K nox: /*********************************************************************/ /* No valid record read yet for secondary file. */ /*********************************************************************/ Cur_K = Num_K; /*********************************************************************/ /* Save the xindex so it has not to be recalculated. */ /*********************************************************************/ if (X) { if (SaveIndex(g, sxp)) brc = true; } else { // Dynamic index // Indicate that key column values can be found from KEYCOL's for (kcp = To_KeyCol; kcp; kcp = kcp->Next) kcp->Colp->SetKcol(kcp); Tdbp->SetFilter(NULL); // Not used anymore } // endif X err: // We don't need the index anymore if (X || brc) Close(); if (brc) printf("%s\n", g->Message); return brc; } // end of Make /***********************************************************************/ /* Return the max size of the intermediate column. */ /***********************************************************************/ int XINDEX::ColMaxSame(PXCOL kp) { int *kof, i, ck1, ck2, ckn = 1; PXCOL kcp; // Calculate the max same value for this column for (i = 0; i < kp->Ndf; i++) { ck1 = i; ck2 = i + 1; for (kcp = kp; kcp; kcp = kcp->Next) { if (!(kof = (kcp->Next) ? kcp->Kof : Pof)) break; ck1 = kof[ck1]; ck2 = kof[ck2]; } // endfor kcp ckn = MY_MAX(ckn, ck2 - ck1); } // endfor i return ckn; } // end of ColMaxSame /***********************************************************************/ /* Reorder: use the sort index to reorder the data in storage so */ /* it will be physically sorted and sort index can be removed. */ /***********************************************************************/ bool XINDEX::Reorder(PGLOBAL g __attribute__((unused))) { int i, j, k, n; bool sorted = true; PXCOL kcp; #if 0 PDBUSER dup = (PDBUSER)g->Activityp->Aptr; if (Num_K > 500000) { // Update progress information dup->Step = STEP(REORDER_INDEX); dup->ProgMax = Num_K; dup->ProgCur = 0; } else dup = NULL; #endif // 0 if (!Pex) return Srtd; for (i = 0; i < Num_K; i++) { if (Pex[i] == Num_K) { // Already moved continue; } else if (Pex[i] == i) { // Already placed // if (dup) // dup->ProgCur++; continue; } // endif's Pex sorted = false; for (kcp = To_KeyCol; kcp; kcp = kcp->Next) kcp->Save(i); n = To_Rec[i]; for (j = i;; j = k) { k = Pex[j]; Pex[j] = Num_K; // Mark position as set if (k == i) { for (kcp = To_KeyCol; kcp; kcp = kcp->Next) kcp->Restore(j); To_Rec[j] = n; break; // end of loop } else { for (kcp = To_KeyCol; kcp; kcp = kcp->Next) kcp->Move(j, k); // Move k to j To_Rec[j] = To_Rec[k]; } // endif k // if (dup) // dup->ProgCur++; } // endfor j } // endfor i // The index is not used anymore PlgDBfree(Index); return sorted; } // end of Reorder /***********************************************************************/ /* Save the index 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 XINDEX::SaveIndex(PGLOBAL g, PIXDEF sxp) { PCSZ ftype; char fn[_MAX_PATH]; int n[NZ], nof = (Mul) ? (Ndif + 1) : 0; int id = -1, size = 0; bool sep, rc = false; PXCOL kcp = To_KeyCol; PDOSDEF defp = (PDOSDEF)Tdbp->To_Def; //PDBUSER dup = PlgGetUser(g); //dup->Step = STEP(SAVING_INDEX); //dup->ProgMax = 15 + 16 * Nk; //dup->ProgCur = 0; switch (Tdbp->Ftype) { 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: sprintf(g->Message, MSG(INVALID_FTYPE), Tdbp->Ftype); return true; } // endswitch Ftype if ((sep = defp->GetBoolCatInfo("SepIndex", false))) { // Index is saved in a separate file #if defined(_WIN32) char drive[_MAX_DRIVE]; #else char *drive = NULL; #endif char direc[_MAX_DIR]; char fname[_MAX_FNAME]; _splitpath(defp->GetOfn(), drive, direc, fname, NULL); strcat(strcat(fname, "_"), Xdp->GetName()); _makepath(fn, drive, direc, fname, ftype); sxp = NULL; } else { id = ID; strcat(PlugRemoveType(fn, strcpy(fn, defp->GetOfn())), ftype); } // endif sep PlugSetPath(fn, fn, Tdbp->GetPath()); if (X->Open(g, fn, id, (sxp) ? MODE_INSERT : MODE_WRITE)) { printf("%s\n", g->Message); return true; } // endif Open if (!Ndif) goto end; // Void index /*********************************************************************/ /* Write the index values on the index file. */ /*********************************************************************/ n[0] = ID + MAX_INDX; // To check validity n[1] = Nk; // The number of indexed columns n[2] = nof; // The offset array size or 0 n[3] = Num_K; // The index size n[4] = Incr; // Increment of record positions n[5] = Nblk; n[6] = Sblk; n[7] = Srtd ? 1 : 0; // Values are sorted in the file if (trace(1)) { htrc("Saving index %s\n", Xdp->GetName()); htrc("ID=%d Nk=%d nof=%d Num_K=%d Incr=%d Nblk=%d Sblk=%d Srtd=%d\n", ID, Nk, nof, Num_K, Incr, Nblk, Sblk, Srtd); } // endif trace size = X->Write(g, n, NZ, sizeof(int), rc); //dup->ProgCur = 1; if (Mul) // Write the offset array size += X->Write(g, Pof, nof, sizeof(int), rc); //dup->ProgCur = 5; if (!Incr) // Write the record position array(s) size += X->Write(g, To_Rec, Num_K, sizeof(int), rc); //dup->ProgCur = 15; for (; kcp; kcp = kcp->Next) { n[0] = kcp->Ndf; // Number of distinct sub-values n[1] = (kcp->Kof) ? kcp->Ndf + 1 : 0; // 0 if unique n[2] = (kcp == To_KeyCol) ? Nblk : 0; n[3] = kcp->Klen; // To be checked later n[4] = kcp->Type; // To be checked later size += X->Write(g, n, NW, sizeof(int), rc); // dup->ProgCur += 1; if (n[2]) size += X->Write(g, kcp->To_Bkeys, Nblk, kcp->Klen, rc); // dup->ProgCur += 5; size += X->Write(g, kcp->To_Keys, n[0], kcp->Klen, rc); // dup->ProgCur += 5; if (n[1]) size += X->Write(g, kcp->Kof, n[1], sizeof(int), rc); // dup->ProgCur += 5; } // endfor kcp if (trace(1)) htrc("Index %s saved, Size=%d\n", Xdp->GetName(), size); end: X->Close(fn, id); return rc; } // end of SaveIndex /***********************************************************************/ /* Init: Open and Initialize a Key Index. */ /***********************************************************************/ bool XINDEX::Init(PGLOBAL g) { #if defined(XMAP) if (xmap) return MapInit(g); #endif // XMAP /*********************************************************************/ /* Table will be accessed through an index table. */ /* If sorting is required, this will be done later. */ /*********************************************************************/ PCSZ ftype; char fn[_MAX_PATH]; int k, n, nv[NZ], id = -1; bool estim = false; PCOL colp; PXCOL prev = NULL, kcp = NULL; PDOSDEF defp = (PDOSDEF)Tdbp->To_Def; /*********************************************************************/ /* Get the estimated table size. */ /* Note: for fixed tables we must use cardinality to avoid the call */ /* to MaxBlkSize that could reduce the cardinality value. */ /*********************************************************************/ if (Tdbp->Cardinality(NULL)) { // For DBF tables, Cardinality includes bad or soft deleted lines // that are not included in the index, and can be larger then the // index size. estim = (Tdbp->Ftype == RECFM_DBF || Tdbp->Txfp->GetAmType() == TYPE_AM_ZIP); n = Tdbp->Cardinality(g); // n is exact table size } else { // Variable table not optimized estim = true; // n is an estimate of the size n = Tdbp->GetMaxSize(g); } // endif Cardinality if (n <= 0) return !(n == 0); // n < 0 error, n = 0 void table /*********************************************************************/ /* Get the first key column. */ /*********************************************************************/ if (!Nk || !To_Cols || (!To_Vals && Op != OP_FIRST && Op != OP_FSTDIF)) { strcpy(g->Message, MSG(NO_KEY_COL)); return true; // Error } else colp = To_Cols[0]; switch (Tdbp->Ftype) { 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: sprintf(g->Message, MSG(INVALID_FTYPE), Tdbp->Ftype); return true; } // endswitch Ftype if (defp->SepIndex()) { // Index was saved in a separate file #if defined(_WIN32) char drive[_MAX_DRIVE]; #else char *drive = NULL; #endif char direc[_MAX_DIR]; char fname[_MAX_FNAME]; _splitpath(defp->GetOfn(), drive, direc, fname, NULL); strcat(strcat(fname, "_"), Xdp->GetName()); _makepath(fn, drive, direc, fname, ftype); } else { id = ID; strcat(PlugRemoveType(fn, strcpy(fn, defp->GetOfn())), ftype); } // endif sep PlugSetPath(fn, fn, Tdbp->GetPath()); if (trace(1)) htrc("Index %s file: %s\n", Xdp->GetName(), fn); /*********************************************************************/ /* Open the index file and check its validity. */ /*********************************************************************/ if (X->Open(g, fn, id, MODE_READ)) goto err; // No saved values // Now start the reading process. if (X->Read(g, nv, NZ - 1, sizeof(int))) goto err; if (nv[0] >= MAX_INDX) { // New index format if (X->Read(g, nv + 7, 1, sizeof(int))) goto err; Srtd = nv[7] != 0; nv[0] -= MAX_INDX; } else Srtd = false; if (trace(1)) htrc("nv=%d %d %d %d %d %d %d (%d)\n", nv[0], nv[1], nv[2], nv[3], nv[4], nv[5], nv[6], Srtd); // The test on ID was suppressed because MariaDB can change an index ID // when other indexes are added or deleted if (/*nv[0] != ID ||*/ nv[1] != Nk) { sprintf(g->Message, MSG(BAD_INDEX_FILE), fn); if (trace(1)) htrc("nv[0]=%d ID=%d nv[1]=%d Nk=%d\n", nv[0], ID, nv[1], Nk); goto err; } // endif if (nv[2]) { Mul = true; Ndif = nv[2]; // Allocate the storage that will contain the offset array Offset.Size = Ndif * sizeof(int); if (!PlgDBalloc(g, NULL, Offset)) { sprintf(g->Message, MSG(MEM_ALLOC_ERR), "offset", Ndif); goto err; } // endif if (X->Read(g, Pof, Ndif, sizeof(int))) goto err; Ndif--; // nv[2] is offset size, equal to Ndif + 1 } else { Mul = false; Ndif = nv[3]; } // endif nv[2] if (nv[3] < n && estim) n = nv[3]; // n was just an evaluated max value if (nv[3] != n) { sprintf(g->Message, MSG(OPT_NOT_MATCH), fn); goto err; } // endif Num_K = nv[3]; Incr = nv[4]; Nblk = nv[5]; Sblk = nv[6]; if (!Incr) { /*******************************************************************/ /* Allocate the storage that will contain the file positions. */ /*******************************************************************/ Record.Size = Num_K * sizeof(int); if (!PlgDBalloc(g, NULL, Record)) { sprintf(g->Message, MSG(MEM_ALLOC_ERR), "index", Num_K); goto err; } // endif if (X->Read(g, To_Rec, Num_K, sizeof(int))) goto err; } else Srtd = true; // Sorted positions can be calculated /*********************************************************************/ /* Allocate the KXYCOL blocks used to store column values. */ /*********************************************************************/ for (k = 0; k < Nk; k++) { if (k == Nval) To_LastVal = prev; if (X->Read(g, nv, NW, sizeof(int))) goto err; colp = To_Cols[k]; if (nv[4] != colp->GetResultType() || !colp->GetValue() || (nv[3] != colp->GetValue()->GetClen() && nv[4] != TYPE_STRING)) { sprintf(g->Message, MSG(XCOL_MISMATCH), colp->GetName()); goto err; // Error } // endif GetKey kcp = new(g) KXYCOL(this); if (kcp->Init(g, colp, nv[0], true, (int)nv[3])) goto err; // Error /*******************************************************************/ /* Read the index values from the index file. */ /*******************************************************************/ if (k == 0 && Nblk) { if (kcp->MakeBlockArray(g, Nblk, 0)) goto err; // Read block values if (X->Read(g, kcp->To_Bkeys, Nblk, kcp->Klen)) goto err; } // endif Nblk // Read the entire (small) index if (X->Read(g, kcp->To_Keys, nv[0], kcp->Klen)) goto err; if (nv[1]) { if (!kcp->MakeOffset(g, nv[1] - 1)) goto err; // Read the offset array if (X->Read(g, kcp->Kof, nv[1], sizeof(int))) goto err; } // endif n[1] if (!kcp->Prefix) // Indicate that the key column value can be found from KXYCOL colp->SetKcol(kcp); if (prev) { kcp->Previous = prev; prev->Next = kcp; } else To_KeyCol = kcp; prev = kcp; } // endfor k To_LastCol = prev; if (Mul && prev) { // Last key offset is the index offset kcp->Koff = Offset; kcp->Koff.Sub = true; } // endif Mul X->Close(); /*********************************************************************/ /* No valid record read yet for secondary file. */ /*********************************************************************/ Cur_K = Num_K; return false; err: Close(); return true; } // end of Init #if defined(XMAP) /***********************************************************************/ /* Init: Open and Initialize a Key Index. */ /***********************************************************************/ bool XINDEX::MapInit(PGLOBAL g) { /*********************************************************************/ /* Table will be accessed through an index table. */ /* If sorting is required, this will be done later. */ /*********************************************************************/ const char *ftype; BYTE *mbase; char fn[_MAX_PATH]; int *nv, nv0, k, n, id = -1; bool estim; PCOL colp; PXCOL prev = NULL, kcp = NULL; PDOSDEF defp = (PDOSDEF)Tdbp->To_Def; /*********************************************************************/ /* Get the estimated table size. */ /* Note: for fixed tables we must use cardinality to avoid the call */ /* to MaxBlkSize that could reduce the cardinality value. */ /*********************************************************************/ if (Tdbp->Cardinality(NULL)) { // For DBF tables, Cardinality includes bad or soft deleted lines // that are not included in the index, and can be larger then the // index size. estim = (Tdbp->Ftype == RECFM_DBF); n = Tdbp->Cardinality(g); // n is exact table size } else { // Variable table not optimized estim = true; // n is an estimate of the size n = Tdbp->GetMaxSize(g); } // endif Cardinality if (n <= 0) return !(n == 0); // n < 0 error, n = 0 void table /*********************************************************************/ /* Get the first key column. */ /*********************************************************************/ if (!Nk || !To_Cols || (!To_Vals && Op != OP_FIRST && Op != OP_FSTDIF)) { strcpy(g->Message, MSG(NO_KEY_COL)); return true; // Error } else colp = To_Cols[0]; switch (Tdbp->Ftype) { 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: sprintf(g->Message, MSG(INVALID_FTYPE), Tdbp->Ftype); return true; } // endswitch Ftype if (defp->SepIndex()) { // Index was save in a separate file #if defined(_WIN32) char drive[_MAX_DRIVE]; #else char *drive = NULL; #endif char direc[_MAX_DIR]; char fname[_MAX_FNAME]; _splitpath(defp->GetOfn(), drive, direc, fname, NULL); strcat(strcat(fname, "_"), Xdp->GetName()); _makepath(fn, drive, direc, fname, ftype); } else { id = ID; strcat(PlugRemoveType(fn, strcpy(fn, defp->GetOfn())), ftype); } // endif SepIndex PlugSetPath(fn, fn, Tdbp->GetPath()); if (trace(1)) htrc("Index %s file: %s\n", Xdp->GetName(), fn); /*********************************************************************/ /* Get a view on the part of the index file containing this index. */ /*********************************************************************/ if (!(mbase = (BYTE*)X->FileView(g, fn))) goto err; if (id >= 0) { // Get offset from the header IOFF *noff = (IOFF*)mbase; // Position the memory base at the offset of this index mbase += noff[id].v.Low; } // endif id // Now start the mapping process. nv = (int*)mbase; if (nv[0] >= MAX_INDX) { // New index format Srtd = nv[7] != 0; nv0 = nv[0] - MAX_INDX; mbase += NZ * sizeof(int); } else { Srtd = false; mbase += (NZ - 1) * sizeof(int); nv0 = nv[0]; } // endif nv if (trace(1)) htrc("nv=%d %d %d %d %d %d %d %d\n", nv0, nv[1], nv[2], nv[3], nv[4], nv[5], nv[6], Srtd); // The test on ID was suppressed because MariaDB can change an index ID // when other indexes are added or deleted if (/*nv0 != ID ||*/ nv[1] != Nk) { // Not this index sprintf(g->Message, MSG(BAD_INDEX_FILE), fn); if (trace(1)) htrc("nv0=%d ID=%d nv[1]=%d Nk=%d\n", nv0, ID, nv[1], Nk); goto err; } // endif nv if (nv[2]) { // Set the offset array memory block Offset.Memp = mbase; Offset.Size = nv[2] * sizeof(int); Offset.Sub = true; Mul = true; Ndif = nv[2] - 1; mbase += Offset.Size; } else { Mul = false; Ndif = nv[3]; } // endif nv[2] if (nv[3] < n && estim) n = nv[3]; // n was just an evaluated max value if (nv[3] != n) { sprintf(g->Message, MSG(OPT_NOT_MATCH), fn); goto err; } // endif Num_K = nv[3]; Incr = nv[4]; Nblk = nv[5]; Sblk = nv[6]; if (!Incr) { /*******************************************************************/ /* Point to the storage that contains the file positions. */ /*******************************************************************/ Record.Size = Num_K * sizeof(int); Record.Memp = mbase; Record.Sub = true; mbase += Record.Size; } else Srtd = true; // Sorted positions can be calculated /*********************************************************************/ /* Allocate the KXYCOL blocks used to store column values. */ /*********************************************************************/ for (k = 0; k < Nk; k++) { if (k == Nval) To_LastVal = prev; nv = (int*)mbase; mbase += (NW * sizeof(int)); colp = To_Cols[k]; if (nv[4] != colp->GetResultType() || !colp->GetValue() || (nv[3] != colp->GetValue()->GetClen() && nv[4] != TYPE_STRING)) { sprintf(g->Message, MSG(XCOL_MISMATCH), colp->GetName()); goto err; // Error } // endif GetKey kcp = new(g) KXYCOL(this); if (!(mbase = kcp->MapInit(g, colp, nv, mbase))) goto err; if (!kcp->Prefix) // Indicate that the key column value can be found from KXYCOL colp->SetKcol(kcp); if (prev) { kcp->Previous = prev; prev->Next = kcp; } else To_KeyCol = kcp; prev = kcp; } // endfor k To_LastCol = prev; if (Mul && prev) // Last key offset is the index offset kcp->Koff = Offset; /*********************************************************************/ /* No valid record read yet for secondary file. */ /*********************************************************************/ Cur_K = Num_K; return false; err: Close(); return true; } // end of MapInit #endif // XMAP /***********************************************************************/ /* Get Ndif and Num_K from the index file. */ /***********************************************************************/ bool XINDEX::GetAllSizes(PGLOBAL g,/* int &ndif,*/ int &numk) { PCSZ ftype; char fn[_MAX_PATH]; int nv[NZ], id = -1; // n //bool estim = false; bool rc = true; PDOSDEF defp = (PDOSDEF)Tdbp->To_Def; // ndif = numk = 0; numk = 0; #if 0 /*********************************************************************/ /* Get the estimated table size. */ /* Note: for fixed tables we must use cardinality to avoid the call */ /* to MaxBlkSize that could reduce the cardinality value. */ /*********************************************************************/ if (Tdbp->Cardinality(NULL)) { // For DBF tables, Cardinality includes bad or soft deleted lines // that are not included in the index, and can be larger then the // index size. estim = (Tdbp->Ftype == RECFM_DBF); n = Tdbp->Cardinality(g); // n is exact table size } else { // Variable table not optimized estim = true; // n is an estimate of the size n = Tdbp->GetMaxSize(g); } // endif Cardinality if (n <= 0) return !(n == 0); // n < 0 error, n = 0 void table /*********************************************************************/ /* Check the key part number. */ /*********************************************************************/ if (!Nk) { strcpy(g->Message, MSG(NO_KEY_COL)); return true; // Error } // endif Nk #endif // 0 switch (Tdbp->Ftype) { 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: sprintf(g->Message, MSG(INVALID_FTYPE), Tdbp->Ftype); return true; } // endswitch Ftype if (defp->SepIndex()) { // Index was saved in a separate file #if defined(_WIN32) char drive[_MAX_DRIVE]; #else char *drive = NULL; #endif char direc[_MAX_DIR]; char fname[_MAX_FNAME]; _splitpath(defp->GetOfn(), drive, direc, fname, NULL); strcat(strcat(fname, "_"), Xdp->GetName()); _makepath(fn, drive, direc, fname, ftype); } else { id = ID; strcat(PlugRemoveType(fn, strcpy(fn, defp->GetOfn())), ftype); } // endif sep PlugSetPath(fn, fn, Tdbp->GetPath()); if (trace(1)) htrc("Index %s file: %s\n", Xdp->GetName(), fn); /*********************************************************************/ /* Open the index file and check its validity. */ /*********************************************************************/ if (X->Open(g, fn, id, MODE_READ)) goto err; // No saved values // Get offset from XDB file //if (X->Seek(g, Defoff, Defhigh, SEEK_SET)) // goto err; // Now start the reading process. if (X->Read(g, nv, NZ, sizeof(int))) goto err; if (trace(1)) htrc("nv=%d %d %d %d\n", nv[0], nv[1], nv[2], nv[3]); // The test on ID was suppressed because MariaDB can change an index ID // when other indexes are added or deleted if (/*nv[0] != ID ||*/ nv[1] != Nk) { sprintf(g->Message, MSG(BAD_INDEX_FILE), fn); if (trace(1)) htrc("nv[0]=%d ID=%d nv[1]=%d Nk=%d\n", nv[0], ID, nv[1], Nk); goto err; } // endif #if 0 if (nv[2]) { Mul = true; Ndif = nv[2] - 1; // nv[2] is offset size, equal to Ndif + 1 } else { Mul = false; Ndif = nv[3]; } // endif nv[2] if (nv[3] < n && estim) n = nv[3]; // n was just an evaluated max value if (nv[3] != n) { sprintf(g->Message, MSG(OPT_NOT_MATCH), fn); goto err; } // endif #endif // 0 Num_K = nv[3]; #if 0 if (Nk > 1) { if (nv[2] && X->Seek(g, nv[2] * sizeof(int), 0, SEEK_CUR)) goto err; if (!nv[4] && X->Seek(g, Num_K * sizeof(int), 0, SEEK_CUR)) goto err; if (X->Read(g, nv, NW, sizeof(int))) goto err; PCOL colp = *To_Cols; if (nv[4] != colp->GetResultType() || (nv[3] != colp->GetValue()->GetClen() && nv[4] != TYPE_STRING)) { sprintf(g->Message, MSG(XCOL_MISMATCH), colp->GetName()); goto err; // Error } // endif GetKey Ndif = nv[0]; } // endif Nk #endif // 0 /*********************************************************************/ /* Set size values. */ /*********************************************************************/ //ndif = Ndif; numk = Num_K; rc = false; err: X->Close(); return rc; } // end of GetAllSizes /***********************************************************************/ /* RANGE: Tell how many records exist for a given value, for an array */ /* of values, or in a given value range. */ /***********************************************************************/ int XINDEX::Range(PGLOBAL g, int limit, bool incl) { int i, k, n = 0; PXOB *xp = To_Vals; PXCOL kp = To_KeyCol; OPVAL op = Op; switch (limit) { case 1: Op = (incl) ? OP_GE : OP_GT; break; case 2: Op = (incl) ? OP_GT : OP_GE; break; default: return 0; } // endswitch limit /*********************************************************************/ /* Currently only range of constant values with an EQ operator is */ /* implemented. Find the number of rows for each given values. */ /*********************************************************************/ if (xp[0]->GetType() == TYPE_CONST) { for (i = 0; kp; kp = kp->Next) { kp->Valp->SetValue_pval(xp[i]->GetValue(), !kp->Prefix); if (++i == Nval) break; } // endfor kp if ((k = FastFind()) < Num_K) n = k; // if (limit) // n = (Mul) ? k : kp->Val_K; // else // n = (Mul) ? Pof[kp->Val_K + 1] - k : 1; } else { strcpy(g->Message, MSG(RANGE_NO_JOIN)); n = -1; // Logical error } // endif'f Type Op = op; return n; } // end of Range /***********************************************************************/ /* Return the size of the group (equal values) of the current value. */ /***********************************************************************/ int XINDEX::GroupSize(void) { #if defined(_DEBUG) assert(To_LastCol->Val_K >= 0 && To_LastCol->Val_K < Ndif); #endif // _DEBUG if (Nval == Nk) return (Pof) ? Pof[To_LastCol->Val_K + 1] - Pof[To_LastCol->Val_K] : 1; #if defined(_DEBUG) assert(To_LastVal); #endif // _DEBUG // Index whose only some columns are used int ck1, ck2; ck1 = To_LastVal->Val_K; ck2 = ck1 + 1; #if defined(_DEBUG) assert(ck1 >= 0 && ck1 < To_LastVal->Ndf); #endif // _DEBUG for (PXCOL kcp = To_LastVal; kcp; kcp = kcp->Next) { ck1 = (kcp->Kof) ? kcp->Kof[ck1] : ck1; ck2 = (kcp->Kof) ? kcp->Kof[ck2] : ck2; } // endfor kcp return ck2 - ck1; } // end of GroupSize /***********************************************************************/ /* Find Cur_K and Val_K's of the next distinct value of the index. */ /* Returns false if Ok, true if there are no more different values. */ /***********************************************************************/ bool XINDEX::NextValDif(void) { int curk; PXCOL kcp = (To_LastVal) ? To_LastVal : To_LastCol; if (++kcp->Val_K < kcp->Ndf) { Cur_K = curk = kcp->Val_K; // (Cur_K return is currently not used by SQLGBX) for (PXCOL kp = kcp; kp; kp = kp->Next) Cur_K = (kp->Kof) ? kp->Kof[Cur_K] : Cur_K; } else return true; for (kcp = kcp->Previous; kcp; kcp = kcp->Previous) { if (kcp->Kof && curk < kcp->Kof[kcp->Val_K + 1]) break; // all previous columns have same value curk = ++kcp->Val_K; // This is a break, get new column value } // endfor kcp return false; } // end of NextValDif /***********************************************************************/ /* XINDEX: Find Cur_K and Val_K's of next index entry. */ /* If eq is true next values must be equal to last ones up to Nval. */ /* Returns false if Ok, true if there are no more (equal) values. */ /***********************************************************************/ bool XINDEX::NextVal(bool eq) { int n, neq = Nk + 1, curk; PXCOL kcp; if (Cur_K == Num_K) return true; else curk = ++Cur_K; for (n = Nk, kcp = To_LastCol; kcp; n--, kcp = kcp->Previous) { if (kcp->Kof) { if (curk == kcp->Kof[kcp->Val_K + 1]) neq = n; } else { #ifdef _DEBUG assert(curk == kcp->Val_K + 1); #endif // _DEBUG neq = n; } // endif Kof #ifdef _DEBUG assert(kcp->Val_K < kcp->Ndf); #endif // _DEBUG // If this is not a break... if (neq > n) break; // all previous columns have same value curk = ++kcp->Val_K; // This is a break, get new column value } // endfor kcp // Return true if no more values or, in case of "equal" values, // if the last used column value has changed return (Cur_K == Num_K || (eq && neq <= Nval)); } // end of NextVal /***********************************************************************/ /* XINDEX: Find Cur_K and Val_K's of previous index entry. */ /* Returns false if Ok, true if there are no more values. */ /***********************************************************************/ bool XINDEX::PrevVal(void) { int n, neq = Nk + 1, curk; PXCOL kcp; if (Cur_K == 0) return true; else curk = --Cur_K; for (n = Nk, kcp = To_LastCol; kcp; n--, kcp = kcp->Previous) { if (kcp->Kof) { if (curk < kcp->Kof[kcp->Val_K]) neq = n; } else { #ifdef _DEBUG assert(curk == kcp->Val_K -1); #endif // _DEBUG neq = n; } // endif Kof #ifdef _DEBUG assert(kcp->Val_K >= 0); #endif // _DEBUG // If this is not a break... if (neq > n) break; // all previous columns have same value curk = --kcp->Val_K; // This is a break, get new column value } // endfor kcp return false; } // end of PrevVal /***********************************************************************/ /* XINDEX: Fetch a physical or logical record. */ /***********************************************************************/ int XINDEX::Fetch(PGLOBAL g) { int n; PXCOL kp; if (Num_K == 0) return -1; // means end of file if (trace(2)) htrc("XINDEX Fetch: Op=%d\n", Op); /*********************************************************************/ /* Table read through a sorted index. */ /*********************************************************************/ switch (Op) { case OP_NEXT: // Read next if (NextVal(false)) return -1; // End of indexed file break; case OP_FIRST: // Read first for (Cur_K = 0, kp = To_KeyCol; kp; kp = kp->Next) kp->Val_K = 0; Op = OP_NEXT; break; case OP_SAME: // Read next same // Logically the key values should be the same as before if (NextVal(true)) { Op = OP_EQ; return -2; // no more equal values } // endif NextVal break; case OP_NXTDIF: // Read next dif // while (!NextVal(true)) ; // if (Cur_K >= Num_K) // return -1; // End of indexed file if (NextValDif()) return -1; // End of indexed file break; case OP_FSTDIF: // Read first diff for (Cur_K = 0, kp = To_KeyCol; kp; kp = kp->Next) kp->Val_K = 0; Op = (Mul || Nval < Nk) ? OP_NXTDIF : OP_NEXT; break; case OP_LAST: // Read last key for (Cur_K = Num_K - 1, kp = To_KeyCol; kp; kp = kp->Next) kp->Val_K = kp->Kblp->GetNval() - 1; Op = OP_NEXT; break; case OP_PREV: // Read previous if (PrevVal()) return -1; // End of indexed file break; default: // Should be OP_EQ // if (Tbxp->Key_Rank < 0) { /***************************************************************/ /* Look for the first key equal to the link column values */ /* and return its rank whithin the index table. */ /***************************************************************/ for (n = 0, kp = To_KeyCol; n < Nval && kp; n++, kp = kp->Next) if (kp->InitFind(g, To_Vals[n])) return -1; // No more constant values Nth++; if (trace(2)) htrc("Fetch: Looking for new value Nth=%d\n", Nth); Cur_K = FastFind(); if (Cur_K >= Num_K) /*************************************************************/ /* Rank not whithin index table, signal record not found. */ /*************************************************************/ return -2; else if (Mul || Nval < Nk) Op = OP_SAME; } // endswitch Op /*********************************************************************/ /* If rank is equal to stored rank, record is already there. */ /*********************************************************************/ if (Cur_K == Old_K) return -3; // Means record already there else Old_K = Cur_K; // Store rank of newly read record /*********************************************************************/ /* Return the position of the required record. */ /*********************************************************************/ return (Incr) ? Cur_K * Incr : To_Rec[Cur_K]; } // end of Fetch /***********************************************************************/ /* FastFind: Returns the index of matching record in a join using an */ /* optimized algorithm based on dichotomie and optimized comparing. */ /***********************************************************************/ int XINDEX::FastFind(void) { int curk, sup, inf, i= 0, k, n = 2; PXCOL kp, kcp; //assert((int)nv == Nval); if (Nblk && Op == OP_EQ) { // Look in block values to find in which block to search sup = Nblk; inf = -1; while (n && sup - inf > 1) { i = (inf + sup) >> 1; n = To_KeyCol->CompBval(i); if (n < 0) sup = i; else inf = i; } // endwhile if (inf < 0) return Num_K; // i = inf; inf *= Sblk; if ((sup = inf + Sblk) > To_KeyCol->Ndf) sup = To_KeyCol->Ndf; inf--; } else { inf = -1; sup = To_KeyCol->Ndf; } // endif Nblk if (trace(4)) htrc("XINDEX FastFind: Nblk=%d Op=%d inf=%d sup=%d\n", Nblk, Op, inf, sup); for (k = 0, kcp = To_KeyCol; kcp; kcp = kcp->Next) { while (sup - inf > 1) { i = (inf + sup) >> 1; n = kcp->CompVal(i); if (n < 0) sup = i; else if (n > 0) inf = i; else break; } // endwhile if (n) { if (Op != OP_EQ) { // Currently only OP_GT or OP_GE kcp->Val_K = curk = sup; // Check for value changes in previous key parts for (kp = kcp->Previous; kp; kp = kp->Previous) if (kp->Kof && curk < kp->Kof[kp->Val_K + 1]) break; else curk = ++kp->Val_K; n = 0; } // endif Op break; } // endif n kcp->Val_K = i; if (++k == Nval) { if (Op == OP_GT) { // n is always 0 curk = ++kcp->Val_K; // Increment value by 1 // Check for value changes in previous key parts for (kp = kcp->Previous; kp; kp = kp->Previous) if (kp->Kof && curk < kp->Kof[kp->Val_K + 1]) break; // Not changed else curk = ++kp->Val_K; } // endif Op break; // So kcp remains pointing the last tested block } // endif k if (kcp->Kof) { inf = kcp->Kof[i] - 1; sup = kcp->Kof[i + 1]; } else { inf = i - 1; sup = i + 1; } // endif Kof } // endfor k, kcp if (n) { // Record not found for (kcp = To_KeyCol; kcp; kcp = kcp->Next) kcp->Val_K = kcp->Ndf; // Not a valid value return Num_K; } // endif n for (curk = kcp->Val_K; kcp; kcp = kcp->Next) { kcp->Val_K = curk; curk = (kcp->Kof) ? kcp->Kof[kcp->Val_K] : kcp->Val_K; } // endfor kcp if (trace(4)) htrc("XINDEX FastFind: curk=%d\n", curk); return curk; } // end of FastFind /* -------------------------- XINDXS Class --------------------------- */ /***********************************************************************/ /* XINDXS public constructor. */ /***********************************************************************/ XINDXS::XINDXS(PTDBDOS tdbp, PIXDEF xdp, PXLOAD pxp, PCOL *cp, PXOB *xp) : XINDEX(tdbp, xdp, pxp, cp, xp) { Srtd = To_Cols[0]->GetOpt() == 2; } // end of XINDXS constructor /***********************************************************************/ /* XINDXS compare routine for C Quick/Insertion sort. */ /***********************************************************************/ int XINDXS::Qcompare(int *i1, int *i2) { //num_comp++; return To_KeyCol->Compare(*i1, *i2); } // end of Qcompare /***********************************************************************/ /* Range: Tell how many records exist for given value(s): */ /* If limit=0 return range for these values. */ /* If limit=1 return the start of range. */ /* If limit=2 return the end of range. */ /***********************************************************************/ int XINDXS::Range(PGLOBAL g, int limit, bool incl) { int k, n = 0; PXOB xp = To_Vals[0]; PXCOL kp = To_KeyCol; OPVAL op = Op; switch (limit) { case 1: Op = (incl) ? OP_GE : OP_GT; break; case 2: Op = (incl) ? OP_GT : OP_GE; break; default: Op = OP_EQ; } // endswitch limit /*********************************************************************/ /* Currently only range of constant values with an EQ operator is */ /* implemented. Find the number of rows for each given values. */ /*********************************************************************/ if (xp->GetType() == TYPE_CONST) { kp->Valp->SetValue_pval(xp->GetValue(), !kp->Prefix); k = FastFind(); if (k < Num_K || Op != OP_EQ) { if (limit) n = (Mul) ? k : kp->Val_K; else n = (Mul) ? Pof[kp->Val_K + 1] - k : 1; } } else { strcpy(g->Message, MSG(RANGE_NO_JOIN)); n = -1; // Logical error } // endif'f Type Op = op; return n; } // end of Range /***********************************************************************/ /* Return the size of the group (equal values) of the current value. */ /***********************************************************************/ int XINDXS::GroupSize(void) { #if defined(_DEBUG) assert(To_KeyCol->Val_K >= 0 && To_KeyCol->Val_K < Ndif); #endif // _DEBUG return (Pof) ? Pof[To_KeyCol->Val_K + 1] - Pof[To_KeyCol->Val_K] : 1; } // end of GroupSize /***********************************************************************/ /* XINDXS: Find Cur_K and Val_K of previous index value. */ /* Returns false if Ok, true if there are no more values. */ /***********************************************************************/ bool XINDXS::PrevVal(void) { if (--Cur_K < 0) return true; if (Mul) { if (Cur_K < Pof[To_KeyCol->Val_K]) To_KeyCol->Val_K--; } else To_KeyCol->Val_K = Cur_K; return false; } // end of PrevVal /***********************************************************************/ /* XINDXS: Find Cur_K and Val_K of next index value. */ /* If b is true next value must be equal to last one. */ /* Returns false if Ok, true if there are no more (equal) values. */ /***********************************************************************/ bool XINDXS::NextVal(bool eq) { bool rc; if (To_KeyCol->Val_K == Ndif) return true; if (Mul) { int limit = Pof[To_KeyCol->Val_K + 1]; #ifdef _DEBUG assert(Cur_K < limit); assert(To_KeyCol->Val_K < Ndif); #endif // _DEBUG if (++Cur_K == limit) { To_KeyCol->Val_K++; rc = (eq || limit == Num_K); } else rc = false; } else rc = (To_KeyCol->Val_K = ++Cur_K) == Num_K || eq; return rc; } // end of NextVal /***********************************************************************/ /* XINDXS: Fetch a physical or logical record. */ /***********************************************************************/ int XINDXS::Fetch(PGLOBAL g) { if (Num_K == 0) return -1; // means end of file if (trace(2)) htrc("XINDXS Fetch: Op=%d\n", Op); /*********************************************************************/ /* Table read through a sorted index. */ /*********************************************************************/ switch (Op) { case OP_NEXT: // Read next if (NextVal(false)) return -1; // End of indexed file break; case OP_FIRST: // Read first To_KeyCol->Val_K = Cur_K = 0; Op = OP_NEXT; break; case OP_SAME: // Read next same if (!Mul || NextVal(true)) { Op = OP_EQ; return -2; // No more equal values } // endif Mul break; case OP_NXTDIF: // Read next dif if (++To_KeyCol->Val_K == Ndif) return -1; // End of indexed file Cur_K = Pof[To_KeyCol->Val_K]; break; case OP_FSTDIF: // Read first diff To_KeyCol->Val_K = Cur_K = 0; Op = (Mul) ? OP_NXTDIF : OP_NEXT; break; case OP_LAST: // Read first Cur_K = Num_K - 1; To_KeyCol->Val_K = Ndif - 1; Op = OP_PREV; break; case OP_PREV: // Read previous if (PrevVal()) return -1; // End of indexed file break; default: // Should be OP_EQ /*****************************************************************/ /* Look for the first key equal to the link column values */ /* and return its rank whithin the index table. */ /*****************************************************************/ if (To_KeyCol->InitFind(g, To_Vals[0])) return -1; // No more constant values else Nth++; if (trace(2)) htrc("Fetch: Looking for new value Nth=%d\n", Nth); Cur_K = FastFind(); if (Cur_K >= Num_K) // Rank not whithin index table, signal record not found return -2; else if (Mul) Op = OP_SAME; } // endswitch Op /*********************************************************************/ /* If rank is equal to stored rank, record is already there. */ /*********************************************************************/ if (Cur_K == Old_K) return -3; // Means record already there else Old_K = Cur_K; // Store rank of newly read record /*********************************************************************/ /* Return the position of the required record. */ /*********************************************************************/ return (Incr) ? Cur_K * Incr : To_Rec[Cur_K]; } // end of Fetch /***********************************************************************/ /* FastFind: Returns the index of matching indexed record using an */ /* optimized algorithm based on dichotomie and optimized comparing. */ /***********************************************************************/ int XINDXS::FastFind(void) { int sup, inf, i= 0, n = 2; PXCOL kcp = To_KeyCol; if (Nblk && Op == OP_EQ) { // Look in block values to find in which block to search sup = Nblk; inf = -1; while (n && sup - inf > 1) { i = (inf + sup) >> 1; n = kcp->CompBval(i); if (n < 0) sup = i; else inf = i; } // endwhile if (inf < 0) return Num_K; inf *= Sblk; if ((sup = inf + Sblk) > Ndif) sup = Ndif; inf--; } else { inf = -1; sup = Ndif; } // endif Nblk if (trace(4)) htrc("XINDXS FastFind: Nblk=%d Op=%d inf=%d sup=%d\n", Nblk, Op, inf, sup); while (sup - inf > 1) { i = (inf + sup) >> 1; n = kcp->CompVal(i); if (n < 0) sup = i; else if (n > 0) inf = i; else break; } // endwhile if (!n && Op == OP_GT) { ++i; } else if (n && Op != OP_EQ) { // Currently only OP_GT or OP_GE i = sup; n = 0; } // endif sup if (trace(4)) htrc("XINDXS FastFind: n=%d i=%d\n", n, i); // Loop on kcp because of dynamic indexing for (; kcp; kcp = kcp->Next) kcp->Val_K = i; // Used by FillValue return ((n) ? Num_K : (Mul) ? Pof[i] : i); } // end of FastFind /* -------------------------- XLOAD Class --------------------------- */ /***********************************************************************/ /* XLOAD constructor. */ /***********************************************************************/ XLOAD::XLOAD(void) { Hfile = INVALID_HANDLE_VALUE; NewOff.Val = 0LL; } // end of XLOAD constructor /***********************************************************************/ /* Close the index huge file. */ /***********************************************************************/ void XLOAD::Close(void) { if (Hfile != INVALID_HANDLE_VALUE) { CloseFileHandle(Hfile); Hfile = INVALID_HANDLE_VALUE; } // endif Hfile } // end of Close /* --------------------------- XFILE Class --------------------------- */ /***********************************************************************/ /* XFILE constructor. */ /***********************************************************************/ XFILE::XFILE(void) : XLOAD() { Xfile = NULL; #if defined(XMAP) Mmp = NULL; #endif // XMAP } // end of XFILE constructor /***********************************************************************/ /* Xopen function: opens a file using native API's. */ /***********************************************************************/ bool XFILE::Open(PGLOBAL g, char *filename, int id, MODE mode) { PCSZ pmod; bool rc; IOFF noff[MAX_INDX]; /*********************************************************************/ /* Open the index file according to mode. */ /*********************************************************************/ switch (mode) { case MODE_READ: pmod = "rb"; break; case MODE_WRITE: pmod = "wb"; break; case MODE_INSERT: pmod = "ab"; break; default: sprintf(g->Message, MSG(BAD_FUNC_MODE), "Xopen", mode); return true; } // endswitch mode if (!(Xfile= global_fopen(g, MSGID_OPEN_ERROR_AND_STRERROR, filename, pmod))) { if (trace(1)) htrc("Open: %s\n", g->Message); return true; } // endif Xfile if (mode == MODE_INSERT) { /*******************************************************************/ /* Position the cursor at end of file so ftell returns file size. */ /*******************************************************************/ if (fseek(Xfile, 0, SEEK_END)) { sprintf(g->Message, MSG(FUNC_ERRNO), errno, "Xseek"); return true; } // endif NewOff.v.Low = (int)ftell(Xfile); if (trace(1)) htrc("XFILE Open: NewOff.v.Low=%d\n", NewOff.v.Low); } else if (mode == MODE_WRITE) { if (id >= 0) { // New not sep index file. Write the header. memset(noff, 0, sizeof(noff)); Write(g, noff, sizeof(IOFF), MAX_INDX, rc); fseek(Xfile, 0, SEEK_END); NewOff.v.Low = (int)ftell(Xfile); if (trace(1)) htrc("XFILE Open: NewOff.v.Low=%d\n", NewOff.v.Low); } // endif id } else if (mode == MODE_READ && id >= 0) { // Get offset from the header if (fread(noff, sizeof(IOFF), MAX_INDX, Xfile) != MAX_INDX) { sprintf(g->Message, MSG(XFILE_READERR), errno); return true; } // endif MAX_INDX if (trace(1)) htrc("XFILE Open: noff[%d].v.Low=%d\n", id, noff[id].v.Low); // Position the cursor at the offset of this index if (fseek(Xfile, noff[id].v.Low, SEEK_SET)) { sprintf(g->Message, MSG(FUNC_ERRNO), errno, "Xseek"); return true; } // endif } // endif mode return false; } // end of Open /***********************************************************************/ /* Move into an index file. */ /***********************************************************************/ bool XFILE::Seek(PGLOBAL g, int low, int high __attribute__((unused)), int origin) { #if defined(_DEBUG) assert(high == 0); #endif // !_DEBUG if (fseek(Xfile, low, origin)) { sprintf(g->Message, MSG(FUNC_ERRNO), errno, "Xseek"); return true; } // endif return false; } // end of Seek /***********************************************************************/ /* Read from the index file. */ /***********************************************************************/ bool XFILE::Read(PGLOBAL g, void *buf, int n, int size) { if (fread(buf, size, n, Xfile) != (size_t)n) { sprintf(g->Message, MSG(XFILE_READERR), errno); return true; } // endif size return false; } // end of Read /***********************************************************************/ /* Write on index file, set rc and return the number of bytes written */ /***********************************************************************/ int XFILE::Write(PGLOBAL g, void *buf, int n, int size, bool& rc) { int niw = (int)fwrite(buf, size, n, Xfile); if (niw != n) { sprintf(g->Message, MSG(XFILE_WRITERR), strerror(errno)); rc = true; } // endif size return niw * size; } // end of Write /***********************************************************************/ /* Update the file header and close the index file. */ /***********************************************************************/ void XFILE::Close(char *fn, int id) { if (id >= 0 && fn && Xfile) { fclose(Xfile); if ((Xfile = fopen(fn, "r+b"))) if (!fseek(Xfile, id * sizeof(IOFF), SEEK_SET)) fwrite(&NewOff, sizeof(int), 2, Xfile); } // endif id Close(); } // end of Close /***********************************************************************/ /* Close the index file. */ /***********************************************************************/ void XFILE::Close(void) { XLOAD::Close(); if (Xfile) { fclose(Xfile); Xfile = NULL; } // endif Xfile #if defined(XMAP) if (Mmp && CloseMemMap(Mmp->memory, Mmp->lenL)) printf("Error closing mapped index\n"); #endif // XMAP } // end of Close #if defined(XMAP) /*********************************************************************/ /* Map the entire index file. */ /*********************************************************************/ void *XFILE::FileView(PGLOBAL g, char *fn) { HANDLE h; Mmp = (MMP)PlugSubAlloc(g, NULL, sizeof(MEMMAP)); h = CreateFileMap(g, fn, Mmp, MODE_READ, false); if (h == INVALID_HANDLE_VALUE || (!Mmp->lenH && !Mmp->lenL)) { if (!(*g->Message)) strcpy(g->Message, MSG(FILE_MAP_ERR)); CloseFileHandle(h); // Not used anymore return NULL; // No saved values } // endif h CloseFileHandle(h); // Not used anymore return Mmp->memory; } // end of FileView #endif // XMAP /* -------------------------- XHUGE Class --------------------------- */ /***********************************************************************/ /* Xopen function: opens a file using native API's. */ /***********************************************************************/ bool XHUGE::Open(PGLOBAL g, char *filename, int id, MODE mode) { IOFF noff[MAX_INDX]; if (Hfile != INVALID_HANDLE_VALUE) { sprintf(g->Message, MSG(FILE_OPEN_YET), filename); return true; } // endif if (trace(1)) htrc(" Xopen: filename=%s id=%d mode=%d\n", filename, id, mode); #if defined(_WIN32) LONG high = 0; DWORD rc, drc, access, share, creation; /*********************************************************************/ /* Create the file object according to access mode */ /*********************************************************************/ switch (mode) { case MODE_READ: access = GENERIC_READ; share = FILE_SHARE_READ; creation = OPEN_EXISTING; break; case MODE_WRITE: access = GENERIC_WRITE; share = 0; creation = CREATE_ALWAYS; break; case MODE_INSERT: access = GENERIC_WRITE; share = 0; creation = OPEN_EXISTING; break; default: sprintf(g->Message, MSG(BAD_FUNC_MODE), "Xopen", mode); return true; } // endswitch Hfile = CreateFile(filename, access, share, NULL, creation, FILE_ATTRIBUTE_NORMAL, NULL); if (Hfile == INVALID_HANDLE_VALUE) { rc = GetLastError(); sprintf(g->Message, MSG(OPEN_ERROR), rc, mode, filename); FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, rc, 0, (LPTSTR)filename, sizeof(filename), NULL); strcat(g->Message, filename); return true; } // endif Hfile if (trace(1)) htrc(" access=%p share=%p creation=%d handle=%p fn=%s\n", access, share, creation, Hfile, filename); if (mode == MODE_INSERT) { /*******************************************************************/ /* In Insert mode we must position the cursor at end of file. */ /*******************************************************************/ rc = SetFilePointer(Hfile, 0, &high, FILE_END); if (rc == INVALID_SET_FILE_POINTER && (drc = GetLastError()) != NO_ERROR) { sprintf(g->Message, MSG(ERROR_IN_SFP), drc); CloseHandle(Hfile); Hfile = INVALID_HANDLE_VALUE; return true; } // endif NewOff.v.Low = (int)rc; NewOff.v.High = (int)high; } else if (mode == MODE_WRITE) { if (id >= 0) { // New not sep index file. Write the header. memset(noff, 0, sizeof(noff)); rc = WriteFile(Hfile, noff, sizeof(noff), &drc, NULL); NewOff.v.Low = (int)drc; } // endif id } else if (mode == MODE_READ && id >= 0) { // Get offset from the header rc = ReadFile(Hfile, noff, sizeof(noff), &drc, NULL); if (!rc) { sprintf(g->Message, MSG(XFILE_READERR), GetLastError()); return true; } // endif rc // Position the cursor at the offset of this index rc = SetFilePointer(Hfile, noff[id].v.Low, (PLONG)&noff[id].v.High, FILE_BEGIN); if (rc == INVALID_SET_FILE_POINTER) { sprintf(g->Message, MSG(FUNC_ERRNO), GetLastError(), "SetFilePointer"); return true; } // endif } // endif Mode #else // UNIX int oflag = O_LARGEFILE; // Enable file size > 2G mode_t pmod = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; /*********************************************************************/ /* Create the file object according to access mode */ /*********************************************************************/ switch (mode) { case MODE_READ: oflag |= O_RDONLY; break; case MODE_WRITE: oflag |= O_WRONLY | O_CREAT | O_TRUNC; // pmod = S_IREAD | S_IWRITE; break; case MODE_INSERT: oflag |= (O_WRONLY | O_APPEND); break; default: sprintf(g->Message, MSG(BAD_FUNC_MODE), "Xopen", mode); return true; } // endswitch Hfile= global_open(g, MSGID_OPEN_ERROR_AND_STRERROR, filename, oflag, pmod); if (Hfile == INVALID_HANDLE_VALUE) { /*rc = errno;*/ if (trace(1)) htrc("Open: %s\n", g->Message); return true; } // endif Hfile if (trace(1)) htrc(" oflag=%p mode=%d handle=%d fn=%s\n", oflag, mode, Hfile, filename); if (mode == MODE_INSERT) { /*******************************************************************/ /* Position the cursor at end of file so ftell returns file size. */ /*******************************************************************/ if (!(NewOff.Val = (longlong)lseek64(Hfile, 0LL, SEEK_END))) { sprintf(g->Message, MSG(FUNC_ERRNO), errno, "Seek"); return true; } // endif if (trace(1)) htrc("INSERT: NewOff=%lld\n", NewOff.Val); } else if (mode == MODE_WRITE) { if (id >= 0) { // New not sep index file. Write the header. memset(noff, 0, sizeof(noff)); NewOff.v.Low = write(Hfile, &noff, sizeof(noff)); } // endif id if (trace(1)) htrc("WRITE: NewOff=%lld\n", NewOff.Val); } else if (mode == MODE_READ && id >= 0) { // Get offset from the header if (read(Hfile, noff, sizeof(noff)) != sizeof(noff)) { sprintf(g->Message, MSG(READ_ERROR), "Index file", strerror(errno)); return true; } // endif read if (trace(1)) htrc("noff[%d]=%lld\n", id, noff[id].Val); // Position the cursor at the offset of this index if (lseek64(Hfile, noff[id].Val, SEEK_SET) < 0) { sprintf(g->Message, "(XHUGE)lseek64: %s (%lld)", strerror(errno), noff[id].Val); printf("%s\n", g->Message); // sprintf(g->Message, MSG(FUNC_ERRNO), errno, "Hseek"); return true; } // endif lseek64 } // endif mode #endif // UNIX return false; } // end of Open /***********************************************************************/ /* Go to position in a huge file. */ /***********************************************************************/ bool XHUGE::Seek(PGLOBAL g, int low, int high, int origin) { #if defined(_WIN32) LONG hi = high; DWORD rc = SetFilePointer(Hfile, low, &hi, origin); if (rc == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR) { sprintf(g->Message, MSG(FUNC_ERROR), "Xseek"); return true; } // endif #else // UNIX off64_t pos = (off64_t)low + (off64_t)high * ((off64_t)0x100 * (off64_t)0x1000000); if (lseek64(Hfile, pos, origin) < 0) { sprintf(g->Message, MSG(ERROR_IN_LSK), errno); if (trace(1)) htrc("lseek64 error %d\n", errno); return true; } // endif lseek64 if (trace(1)) htrc("Seek: low=%d high=%d\n", low, high); #endif // UNIX return false; } // end of Seek /***********************************************************************/ /* Read from a huge index file. */ /***********************************************************************/ bool XHUGE::Read(PGLOBAL g, void *buf, int n, int size) { bool rc = false; #if defined(_WIN32) bool brc; DWORD nbr, count = (DWORD)(n * size); brc = ReadFile(Hfile, buf, count, &nbr, NULL); if (brc) { if (nbr != count) { strcpy(g->Message, MSG(EOF_INDEX_FILE)); rc = true; } // endif nbr } else { char buf[256]; DWORD drc = GetLastError(); FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, drc, 0, (LPTSTR)buf, sizeof(buf), NULL); sprintf(g->Message, MSG(READ_ERROR), "index file", buf); rc = true; } // endif brc #else // UNIX ssize_t count = (ssize_t)(n * size); if (trace(1)) htrc("Hfile=%d n=%d size=%d count=%d\n", Hfile, n, size, count); if (read(Hfile, buf, count) != count) { sprintf(g->Message, MSG(READ_ERROR), "Index file", strerror(errno)); if (trace(1)) htrc("read error %d\n", errno); rc = true; } // endif nbr #endif // UNIX return rc; } // end of Read /***********************************************************************/ /* Write on a huge index file. */ /***********************************************************************/ int XHUGE::Write(PGLOBAL g, void *buf, int n, int size, bool& rc) { #if defined(_WIN32) bool brc; DWORD nbw, count = (DWORD)n * (DWORD) size; brc = WriteFile(Hfile, buf, count, &nbw, NULL); if (!brc) { char msg[256]; DWORD drc = GetLastError(); FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, drc, 0, (LPTSTR)msg, sizeof(msg), NULL); sprintf(g->Message, MSG(WRITING_ERROR), "index file", msg); rc = true; } // endif size return (int)nbw; #else // UNIX ssize_t nbw; size_t count = (size_t)n * (size_t)size; nbw = write(Hfile, buf, count); if (nbw != (signed)count) { sprintf(g->Message, MSG(WRITING_ERROR), "index file", strerror(errno)); rc = true; } // endif nbw return (int)nbw; #endif // UNIX } // end of Write /***********************************************************************/ /* Update the file header and close the index file. */ /***********************************************************************/ void XHUGE::Close(char *fn, int id) { if (trace(1)) htrc("XHUGE::Close: fn=%s id=%d NewOff=%lld\n", fn, id, NewOff.Val); #if defined(_WIN32) if (id >= 0 && fn) { CloseFileHandle(Hfile); Hfile = CreateFile(fn, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (Hfile != INVALID_HANDLE_VALUE) if (SetFilePointer(Hfile, id * sizeof(IOFF), NULL, FILE_BEGIN) != INVALID_SET_FILE_POINTER) { DWORD nbw; WriteFile(Hfile, &NewOff, sizeof(IOFF), &nbw, NULL); } // endif SetFilePointer } // endif id #else // !_WIN32 if (id >= 0 && fn) { if (Hfile != INVALID_HANDLE_VALUE) { if (lseek64(Hfile, id * sizeof(IOFF), SEEK_SET) >= 0) { ssize_t nbw = write(Hfile, &NewOff, sizeof(IOFF)); if (nbw != (signed)sizeof(IOFF)) htrc("Error writing index file header: %s\n", strerror(errno)); } else htrc("(XHUGE::Close)lseek64: %s (%d)\n", strerror(errno), id); } else htrc("(XHUGE)error reopening %s: %s\n", fn, strerror(errno)); } // endif id #endif // !_WIN32 XLOAD::Close(); } // end of Close #if defined(XMAP) /***********************************************************************/ /* Don't know whether this is possible for huge files. */ /***********************************************************************/ void *XHUGE::FileView(PGLOBAL g, char *) { strcpy(g->Message, MSG(NO_PART_MAP)); return NULL; } // end of FileView #endif // XMAP /* -------------------------- XXROW Class --------------------------- */ /***********************************************************************/ /* XXROW Public Constructor. */ /***********************************************************************/ XXROW::XXROW(PTDBDOS tdbp) : XXBASE(tdbp, false) { Srtd = true; Tdbp = tdbp; Valp = NULL; } // end of XXROW constructor /***********************************************************************/ /* XXROW Reset: re-initialize a Kindex block. */ /***********************************************************************/ void XXROW::Reset(void) { #if defined(_DEBUG) assert(Tdbp->GetLink()); // This a join index #endif // _DEBUG } // end of Reset /***********************************************************************/ /* Init: Open and Initialize a Key Index. */ /***********************************************************************/ bool XXROW::Init(PGLOBAL g) { /*********************************************************************/ /* Table will be accessed through an index table. */ /* To_Link should not be NULL. */ /*********************************************************************/ if (!Tdbp->GetLink() || Tbxp->GetKnum() != 1) return true; if ((*Tdbp->GetLink())->GetResultType() != TYPE_INT) { strcpy(g->Message, MSG(TYPE_MISMATCH)); return true; } else Valp = (*Tdbp->GetLink())->GetValue(); if ((Num_K = Tbxp->Cardinality(g)) < 0) return true; // Not a fixed file /*********************************************************************/ /* The entire table is indexed, no need to construct the index. */ /*********************************************************************/ Cur_K = Num_K; return false; } // end of Init /***********************************************************************/ /* RANGE: Tell how many record exist in a given value range. */ /***********************************************************************/ int XXROW::Range(PGLOBAL, int limit, bool incl) { int n = Valp->GetIntValue(); switch (limit) { case 1: n += ((incl) ? 0 : 1); break; case 2: n += ((incl) ? 1 : 0); break; default: n = 1; } // endswitch limit return n; } // end of Range /***********************************************************************/ /* XXROW: Fetch a physical or logical record. */ /***********************************************************************/ int XXROW::Fetch(PGLOBAL) { if (Num_K == 0) return -1; // means end of file /*********************************************************************/ /* Look for a key equal to the link column of previous table, */ /* and return its rank whithin the index table. */ /*********************************************************************/ Cur_K = FastFind(); if (Cur_K >= Num_K) /*******************************************************************/ /* Rank not whithin index table, signal record not found. */ /*******************************************************************/ return -2; // Means record not found /*********************************************************************/ /* If rank is equal to stored rank, record is already there. */ /*********************************************************************/ if (Cur_K == Old_K) return -3; // Means record already there else Old_K = Cur_K; // Store rank of newly read record return Cur_K; } // end of Fetch /***********************************************************************/ /* FastFind: Returns the index of matching record in a join. */ /***********************************************************************/ int XXROW::FastFind(void) { int n = Valp->GetIntValue(); if (n < 0) return (Op == OP_EQ) ? (-1) : 0; else if (n > Num_K) return Num_K; else return (Op == OP_GT) ? n : (n - 1); } // end of FastFind /* ------------------------- KXYCOL Classes -------------------------- */ /***********************************************************************/ /* KXYCOL public constructor. */ /***********************************************************************/ KXYCOL::KXYCOL(PKXBASE kp) : To_Keys(Keys.Memp), To_Bkeys(Bkeys.Memp), Kof((CPINT&)Koff.Memp) { Next = NULL; Previous = NULL; Kxp = kp; Colp = NULL; IsSorted = false; Asc = true; Keys = Nmblk; Kblp = NULL; Bkeys = Nmblk; Blkp = NULL; Valp = NULL; Klen = 0; Kprec = 0; Type = TYPE_ERROR; Prefix = false; Koff = Nmblk; Val_K = 0; Ndf = 0; Mxs = 0; } // end of KXYCOL constructor /***********************************************************************/ /* KXYCOL Init: initialize and allocate storage. */ /* Key length kln can be smaller than column length for CHAR columns. */ /***********************************************************************/ bool KXYCOL::Init(PGLOBAL g, PCOL colp, int n, bool sm, int kln) { int len = colp->GetLength(), prec = colp->GetScale(); bool un = colp->IsUnsigned(); // Currently no indexing on NULL columns if (colp->IsNullable() && kln) { sprintf(g->Message, "Cannot index nullable column %s", colp->GetName()); return true; } // endif nullable if (kln && len > kln && colp->GetResultType() == TYPE_STRING) { len = kln; Prefix = true; } // endif kln if (trace(1)) htrc("KCOL(%p) Init: col=%s n=%d type=%d sm=%d\n", this, colp->GetName(), n, colp->GetResultType(), sm); // Allocate the Value object used when moving items Type = colp->GetResultType(); if (!(Valp = AllocateValue(g, Type, len, prec, un))) return true; Klen = Valp->GetClen(); Keys.Size = (size_t)n * (size_t)Klen; if (!PlgDBalloc(g, NULL, Keys)) { sprintf(g->Message, MSG(KEY_ALLOC_ERROR), Klen, n); return true; // Error } // endif // Allocate the Valblock. The last parameter is to have rows filled // by blanks (if true) or keep the zero ending char (if false). // Currently we set it to true to be compatible with QRY blocks, // and the one before last is to enable length/type checking, set to // true if not a prefix key. Kblp = AllocValBlock(g, To_Keys, Type, n, len, prec, !Prefix, true, un); Asc = sm; // Sort mode: Asc=true Desc=false Ndf = n; // Store this information to avoid sorting when already done if (Asc) IsSorted = colp->GetOpt() == 2; //SetNulls(colp->IsNullable()); for when null columns will be indexable Colp = colp; return false; } // end of Init #if defined(XMAP) /***********************************************************************/ /* KXYCOL MapInit: initialize and address storage. */ /* Key length kln can be smaller than column length for CHAR columns. */ /***********************************************************************/ BYTE* KXYCOL::MapInit(PGLOBAL g, PCOL colp, int *n, BYTE *m) { int len = colp->GetLength(), prec = colp->GetScale(); bool un = colp->IsUnsigned(); if (n[3] && colp->GetLength() > n[3] && colp->GetResultType() == TYPE_STRING) { len = n[3]; Prefix = true; } // endif kln Type = colp->GetResultType(); if (trace(1)) htrc("MapInit(%p): colp=%p type=%d n=%d len=%d m=%p\n", this, colp, Type, n[0], len, m); // Allocate the Value object used when moving items Valp = AllocateValue(g, Type, len, prec, un); Klen = Valp->GetClen(); if (n[2]) { Bkeys.Size = n[2] * Klen; Bkeys.Memp = m; Bkeys.Sub = true; // Allocate the Valblk containing initial block key values Blkp = AllocValBlock(g, To_Bkeys, Type, n[2], len, prec, true, true, un); } // endif nb Keys.Size = n[0] * Klen; Keys.Memp = m + Bkeys.Size; Keys.Sub = true; // Allocate the Valblock. Last two parameters are to have rows filled // by blanks (if true) or keep the zero ending char (if false). // Currently we set it to true to be compatible with QRY blocks, // and last one to enable type checking (no conversion). Kblp = AllocValBlock(g, To_Keys, Type, n[0], len, prec, !Prefix, true, un); if (n[1]) { Koff.Size = n[1] * sizeof(int); Koff.Memp = m + Bkeys.Size + Keys.Size; Koff.Sub = true; } // endif n[1] Ndf = n[0]; //IsSorted = colp->GetOpt() < 0; IsSorted = false; Colp = colp; return m + Bkeys.Size + Keys.Size + Koff.Size; } // end of MapInit #endif // XMAP /***********************************************************************/ /* Allocate the offset block used by intermediate key columns. */ /***********************************************************************/ int *KXYCOL::MakeOffset(PGLOBAL g, int n) { if (!Kof) { // Calculate the initial size of the offset Koff.Size = (n + 1) * sizeof(int); // Allocate the required memory if (!PlgDBalloc(g, NULL, Koff)) { strcpy(g->Message, MSG(KEY_ALLOC_ERR)); return NULL; // Error } // endif } else if (n) { // This is a reallocation call PlgDBrealloc(g, NULL, Koff, (n + 1) * sizeof(int)); } else PlgDBfree(Koff); return (int*)Kof; } // end of MakeOffset /***********************************************************************/ /* Make a front end array of key values that are the first value of */ /* each blocks (of size n). This to reduce paging in FastFind. */ /***********************************************************************/ bool KXYCOL::MakeBlockArray(PGLOBAL g, int nb, int size) { int i, k; // Calculate the size of the block array in the index Bkeys.Size = nb * Klen; // Allocate the required memory if (!PlgDBalloc(g, NULL, Bkeys)) { sprintf(g->Message, MSG(KEY_ALLOC_ERROR), Klen, nb); return true; // Error } // endif // Allocate the Valblk used to contains initial block key values Blkp = AllocValBlock(g, To_Bkeys, Type, nb, Klen, Kprec); // Populate the array with values for (i = k = 0; i < nb; i++, k += size) Blkp->SetValue(Kblp, i, k); return false; } // end of MakeBlockArray /***********************************************************************/ /* KXYCOL SetValue: read column value for nth array element. */ /***********************************************************************/ void KXYCOL::SetValue(PCOL colp, int i) { #if defined(_DEBUG) assert (Kblp != NULL); #endif Kblp->SetValue(colp->GetValue(), i); } // end of SetValue /***********************************************************************/ /* InitFind: initialize finding the rank of column value in index. */ /***********************************************************************/ bool KXYCOL::InitFind(PGLOBAL g, PXOB xp) { if (xp->GetType() == TYPE_CONST) { if (Kxp->Nth) return true; Valp->SetValue_pval(xp->GetValue(), !Prefix); } else { xp->Reset(); xp->Eval(g); Valp->SetValue_pval(xp->GetValue(), false); } // endif Type if (trace(2)) { char buf[32]; htrc("KCOL InitFind: value=%s\n", Valp->GetCharString(buf)); } // endif trace return false; } // end of InitFind #if 0 /***********************************************************************/ /* InitBinFind: initialize Value to the value pointed by vp. */ /***********************************************************************/ void KXYCOL::InitBinFind(void *vp) { Valp->SetBinValue(vp); } // end of InitBinFind #endif // 0 /***********************************************************************/ /* KXYCOL FillValue: called by COLBLK::Eval when a column value is */ /* already in storage in the corresponding KXYCOL. */ /***********************************************************************/ void KXYCOL::FillValue(PVAL valp) { valp->SetValue_pvblk(Kblp, Val_K); // Set null when applicable (NIY) //if (valp->GetNullable()) // valp->SetNull(valp->IsZero()); } // end of FillValue /***********************************************************************/ /* KXYCOL: Compare routine for one numeric value. */ /***********************************************************************/ int KXYCOL::Compare(int i1, int i2) { // Do the actual comparison between values. int k = Kblp->CompVal(i1, i2); if (trace(4)) htrc("Compare done result=%d\n", k); return (Asc) ? k : -k; } // end of Compare /***********************************************************************/ /* KXYCOL: Compare the ith key to the stored Value. */ /***********************************************************************/ int KXYCOL::CompVal(int i) { // Do the actual comparison between numerical values. if (trace(4)) { int k = (int)Kblp->CompVal(Valp, (int)i); htrc("Compare done result=%d\n", k); return k; } else return Kblp->CompVal(Valp, i); } // end of CompVal /***********************************************************************/ /* KXYCOL: Compare the key to the stored block value. */ /***********************************************************************/ int KXYCOL::CompBval(int i) { // Do the actual comparison between key values. return Blkp->CompVal(Valp, i); } // end of CompBval /***********************************************************************/ /* KXYCOL ReAlloc: ReAlloc To_Data if it is not suballocated. */ /***********************************************************************/ void KXYCOL::ReAlloc(PGLOBAL g, int n) { PlgDBrealloc(g, NULL, Keys, n * Klen); Kblp->ReAlloc(To_Keys, n); Ndf = n; } // end of ReAlloc /***********************************************************************/ /* KXYCOL FreeData: Free To_Keys if it is not suballocated. */ /***********************************************************************/ void KXYCOL::FreeData(void) { PlgDBfree(Keys); Kblp = NULL; PlgDBfree(Bkeys); Blkp = NULL; PlgDBfree(Koff); Ndf = 0; } // end of FreeData