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