1 /***************** Xindex C++ Class Xindex Code (.CPP) *****************/
2 /*  Name: XINDEX.CPP  Version 3.0                                      */
3 /*                                                                     */
4 /*  (C) Copyright to the author Olivier BERTRAND          2004-2017    */
5 /*                                                                     */
6 /*  This file contains the class XINDEX implementation code.           */
7 /***********************************************************************/
8 
9 /***********************************************************************/
10 /*  Include relevant sections of the System header files.              */
11 /***********************************************************************/
12 #include "my_global.h"
13 #if defined(_WIN32)
14 #include <io.h>
15 #include <fcntl.h>
16 #include <errno.h>
17 //#include <windows.h>
18 #else   // !_WIN32
19 #if defined(UNIX)
20 #include <sys/types.h>
21 #include <sys/stat.h>
22 #include <errno.h>
23 #include <unistd.h>
24 #else   // !UNIX
25 #include <io.h>
26 #endif  // !UNIX
27 #include <fcntl.h>
28 #endif  // !_WIN32
29 
30 /***********************************************************************/
31 /*  Include required application header files                          */
32 /*  global.h    is header containing all global Plug declarations.     */
33 /*  plgdbsem.h  is header containing the DB applic. declarations.      */
34 /*  kindex.h    is header containing the KINDEX class definition.      */
35 /***********************************************************************/
36 #include "global.h"
37 #include "plgdbsem.h"
38 #include "osutil.h"
39 #include "maputil.h"
40 //nclude "filter.h"
41 #include "tabcol.h"
42 #include "xindex.h"
43 #include "xobject.h"
44 //nclude "scalfnc.h"
45 //nclude "array.h"
46 #include "filamtxt.h"
47 #include "tabdos.h"
48 #if defined(VCT_SUPPORT)
49 #include "tabvct.h"
50 #endif   // VCT_SUPPORT
51 
52 /***********************************************************************/
53 /*  Macro or external routine definition                               */
54 /***********************************************************************/
55 #define NZ 8
56 #define NW 5
57 #define MAX_INDX 10
58 #ifndef INVALID_SET_FILE_POINTER
59 #define INVALID_SET_FILE_POINTER  0xFFFFFFFF
60 #endif
61 
62 /***********************************************************************/
63 /*  DB external variables.                                             */
64 /***********************************************************************/
65 extern MBLOCK Nmblk;                /* Used to initialize MBLOCK's     */
66 #if defined(XMAP)
67 extern my_bool xmap;
68 #endif   // XMAP
69 
70 /***********************************************************************/
71 /*  Last two parameters are true to enable type checking, and last one */
72 /*  to have rows filled by blanks to be compatible with QRY blocks.    */
73 /***********************************************************************/
74 PVBLK AllocValBlock(PGLOBAL, void *, int, int, int, int,
75                     bool check = true, bool blank = true, bool un = false);
76 
77 /***********************************************************************/
78 /*  Check whether we have to create/update permanent indexes.          */
79 /***********************************************************************/
PlgMakeIndex(PGLOBAL g,PSZ name,PIXDEF pxdf,bool add)80 int PlgMakeIndex(PGLOBAL g, PSZ name, PIXDEF pxdf, bool add)
81   {
82   int     rc;
83   PTABLE  tablep;
84   PTDB    tdbp;
85   PCATLG  cat = PlgGetCatalog(g, true);
86 
87   /*********************************************************************/
88   /*  Open a new table in mode read and with only the keys columns.    */
89   /*********************************************************************/
90   tablep = new(g) XTAB(name);
91 
92   if (!(tdbp = cat->GetTable(g, tablep)))
93     rc = RC_NF;
94   else if (!tdbp->GetDef()->Indexable()) {
95     sprintf(g->Message, MSG(TABLE_NO_INDEX), name);
96     rc = RC_NF;
97   } else if ((rc = ((PTDBASE)tdbp)->MakeIndex(g, pxdf, add)) == RC_INFO)
98     rc = RC_OK;            // No or remote index
99 
100   return rc;
101   } // end of PlgMakeIndex
102 
103 /* -------------------------- Class INDEXDEF ------------------------- */
104 
105 /***********************************************************************/
106 /*  INDEXDEF Constructor.                                              */
107 /***********************************************************************/
INDEXDEF(char * name,bool uniq,int n)108 INDEXDEF::INDEXDEF(char *name, bool uniq, int n)
109   {
110 //To_Def = NULL;
111   Next = NULL;
112   ToKeyParts = NULL;
113   Name = name;
114   Unique = uniq;
115   Invalid = false;
116   AutoInc = false;
117   Dynamic = false;
118   Mapped = false;
119   Nparts = 0;
120   ID = n;
121 //Offset = 0;
122 //Offhigh = 0;
123 //Size = 0;
124   MaxSame = 1;
125   } // end of INDEXDEF constructor
126 
127 /***********************************************************************/
128 /*  Set the max same values for each colum after making the index.     */
129 /***********************************************************************/
SetMxsame(PXINDEX x)130 void INDEXDEF::SetMxsame(PXINDEX x)
131   {
132   PKPDEF  kdp;
133   PXCOL   xcp;
134 
135   for (kdp = ToKeyParts, xcp = x->To_KeyCol;
136        kdp && xcp; kdp = kdp->Next, xcp = xcp->Next)
137     kdp->Mxsame = xcp->Mxs;
138   } // end of SetMxsame
139 
140 /* -------------------------- Class KPARTDEF ------------------------- */
141 
142 /***********************************************************************/
143 /*  KPARTDEF Constructor.                                              */
144 /***********************************************************************/
KPARTDEF(PSZ name,int n)145 KPARTDEF::KPARTDEF(PSZ name, int n)
146   {
147   Next = NULL;
148   Name = name;
149   Mxsame = 0;
150   Ncol = n;
151   Klen = 0;
152   } // end of KPARTDEF constructor
153 
154 /* -------------------------- XXBASE Class --------------------------- */
155 
156 /***********************************************************************/
157 /*  XXBASE public constructor.                                         */
158 /***********************************************************************/
XXBASE(PTDBDOS tbxp,bool b)159 XXBASE::XXBASE(PTDBDOS tbxp, bool b) : CSORT(b),
160         To_Rec((int*&)Record.Memp)
161   {
162   Tbxp = tbxp;
163   Record = Nmblk;
164   Cur_K = -1;
165   Old_K = -1;
166   Num_K = 0;
167   Ndif = 0;
168   Bot = Top = Inf = Sup = 0;
169   Op = OP_EQ;
170   To_KeyCol = NULL;
171   Mul = false;
172   Srtd = false;
173   Dynamic = false;
174   Val_K = -1;
175   Nblk = Sblk = 0;
176   Thresh = 7;
177   ID = -1;
178   Nth = 0;
179   } // end of XXBASE constructor
180 
181 /***********************************************************************/
182 /*  Make file output of XINDEX contents.                               */
183 /***********************************************************************/
Printf(PGLOBAL,FILE * f,uint n)184 void XXBASE::Printf(PGLOBAL, FILE *f, uint n)
185   {
186   char m[64];
187 
188   memset(m, ' ', n);                    // Make margin string
189   m[n] = '\0';
190   fprintf(f, "%sXINDEX: Tbxp=%p Num=%d\n", m, Tbxp, Num_K);
191   } // end of Printf
192 
193 /***********************************************************************/
194 /*  Make string output of XINDEX contents.                             */
195 /***********************************************************************/
Prints(PGLOBAL,char * ps,uint z)196 void XXBASE::Prints(PGLOBAL, char *ps, uint z)
197   {
198   *ps = '\0';
199   strncat(ps, "Xindex", z);
200   } // end of Prints
201 
202 /* -------------------------- XINDEX Class --------------------------- */
203 
204 /***********************************************************************/
205 /*  XINDEX public constructor.                                         */
206 /***********************************************************************/
XINDEX(PTDBDOS tdbp,PIXDEF xdp,PXLOAD pxp,PCOL * cp,PXOB * xp,int k)207 XINDEX::XINDEX(PTDBDOS tdbp, PIXDEF xdp, PXLOAD pxp, PCOL *cp, PXOB *xp, int k)
208       : XXBASE(tdbp, !xdp->IsUnique())
209   {
210   Xdp = xdp;
211   ID = xdp->GetID();
212   Tdbp = tdbp;
213   X = pxp;
214   To_LastCol = NULL;
215   To_LastVal = NULL;
216   To_Cols = cp;
217   To_Vals = xp;
218   Mul = !xdp->IsUnique();
219   Srtd = false;
220   Nk = xdp->GetNparts();
221   Nval = (k) ? k : Nk;
222   Incr = 0;
223 //Defoff = xdp->GetOffset();
224 //Defhigh = xdp->GetOffhigh();
225 //Size = xdp->GetSize();
226   MaxSame = xdp->GetMaxSame();
227   } // end of XINDEX constructor
228 
229 /***********************************************************************/
230 /*  XINDEX Reset: re-initialize a Xindex block.                        */
231 /***********************************************************************/
Reset(void)232 void XINDEX::Reset(void)
233   {
234   for (PXCOL kp = To_KeyCol; kp; kp = kp->Next)
235     kp->Val_K = kp->Ndf;
236 
237   Cur_K = Num_K;
238   Old_K = -1;  // Needed to avoid not setting CurBlk for Update
239   Op = (Op == OP_FIRST  || Op == OP_NEXT)   ? OP_FIRST  :
240        (Op == OP_FSTDIF || Op == OP_NXTDIF) ? OP_FSTDIF : OP_EQ;
241   Nth = 0;
242   } // end of Reset
243 
244 /***********************************************************************/
245 /*  XINDEX Close: terminate index and free all allocated data.         */
246 /*  Do not reset values that are used at return to make.               */
247 /***********************************************************************/
Close(void)248 void XINDEX::Close(void)
249   {
250   // Close file or view of file
251   if (X)
252     X->Close();
253 
254   // De-allocate data
255   PlgDBfree(Record);
256   PlgDBfree(Index);
257   PlgDBfree(Offset);
258 
259   for (PXCOL kcp = To_KeyCol; kcp; kcp = kcp->Next) {
260     // Column values cannot be retrieved from key anymore
261     if (kcp->Colp)
262       kcp->Colp->SetKcol(NULL);
263 
264     // De-allocate Key data
265     kcp->FreeData();
266     } // endfor kcp
267 
268   } // end of Close
269 
270 /***********************************************************************/
271 /*  XINDEX compare routine for C Quick/Insertion sort.                 */
272 /***********************************************************************/
Qcompare(int * i1,int * i2)273 int XINDEX::Qcompare(int *i1, int *i2)
274   {
275   int  k;
276   PXCOL kcp;
277 
278   for (kcp = To_KeyCol, k = 0; kcp; kcp = kcp->Next)
279     if ((k = kcp->Compare(*i1, *i2)))
280       break;
281 
282 //num_comp++;
283   return k;
284   } // end of Qcompare
285 
286 /***********************************************************************/
287 /*  AddColumns: here we try to determine whether it is worthwhile to   */
288 /*  add to the keys the values of the columns selected for this table. */
289 /*  Sure enough, it is done while records are read and permit to avoid */
290 /*  reading the table while doing the join (Dynamic index only)        */
291 /***********************************************************************/
AddColumns(void)292 bool XINDEX::AddColumns(void)
293   {
294   if (!Dynamic)
295     return false;     // Not applying to static index
296   else if (IsMul())
297     return false;     // Not done yet for multiple index
298 #if defined(VCT_SUPPORT)
299 	else if (Tbxp->GetAmType() == TYPE_AM_VCT && ((PTDBVCT)Tbxp)->IsSplit())
300     return false;     // This would require to read additional files
301 #endif   // VCT_SUPPORT
302 	else
303     return true;
304 
305   } // end of AddColumns
306 
307 /***********************************************************************/
308 /*  Make: Make and index on key column(s).                             */
309 /***********************************************************************/
Make(PGLOBAL g,PIXDEF sxp)310 bool XINDEX::Make(PGLOBAL g, PIXDEF sxp)
311   {
312   /*********************************************************************/
313   /*  Table can be accessed through an index.                          */
314   /*********************************************************************/
315   int     k, nk = Nk, rc = RC_OK;
316   int    *bof, i, j, n, ndf, nkey;
317   PKPDEF  kdfp = Xdp->GetToKeyParts();
318   bool    brc = false;
319   PCOL    colp;
320   PFIL    filp = Tdbp->GetFilter();
321   PXCOL   kp, addcolp, prev = NULL, kcp = NULL;
322 //PDBUSER dup = (PDBUSER)g->Activityp->Aptr;
323 
324 #if defined(_DEBUG)
325   assert(X || Nk == 1);
326 #endif   // _DEBUG
327 
328   /*********************************************************************/
329   /*  Allocate the storage that will contain the keys and the file     */
330   /*  positions corresponding to them.                                 */
331   /*********************************************************************/
332   if ((n = Tdbp->GetMaxSize(g)) < 0)
333     return true;
334   else if (!n) {
335     Num_K = Ndif = 0;
336     MaxSame = 1;
337 
338     // The if condition was suppressed because this may be an existing
339     // index that is now void because all table lines were deleted.
340 //  if (sxp)
341       goto nox;            // Truncate eventually existing index file
342 //  else
343 //    return false;
344 
345     } // endif n
346 
347   if (trace(1))
348     htrc("XINDEX Make: n=%d\n", n);
349 
350   // File position must be stored
351   Record.Size = n * sizeof(int);
352 
353   if (!PlgDBalloc(g, NULL, Record)) {
354     sprintf(g->Message, MSG(MEM_ALLOC_ERR), "index", n);
355     goto err;    // Error
356     } // endif
357 
358   /*********************************************************************/
359   /*  Allocate the KXYCOL blocks used to store column values.          */
360   /*********************************************************************/
361   for (k = 0; k < Nk; k++) {
362     colp = To_Cols[k];
363 
364     if (!kdfp) {
365       sprintf(g->Message, MSG(INT_COL_ERROR),
366                           (colp) ? colp->GetName() : "???");
367       goto err;    // Error
368       } // endif kdfp
369 
370     kcp = new(g) KXYCOL(this);
371 
372     if (kcp->Init(g, colp, n, true, kdfp->Klen))
373       goto err;    // Error
374 
375     if (prev) {
376       kcp->Previous = prev;
377       prev->Next = kcp;
378     } else
379       To_KeyCol = kcp;
380 
381     prev = kcp;
382     kdfp = kdfp->Next;
383     } // endfor k
384 
385   To_LastCol = prev;
386 
387   if (AddColumns()) {
388     PCOL kolp = To_Cols[0];    // Temporary while imposing Nk = 1
389 
390     i = 0;
391 
392     // Allocate the accompanying
393     for (colp = Tbxp->GetColumns(); colp; colp = colp->GetNext()) {
394       // Count how many columns to add
395 //    for (k = 0; k < Nk; k++)
396 //      if (colp == To_Cols[k])
397 //        break;
398 
399 //    if (k == nk)
400       if (colp != kolp)
401         i++;
402 
403       } // endfor colp
404 
405     if (i && i < 10)                  // Should be a parameter
406       for (colp = Tbxp->GetColumns(); colp; colp = colp->GetNext()) {
407 //      for (k = 0; k < Nk; k++)
408 //        if (colp == To_Cols[k])
409 //          break;
410 
411 //      if (k < nk)
412         if (colp == kolp)
413           continue;                   // This is a key column
414 
415         kcp = new(g) KXYCOL(this);
416 
417         if (kcp->Init(g, colp, n, true, 0))
418           return true;
419 
420         if (trace(1))
421           htrc("Adding colp=%p Buf_Type=%d size=%d\n",
422                 colp, colp->GetResultType(), n);
423 
424         nk++;
425         prev->Next = kcp;
426         prev = kcp;
427         } // endfor colp
428 
429     } // endif AddColumns
430 
431 #if 0
432   /*********************************************************************/
433   /*  Get the starting information for progress.                       */
434   /*********************************************************************/
435   dup->Step = (char*)PlugSubAlloc(g, NULL, 128);
436   sprintf((char*)dup->Step, MSG(BUILD_INDEX), Xdp->GetName(), Tdbp->Name);
437   dup->ProgMax = Tdbp->GetProgMax(g);
438   dup->ProgCur = 0;
439 #endif // 0
440 
441   /*********************************************************************/
442   /*  Standard init: read the file and construct the index table.      */
443   /*  Note: reading will be sequential as To_Kindex is not set.        */
444   /*********************************************************************/
445   for (i = nkey = 0; rc != RC_EF; i++) {
446 #if 0
447     if (!dup->Step) {
448       strcpy(g->Message, MSG(QUERY_CANCELLED));
449 			throw 99;
450 	} // endif Step
451 #endif // 0
452 
453     /*******************************************************************/
454     /*  Read a valid record from table file.                           */
455     /*******************************************************************/
456     rc = Tdbp->ReadDB(g);
457 
458     // Update progress information
459 //  dup->ProgCur = Tdbp->GetProgCur();
460 
461     // Check return code and do whatever must be done according to it
462     switch (rc) {
463       case RC_OK:
464         if (ApplyFilter(g, filp))
465           break;
466 
467         // fall through
468       case RC_NF:
469         continue;
470       case RC_EF:
471         goto end_of_file;
472       default:
473         sprintf(g->Message, MSG(RC_READING), rc, Tdbp->Name);
474         goto err;
475       } // endswitch rc
476 
477     /*******************************************************************/
478     /*  Get and Store the file position of the last read record for    */
479     /*  future direct access.                                          */
480     /*******************************************************************/
481     if (nkey == n) {
482       sprintf(g->Message, MSG(TOO_MANY_KEYS), nkey);
483       return true;
484     } else
485       To_Rec[nkey] = Tdbp->GetRecpos();
486 
487     if (trace(2))
488       htrc("Make: To_Rec[%d]=%d\n", nkey, To_Rec[nkey]);
489 
490     /*******************************************************************/
491     /*  Get the keys and place them in the key blocks.                 */
492     /*******************************************************************/
493     for (k = 0, kcp = To_KeyCol;
494          k < nk && kcp;
495          k++, kcp = kcp->Next) {
496 //    colp = To_Cols[k];
497       colp = kcp->Colp;
498 
499       if (!colp->GetStatus(BUF_READ))
500         colp->ReadColumn(g);
501       else
502         colp->Reset();
503 
504       kcp->SetValue(colp, nkey);
505       } // endfor k
506 
507     nkey++;                    // A new valid key was found
508     } // endfor i
509 
510  end_of_file:
511 
512   // Update progress information
513 //dup->ProgCur = Tdbp->GetProgMax(g);
514 
515   /*********************************************************************/
516   /* Record the Index size and eventually resize memory allocation.    */
517   /*********************************************************************/
518   if ((Num_K = nkey) < n) {
519     PlgDBrealloc(g, NULL, Record, Num_K * sizeof(int));
520 
521     for (kcp = To_KeyCol; kcp; kcp = kcp->Next)
522       kcp->ReAlloc(g, Num_K);
523 
524     } // endif Num_K
525 
526   /*********************************************************************/
527   /*  Sort the index so we can use an optimized Find algorithm.        */
528   /*  Note: for a unique index we use the non conservative sort        */
529   /*  version because normally all index values are different.         */
530   /*  This was set at CSORT class construction.                        */
531   /*  For all indexes, an offset array is made so we can check the     */
532   /*  uniqueness of unique indexes.                                    */
533   /*********************************************************************/
534   Index.Size = Num_K * sizeof(int);
535 
536   if (!PlgDBalloc(g, NULL, Index)) {
537     sprintf(g->Message, MSG(MEM_ALLOC_ERR), "index", Num_K);
538     goto err;    // Error
539     } // endif alloc
540 
541   Offset.Size = (Num_K + 1) * sizeof(int);
542 
543   if (!PlgDBalloc(g, NULL, Offset)) {
544     sprintf(g->Message, MSG(MEM_ALLOC_ERR), "offset", Num_K + 1);
545     goto err;    // Error
546     } // endif alloc
547 
548   // We must separate keys and added columns before sorting
549   addcolp = To_LastCol->Next;
550   To_LastCol->Next = NULL;
551 
552   // Call the sort program, it returns the number of distinct values
553   if ((Ndif = Qsort(g, Num_K)) < 0)
554     goto err;       // Error during sort
555 
556   if (trace(1))
557     htrc("Make: Nk=%d n=%d Num_K=%d Ndif=%d addcolp=%p BlkFil=%p X=%p\n",
558           Nk, n, Num_K, Ndif, addcolp, Tdbp->To_BlkFil, X);
559 
560   // Check whether the unique index is unique indeed
561   if (!Mul)
562   {
563     if (Ndif < Num_K) {
564       strcpy(g->Message, MSG(INDEX_NOT_UNIQ));
565       brc = true;
566       goto err;
567     } else
568       PlgDBfree(Offset);           // Not used anymore
569   }
570 
571   // Restore kcp list
572   To_LastCol->Next = addcolp;
573 
574   // Use the index to physically reorder the xindex
575   Srtd = Reorder(g);
576 
577   if (Ndif < Num_K) {
578     // Resize the offset array
579     PlgDBrealloc(g, NULL, Offset, (Ndif + 1) * sizeof(int));
580 
581     // Initial value of MaxSame
582     MaxSame = Pof[1] - Pof[0];
583 
584     // Resize the Key array by only keeping the distinct values
585     for (i = 1; i < Ndif; i++) {
586       for (kcp = To_KeyCol; kcp; kcp = kcp->Next)
587         kcp->Move(i, Pof[i]);
588 
589       MaxSame = MY_MAX(MaxSame, Pof[i + 1] - Pof[i]);
590       } // endfor i
591 
592     for (kcp = To_KeyCol; kcp; kcp = kcp->Next)
593       kcp->ReAlloc(g, Ndif);
594 
595   } else {
596     Mul = false;                   // Current index is unique
597     PlgDBfree(Offset);             // Not used anymore
598     MaxSame = 1;                   // Reset it when remaking an index
599   } // endif Ndif
600 
601   /*********************************************************************/
602   /*  Now do the reduction of the index. Indeed a multi-column index   */
603   /*  can be used for only some of the first columns. For instance if  */
604   /*  an index is defined for column A, B, C PlugDB can use it for     */
605   /*  only the column A or the columns A, B.                           */
606   /*  What we do here is to reduce the data so column A will contain   */
607   /*  only the sorted distinct values of A, B will contain data such   */
608   /*  as only distinct values of A,B are stored etc.                   */
609   /*  This implies that for each column set an offset array is made    */
610   /*  except if the subset originally contains unique values.          */
611   /*********************************************************************/
612   // Update progress information
613 //dup->Step = STEP(REDUCE_INDEX);
614 
615   ndf = Ndif;
616   To_LastCol->Mxs = MaxSame;
617 
618   for (kcp = To_LastCol->Previous; kcp; kcp = kcp->Previous) {
619     if (!(bof = kcp->MakeOffset(g, ndf)))
620       goto err;
621     else
622       *bof = 0;
623 
624     for (n = 0, i = j = 1; i < ndf; i++)
625       for (kp = kcp; kp; kp = kp->Previous)
626         if (kp->Compare(n, i)) {
627           // Values are not equal to last ones
628           bof[j++] = n = i;
629           break;
630           } // endif Compare
631 
632     if (j < ndf) {
633       // Sub-index is multiple
634       bof[j] = ndf;
635       ndf = j;                  // New number of distinct values
636 
637       // Resize the Key array by only keeping the distinct values
638       for (kp = kcp; kp; kp = kp->Previous) {
639         for (i = 1; i < ndf; i++)
640           kp->Move(i, bof[i]);
641 
642         kp->ReAlloc(g, ndf);
643         } // endif kcp
644 
645       // Resize the offset array
646       kcp->MakeOffset(g, ndf);
647 
648       // Calculate the max same value for this column
649       kcp->Mxs = ColMaxSame(kcp);
650     } else {
651       // Current sub-index is unique
652       kcp->MakeOffset(g, 0);   // The offset is not used anymore
653       kcp->Mxs = 1;            // Unique
654     } // endif j
655 
656     } // endfor kcp
657 
658   /*********************************************************************/
659   /*  For sorted columns and fixed record size, file position can be   */
660   /*  calculated, so the Record array can be discarted.                */
661   /*  Not true for DBF tables because of eventual soft deleted lines.  */
662   /*  Note: for Num_K = 1 any non null value is Ok.                    */
663   /*********************************************************************/
664   if (Srtd && !filp && Tdbp->Ftype != RECFM_VAR && Tdbp->Ftype != RECFM_CSV
665                     && Tdbp->Txfp->GetAmType() != TYPE_AM_DBF) {
666     Incr = (Num_K > 1) ? To_Rec[1] : Num_K;
667     PlgDBfree(Record);
668     } // endif Srtd
669 
670   /*********************************************************************/
671   /*  Check whether a two-tier find algorithm can be implemented.      */
672   /*  It is currently implemented only for single key indexes.         */
673   /*********************************************************************/
674   if (Nk == 1 && ndf >= 65536) {
675     // Implement a two-tier find algorithm
676     for (Sblk = 256; (Sblk * Sblk * 4) < ndf; Sblk *= 2) ;
677 
678     Nblk = (ndf -1) / Sblk + 1;
679 
680     if (To_KeyCol->MakeBlockArray(g, Nblk, Sblk))
681       goto err;    // Error
682 
683     } // endif Num_K
684 
685  nox:
686   /*********************************************************************/
687   /*  No valid record read yet for secondary file.                     */
688   /*********************************************************************/
689   Cur_K = Num_K;
690 
691   /*********************************************************************/
692   /*  Save the xindex so it has not to be recalculated.                */
693   /*********************************************************************/
694   if (X) {
695     if (SaveIndex(g, sxp))
696       brc = true;
697 
698   } else {                     // Dynamic index
699     // Indicate that key column values can be found from KEYCOL's
700     for (kcp = To_KeyCol; kcp; kcp = kcp->Next)
701       kcp->Colp->SetKcol(kcp);
702 
703     Tdbp->SetFilter(NULL);     // Not used anymore
704   } // endif X
705 
706  err:
707   // We don't need the index anymore
708   if (X || brc)
709     Close();
710 
711   if (brc)
712     printf("%s\n", g->Message);
713 
714   return brc;
715   } // end of Make
716 
717 /***********************************************************************/
718 /*  Return the max size of the intermediate column.                    */
719 /***********************************************************************/
ColMaxSame(PXCOL kp)720 int XINDEX::ColMaxSame(PXCOL kp)
721   {
722   int *kof, i, ck1, ck2, ckn = 1;
723   PXCOL kcp;
724 
725   // Calculate the max same value for this column
726   for (i = 0; i < kp->Ndf; i++) {
727     ck1 = i;
728     ck2 = i + 1;
729 
730     for (kcp = kp; kcp; kcp = kcp->Next) {
731       if (!(kof = (kcp->Next) ? kcp->Kof : Pof))
732         break;
733 
734       ck1 = kof[ck1];
735       ck2 = kof[ck2];
736       } // endfor kcp
737 
738     ckn = MY_MAX(ckn, ck2 - ck1);
739     } // endfor i
740 
741   return ckn;
742   } // end of ColMaxSame
743 
744 /***********************************************************************/
745 /*  Reorder: use the sort index to reorder the data in storage so      */
746 /*  it will be physically sorted and sort index can be removed.        */
747 /***********************************************************************/
Reorder(PGLOBAL g)748 bool XINDEX::Reorder(PGLOBAL g __attribute__((unused)))
749   {
750   int i, j, k, n;
751   bool          sorted = true;
752   PXCOL         kcp;
753 #if 0
754   PDBUSER       dup = (PDBUSER)g->Activityp->Aptr;
755 
756   if (Num_K > 500000) {
757     // Update progress information
758     dup->Step = STEP(REORDER_INDEX);
759     dup->ProgMax = Num_K;
760     dup->ProgCur = 0;
761   } else
762     dup = NULL;
763 #endif // 0
764 
765   if (!Pex)
766     return Srtd;
767 
768   for (i = 0; i < Num_K; i++) {
769     if (Pex[i] == Num_K) {        // Already moved
770       continue;
771     } else if (Pex[i] == i) {     // Already placed
772 //    if (dup)
773 //      dup->ProgCur++;
774 
775       continue;
776     } // endif's Pex
777 
778     sorted = false;
779 
780     for (kcp = To_KeyCol; kcp; kcp = kcp->Next)
781       kcp->Save(i);
782 
783     n = To_Rec[i];
784 
785     for (j = i;; j = k) {
786       k = Pex[j];
787       Pex[j] = Num_K;           // Mark position as set
788 
789       if (k == i) {
790         for (kcp = To_KeyCol; kcp; kcp = kcp->Next)
791           kcp->Restore(j);
792 
793         To_Rec[j] = n;
794         break;                  // end of loop
795       } else {
796         for (kcp = To_KeyCol; kcp; kcp = kcp->Next)
797           kcp->Move(j, k);      // Move k to j
798 
799         To_Rec[j] = To_Rec[k];
800       } // endif k
801 
802 //    if (dup)
803 //      dup->ProgCur++;
804 
805       } // endfor j
806 
807     } // endfor i
808 
809   // The index is not used anymore
810   PlgDBfree(Index);
811   return sorted;
812   } // end of Reorder
813 
814 /***********************************************************************/
815 /*  Save the index values for this table.                              */
816 /*  The problem here is to avoid name duplication, because more than   */
817 /*  one data file can have the same name (but different types) and/or  */
818 /*  the same data file can be used with different block sizes. This is */
819 /*  why we use Ofn that defaults to the file name but can be set to a  */
820 /*  different name if necessary.                                       */
821 /***********************************************************************/
SaveIndex(PGLOBAL g,PIXDEF sxp)822 bool XINDEX::SaveIndex(PGLOBAL g, PIXDEF sxp)
823   {
824   PCSZ    ftype;
825   char    fn[_MAX_PATH];
826   int     n[NZ], nof = (Mul) ? (Ndif + 1) : 0;
827   int     id = -1, size = 0;
828   bool    sep, rc = false;
829   PXCOL   kcp = To_KeyCol;
830   PDOSDEF defp = (PDOSDEF)Tdbp->To_Def;
831 //PDBUSER dup = PlgGetUser(g);
832 
833 //dup->Step = STEP(SAVING_INDEX);
834 //dup->ProgMax = 15 + 16 * Nk;
835 //dup->ProgCur = 0;
836 
837   switch (Tdbp->Ftype) {
838     case RECFM_VAR: ftype = ".dnx"; break;
839     case RECFM_FIX: ftype = ".fnx"; break;
840     case RECFM_BIN: ftype = ".bnx"; break;
841     case RECFM_VCT: ftype = ".vnx"; break;
842 		case RECFM_CSV: ftype = ".cnx"; break;
843 		case RECFM_DBF: ftype = ".dbx"; break;
844     default:
845       sprintf(g->Message, MSG(INVALID_FTYPE), Tdbp->Ftype);
846       return true;
847     } // endswitch Ftype
848 
849   if ((sep = defp->GetBoolCatInfo("SepIndex", false))) {
850     // Index is saved in a separate file
851 #if defined(_WIN32)
852     char drive[_MAX_DRIVE];
853 #else
854     char *drive = NULL;
855 #endif
856     char direc[_MAX_DIR];
857     char fname[_MAX_FNAME];
858 
859     _splitpath(defp->GetOfn(), drive, direc, fname, NULL);
860     strcat(strcat(fname, "_"), Xdp->GetName());
861     _makepath(fn, drive, direc, fname, ftype);
862     sxp = NULL;
863   } else {
864     id = ID;
865     strcat(PlugRemoveType(fn, strcpy(fn, defp->GetOfn())), ftype);
866   } // endif sep
867 
868   PlugSetPath(fn, fn, Tdbp->GetPath());
869 
870   if (X->Open(g, fn, id, (sxp) ? MODE_INSERT : MODE_WRITE)) {
871     printf("%s\n", g->Message);
872     return true;
873     } // endif Open
874 
875   if (!Ndif)
876     goto end;                // Void index
877 
878   /*********************************************************************/
879   /*  Write the index values on the index file.                        */
880   /*********************************************************************/
881   n[0] = ID + MAX_INDX;       // To check validity
882   n[1] = Nk;                  // The number of indexed columns
883   n[2] = nof;                 // The offset array size or 0
884   n[3] = Num_K;               // The index size
885   n[4] = Incr;                // Increment of record positions
886   n[5] = Nblk; n[6] = Sblk;
887   n[7] = Srtd ? 1 : 0;        // Values are sorted in the file
888 
889   if (trace(1)) {
890     htrc("Saving index %s\n", Xdp->GetName());
891     htrc("ID=%d Nk=%d nof=%d Num_K=%d Incr=%d Nblk=%d Sblk=%d Srtd=%d\n",
892           ID, Nk, nof, Num_K, Incr, Nblk, Sblk, Srtd);
893     } // endif trace
894 
895   size = X->Write(g, n, NZ, sizeof(int), rc);
896 //dup->ProgCur = 1;
897 
898   if (Mul)             // Write the offset array
899     size += X->Write(g, Pof, nof, sizeof(int), rc);
900 
901 //dup->ProgCur = 5;
902 
903   if (!Incr)           // Write the record position array(s)
904     size += X->Write(g, To_Rec, Num_K, sizeof(int), rc);
905 
906 //dup->ProgCur = 15;
907 
908   for (; kcp; kcp = kcp->Next) {
909     n[0] = kcp->Ndf;                 // Number of distinct sub-values
910     n[1] = (kcp->Kof) ? kcp->Ndf + 1 : 0;     // 0 if unique
911     n[2] = (kcp == To_KeyCol) ? Nblk : 0;
912     n[3] = kcp->Klen;                // To be checked later
913     n[4] = kcp->Type;                // To be checked later
914 
915     size += X->Write(g, n, NW, sizeof(int), rc);
916 //  dup->ProgCur += 1;
917 
918     if (n[2])
919       size += X->Write(g, kcp->To_Bkeys, Nblk, kcp->Klen, rc);
920 
921 //  dup->ProgCur += 5;
922 
923     size += X->Write(g, kcp->To_Keys, n[0], kcp->Klen, rc);
924 //  dup->ProgCur += 5;
925 
926     if (n[1])
927       size += X->Write(g, kcp->Kof, n[1], sizeof(int), rc);
928 
929 //  dup->ProgCur += 5;
930     } // endfor kcp
931 
932   if (trace(1))
933     htrc("Index %s saved, Size=%d\n", Xdp->GetName(), size);
934 
935  end:
936   X->Close(fn, id);
937   return rc;
938   } // end of SaveIndex
939 
940 /***********************************************************************/
941 /*  Init: Open and Initialize a Key Index.                             */
942 /***********************************************************************/
Init(PGLOBAL g)943 bool XINDEX::Init(PGLOBAL g)
944   {
945 #if defined(XMAP)
946   if (xmap)
947     return MapInit(g);
948 #endif   // XMAP
949 
950   /*********************************************************************/
951   /*  Table will be accessed through an index table.                   */
952   /*  If sorting is required, this will be done later.                 */
953   /*********************************************************************/
954   PCSZ    ftype;
955   char    fn[_MAX_PATH];
956   int     k, n, nv[NZ], id = -1;
957   bool    estim = false;
958   PCOL    colp;
959   PXCOL   prev = NULL, kcp = NULL;
960   PDOSDEF defp = (PDOSDEF)Tdbp->To_Def;
961 
962   /*********************************************************************/
963   /*  Get the estimated table size.                                    */
964   /*  Note: for fixed tables we must use cardinality to avoid the call */
965   /*  to MaxBlkSize that could reduce the cardinality value.           */
966   /*********************************************************************/
967   if (Tdbp->Cardinality(NULL)) {
968     // For DBF tables, Cardinality includes bad or soft deleted lines
969     // that are not included in the index, and can be larger then the
970     // index size.
971     estim = (Tdbp->Ftype == RECFM_DBF || Tdbp->Txfp->GetAmType() == TYPE_AM_ZIP);
972     n = Tdbp->Cardinality(g);      // n is exact table size
973   } else {
974     // Variable table not optimized
975     estim = true;                  // n is an estimate of the size
976     n = Tdbp->GetMaxSize(g);
977   } // endif Cardinality
978 
979   if (n <= 0)
980     return !(n == 0);             // n < 0 error, n = 0 void table
981 
982   /*********************************************************************/
983   /*  Get the first key column.                                        */
984   /*********************************************************************/
985   if (!Nk || !To_Cols || (!To_Vals && Op != OP_FIRST && Op != OP_FSTDIF)) {
986     strcpy(g->Message, MSG(NO_KEY_COL));
987     return true;    // Error
988   } else
989     colp = To_Cols[0];
990 
991   switch (Tdbp->Ftype) {
992     case RECFM_VAR: ftype = ".dnx"; break;
993     case RECFM_FIX: ftype = ".fnx"; break;
994     case RECFM_BIN: ftype = ".bnx"; break;
995     case RECFM_VCT: ftype = ".vnx"; break;
996 		case RECFM_CSV: ftype = ".cnx"; break;
997 		case RECFM_DBF: ftype = ".dbx"; break;
998     default:
999       sprintf(g->Message, MSG(INVALID_FTYPE), Tdbp->Ftype);
1000       return true;
1001     } // endswitch Ftype
1002 
1003   if (defp->SepIndex()) {
1004     // Index was saved in a separate file
1005 #if defined(_WIN32)
1006     char drive[_MAX_DRIVE];
1007 #else
1008     char *drive = NULL;
1009 #endif
1010     char direc[_MAX_DIR];
1011     char fname[_MAX_FNAME];
1012 
1013     _splitpath(defp->GetOfn(), drive, direc, fname, NULL);
1014     strcat(strcat(fname, "_"), Xdp->GetName());
1015     _makepath(fn, drive, direc, fname, ftype);
1016   } else {
1017     id = ID;
1018     strcat(PlugRemoveType(fn, strcpy(fn, defp->GetOfn())), ftype);
1019   } // endif sep
1020 
1021   PlugSetPath(fn, fn, Tdbp->GetPath());
1022 
1023   if (trace(1))
1024     htrc("Index %s file: %s\n", Xdp->GetName(), fn);
1025 
1026   /*********************************************************************/
1027   /*  Open the index file and check its validity.                      */
1028   /*********************************************************************/
1029   if (X->Open(g, fn, id, MODE_READ))
1030     goto err;               // No saved values
1031 
1032   //  Now start the reading process.
1033   if (X->Read(g, nv, NZ - 1, sizeof(int)))
1034     goto err;
1035 
1036   if (nv[0] >= MAX_INDX) {
1037     // New index format
1038     if (X->Read(g, nv + 7, 1, sizeof(int)))
1039       goto err;
1040 
1041     Srtd = nv[7] != 0;
1042     nv[0] -= MAX_INDX;
1043   } else
1044     Srtd = false;
1045 
1046   if (trace(1))
1047     htrc("nv=%d %d %d %d %d %d %d (%d)\n",
1048           nv[0], nv[1], nv[2], nv[3], nv[4], nv[5], nv[6], Srtd);
1049 
1050   // The test on ID was suppressed because MariaDB can change an index ID
1051   // when other indexes are added or deleted
1052   if (/*nv[0] != ID ||*/ nv[1] != Nk) {
1053     sprintf(g->Message, MSG(BAD_INDEX_FILE), fn);
1054 
1055     if (trace(1))
1056       htrc("nv[0]=%d ID=%d nv[1]=%d Nk=%d\n", nv[0], ID, nv[1], Nk);
1057 
1058     goto err;
1059     } // endif
1060 
1061   if (nv[2]) {
1062     Mul = true;
1063     Ndif = nv[2];
1064 
1065     // Allocate the storage that will contain the offset array
1066     Offset.Size = Ndif * sizeof(int);
1067 
1068     if (!PlgDBalloc(g, NULL, Offset)) {
1069       sprintf(g->Message, MSG(MEM_ALLOC_ERR), "offset", Ndif);
1070       goto err;
1071       } // endif
1072 
1073     if (X->Read(g, Pof, Ndif, sizeof(int)))
1074       goto err;
1075 
1076     Ndif--;   // nv[2] is offset size, equal to Ndif + 1
1077   } else {
1078     Mul = false;
1079     Ndif = nv[3];
1080   } // endif nv[2]
1081 
1082   if (nv[3] < n && estim)
1083     n = nv[3];              // n was just an evaluated max value
1084 
1085   if (nv[3] != n) {
1086     sprintf(g->Message, MSG(OPT_NOT_MATCH), fn);
1087     goto err;
1088     } // endif
1089 
1090   Num_K = nv[3];
1091   Incr = nv[4];
1092   Nblk = nv[5];
1093   Sblk = nv[6];
1094 
1095   if (!Incr) {
1096     /*******************************************************************/
1097     /*  Allocate the storage that will contain the file positions.     */
1098     /*******************************************************************/
1099     Record.Size = Num_K * sizeof(int);
1100 
1101     if (!PlgDBalloc(g, NULL, Record)) {
1102       sprintf(g->Message, MSG(MEM_ALLOC_ERR), "index", Num_K);
1103       goto err;
1104       } // endif
1105 
1106     if (X->Read(g, To_Rec, Num_K, sizeof(int)))
1107       goto err;
1108 
1109   } else
1110     Srtd = true;    // Sorted positions can be calculated
1111 
1112   /*********************************************************************/
1113   /*  Allocate the KXYCOL blocks used to store column values.          */
1114   /*********************************************************************/
1115   for (k = 0; k < Nk; k++) {
1116     if (k == Nval)
1117       To_LastVal = prev;
1118 
1119     if (X->Read(g, nv, NW, sizeof(int)))
1120       goto err;
1121 
1122     colp = To_Cols[k];
1123 
1124     if (nv[4] != colp->GetResultType() || !colp->GetValue() ||
1125        (nv[3] != colp->GetValue()->GetClen() && nv[4] != TYPE_STRING)) {
1126       sprintf(g->Message, MSG(XCOL_MISMATCH), colp->GetName());
1127       goto err;    // Error
1128       } // endif GetKey
1129 
1130     kcp = new(g) KXYCOL(this);
1131 
1132     if (kcp->Init(g, colp, nv[0], true, (int)nv[3]))
1133       goto err;    // Error
1134 
1135     /*******************************************************************/
1136     /*  Read the index values from the index file.                     */
1137     /*******************************************************************/
1138     if (k == 0 && Nblk) {
1139       if (kcp->MakeBlockArray(g, Nblk, 0))
1140         goto err;
1141 
1142       // Read block values
1143       if (X->Read(g, kcp->To_Bkeys, Nblk, kcp->Klen))
1144         goto err;
1145 
1146       } // endif Nblk
1147 
1148     // Read the entire (small) index
1149     if (X->Read(g, kcp->To_Keys, nv[0], kcp->Klen))
1150       goto err;
1151 
1152     if (nv[1]) {
1153       if (!kcp->MakeOffset(g, nv[1] - 1))
1154         goto err;
1155 
1156       // Read the offset array
1157       if (X->Read(g, kcp->Kof, nv[1], sizeof(int)))
1158         goto err;
1159 
1160       } // endif n[1]
1161 
1162     if (!kcp->Prefix)
1163       // Indicate that the key column value can be found from KXYCOL
1164       colp->SetKcol(kcp);
1165 
1166     if (prev) {
1167       kcp->Previous = prev;
1168       prev->Next = kcp;
1169     } else
1170       To_KeyCol = kcp;
1171 
1172     prev = kcp;
1173     } // endfor k
1174 
1175   To_LastCol = prev;
1176 
1177   if (Mul && prev) {
1178     // Last key offset is the index offset
1179     kcp->Koff = Offset;
1180     kcp->Koff.Sub = true;
1181     } // endif Mul
1182 
1183   X->Close();
1184 
1185   /*********************************************************************/
1186   /*  No valid record read yet for secondary file.                     */
1187   /*********************************************************************/
1188   Cur_K = Num_K;
1189   return false;
1190 
1191 err:
1192   Close();
1193   return true;
1194   } // end of Init
1195 
1196 #if defined(XMAP)
1197 /***********************************************************************/
1198 /*  Init: Open and Initialize a Key Index.                             */
1199 /***********************************************************************/
MapInit(PGLOBAL g)1200 bool XINDEX::MapInit(PGLOBAL g)
1201   {
1202   /*********************************************************************/
1203   /*  Table will be accessed through an index table.                   */
1204   /*  If sorting is required, this will be done later.                 */
1205   /*********************************************************************/
1206   const char *ftype;
1207   BYTE   *mbase;
1208   char    fn[_MAX_PATH];
1209   int    *nv, nv0, k, n, id = -1;
1210   bool    estim;
1211   PCOL    colp;
1212   PXCOL   prev = NULL, kcp = NULL;
1213   PDOSDEF defp = (PDOSDEF)Tdbp->To_Def;
1214 
1215   /*********************************************************************/
1216   /*  Get the estimated table size.                                    */
1217   /*  Note: for fixed tables we must use cardinality to avoid the call */
1218   /*  to MaxBlkSize that could reduce the cardinality value.           */
1219   /*********************************************************************/
1220   if (Tdbp->Cardinality(NULL)) {
1221     // For DBF tables, Cardinality includes bad or soft deleted lines
1222     // that are not included in the index, and can be larger then the
1223     // index size.
1224     estim = (Tdbp->Ftype == RECFM_DBF);
1225     n = Tdbp->Cardinality(g);      // n is exact table size
1226   } else {
1227     // Variable table not optimized
1228     estim = true;                  // n is an estimate of the size
1229     n = Tdbp->GetMaxSize(g);
1230   } // endif Cardinality
1231 
1232   if (n <= 0)
1233     return !(n == 0);             // n < 0 error, n = 0 void table
1234 
1235   /*********************************************************************/
1236   /*  Get the first key column.                                        */
1237   /*********************************************************************/
1238   if (!Nk || !To_Cols || (!To_Vals && Op != OP_FIRST && Op != OP_FSTDIF)) {
1239     strcpy(g->Message, MSG(NO_KEY_COL));
1240     return true;    // Error
1241   } else
1242     colp = To_Cols[0];
1243 
1244   switch (Tdbp->Ftype) {
1245     case RECFM_VAR: ftype = ".dnx"; break;
1246     case RECFM_FIX: ftype = ".fnx"; break;
1247     case RECFM_BIN: ftype = ".bnx"; break;
1248     case RECFM_VCT: ftype = ".vnx"; break;
1249 		case RECFM_CSV: ftype = ".cnx"; break;
1250 		case RECFM_DBF: ftype = ".dbx"; break;
1251     default:
1252       sprintf(g->Message, MSG(INVALID_FTYPE), Tdbp->Ftype);
1253       return true;
1254     } // endswitch Ftype
1255 
1256   if (defp->SepIndex()) {
1257     // Index was save in a separate file
1258 #if defined(_WIN32)
1259     char drive[_MAX_DRIVE];
1260 #else
1261     char *drive = NULL;
1262 #endif
1263     char direc[_MAX_DIR];
1264     char fname[_MAX_FNAME];
1265 
1266     _splitpath(defp->GetOfn(), drive, direc, fname, NULL);
1267     strcat(strcat(fname, "_"), Xdp->GetName());
1268     _makepath(fn, drive, direc, fname, ftype);
1269   } else {
1270     id = ID;
1271     strcat(PlugRemoveType(fn, strcpy(fn, defp->GetOfn())), ftype);
1272   } // endif SepIndex
1273 
1274   PlugSetPath(fn, fn, Tdbp->GetPath());
1275 
1276   if (trace(1))
1277     htrc("Index %s file: %s\n", Xdp->GetName(), fn);
1278 
1279   /*********************************************************************/
1280   /*  Get a view on the part of the index file containing this index.  */
1281   /*********************************************************************/
1282   if (!(mbase = (BYTE*)X->FileView(g, fn)))
1283     goto err;
1284 
1285   if (id >= 0) {
1286     // Get offset from the header
1287     IOFF *noff = (IOFF*)mbase;
1288 
1289     // Position the memory base at the offset of this index
1290     mbase += noff[id].v.Low;
1291     } // endif id
1292 
1293   //  Now start the mapping process.
1294   nv = (int*)mbase;
1295 
1296   if (nv[0] >= MAX_INDX) {
1297     // New index format
1298     Srtd = nv[7] != 0;
1299     nv0 = nv[0] - MAX_INDX;
1300     mbase += NZ * sizeof(int);
1301   } else {
1302     Srtd = false;
1303     mbase += (NZ - 1) * sizeof(int);
1304 		nv0 = nv[0];
1305   } // endif nv
1306 
1307   if (trace(1))
1308     htrc("nv=%d %d %d %d %d %d %d %d\n",
1309           nv0, nv[1], nv[2], nv[3], nv[4], nv[5], nv[6], Srtd);
1310 
1311   // The test on ID was suppressed because MariaDB can change an index ID
1312   // when other indexes are added or deleted
1313   if (/*nv0 != ID ||*/ nv[1] != Nk) {
1314     // Not this index
1315     sprintf(g->Message, MSG(BAD_INDEX_FILE), fn);
1316 
1317     if (trace(1))
1318       htrc("nv0=%d ID=%d nv[1]=%d Nk=%d\n", nv0, ID, nv[1], Nk);
1319 
1320     goto err;
1321     } // endif nv
1322 
1323   if (nv[2]) {
1324     // Set the offset array memory block
1325     Offset.Memp = mbase;
1326     Offset.Size = nv[2] * sizeof(int);
1327     Offset.Sub = true;
1328     Mul = true;
1329     Ndif = nv[2] - 1;
1330     mbase += Offset.Size;
1331   } else {
1332     Mul = false;
1333     Ndif = nv[3];
1334   } // endif nv[2]
1335 
1336   if (nv[3] < n && estim)
1337     n = nv[3];              // n was just an evaluated max value
1338 
1339   if (nv[3] != n) {
1340     sprintf(g->Message, MSG(OPT_NOT_MATCH), fn);
1341     goto err;
1342     } // endif
1343 
1344   Num_K = nv[3];
1345   Incr = nv[4];
1346   Nblk = nv[5];
1347   Sblk = nv[6];
1348 
1349   if (!Incr) {
1350     /*******************************************************************/
1351     /*  Point to the storage that contains the file positions.         */
1352     /*******************************************************************/
1353     Record.Size = Num_K * sizeof(int);
1354     Record.Memp = mbase;
1355     Record.Sub = true;
1356     mbase += Record.Size;
1357   } else
1358     Srtd = true;    // Sorted positions can be calculated
1359 
1360   /*********************************************************************/
1361   /*  Allocate the KXYCOL blocks used to store column values.          */
1362   /*********************************************************************/
1363   for (k = 0; k < Nk; k++) {
1364     if (k == Nval)
1365       To_LastVal = prev;
1366 
1367     nv = (int*)mbase;
1368     mbase += (NW * sizeof(int));
1369 
1370     colp = To_Cols[k];
1371 
1372     if (nv[4] != colp->GetResultType() || !colp->GetValue() ||
1373        (nv[3] != colp->GetValue()->GetClen() && nv[4] != TYPE_STRING)) {
1374       sprintf(g->Message, MSG(XCOL_MISMATCH), colp->GetName());
1375       goto err;    // Error
1376       } // endif GetKey
1377 
1378     kcp = new(g) KXYCOL(this);
1379 
1380     if (!(mbase = kcp->MapInit(g, colp, nv, mbase)))
1381       goto err;
1382 
1383     if (!kcp->Prefix)
1384       // Indicate that the key column value can be found from KXYCOL
1385       colp->SetKcol(kcp);
1386 
1387     if (prev) {
1388       kcp->Previous = prev;
1389       prev->Next = kcp;
1390     } else
1391       To_KeyCol = kcp;
1392 
1393     prev = kcp;
1394     } // endfor k
1395 
1396   To_LastCol = prev;
1397 
1398   if (Mul && prev)
1399     // Last key offset is the index offset
1400     kcp->Koff = Offset;
1401 
1402   /*********************************************************************/
1403   /*  No valid record read yet for secondary file.                     */
1404   /*********************************************************************/
1405   Cur_K = Num_K;
1406   return false;
1407 
1408 err:
1409   Close();
1410   return true;
1411   } // end of MapInit
1412 #endif   // XMAP
1413 
1414 /***********************************************************************/
1415 /*  Get Ndif and Num_K from the index file.                            */
1416 /***********************************************************************/
GetAllSizes(PGLOBAL g,int & numk)1417 bool XINDEX::GetAllSizes(PGLOBAL g,/* int &ndif,*/ int &numk)
1418   {
1419   PCSZ    ftype;
1420   char    fn[_MAX_PATH];
1421   int     nv[NZ], id = -1; // n
1422 //bool    estim = false;
1423   bool    rc = true;
1424   PDOSDEF defp = (PDOSDEF)Tdbp->To_Def;
1425 
1426 //  ndif = numk = 0;
1427   numk = 0;
1428 
1429 #if 0
1430   /*********************************************************************/
1431   /*  Get the estimated table size.                                    */
1432   /*  Note: for fixed tables we must use cardinality to avoid the call */
1433   /*  to MaxBlkSize that could reduce the cardinality value.           */
1434   /*********************************************************************/
1435   if (Tdbp->Cardinality(NULL)) {
1436     // For DBF tables, Cardinality includes bad or soft deleted lines
1437     // that are not included in the index, and can be larger then the
1438     // index size.
1439     estim = (Tdbp->Ftype == RECFM_DBF);
1440     n = Tdbp->Cardinality(g);      // n is exact table size
1441   } else {
1442     // Variable table not optimized
1443     estim = true;                  // n is an estimate of the size
1444     n = Tdbp->GetMaxSize(g);
1445   } // endif Cardinality
1446 
1447   if (n <= 0)
1448     return !(n == 0);             // n < 0 error, n = 0 void table
1449 
1450   /*********************************************************************/
1451   /*  Check the key part number.                                       */
1452   /*********************************************************************/
1453   if (!Nk) {
1454     strcpy(g->Message, MSG(NO_KEY_COL));
1455     return true;    // Error
1456     } // endif Nk
1457 #endif // 0
1458 
1459   switch (Tdbp->Ftype) {
1460     case RECFM_VAR: ftype = ".dnx"; break;
1461     case RECFM_FIX: ftype = ".fnx"; break;
1462     case RECFM_BIN: ftype = ".bnx"; break;
1463     case RECFM_VCT: ftype = ".vnx"; break;
1464 		case RECFM_CSV: ftype = ".cnx"; break;
1465 		case RECFM_DBF: ftype = ".dbx"; break;
1466     default:
1467       sprintf(g->Message, MSG(INVALID_FTYPE), Tdbp->Ftype);
1468       return true;
1469     } // endswitch Ftype
1470 
1471   if (defp->SepIndex()) {
1472     // Index was saved in a separate file
1473 #if defined(_WIN32)
1474     char drive[_MAX_DRIVE];
1475 #else
1476     char *drive = NULL;
1477 #endif
1478     char direc[_MAX_DIR];
1479     char fname[_MAX_FNAME];
1480 
1481     _splitpath(defp->GetOfn(), drive, direc, fname, NULL);
1482     strcat(strcat(fname, "_"), Xdp->GetName());
1483     _makepath(fn, drive, direc, fname, ftype);
1484   } else {
1485     id = ID;
1486     strcat(PlugRemoveType(fn, strcpy(fn, defp->GetOfn())), ftype);
1487   } // endif sep
1488 
1489   PlugSetPath(fn, fn, Tdbp->GetPath());
1490 
1491   if (trace(1))
1492     htrc("Index %s file: %s\n", Xdp->GetName(), fn);
1493 
1494   /*********************************************************************/
1495   /*  Open the index file and check its validity.                      */
1496   /*********************************************************************/
1497   if (X->Open(g, fn, id, MODE_READ))
1498     goto err;               // No saved values
1499 
1500   // Get offset from XDB file
1501 //if (X->Seek(g, Defoff, Defhigh, SEEK_SET))
1502 //  goto err;
1503 
1504   //  Now start the reading process.
1505   if (X->Read(g, nv, NZ, sizeof(int)))
1506     goto err;
1507 
1508   if (trace(1))
1509     htrc("nv=%d %d %d %d\n", nv[0], nv[1], nv[2], nv[3]);
1510 
1511   // The test on ID was suppressed because MariaDB can change an index ID
1512   // when other indexes are added or deleted
1513   if (/*nv[0] != ID ||*/ nv[1] != Nk) {
1514     sprintf(g->Message, MSG(BAD_INDEX_FILE), fn);
1515 
1516     if (trace(1))
1517       htrc("nv[0]=%d ID=%d nv[1]=%d Nk=%d\n", nv[0], ID, nv[1], Nk);
1518 
1519     goto err;
1520     } // endif
1521 
1522 #if 0
1523   if (nv[2]) {
1524     Mul = true;
1525     Ndif = nv[2] - 1;  // nv[2] is offset size, equal to Ndif + 1
1526   } else {
1527     Mul = false;
1528     Ndif = nv[3];
1529   } // endif nv[2]
1530 
1531   if (nv[3] < n && estim)
1532     n = nv[3];              // n was just an evaluated max value
1533 
1534   if (nv[3] != n) {
1535     sprintf(g->Message, MSG(OPT_NOT_MATCH), fn);
1536     goto err;
1537     } // endif
1538 #endif // 0
1539 
1540   Num_K = nv[3];
1541 
1542 #if 0
1543   if (Nk > 1) {
1544     if (nv[2] && X->Seek(g, nv[2] * sizeof(int), 0, SEEK_CUR))
1545       goto err;
1546 
1547     if (!nv[4] && X->Seek(g, Num_K * sizeof(int), 0, SEEK_CUR))
1548       goto err;
1549 
1550     if (X->Read(g, nv, NW, sizeof(int)))
1551       goto err;
1552 
1553     PCOL colp = *To_Cols;
1554 
1555     if (nv[4] != colp->GetResultType()  ||
1556        (nv[3] != colp->GetValue()->GetClen() && nv[4] != TYPE_STRING)) {
1557       sprintf(g->Message, MSG(XCOL_MISMATCH), colp->GetName());
1558       goto err;    // Error
1559       } // endif GetKey
1560 
1561     Ndif = nv[0];
1562     } // endif Nk
1563 #endif // 0
1564 
1565   /*********************************************************************/
1566   /*  Set size values.                                                 */
1567   /*********************************************************************/
1568 //ndif = Ndif;
1569   numk = Num_K;
1570   rc = false;
1571 
1572 err:
1573   X->Close();
1574   return rc;
1575   } // end of GetAllSizes
1576 
1577 /***********************************************************************/
1578 /*  RANGE: Tell how many records exist for a given value, for an array */
1579 /*  of values, or in a given value range.                              */
1580 /***********************************************************************/
Range(PGLOBAL g,int limit,bool incl)1581 int XINDEX::Range(PGLOBAL g, int limit, bool incl)
1582   {
1583   int  i, k, n = 0;
1584   PXOB *xp = To_Vals;
1585   PXCOL kp = To_KeyCol;
1586   OPVAL op = Op;
1587 
1588   switch (limit) {
1589     case 1: Op = (incl) ? OP_GE : OP_GT; break;
1590     case 2: Op = (incl) ? OP_GT : OP_GE; break;
1591     default: return 0;
1592     } // endswitch limit
1593 
1594   /*********************************************************************/
1595   /*  Currently only range of constant values with an EQ operator is   */
1596   /*  implemented.  Find the number of rows for each given values.     */
1597   /*********************************************************************/
1598   if (xp[0]->GetType() == TYPE_CONST) {
1599     for (i = 0; kp; kp = kp->Next) {
1600       kp->Valp->SetValue_pval(xp[i]->GetValue(), !kp->Prefix);
1601       if (++i == Nval) break;
1602       } // endfor kp
1603 
1604     if ((k = FastFind()) < Num_K)
1605       n = k;
1606 //      if (limit)
1607 //        n = (Mul) ? k : kp->Val_K;
1608 //      else
1609 //        n = (Mul) ? Pof[kp->Val_K + 1] - k : 1;
1610 
1611   } else {
1612     strcpy(g->Message, MSG(RANGE_NO_JOIN));
1613     n = -1;                        // Logical error
1614   } // endif'f Type
1615 
1616   Op = op;
1617   return n;
1618   } // end of Range
1619 
1620 /***********************************************************************/
1621 /*  Return the size of the group (equal values) of the current value.  */
1622 /***********************************************************************/
GroupSize(void)1623 int XINDEX::GroupSize(void)
1624   {
1625 #if defined(_DEBUG)
1626   assert(To_LastCol->Val_K >= 0 && To_LastCol->Val_K < Ndif);
1627 #endif   // _DEBUG
1628 
1629   if (Nval == Nk)
1630     return (Pof) ? Pof[To_LastCol->Val_K + 1] - Pof[To_LastCol->Val_K]
1631                  : 1;
1632 
1633 #if defined(_DEBUG)
1634   assert(To_LastVal);
1635 #endif   // _DEBUG
1636 
1637   // Index whose only some columns are used
1638   int ck1, ck2;
1639 
1640   ck1 = To_LastVal->Val_K;
1641   ck2 = ck1 + 1;
1642 
1643 #if defined(_DEBUG)
1644   assert(ck1 >= 0 && ck1 < To_LastVal->Ndf);
1645 #endif   // _DEBUG
1646 
1647   for (PXCOL kcp = To_LastVal; kcp; kcp = kcp->Next) {
1648     ck1 = (kcp->Kof) ? kcp->Kof[ck1] : ck1;
1649     ck2 = (kcp->Kof) ? kcp->Kof[ck2] : ck2;
1650     } // endfor kcp
1651 
1652   return ck2 - ck1;
1653   } // end of GroupSize
1654 
1655 /***********************************************************************/
1656 /*  Find Cur_K and Val_K's of the next distinct value of the index.    */
1657 /*  Returns false if Ok, true if there are no more different values.   */
1658 /***********************************************************************/
NextValDif(void)1659 bool XINDEX::NextValDif(void)
1660   {
1661   int  curk;
1662   PXCOL kcp = (To_LastVal) ? To_LastVal : To_LastCol;
1663 
1664   if (++kcp->Val_K < kcp->Ndf) {
1665     Cur_K = curk = kcp->Val_K;
1666 
1667     // (Cur_K return is currently not used by SQLGBX)
1668     for (PXCOL kp = kcp; kp; kp = kp->Next)
1669       Cur_K = (kp->Kof) ? kp->Kof[Cur_K] : Cur_K;
1670 
1671   } else
1672     return true;
1673 
1674   for (kcp = kcp->Previous; kcp; kcp = kcp->Previous) {
1675     if (kcp->Kof && curk < kcp->Kof[kcp->Val_K + 1])
1676       break;                  // all previous columns have same value
1677 
1678     curk = ++kcp->Val_K;      // This is a break, get new column value
1679     } // endfor kcp
1680 
1681   return false;
1682   } // end of NextValDif
1683 
1684 /***********************************************************************/
1685 /*  XINDEX: Find Cur_K and Val_K's of next index entry.                */
1686 /*  If eq is true next values must be equal to last ones up to Nval.   */
1687 /*  Returns false if Ok, true if there are no more (equal) values.     */
1688 /***********************************************************************/
NextVal(bool eq)1689 bool XINDEX::NextVal(bool eq)
1690   {
1691   int  n, neq = Nk + 1, curk;
1692   PXCOL kcp;
1693 
1694   if (Cur_K == Num_K)
1695     return true;
1696   else
1697     curk = ++Cur_K;
1698 
1699   for (n = Nk, kcp = To_LastCol; kcp; n--, kcp = kcp->Previous) {
1700     if (kcp->Kof) {
1701       if (curk == kcp->Kof[kcp->Val_K + 1])
1702         neq = n;
1703 
1704     } else {
1705 #ifdef _DEBUG
1706       assert(curk == kcp->Val_K + 1);
1707 #endif // _DEBUG
1708       neq = n;
1709     } // endif Kof
1710 
1711 #ifdef _DEBUG
1712     assert(kcp->Val_K < kcp->Ndf);
1713 #endif // _DEBUG
1714 
1715     // If this is not a break...
1716     if (neq > n)
1717       break;                  // all previous columns have same value
1718 
1719     curk = ++kcp->Val_K;      // This is a break, get new column value
1720     } // endfor kcp
1721 
1722   // Return true if no more values or, in case of "equal" values,
1723   // if the last used column value has changed
1724   return (Cur_K == Num_K || (eq && neq <= Nval));
1725   } // end of NextVal
1726 
1727 /***********************************************************************/
1728 /*  XINDEX: Find Cur_K and Val_K's of previous index entry.            */
1729 /*  Returns false if Ok, true if there are no more values.             */
1730 /***********************************************************************/
PrevVal(void)1731 bool XINDEX::PrevVal(void)
1732   {
1733   int  n, neq = Nk + 1, curk;
1734   PXCOL kcp;
1735 
1736   if (Cur_K == 0)
1737     return true;
1738   else
1739     curk = --Cur_K;
1740 
1741   for (n = Nk, kcp = To_LastCol; kcp; n--, kcp = kcp->Previous) {
1742     if (kcp->Kof) {
1743       if (curk < kcp->Kof[kcp->Val_K])
1744         neq = n;
1745 
1746     } else {
1747 #ifdef _DEBUG
1748       assert(curk == kcp->Val_K -1);
1749 #endif // _DEBUG
1750       neq = n;
1751     } // endif Kof
1752 
1753 #ifdef _DEBUG
1754     assert(kcp->Val_K >= 0);
1755 #endif // _DEBUG
1756 
1757     // If this is not a break...
1758     if (neq > n)
1759       break;                  // all previous columns have same value
1760 
1761     curk = --kcp->Val_K;      // This is a break, get new column value
1762     } // endfor kcp
1763 
1764   return false;
1765   } // end of PrevVal
1766 
1767 /***********************************************************************/
1768 /*  XINDEX: Fetch a physical or logical record.                        */
1769 /***********************************************************************/
Fetch(PGLOBAL g)1770 int XINDEX::Fetch(PGLOBAL g)
1771   {
1772   int  n;
1773   PXCOL kp;
1774 
1775   if (Num_K == 0)
1776     return -1;                   // means end of file
1777 
1778   if (trace(2))
1779     htrc("XINDEX Fetch: Op=%d\n", Op);
1780 
1781   /*********************************************************************/
1782   /*  Table read through a sorted index.                               */
1783   /*********************************************************************/
1784   switch (Op) {
1785     case OP_NEXT:                 // Read next
1786       if (NextVal(false))
1787         return -1;                // End of indexed file
1788 
1789       break;
1790     case OP_FIRST:                // Read first
1791       for (Cur_K = 0, kp = To_KeyCol; kp; kp = kp->Next)
1792         kp->Val_K = 0;
1793 
1794       Op = OP_NEXT;
1795       break;
1796     case OP_SAME:                 // Read next same
1797       // Logically the key values should be the same as before
1798       if (NextVal(true)) {
1799         Op = OP_EQ;
1800         return -2;                // no more equal values
1801         } // endif NextVal
1802 
1803       break;
1804     case OP_NXTDIF:               // Read next dif
1805 //      while (!NextVal(true)) ;
1806 
1807 //      if (Cur_K >= Num_K)
1808 //        return -1;              // End of indexed file
1809       if (NextValDif())
1810         return -1;                // End of indexed file
1811 
1812       break;
1813     case OP_FSTDIF:               // Read first diff
1814       for (Cur_K = 0, kp = To_KeyCol; kp; kp = kp->Next)
1815         kp->Val_K = 0;
1816 
1817       Op = (Mul || Nval < Nk) ? OP_NXTDIF : OP_NEXT;
1818       break;
1819     case OP_LAST:                 // Read last key
1820       for (Cur_K = Num_K - 1, kp = To_KeyCol; kp; kp = kp->Next)
1821         kp->Val_K = kp->Kblp->GetNval() - 1;
1822 
1823       Op = OP_NEXT;
1824       break;
1825     case OP_PREV:                 // Read previous
1826       if (PrevVal())
1827         return -1;                // End of indexed file
1828 
1829       break;
1830     default:                      // Should be OP_EQ
1831 //    if (Tbxp->Key_Rank < 0) {
1832         /***************************************************************/
1833         /*  Look for the first key equal to the link column values     */
1834         /*  and return its rank whithin the index table.               */
1835         /***************************************************************/
1836         for (n = 0, kp = To_KeyCol; n < Nval && kp; n++, kp = kp->Next)
1837           if (kp->InitFind(g, To_Vals[n]))
1838             return -1;               // No more constant values
1839 
1840         Nth++;
1841 
1842         if (trace(2))
1843           htrc("Fetch: Looking for new value Nth=%d\n", Nth);
1844 
1845         Cur_K = FastFind();
1846 
1847         if (Cur_K >= Num_K)
1848           /*************************************************************/
1849           /* Rank not whithin index table, signal record not found.    */
1850           /*************************************************************/
1851           return -2;
1852 
1853         else if (Mul || Nval < Nk)
1854           Op = OP_SAME;
1855 
1856     } // endswitch Op
1857 
1858   /*********************************************************************/
1859   /*  If rank is equal to stored rank, record is already there.        */
1860   /*********************************************************************/
1861   if (Cur_K == Old_K)
1862     return -3;                   // Means record already there
1863   else
1864     Old_K = Cur_K;                // Store rank of newly read record
1865 
1866   /*********************************************************************/
1867   /*  Return the position of the required record.                      */
1868   /*********************************************************************/
1869   return (Incr) ? Cur_K * Incr : To_Rec[Cur_K];
1870   } // end of Fetch
1871 
1872 /***********************************************************************/
1873 /*  FastFind: Returns the index of matching record in a join using an  */
1874 /*  optimized algorithm based on dichotomie and optimized comparing.   */
1875 /***********************************************************************/
FastFind(void)1876 int XINDEX::FastFind(void)
1877   {
1878   int  curk, sup, inf, i= 0, k, n = 2;
1879   PXCOL kp, kcp;
1880 
1881 //assert((int)nv == Nval);
1882 
1883   if (Nblk && Op == OP_EQ) {
1884     // Look in block values to find in which block to search
1885     sup = Nblk;
1886     inf = -1;
1887 
1888     while (n && sup - inf > 1) {
1889       i = (inf + sup) >> 1;
1890 
1891       n = To_KeyCol->CompBval(i);
1892 
1893       if (n < 0)
1894         sup = i;
1895       else
1896         inf = i;
1897 
1898       } // endwhile
1899 
1900     if (inf < 0)
1901       return Num_K;
1902 
1903 //  i = inf;
1904     inf *= Sblk;
1905 
1906     if ((sup = inf + Sblk) > To_KeyCol->Ndf)
1907       sup = To_KeyCol->Ndf;
1908 
1909     inf--;
1910   } else {
1911     inf = -1;
1912     sup = To_KeyCol->Ndf;
1913   } // endif Nblk
1914 
1915   if (trace(4))
1916     htrc("XINDEX FastFind: Nblk=%d Op=%d inf=%d sup=%d\n",
1917                            Nblk, Op, inf, sup);
1918 
1919   for (k = 0, kcp = To_KeyCol; kcp; kcp = kcp->Next) {
1920     while (sup - inf > 1) {
1921       i = (inf + sup) >> 1;
1922 
1923       n = kcp->CompVal(i);
1924 
1925       if      (n < 0)
1926         sup = i;
1927       else if (n > 0)
1928         inf = i;
1929       else
1930         break;
1931 
1932       } // endwhile
1933 
1934     if (n) {
1935       if (Op != OP_EQ) {
1936         // Currently only OP_GT or OP_GE
1937         kcp->Val_K = curk = sup;
1938 
1939         // Check for value changes in previous key parts
1940         for (kp = kcp->Previous; kp; kp = kp->Previous)
1941           if (kp->Kof && curk < kp->Kof[kp->Val_K + 1])
1942             break;
1943           else
1944             curk = ++kp->Val_K;
1945 
1946         n = 0;
1947         } // endif Op
1948 
1949       break;
1950       } // endif n
1951 
1952     kcp->Val_K = i;
1953 
1954     if (++k == Nval) {
1955       if (Op == OP_GT) {            // n is always 0
1956         curk = ++kcp->Val_K;        // Increment value by 1
1957 
1958         // Check for value changes in previous key parts
1959         for (kp = kcp->Previous; kp; kp = kp->Previous)
1960           if (kp->Kof && curk < kp->Kof[kp->Val_K + 1])
1961             break;                  // Not changed
1962           else
1963             curk = ++kp->Val_K;
1964 
1965         } // endif Op
1966 
1967       break;      // So kcp remains pointing the last tested block
1968       } // endif k
1969 
1970     if (kcp->Kof) {
1971       inf = kcp->Kof[i] - 1;
1972       sup = kcp->Kof[i + 1];
1973     } else {
1974       inf = i - 1;
1975       sup = i + 1;
1976     } // endif Kof
1977 
1978     } // endfor k, kcp
1979 
1980   if (n) {
1981     // Record not found
1982     for (kcp = To_KeyCol; kcp; kcp = kcp->Next)
1983       kcp->Val_K = kcp->Ndf;       // Not a valid value
1984 
1985     return Num_K;
1986     } // endif n
1987 
1988   for (curk = kcp->Val_K; kcp; kcp = kcp->Next) {
1989     kcp->Val_K = curk;
1990     curk = (kcp->Kof) ? kcp->Kof[kcp->Val_K] : kcp->Val_K;
1991     } // endfor kcp
1992 
1993   if (trace(4))
1994     htrc("XINDEX FastFind: curk=%d\n", curk);
1995 
1996   return curk;
1997   } // end of FastFind
1998 
1999 /* -------------------------- XINDXS Class --------------------------- */
2000 
2001 /***********************************************************************/
2002 /*  XINDXS public constructor.                                         */
2003 /***********************************************************************/
XINDXS(PTDBDOS tdbp,PIXDEF xdp,PXLOAD pxp,PCOL * cp,PXOB * xp)2004 XINDXS::XINDXS(PTDBDOS tdbp, PIXDEF xdp, PXLOAD pxp, PCOL *cp, PXOB *xp)
2005       : XINDEX(tdbp, xdp, pxp, cp, xp)
2006   {
2007   Srtd = To_Cols[0]->GetOpt() == 2;
2008   } // end of XINDXS constructor
2009 
2010 /***********************************************************************/
2011 /*  XINDXS compare routine for C Quick/Insertion sort.                 */
2012 /***********************************************************************/
Qcompare(int * i1,int * i2)2013 int XINDXS::Qcompare(int *i1, int *i2)
2014   {
2015 //num_comp++;
2016   return To_KeyCol->Compare(*i1, *i2);
2017   } // end of Qcompare
2018 
2019 /***********************************************************************/
2020 /*  Range: Tell how many records exist for given value(s):             */
2021 /*  If limit=0 return range for these values.                          */
2022 /*  If limit=1 return the start of range.                              */
2023 /*  If limit=2 return the end of range.                                */
2024 /***********************************************************************/
Range(PGLOBAL g,int limit,bool incl)2025 int XINDXS::Range(PGLOBAL g, int limit, bool incl)
2026   {
2027   int  k, n = 0;
2028   PXOB  xp = To_Vals[0];
2029   PXCOL kp = To_KeyCol;
2030   OPVAL op = Op;
2031 
2032   switch (limit) {
2033     case 1: Op = (incl) ? OP_GE : OP_GT; break;
2034     case 2: Op = (incl) ? OP_GT : OP_GE; break;
2035     default: Op = OP_EQ;
2036     } // endswitch limit
2037 
2038   /*********************************************************************/
2039   /*  Currently only range of constant values with an EQ operator is   */
2040   /*  implemented.  Find the number of rows for each given values.     */
2041   /*********************************************************************/
2042   if (xp->GetType() == TYPE_CONST) {
2043     kp->Valp->SetValue_pval(xp->GetValue(), !kp->Prefix);
2044     k = FastFind();
2045 
2046     if (k < Num_K || Op != OP_EQ)
2047     {
2048       if (limit)
2049         n = (Mul) ? k : kp->Val_K;
2050       else
2051         n = (Mul) ? Pof[kp->Val_K + 1] - k : 1;
2052     }
2053   } else {
2054     strcpy(g->Message, MSG(RANGE_NO_JOIN));
2055     n = -1;                        // Logical error
2056   } // endif'f Type
2057 
2058   Op = op;
2059   return n;
2060   } // end of Range
2061 
2062 /***********************************************************************/
2063 /*  Return the size of the group (equal values) of the current value.  */
2064 /***********************************************************************/
GroupSize(void)2065 int XINDXS::GroupSize(void)
2066   {
2067 #if defined(_DEBUG)
2068   assert(To_KeyCol->Val_K >= 0 && To_KeyCol->Val_K < Ndif);
2069 #endif   // _DEBUG
2070   return (Pof) ? Pof[To_KeyCol->Val_K + 1] - Pof[To_KeyCol->Val_K] : 1;
2071   } // end of GroupSize
2072 
2073 /***********************************************************************/
2074 /*  XINDXS: Find Cur_K and Val_K of previous index value.              */
2075 /*  Returns false if Ok, true if there are no more values.             */
2076 /***********************************************************************/
PrevVal(void)2077 bool XINDXS::PrevVal(void)
2078   {
2079   if (--Cur_K < 0)
2080     return true;
2081 
2082   if (Mul) {
2083     if (Cur_K < Pof[To_KeyCol->Val_K])
2084       To_KeyCol->Val_K--;
2085 
2086   } else
2087     To_KeyCol->Val_K = Cur_K;
2088 
2089   return false;
2090   } // end of PrevVal
2091 
2092 /***********************************************************************/
2093 /*  XINDXS: Find Cur_K and Val_K of next index value.                  */
2094 /*  If b is true next value must be equal to last one.                 */
2095 /*  Returns false if Ok, true if there are no more (equal) values.     */
2096 /***********************************************************************/
NextVal(bool eq)2097 bool XINDXS::NextVal(bool eq)
2098   {
2099   bool rc;
2100 
2101   if (To_KeyCol->Val_K == Ndif)
2102     return true;
2103 
2104   if (Mul) {
2105     int limit = Pof[To_KeyCol->Val_K + 1];
2106 
2107 #ifdef _DEBUG
2108     assert(Cur_K < limit);
2109     assert(To_KeyCol->Val_K < Ndif);
2110 #endif // _DEBUG
2111 
2112     if (++Cur_K == limit) {
2113       To_KeyCol->Val_K++;
2114       rc = (eq || limit == Num_K);
2115     } else
2116       rc = false;
2117 
2118   } else
2119     rc = (To_KeyCol->Val_K = ++Cur_K) == Num_K || eq;
2120 
2121   return rc;
2122   } // end of NextVal
2123 
2124 /***********************************************************************/
2125 /*  XINDXS: Fetch a physical or logical record.                        */
2126 /***********************************************************************/
Fetch(PGLOBAL g)2127 int XINDXS::Fetch(PGLOBAL g)
2128   {
2129   if (Num_K == 0)
2130     return -1;                   // means end of file
2131 
2132   if (trace(2))
2133     htrc("XINDXS Fetch: Op=%d\n", Op);
2134 
2135   /*********************************************************************/
2136   /*  Table read through a sorted index.                               */
2137   /*********************************************************************/
2138   switch (Op) {
2139     case OP_NEXT:                // Read next
2140       if (NextVal(false))
2141         return -1;               // End of indexed file
2142 
2143       break;
2144     case OP_FIRST:               // Read first
2145       To_KeyCol->Val_K = Cur_K = 0;
2146       Op = OP_NEXT;
2147       break;
2148     case OP_SAME:                 // Read next same
2149       if (!Mul || NextVal(true)) {
2150         Op = OP_EQ;
2151         return -2;               // No more equal values
2152         } // endif Mul
2153 
2154       break;
2155     case OP_NXTDIF:              // Read next dif
2156       if (++To_KeyCol->Val_K == Ndif)
2157         return -1;               // End of indexed file
2158 
2159       Cur_K = Pof[To_KeyCol->Val_K];
2160       break;
2161     case OP_FSTDIF:               // Read first diff
2162       To_KeyCol->Val_K = Cur_K = 0;
2163       Op = (Mul) ? OP_NXTDIF : OP_NEXT;
2164       break;
2165     case OP_LAST:                // Read first
2166       Cur_K = Num_K - 1;
2167       To_KeyCol->Val_K = Ndif - 1;
2168       Op = OP_PREV;
2169       break;
2170     case OP_PREV:                // Read previous
2171       if (PrevVal())
2172         return -1;               // End of indexed file
2173 
2174       break;
2175     default:                     // Should be OP_EQ
2176       /*****************************************************************/
2177       /*  Look for the first key equal to the link column values       */
2178       /*  and return its rank whithin the index table.                 */
2179       /*****************************************************************/
2180       if (To_KeyCol->InitFind(g, To_Vals[0]))
2181         return -1;                 // No more constant values
2182       else
2183         Nth++;
2184 
2185       if (trace(2))
2186         htrc("Fetch: Looking for new value Nth=%d\n", Nth);
2187 
2188       Cur_K = FastFind();
2189 
2190       if (Cur_K >= Num_K)
2191         // Rank not whithin index table, signal record not found
2192         return -2;
2193       else if (Mul)
2194         Op = OP_SAME;
2195 
2196     } // endswitch Op
2197 
2198   /*********************************************************************/
2199   /*  If rank is equal to stored rank, record is already there.        */
2200   /*********************************************************************/
2201   if (Cur_K == Old_K)
2202     return -3;                   // Means record already there
2203   else
2204     Old_K = Cur_K;                // Store rank of newly read record
2205 
2206   /*********************************************************************/
2207   /*  Return the position of the required record.                      */
2208   /*********************************************************************/
2209   return (Incr) ? Cur_K * Incr : To_Rec[Cur_K];
2210   } // end of Fetch
2211 
2212 /***********************************************************************/
2213 /*  FastFind: Returns the index of matching indexed record using an    */
2214 /*  optimized algorithm based on dichotomie and optimized comparing.   */
2215 /***********************************************************************/
FastFind(void)2216 int XINDXS::FastFind(void)
2217   {
2218   int   sup, inf, i= 0, n = 2;
2219   PXCOL kcp = To_KeyCol;
2220 
2221   if (Nblk && Op == OP_EQ) {
2222     // Look in block values to find in which block to search
2223     sup = Nblk;
2224     inf = -1;
2225 
2226     while (n && sup - inf > 1) {
2227       i = (inf + sup) >> 1;
2228 
2229       n = kcp->CompBval(i);
2230 
2231       if (n < 0)
2232         sup = i;
2233       else
2234         inf = i;
2235 
2236       } // endwhile
2237 
2238     if (inf < 0)
2239       return Num_K;
2240 
2241     inf *= Sblk;
2242 
2243     if ((sup = inf + Sblk) > Ndif)
2244       sup = Ndif;
2245 
2246     inf--;
2247   } else {
2248     inf = -1;
2249     sup = Ndif;
2250   } // endif Nblk
2251 
2252   if (trace(4))
2253     htrc("XINDXS FastFind: Nblk=%d Op=%d inf=%d sup=%d\n",
2254                            Nblk, Op, inf, sup);
2255 
2256   while (sup - inf > 1) {
2257     i = (inf + sup) >> 1;
2258 
2259     n = kcp->CompVal(i);
2260 
2261     if      (n < 0)
2262       sup = i;
2263     else if (n > 0)
2264       inf = i;
2265     else
2266       break;
2267 
2268     } // endwhile
2269 
2270   if (!n && Op == OP_GT) {
2271     ++i;
2272   } else if (n && Op != OP_EQ) {
2273     // Currently only OP_GT or OP_GE
2274     i = sup;
2275     n = 0;
2276   } // endif sup
2277 
2278   if (trace(4))
2279     htrc("XINDXS FastFind: n=%d i=%d\n", n, i);
2280 
2281   // Loop on kcp because of dynamic indexing
2282   for (; kcp; kcp = kcp->Next)
2283     kcp->Val_K = i;                 // Used by FillValue
2284 
2285   return ((n) ? Num_K : (Mul) ? Pof[i] : i);
2286   } // end of FastFind
2287 
2288 /* -------------------------- XLOAD Class --------------------------- */
2289 
2290 /***********************************************************************/
2291 /*  XLOAD constructor.                                                 */
2292 /***********************************************************************/
XLOAD(void)2293 XLOAD::XLOAD(void)
2294   {
2295   Hfile = INVALID_HANDLE_VALUE;
2296   NewOff.Val = 0LL;
2297 } // end of XLOAD constructor
2298 
2299 /***********************************************************************/
2300 /*  Close the index huge file.                                         */
2301 /***********************************************************************/
Close(void)2302 void XLOAD::Close(void)
2303   {
2304   if (Hfile != INVALID_HANDLE_VALUE) {
2305     CloseFileHandle(Hfile);
2306     Hfile = INVALID_HANDLE_VALUE;
2307     } // endif Hfile
2308 
2309   } // end of Close
2310 
2311 /* --------------------------- XFILE Class --------------------------- */
2312 
2313 /***********************************************************************/
2314 /*  XFILE constructor.                                                 */
2315 /***********************************************************************/
XFILE(void)2316 XFILE::XFILE(void) : XLOAD()
2317   {
2318   Xfile = NULL;
2319 #if defined(XMAP)
2320   Mmp = NULL;
2321 #endif   // XMAP
2322   } // end of XFILE constructor
2323 
2324 /***********************************************************************/
2325 /*  Xopen function: opens a file using native API's.                   */
2326 /***********************************************************************/
Open(PGLOBAL g,char * filename,int id,MODE mode)2327 bool XFILE::Open(PGLOBAL g, char *filename, int id, MODE mode)
2328   {
2329   PCSZ pmod;
2330   bool rc;
2331   IOFF noff[MAX_INDX];
2332 
2333   /*********************************************************************/
2334   /*  Open the index file according to mode.                           */
2335   /*********************************************************************/
2336   switch (mode) {
2337     case MODE_READ:   pmod = "rb"; break;
2338     case MODE_WRITE:  pmod = "wb"; break;
2339     case MODE_INSERT: pmod = "ab"; break;
2340     default:
2341       sprintf(g->Message, MSG(BAD_FUNC_MODE), "Xopen", mode);
2342       return true;
2343     } // endswitch mode
2344 
2345   if (!(Xfile= global_fopen(g, MSGID_OPEN_ERROR_AND_STRERROR, filename, pmod))) {
2346     if (trace(1))
2347       htrc("Open: %s\n", g->Message);
2348 
2349     return true;
2350     } // endif Xfile
2351 
2352   if (mode == MODE_INSERT) {
2353     /*******************************************************************/
2354     /* Position the cursor at end of file so ftell returns file size.  */
2355     /*******************************************************************/
2356     if (fseek(Xfile, 0, SEEK_END)) {
2357       sprintf(g->Message, MSG(FUNC_ERRNO), errno, "Xseek");
2358       return true;
2359       } // endif
2360 
2361     NewOff.v.Low = (int)ftell(Xfile);
2362 
2363     if (trace(1))
2364       htrc("XFILE Open: NewOff.v.Low=%d\n", NewOff.v.Low);
2365 
2366   } else if (mode == MODE_WRITE) {
2367     if (id >= 0) {
2368       // New not sep index file. Write the header.
2369       memset(noff, 0, sizeof(noff));
2370       Write(g, noff, sizeof(IOFF), MAX_INDX, rc);
2371       fseek(Xfile, 0, SEEK_END);
2372       NewOff.v.Low = (int)ftell(Xfile);
2373 
2374       if (trace(1))
2375         htrc("XFILE Open: NewOff.v.Low=%d\n", NewOff.v.Low);
2376 
2377       } // endif id
2378 
2379   } else if (mode == MODE_READ && id >= 0) {
2380     // Get offset from the header
2381     if (fread(noff, sizeof(IOFF), MAX_INDX, Xfile) != MAX_INDX) {
2382       sprintf(g->Message, MSG(XFILE_READERR), errno);
2383       return true;
2384       } // endif MAX_INDX
2385 
2386       if (trace(1))
2387         htrc("XFILE Open: noff[%d].v.Low=%d\n", id, noff[id].v.Low);
2388 
2389     // Position the cursor at the offset of this index
2390     if (fseek(Xfile, noff[id].v.Low, SEEK_SET)) {
2391       sprintf(g->Message, MSG(FUNC_ERRNO), errno, "Xseek");
2392       return true;
2393       } // endif
2394 
2395   } // endif mode
2396 
2397   return false;
2398   } // end of Open
2399 
2400 /***********************************************************************/
2401 /*  Move into an index file.                                           */
2402 /***********************************************************************/
Seek(PGLOBAL g,int low,int high,int origin)2403 bool XFILE::Seek(PGLOBAL g, int low, int high __attribute__((unused)),
2404                             int origin)
2405   {
2406 #if defined(_DEBUG)
2407   assert(high == 0);
2408 #endif  // !_DEBUG
2409 
2410   if (fseek(Xfile, low, origin)) {
2411     sprintf(g->Message, MSG(FUNC_ERRNO), errno, "Xseek");
2412     return true;
2413     } // endif
2414 
2415   return false;
2416   } // end of Seek
2417 
2418 /***********************************************************************/
2419 /*  Read from the index file.                                          */
2420 /***********************************************************************/
Read(PGLOBAL g,void * buf,int n,int size)2421 bool XFILE::Read(PGLOBAL g, void *buf, int n, int size)
2422   {
2423   if (fread(buf, size, n, Xfile) != (size_t)n) {
2424     sprintf(g->Message, MSG(XFILE_READERR), errno);
2425     return true;
2426     } // endif size
2427 
2428   return false;
2429   } // end of Read
2430 
2431 /***********************************************************************/
2432 /*  Write on index file, set rc and return the number of bytes written */
2433 /***********************************************************************/
Write(PGLOBAL g,void * buf,int n,int size,bool & rc)2434 int XFILE::Write(PGLOBAL g, void *buf, int n, int size, bool& rc)
2435   {
2436   int niw = (int)fwrite(buf, size, n, Xfile);
2437 
2438   if (niw != n) {
2439     sprintf(g->Message, MSG(XFILE_WRITERR), strerror(errno));
2440     rc = true;
2441     } // endif size
2442 
2443   return niw * size;
2444   } // end of Write
2445 
2446 /***********************************************************************/
2447 /*  Update the file header and close the index file.                   */
2448 /***********************************************************************/
Close(char * fn,int id)2449 void XFILE::Close(char *fn, int id)
2450   {
2451   if (id >= 0 && fn && Xfile) {
2452     fclose(Xfile);
2453 
2454     if ((Xfile = fopen(fn, "r+b")))
2455       if (!fseek(Xfile, id * sizeof(IOFF), SEEK_SET))
2456         fwrite(&NewOff,  sizeof(int), 2, Xfile);
2457 
2458     } // endif id
2459 
2460   Close();
2461   } // end of Close
2462 
2463 /***********************************************************************/
2464 /*  Close the index file.                                              */
2465 /***********************************************************************/
Close(void)2466 void XFILE::Close(void)
2467   {
2468   XLOAD::Close();
2469 
2470   if (Xfile) {
2471     fclose(Xfile);
2472     Xfile = NULL;
2473     } // endif Xfile
2474 
2475 #if defined(XMAP)
2476   if (Mmp && CloseMemMap(Mmp->memory, Mmp->lenL))
2477     printf("Error closing mapped index\n");
2478 #endif   // XMAP
2479   } // end of Close
2480 
2481 #if defined(XMAP)
2482   /*********************************************************************/
2483   /*  Map the entire index file.                                       */
2484   /*********************************************************************/
FileView(PGLOBAL g,char * fn)2485 void *XFILE::FileView(PGLOBAL g, char *fn)
2486   {
2487   HANDLE  h;
2488 
2489   Mmp = (MMP)PlugSubAlloc(g, NULL, sizeof(MEMMAP));
2490   h = CreateFileMap(g, fn, Mmp, MODE_READ, false);
2491 
2492   if (h == INVALID_HANDLE_VALUE || (!Mmp->lenH && !Mmp->lenL)) {
2493     if (!(*g->Message))
2494       strcpy(g->Message, MSG(FILE_MAP_ERR));
2495 
2496     CloseFileHandle(h);                    // Not used anymore
2497     return NULL;               // No saved values
2498     } // endif h
2499 
2500   CloseFileHandle(h);                    // Not used anymore
2501   return Mmp->memory;
2502   } // end of FileView
2503 #endif // XMAP
2504 
2505 /* -------------------------- XHUGE Class --------------------------- */
2506 
2507 /***********************************************************************/
2508 /*  Xopen function: opens a file using native API's.                   */
2509 /***********************************************************************/
Open(PGLOBAL g,char * filename,int id,MODE mode)2510 bool XHUGE::Open(PGLOBAL g, char *filename, int id, MODE mode)
2511   {
2512   IOFF noff[MAX_INDX];
2513 
2514   if (Hfile != INVALID_HANDLE_VALUE) {
2515     sprintf(g->Message, MSG(FILE_OPEN_YET), filename);
2516     return true;
2517     } // endif
2518 
2519   if (trace(1))
2520     htrc(" Xopen: filename=%s id=%d mode=%d\n", filename, id, mode);
2521 
2522 #if defined(_WIN32)
2523   LONG  high = 0;
2524   DWORD rc, drc, access, share, creation;
2525 
2526   /*********************************************************************/
2527   /*  Create the file object according to access mode                  */
2528   /*********************************************************************/
2529   switch (mode) {
2530     case MODE_READ:
2531       access = GENERIC_READ;
2532       share = FILE_SHARE_READ;
2533       creation = OPEN_EXISTING;
2534       break;
2535     case MODE_WRITE:
2536       access = GENERIC_WRITE;
2537       share = 0;
2538       creation = CREATE_ALWAYS;
2539       break;
2540     case MODE_INSERT:
2541       access = GENERIC_WRITE;
2542       share = 0;
2543       creation = OPEN_EXISTING;
2544       break;
2545     default:
2546       sprintf(g->Message, MSG(BAD_FUNC_MODE), "Xopen", mode);
2547       return true;
2548     } // endswitch
2549 
2550   Hfile = CreateFile(filename, access, share, NULL, creation,
2551                                FILE_ATTRIBUTE_NORMAL, NULL);
2552 
2553   if (Hfile == INVALID_HANDLE_VALUE) {
2554     rc = GetLastError();
2555     sprintf(g->Message, MSG(OPEN_ERROR), rc, mode, filename);
2556     FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM |
2557                   FORMAT_MESSAGE_IGNORE_INSERTS, NULL, rc, 0,
2558                   (LPTSTR)filename, sizeof(filename), NULL);
2559     strcat(g->Message, filename);
2560     return true;
2561     } // endif Hfile
2562 
2563   if (trace(1))
2564     htrc(" access=%p share=%p creation=%d handle=%p fn=%s\n",
2565          access, share, creation, Hfile, filename);
2566 
2567   if (mode == MODE_INSERT) {
2568     /*******************************************************************/
2569     /* In Insert mode we must position the cursor at end of file.      */
2570     /*******************************************************************/
2571     rc = SetFilePointer(Hfile, 0, &high, FILE_END);
2572 
2573     if (rc == INVALID_SET_FILE_POINTER && (drc = GetLastError()) != NO_ERROR) {
2574       sprintf(g->Message, MSG(ERROR_IN_SFP), drc);
2575       CloseHandle(Hfile);
2576       Hfile = INVALID_HANDLE_VALUE;
2577       return true;
2578       } // endif
2579 
2580     NewOff.v.Low = (int)rc;
2581     NewOff.v.High = (int)high;
2582   } else if (mode == MODE_WRITE) {
2583     if (id >= 0) {
2584       // New not sep index file. Write the header.
2585       memset(noff, 0, sizeof(noff));
2586       rc = WriteFile(Hfile, noff, sizeof(noff), &drc, NULL);
2587       NewOff.v.Low = (int)drc;
2588       } // endif id
2589 
2590   } else if (mode == MODE_READ && id >= 0) {
2591     // Get offset from the header
2592     rc = ReadFile(Hfile, noff, sizeof(noff), &drc, NULL);
2593 
2594     if (!rc) {
2595       sprintf(g->Message, MSG(XFILE_READERR), GetLastError());
2596       return true;
2597       } // endif rc
2598 
2599     // Position the cursor at the offset of this index
2600     rc = SetFilePointer(Hfile, noff[id].v.Low,
2601                        (PLONG)&noff[id].v.High, FILE_BEGIN);
2602 
2603     if (rc == INVALID_SET_FILE_POINTER) {
2604       sprintf(g->Message, MSG(FUNC_ERRNO), GetLastError(), "SetFilePointer");
2605       return true;
2606       } // endif
2607 
2608   } // endif Mode
2609 
2610 #else   // UNIX
2611   int    oflag = O_LARGEFILE;         // Enable file size > 2G
2612   mode_t pmod = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
2613 
2614   /*********************************************************************/
2615   /*  Create the file object according to access mode                  */
2616   /*********************************************************************/
2617   switch (mode) {
2618     case MODE_READ:
2619       oflag |= O_RDONLY;
2620       break;
2621     case MODE_WRITE:
2622       oflag |= O_WRONLY | O_CREAT | O_TRUNC;
2623 //    pmod = S_IREAD | S_IWRITE;
2624       break;
2625     case MODE_INSERT:
2626       oflag |= (O_WRONLY | O_APPEND);
2627       break;
2628     default:
2629       sprintf(g->Message, MSG(BAD_FUNC_MODE), "Xopen", mode);
2630       return true;
2631     } // endswitch
2632 
2633   Hfile= global_open(g, MSGID_OPEN_ERROR_AND_STRERROR, filename, oflag, pmod);
2634 
2635   if (Hfile == INVALID_HANDLE_VALUE) {
2636     /*rc = errno;*/
2637     if (trace(1))
2638       htrc("Open: %s\n", g->Message);
2639 
2640     return true;
2641     } // endif Hfile
2642 
2643   if (trace(1))
2644     htrc(" oflag=%p mode=%d handle=%d fn=%s\n",
2645            oflag, mode, Hfile, filename);
2646 
2647   if (mode == MODE_INSERT) {
2648     /*******************************************************************/
2649     /* Position the cursor at end of file so ftell returns file size.  */
2650     /*******************************************************************/
2651     if (!(NewOff.Val = (longlong)lseek64(Hfile, 0LL, SEEK_END))) {
2652       sprintf(g->Message, MSG(FUNC_ERRNO), errno, "Seek");
2653       return true;
2654       } // endif
2655 
2656     if (trace(1))
2657       htrc("INSERT: NewOff=%lld\n", NewOff.Val);
2658 
2659   } else if (mode == MODE_WRITE) {
2660     if (id >= 0) {
2661       // New not sep index file. Write the header.
2662       memset(noff, 0, sizeof(noff));
2663       NewOff.v.Low = write(Hfile, &noff, sizeof(noff));
2664       } // endif id
2665 
2666     if (trace(1))
2667       htrc("WRITE: NewOff=%lld\n", NewOff.Val);
2668 
2669   } else if (mode == MODE_READ && id >= 0) {
2670     // Get offset from the header
2671     if (read(Hfile, noff, sizeof(noff)) != sizeof(noff)) {
2672       sprintf(g->Message, MSG(READ_ERROR), "Index file", strerror(errno));
2673       return true;
2674       } // endif read
2675 
2676 	  if (trace(1))
2677       htrc("noff[%d]=%lld\n", id, noff[id].Val);
2678 
2679     // Position the cursor at the offset of this index
2680     if (lseek64(Hfile, noff[id].Val, SEEK_SET) < 0) {
2681       sprintf(g->Message, "(XHUGE)lseek64: %s (%lld)", strerror(errno), noff[id].Val);
2682       printf("%s\n", g->Message);
2683 //    sprintf(g->Message, MSG(FUNC_ERRNO), errno, "Hseek");
2684       return true;
2685       } // endif lseek64
2686 
2687   } // endif mode
2688 #endif  // UNIX
2689 
2690   return false;
2691   } // end of Open
2692 
2693 /***********************************************************************/
2694 /*  Go to position in a huge file.                                     */
2695 /***********************************************************************/
Seek(PGLOBAL g,int low,int high,int origin)2696 bool XHUGE::Seek(PGLOBAL g, int low, int high, int origin)
2697   {
2698 #if defined(_WIN32)
2699   LONG  hi = high;
2700   DWORD rc = SetFilePointer(Hfile, low, &hi, origin);
2701 
2702   if (rc == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR) {
2703     sprintf(g->Message, MSG(FUNC_ERROR), "Xseek");
2704     return true;
2705     } // endif
2706 
2707 #else // UNIX
2708   off64_t pos = (off64_t)low
2709               + (off64_t)high * ((off64_t)0x100 * (off64_t)0x1000000);
2710 
2711   if (lseek64(Hfile, pos, origin) < 0) {
2712     sprintf(g->Message, MSG(ERROR_IN_LSK), errno);
2713 
2714     if (trace(1))
2715       htrc("lseek64 error %d\n", errno);
2716 
2717     return true;
2718     } // endif lseek64
2719 
2720   if (trace(1))
2721     htrc("Seek: low=%d high=%d\n", low, high);
2722 #endif // UNIX
2723 
2724   return false;
2725   } // end of Seek
2726 
2727 /***********************************************************************/
2728 /*  Read from a huge index file.                                       */
2729 /***********************************************************************/
Read(PGLOBAL g,void * buf,int n,int size)2730 bool XHUGE::Read(PGLOBAL g, void *buf, int n, int size)
2731   {
2732   bool rc = false;
2733 
2734 #if defined(_WIN32)
2735   bool    brc;
2736   DWORD   nbr, count = (DWORD)(n * size);
2737 
2738   brc = ReadFile(Hfile, buf, count, &nbr, NULL);
2739 
2740   if (brc) {
2741     if (nbr != count) {
2742       strcpy(g->Message, MSG(EOF_INDEX_FILE));
2743       rc = true;
2744       } // endif nbr
2745 
2746   } else {
2747     char  buf[256];
2748     DWORD drc = GetLastError();
2749 
2750     FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM |
2751                   FORMAT_MESSAGE_IGNORE_INSERTS, NULL, drc, 0,
2752                   (LPTSTR)buf, sizeof(buf), NULL);
2753     sprintf(g->Message, MSG(READ_ERROR), "index file", buf);
2754     rc = true;
2755   } // endif brc
2756 #else    // UNIX
2757   ssize_t count = (ssize_t)(n * size);
2758 
2759   if (trace(1))
2760     htrc("Hfile=%d n=%d size=%d count=%d\n", Hfile, n, size, count);
2761 
2762   if (read(Hfile, buf, count) != count) {
2763     sprintf(g->Message, MSG(READ_ERROR), "Index file", strerror(errno));
2764 
2765     if (trace(1))
2766       htrc("read error %d\n", errno);
2767 
2768     rc = true;
2769     } // endif nbr
2770 #endif   // UNIX
2771 
2772   return rc;
2773   } // end of Read
2774 
2775 /***********************************************************************/
2776 /*  Write on a huge index file.                                        */
2777 /***********************************************************************/
Write(PGLOBAL g,void * buf,int n,int size,bool & rc)2778 int XHUGE::Write(PGLOBAL g, void *buf, int n, int size, bool& rc)
2779   {
2780 #if defined(_WIN32)
2781   bool    brc;
2782   DWORD   nbw, count = (DWORD)n * (DWORD) size;
2783 
2784   brc = WriteFile(Hfile, buf, count, &nbw, NULL);
2785 
2786   if (!brc) {
2787     char msg[256];
2788     DWORD drc = GetLastError();
2789 
2790     FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM |
2791                   FORMAT_MESSAGE_IGNORE_INSERTS, NULL, drc, 0,
2792                   (LPTSTR)msg, sizeof(msg), NULL);
2793     sprintf(g->Message, MSG(WRITING_ERROR), "index file", msg);
2794     rc = true;
2795     } // endif size
2796 
2797   return (int)nbw;
2798 #else    // UNIX
2799   ssize_t nbw;
2800   size_t  count = (size_t)n * (size_t)size;
2801 
2802   nbw = write(Hfile, buf, count);
2803 
2804   if (nbw != (signed)count) {
2805     sprintf(g->Message, MSG(WRITING_ERROR),
2806                         "index file", strerror(errno));
2807     rc = true;
2808     } // endif nbw
2809 
2810   return (int)nbw;
2811 #endif   // UNIX
2812   } // end of Write
2813 
2814 /***********************************************************************/
2815 /*  Update the file header and close the index file.                   */
2816 /***********************************************************************/
Close(char * fn,int id)2817 void XHUGE::Close(char *fn, int id)
2818   {
2819   if (trace(1))
2820     htrc("XHUGE::Close: fn=%s id=%d NewOff=%lld\n", fn, id, NewOff.Val);
2821 
2822 #if defined(_WIN32)
2823   if (id >= 0 && fn) {
2824     CloseFileHandle(Hfile);
2825     Hfile = CreateFile(fn, GENERIC_READ | GENERIC_WRITE, 0, NULL,
2826                        OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
2827 
2828     if (Hfile != INVALID_HANDLE_VALUE)
2829       if (SetFilePointer(Hfile, id * sizeof(IOFF), NULL, FILE_BEGIN)
2830               != INVALID_SET_FILE_POINTER) {
2831         DWORD nbw;
2832 
2833         WriteFile(Hfile, &NewOff, sizeof(IOFF), &nbw, NULL);
2834         } // endif SetFilePointer
2835 
2836     } // endif id
2837 #else   // !_WIN32
2838   if (id >= 0 && fn) {
2839     if (Hfile != INVALID_HANDLE_VALUE) {
2840       if (lseek64(Hfile, id * sizeof(IOFF), SEEK_SET) >= 0) {
2841         ssize_t nbw = write(Hfile, &NewOff, sizeof(IOFF));
2842 
2843         if (nbw != (signed)sizeof(IOFF))
2844           htrc("Error writing index file header: %s\n", strerror(errno));
2845 
2846       } else
2847         htrc("(XHUGE::Close)lseek64: %s (%d)\n", strerror(errno), id);
2848 
2849     } else
2850       htrc("(XHUGE)error reopening %s: %s\n", fn, strerror(errno));
2851 
2852     } // endif id
2853 #endif  // !_WIN32
2854 
2855   XLOAD::Close();
2856   } // end of Close
2857 
2858 #if defined(XMAP)
2859 /***********************************************************************/
2860 /*  Don't know whether this is possible for huge files.                */
2861 /***********************************************************************/
FileView(PGLOBAL g,char *)2862 void *XHUGE::FileView(PGLOBAL g, char *)
2863   {
2864   strcpy(g->Message, MSG(NO_PART_MAP));
2865   return NULL;
2866   } // end of FileView
2867 #endif   // XMAP
2868 
2869 /* -------------------------- XXROW Class --------------------------- */
2870 
2871 /***********************************************************************/
2872 /*  XXROW Public Constructor.                                          */
2873 /***********************************************************************/
XXROW(PTDBDOS tdbp)2874 XXROW::XXROW(PTDBDOS tdbp) : XXBASE(tdbp, false)
2875   {
2876   Srtd = true;
2877   Tdbp = tdbp;
2878   Valp = NULL;
2879   } // end of XXROW constructor
2880 
2881 /***********************************************************************/
2882 /*  XXROW Reset: re-initialize a Kindex block.                         */
2883 /***********************************************************************/
Reset(void)2884 void XXROW::Reset(void)
2885   {
2886 #if defined(_DEBUG)
2887   assert(Tdbp->GetLink());                // This a join index
2888 #endif   // _DEBUG
2889   } // end of Reset
2890 
2891 /***********************************************************************/
2892 /*  Init: Open and Initialize a Key Index.                             */
2893 /***********************************************************************/
Init(PGLOBAL g)2894 bool XXROW::Init(PGLOBAL g)
2895   {
2896   /*********************************************************************/
2897   /*  Table will be accessed through an index table.                   */
2898   /*  To_Link should not be NULL.                                      */
2899   /*********************************************************************/
2900   if (!Tdbp->GetLink() || Tbxp->GetKnum() != 1)
2901     return true;
2902 
2903   if ((*Tdbp->GetLink())->GetResultType() != TYPE_INT) {
2904     strcpy(g->Message, MSG(TYPE_MISMATCH));
2905     return true;
2906   } else
2907     Valp = (*Tdbp->GetLink())->GetValue();
2908 
2909   if ((Num_K = Tbxp->Cardinality(g)) < 0)
2910     return true;                   // Not a fixed file
2911 
2912   /*********************************************************************/
2913   /*  The entire table is indexed, no need to construct the index.     */
2914   /*********************************************************************/
2915   Cur_K = Num_K;
2916   return false;
2917   } // end of Init
2918 
2919 /***********************************************************************/
2920 /*  RANGE: Tell how many record exist in a given value range.          */
2921 /***********************************************************************/
Range(PGLOBAL,int limit,bool incl)2922 int XXROW::Range(PGLOBAL, int limit, bool incl)
2923   {
2924   int  n = Valp->GetIntValue();
2925 
2926   switch (limit) {
2927     case 1: n += ((incl) ? 0 : 1); break;
2928     case 2: n += ((incl) ? 1 : 0); break;
2929     default: n = 1;
2930     } // endswitch limit
2931 
2932   return n;
2933   } // end of Range
2934 
2935 /***********************************************************************/
2936 /*  XXROW: Fetch a physical or logical record.                         */
2937 /***********************************************************************/
Fetch(PGLOBAL)2938 int XXROW::Fetch(PGLOBAL)
2939   {
2940   if (Num_K == 0)
2941     return -1;       // means end of file
2942 
2943   /*********************************************************************/
2944   /*  Look for a key equal to the link column of previous table,       */
2945   /*  and return its rank whithin the index table.                     */
2946   /*********************************************************************/
2947   Cur_K = FastFind();
2948 
2949   if (Cur_K >= Num_K)
2950     /*******************************************************************/
2951     /* Rank not whithin index table, signal record not found.          */
2952     /*******************************************************************/
2953     return -2;      // Means record not found
2954 
2955   /*********************************************************************/
2956   /*  If rank is equal to stored rank, record is already there.        */
2957   /*********************************************************************/
2958   if (Cur_K == Old_K)
2959     return -3;                   // Means record already there
2960   else
2961     Old_K = Cur_K;                // Store rank of newly read record
2962 
2963   return Cur_K;
2964   } // end of Fetch
2965 
2966 /***********************************************************************/
2967 /*  FastFind: Returns the index of matching record in a join.          */
2968 /***********************************************************************/
FastFind(void)2969 int XXROW::FastFind(void)
2970   {
2971   int n = Valp->GetIntValue();
2972 
2973   if (n < 0)
2974     return (Op == OP_EQ) ? (-1) : 0;
2975   else if (n > Num_K)
2976     return Num_K;
2977   else
2978     return (Op == OP_GT) ? n : (n - 1);
2979 
2980   } // end of FastFind
2981 
2982 /* ------------------------- KXYCOL Classes -------------------------- */
2983 
2984 /***********************************************************************/
2985 /*  KXYCOL public constructor.                                         */
2986 /***********************************************************************/
KXYCOL(PKXBASE kp)2987 KXYCOL::KXYCOL(PKXBASE kp) : To_Keys(Keys.Memp),
2988         To_Bkeys(Bkeys.Memp), Kof((CPINT&)Koff.Memp)
2989   {
2990   Next = NULL;
2991   Previous = NULL;
2992   Kxp = kp;
2993   Colp = NULL;
2994   IsSorted = false;
2995   Asc = true;
2996   Keys = Nmblk;
2997   Kblp = NULL;
2998   Bkeys = Nmblk;
2999   Blkp = NULL;
3000   Valp = NULL;
3001   Klen = 0;
3002   Kprec = 0;
3003   Type = TYPE_ERROR;
3004   Prefix = false;
3005   Koff = Nmblk;
3006   Val_K = 0;
3007   Ndf = 0;
3008   Mxs = 0;
3009   } // end of KXYCOL constructor
3010 
3011 /***********************************************************************/
3012 /*  KXYCOL Init: initialize and allocate storage.                      */
3013 /*  Key length kln can be smaller than column length for CHAR columns. */
3014 /***********************************************************************/
Init(PGLOBAL g,PCOL colp,int n,bool sm,int kln)3015 bool KXYCOL::Init(PGLOBAL g, PCOL colp, int n, bool sm, int kln)
3016   {
3017   int  len = colp->GetLength(), prec = colp->GetScale();
3018 	bool un = colp->IsUnsigned();
3019 
3020   // Currently no indexing on NULL columns
3021   if (colp->IsNullable() && kln) {
3022     sprintf(g->Message, "Cannot index nullable column %s", colp->GetName());
3023     return true;
3024     } // endif nullable
3025 
3026   if (kln && len > kln && colp->GetResultType() == TYPE_STRING) {
3027     len = kln;
3028     Prefix = true;
3029     } // endif kln
3030 
3031   if (trace(1))
3032     htrc("KCOL(%p) Init: col=%s n=%d type=%d sm=%d\n",
3033          this, colp->GetName(), n, colp->GetResultType(), sm);
3034 
3035   // Allocate the Value object used when moving items
3036   Type = colp->GetResultType();
3037 
3038   if (!(Valp = AllocateValue(g, Type, len, prec, un)))
3039     return true;
3040 
3041   Klen = Valp->GetClen();
3042   Keys.Size = (size_t)n * (size_t)Klen;
3043 
3044   if (!PlgDBalloc(g, NULL, Keys)) {
3045     sprintf(g->Message, MSG(KEY_ALLOC_ERROR), Klen, n);
3046     return true;    // Error
3047     } // endif
3048 
3049   // Allocate the Valblock. The last parameter is to have rows filled
3050   // by blanks (if true) or keep the zero ending char (if false).
3051   // Currently we set it to true to be compatible with QRY blocks,
3052   // and the one before last is to enable length/type checking, set to
3053   // true if not a prefix key.
3054   Kblp = AllocValBlock(g, To_Keys, Type, n, len, prec, !Prefix, true, un);
3055   Asc = sm;                    // Sort mode: Asc=true  Desc=false
3056   Ndf = n;
3057 
3058   // Store this information to avoid sorting when already done
3059   if (Asc)
3060     IsSorted = colp->GetOpt() == 2;
3061 
3062 //SetNulls(colp->IsNullable()); for when null columns will be indexable
3063   Colp = colp;
3064   return false;
3065   } // end of Init
3066 
3067 #if defined(XMAP)
3068 /***********************************************************************/
3069 /*  KXYCOL MapInit: initialize and address storage.                    */
3070 /*  Key length kln can be smaller than column length for CHAR columns. */
3071 /***********************************************************************/
MapInit(PGLOBAL g,PCOL colp,int * n,BYTE * m)3072 BYTE* KXYCOL::MapInit(PGLOBAL g, PCOL colp, int *n, BYTE *m)
3073   {
3074   int  len = colp->GetLength(), prec = colp->GetScale();
3075 	bool un = colp->IsUnsigned();
3076 
3077   if (n[3] && colp->GetLength() > n[3]
3078            && colp->GetResultType() == TYPE_STRING) {
3079     len = n[3];
3080     Prefix = true;
3081     } // endif kln
3082 
3083   Type = colp->GetResultType();
3084 
3085   if (trace(1))
3086     htrc("MapInit(%p): colp=%p type=%d n=%d len=%d m=%p\n",
3087          this, colp, Type, n[0], len, m);
3088 
3089   // Allocate the Value object used when moving items
3090   Valp = AllocateValue(g, Type, len, prec, un);
3091   Klen = Valp->GetClen();
3092 
3093   if (n[2]) {
3094     Bkeys.Size = n[2] * Klen;
3095     Bkeys.Memp = m;
3096     Bkeys.Sub = true;
3097 
3098     // Allocate the Valblk containing initial block key values
3099     Blkp = AllocValBlock(g, To_Bkeys, Type, n[2], len, prec, true, true, un);
3100     } // endif nb
3101 
3102   Keys.Size = n[0] * Klen;
3103   Keys.Memp = m + Bkeys.Size;
3104   Keys.Sub = true;
3105 
3106   // Allocate the Valblock. Last two parameters are to have rows filled
3107   // by blanks (if true) or keep the zero ending char (if false).
3108   // Currently we set it to true to be compatible with QRY blocks,
3109   // and last one to enable type checking (no conversion).
3110   Kblp = AllocValBlock(g, To_Keys, Type, n[0], len, prec, !Prefix, true, un);
3111 
3112   if (n[1]) {
3113     Koff.Size = n[1] * sizeof(int);
3114     Koff.Memp = m + Bkeys.Size + Keys.Size;
3115     Koff.Sub = true;
3116     } // endif n[1]
3117 
3118   Ndf = n[0];
3119 //IsSorted = colp->GetOpt() < 0;
3120   IsSorted = false;
3121   Colp = colp;
3122   return m + Bkeys.Size + Keys.Size + Koff.Size;
3123   } // end of MapInit
3124 #endif // XMAP
3125 
3126 /***********************************************************************/
3127 /*  Allocate the offset block used by intermediate key columns.        */
3128 /***********************************************************************/
MakeOffset(PGLOBAL g,int n)3129 int *KXYCOL::MakeOffset(PGLOBAL g, int n)
3130   {
3131   if (!Kof) {
3132     // Calculate the initial size of the offset
3133     Koff.Size = (n + 1) * sizeof(int);
3134 
3135     // Allocate the required memory
3136     if (!PlgDBalloc(g, NULL, Koff)) {
3137       strcpy(g->Message, MSG(KEY_ALLOC_ERR));
3138       return NULL;    // Error
3139      } // endif
3140 
3141   } else if (n) {
3142     // This is a reallocation call
3143     PlgDBrealloc(g, NULL, Koff, (n + 1) * sizeof(int));
3144   } else
3145     PlgDBfree(Koff);
3146 
3147   return (int*)Kof;
3148   } // end of MakeOffset
3149 
3150 /***********************************************************************/
3151 /*  Make a front end array of key values that are the first value of   */
3152 /*  each blocks (of size n). This to reduce paging in FastFind.        */
3153 /***********************************************************************/
MakeBlockArray(PGLOBAL g,int nb,int size)3154 bool KXYCOL::MakeBlockArray(PGLOBAL g, int nb, int size)
3155   {
3156   int i, k;
3157 
3158   // Calculate the size of the block array in the index
3159   Bkeys.Size = nb * Klen;
3160 
3161   // Allocate the required memory
3162   if (!PlgDBalloc(g, NULL, Bkeys)) {
3163     sprintf(g->Message, MSG(KEY_ALLOC_ERROR), Klen, nb);
3164     return true;    // Error
3165     } // endif
3166 
3167   // Allocate the Valblk used to contains initial block key values
3168   Blkp = AllocValBlock(g, To_Bkeys, Type, nb, Klen, Kprec);
3169 
3170   // Populate the array with values
3171   for (i = k = 0; i < nb; i++, k += size)
3172     Blkp->SetValue(Kblp, i, k);
3173 
3174   return false;
3175   } // end of MakeBlockArray
3176 
3177 /***********************************************************************/
3178 /*  KXYCOL SetValue: read column value for nth array element.           */
3179 /***********************************************************************/
SetValue(PCOL colp,int i)3180 void KXYCOL::SetValue(PCOL colp, int i)
3181   {
3182 #if defined(_DEBUG)
3183   assert (Kblp != NULL);
3184 #endif
3185 
3186   Kblp->SetValue(colp->GetValue(), i);
3187   } // end of SetValue
3188 
3189 /***********************************************************************/
3190 /*  InitFind: initialize finding the rank of column value in index.    */
3191 /***********************************************************************/
InitFind(PGLOBAL g,PXOB xp)3192 bool KXYCOL::InitFind(PGLOBAL g, PXOB xp)
3193   {
3194   if (xp->GetType() == TYPE_CONST) {
3195     if (Kxp->Nth)
3196       return true;
3197 
3198     Valp->SetValue_pval(xp->GetValue(), !Prefix);
3199   } else {
3200     xp->Reset();
3201     xp->Eval(g);
3202     Valp->SetValue_pval(xp->GetValue(), false);
3203   } // endif Type
3204 
3205   if (trace(2)) {
3206     char buf[32];
3207 
3208     htrc("KCOL InitFind: value=%s\n", Valp->GetCharString(buf));
3209     } // endif trace
3210 
3211   return false;
3212   } // end of InitFind
3213 
3214 #if 0
3215 /***********************************************************************/
3216 /*  InitBinFind: initialize Value to the value pointed by vp.          */
3217 /***********************************************************************/
3218 void KXYCOL::InitBinFind(void *vp)
3219   {
3220   Valp->SetBinValue(vp);
3221   } // end of InitBinFind
3222 #endif // 0
3223 
3224 /***********************************************************************/
3225 /*  KXYCOL FillValue: called by COLBLK::Eval when a column value is    */
3226 /*  already in storage in the corresponding KXYCOL.                    */
3227 /***********************************************************************/
FillValue(PVAL valp)3228 void KXYCOL::FillValue(PVAL valp)
3229   {
3230   valp->SetValue_pvblk(Kblp, Val_K);
3231 
3232   // Set null when applicable (NIY)
3233 //if (valp->GetNullable())
3234 //  valp->SetNull(valp->IsZero());
3235 
3236   } // end of FillValue
3237 
3238 /***********************************************************************/
3239 /*  KXYCOL: Compare routine for one numeric value.                     */
3240 /***********************************************************************/
Compare(int i1,int i2)3241 int KXYCOL::Compare(int i1, int i2)
3242   {
3243   // Do the actual comparison between values.
3244   int k = Kblp->CompVal(i1, i2);
3245 
3246   if (trace(4))
3247     htrc("Compare done result=%d\n", k);
3248 
3249   return (Asc) ? k : -k;
3250   } // end of Compare
3251 
3252 /***********************************************************************/
3253 /*  KXYCOL: Compare the ith key to the stored Value.                   */
3254 /***********************************************************************/
CompVal(int i)3255 int KXYCOL::CompVal(int i)
3256   {
3257   // Do the actual comparison between numerical values.
3258   if (trace(4)) {
3259     int k = (int)Kblp->CompVal(Valp, (int)i);
3260 
3261     htrc("Compare done result=%d\n", k);
3262     return k;
3263   } else
3264     return Kblp->CompVal(Valp, i);
3265 
3266   } // end of CompVal
3267 
3268 /***********************************************************************/
3269 /*  KXYCOL: Compare the key to the stored block value.                 */
3270 /***********************************************************************/
CompBval(int i)3271 int KXYCOL::CompBval(int i)
3272   {
3273   // Do the actual comparison between key values.
3274   return Blkp->CompVal(Valp, i);
3275   } // end of CompBval
3276 
3277 /***********************************************************************/
3278 /*  KXYCOL ReAlloc: ReAlloc To_Data if it is not suballocated.         */
3279 /***********************************************************************/
ReAlloc(PGLOBAL g,int n)3280 void KXYCOL::ReAlloc(PGLOBAL g, int n)
3281   {
3282   PlgDBrealloc(g, NULL, Keys, n * Klen);
3283   Kblp->ReAlloc(To_Keys, n);
3284   Ndf = n;
3285   } // end of ReAlloc
3286 
3287 /***********************************************************************/
3288 /*  KXYCOL FreeData: Free To_Keys if it is not suballocated.           */
3289 /***********************************************************************/
FreeData(void)3290 void KXYCOL::FreeData(void)
3291   {
3292   PlgDBfree(Keys);
3293   Kblp = NULL;
3294   PlgDBfree(Bkeys);
3295   Blkp = NULL;
3296   PlgDBfree(Koff);
3297   Ndf = 0;
3298   } // end of FreeData
3299