b9bdbd5d8dd89df24f9c3b869e68b2d10fc52ea9
[sdk] / eda / libeda / src / EDB.ec
1 import "EDA.ec"
2
3 public class EDBIndexOptions
4 {
5 public:
6    bool saveIndex:1, deleteIndex:1;
7 };
8
9 public void SetEDBIndexOptions(EDBIndexOptions options)
10 {
11    indexOptions = options;
12 }
13
14 EDBIndexOptions indexOptions { saveIndex = true, deleteIndex = false; };
15
16 static void UnusedFunction()
17 {
18    int a;
19    a.OnGetString(0,0,0);
20    a.OnFree();
21    a.OnCopy(null);
22    a.OnCompare(null);
23    a.OnSaveEdit(null,0);
24    a.OnEdit(null,null,0,0,0,0,0);
25    a.OnDisplay(null,0,0,0,0,0,0);
26    a.OnGetDataFromString(null);
27    a.OnUnserialize(null);
28    a.OnSerialize(null);
29 }
30
31 default:
32 extern int __ecereVMethodID_class_OnGetString;
33 extern int __ecereVMethodID_class_OnGetDataFromString;
34 extern int __ecereVMethodID_class_OnCompare;
35 extern int __ecereVMethodID_class_OnSerialize;
36 extern int __ecereVMethodID_class_OnUnserialize;
37 extern int __ecereVMethodID_class_OnFree;
38 private:
39
40 enum RowsCountFileAction { init, add, del, reuse };
41 void RowsCountFileEdit(Archive archive, const String apath, const RowsCountFileAction action, int * allocatedRowsCount, int * deletedRowsCount, uint * rowsCountPosition)
42 {
43    File f;
44    int temp;
45    if(!*rowsCountPosition)
46    {
47       int c;
48       File f = ((EDBArchive)archive).f;
49       EDBArchiveDir dir = (EDBArchiveDir) archive.OpenDirectory(apath, FileStats { }, readOnlyDir);
50       uint position;
51       EAREntry entry { };
52       f.Seek(dir.position, start);
53       position = dir.first;
54       while(true)
55       {
56          char fileName[MAX_FILENAME];
57          if(!f.Seek(position, start)) break;
58          if(!f.Read(entry, sizeof(EAREntry), 1)) break;
59          f.Read(fileName, 1, entry.nameLen);
60          fileName[entry.nameLen] = '\0';
61          if(!strcmp(fileName, "rowsCount"))
62          {
63             *rowsCountPosition = position;
64             break;
65          }
66          if(entry.next)
67             position = entry.next;
68          else
69             break;
70       }
71       delete dir;
72    }
73
74    f = *rowsCountPosition ? archive.FileOpenAtPosition(*rowsCountPosition) : null;
75    if(f)
76    {
77       f.Get(temp); *allocatedRowsCount = temp;
78       f.Get(temp); *deletedRowsCount = temp;
79       delete f;
80    }
81    else
82    {
83       *allocatedRowsCount = 0;
84       *deletedRowsCount = 0;
85    }
86
87    switch(action)
88    {
89       case init: break;
90       case add: (*allocatedRowsCount)++; break;
91       case del: (*deletedRowsCount)++; break;
92       case reuse: (*deletedRowsCount)--; break;
93    }
94
95    if(action != init)
96    {
97       ArchiveDir dir;
98       TempFile tf { };
99       temp = *allocatedRowsCount; tf.Put(temp);
100       temp = *deletedRowsCount; tf.Put(temp);
101       tf.Seek(0, start);
102
103       dir = archive.OpenDirectory(apath, FileStats { }, replace);
104       dir.AddFromFileAtPosition(*rowsCountPosition, "rowsCount", tf, { size = tf.GetSize() }, replace, 0, null, rowsCountPosition);
105       delete tf;
106       delete dir;
107    }
108 }
109
110 static class EDBDataSource : DataSourceDriver
111 {
112    class_property(name) = "EDB";
113
114    String path;
115    //OldList listDatabases;
116    uint databasesCount;
117
118    String BuildLocator(DataSource ds)
119    {
120       return CopyString(ds.host);
121    }
122
123    uint GetDatabasesCount()
124    {
125       return databasesCount;
126    }
127
128    ~EDBDataSource()
129    {
130       delete path;
131    }
132
133    bool Connect(const String locator)
134    {
135       delete path;
136       path = CopyString(locator);
137       // TODO, use user name and password for local security?
138       // TODO, open ds in read or write mode
139       if(FileExists(path))
140       {
141          int n = 0;
142          FileListing listing { path, "edb" };
143          databasesCount = 0;
144          while(listing.Find())
145             databasesCount++;
146          return true;
147       }
148       return false;
149    }
150
151    void Status()
152    {
153       Log($"Status: Feeling groovy!\n");
154    }
155
156    /*
157    Table OpenDatabasesListTable(subclass(DataDriver) driver)
158    {
159       // get the table for databasesList using a temporary archive of a db with a table databases filled
160       // with the list of edb files in data source's dirPath
161
162       TempFile f { };
163       //ArchiveFile af = ArchiveOpen
164
165       return null;
166    }
167    */
168
169    bool RenameDatabase(const String name, const String rename)
170    {
171       if(name && rename && path && FileExists(path))
172       {
173          String path;
174          path = MakeDatabasePath(name);
175          if(FileExists(path))
176          {
177             bool renamed;
178             String repath;
179             repath = MakeDatabasePath(rename);
180             renamed = RenameFile(path, repath);
181             delete path;
182             delete repath;
183             return renamed;
184          }
185          delete path;
186       }
187       return false;
188    }
189
190    bool DeleteDatabase(const String name)
191    {
192       if(path && FileExists(path))
193       {
194          bool deleted;
195          String path = MakeDatabasePath(name);
196          deleted = DeleteFile(path);  // delete file seems to return true even if the file does not exist
197          databasesCount--;
198          delete path;
199          return deleted;
200       }
201       return false;
202    }
203
204    String MakeDatabasePath(const String name)
205    {
206       if(name)
207       {
208          char build[MAX_LOCATION];
209          strcpy(build, path ? path : "");
210          PathCat(build, name);
211          ChangeExtension(build, "edb", build);
212          return CopyString(build);
213       }
214       return null;
215    }
216
217    Database OpenDatabase(const String name, CreateOptions createOptions, DataSource ds)
218    {
219       if(name && name[0])
220       {
221          String path = MakeDatabasePath(name);
222          Archive archive = null;
223          if(FileExists(path))
224          {
225             // Check if it's a valid archive because we don't want to create an archive at the end of another file
226             archive = ArchiveOpen(path, { false, false, false });
227             if(archive)
228             {
229                // If we want write access
230                delete archive;
231                archive = ArchiveOpen(path, { true, true, true });
232             }
233             else
234                printf($"Invalid, corrupted or in use (%s) database file.\n", path);
235          }
236          else if(createOptions == create)
237          {
238             archive = ArchiveOpen(path, { true, true, true });
239             if(archive)
240                databasesCount++;
241          }
242          if(archive)
243          {
244             EDBDatabase db { path = path, archive = archive };
245             if(!archive.FileExists("-/tables"))
246             {
247                int allocatedRowsCount, deletedRowsCount;
248                RowsCountFileEdit(archive, "-/tables", init, &allocatedRowsCount, &deletedRowsCount, &db.tablesCountPosition);
249             }
250             return db;
251          }
252          else if(createOptions == create)
253             Logf($"Database file (%s) could not be created.\n", path);
254          else
255             Logf($"Database file (%s) could not be opened.\n", path);
256          delete path;
257       }
258       return null;
259    }
260 }
261
262 static enum EAREntryType { ENTRY_FILE = 1, ENTRY_FOLDER = 2 };
263
264 static struct EAREntry
265 {
266    EAREntryType type;
267    TimeStamp32 created, modified;
268    FileSize size, cSize;
269    uint prev, next;
270    uint nameLen;
271    // null terminated file name follows
272 };
273
274 static class EDBArchive : Archive
275 {
276    File f;
277 };
278
279 static class EDBArchiveDir : ArchiveDir
280 {
281    EDBArchive archive;
282    uint position;
283    uint first, last;
284    bool readOnly;
285 };
286
287 static class DBTable : struct
288 {
289    DBTable prev, next;
290    String apath;
291    String apathFields;
292    OldList indexes;
293    OldList fields { offset = (uint)&((EDBField)0).prev; };
294    ArchiveDir dir;
295
296    uint rowsCountPosition;
297    // uint fieldsCountPosition;
298    uint * rowPositions;
299    uint rowPositionsSize;
300
301    int rowsCount;
302    int allocatedRowsCount;
303    int deletedRowsCount;
304    int fieldsCount;
305    DBTable fieldsTable;
306
307    ~DBTable()
308    {
309       // Save Indexes
310       if(indexOptions.saveIndex)
311       {
312          DBIndex index;
313          for(index = indexes.first; index; index = index.next)
314          {
315             if(index.init)
316             {
317                char indexName[1024];
318                int c;
319                TempFile tf { };
320                ArchiveDir dirTable;
321                tf.Put(index.tree);
322                strcpy(indexName, "index_");
323                for(c = 0; c<index.numFields; c++)
324                {
325                   strcat(indexName, index.fieldIndexes[c].field.name);
326                   if(index.fieldIndexes[c].memberField)
327                   {
328                      strcat(indexName, ".");
329                      strcat(indexName, index.fieldIndexes[c].memberField.name);
330                   }
331                   strcat(indexName, (index.fieldIndexes[c].order == ascending) ? "+" : "-");
332                }
333                tf.Seek(0, start);
334
335                dirTable = db.archive.OpenDirectory(apath, FileStats { }, replace);
336                dirTable.AddFromFile(indexName, tf, { size = tf.GetSize() }, replace, 0, null, null);
337                delete dirTable;
338
339                delete tf;
340             }
341          }
342       }
343
344       delete apath;
345       delete apathFields;
346       delete dir;
347       delete rowPositions;
348
349       fields.RemoveAll(EDBField::Free);
350       indexes.RemoveAll(DBIndex::Free);
351    }
352
353    void Free() { delete this; }
354
355 public:
356    EDBDatabase db;
357 }
358
359 class EDBDatabase : Database
360 {
361    String path;
362    Archive archive;
363    OldList dbTables;
364    uint tablesCountPosition;
365
366    property uint bufferSize { set { archive.bufferSize = value; } }
367    property uint bufferRead { set { archive.bufferRead = value; } }
368
369    ~EDBDatabase()
370    {
371       dbTables.RemoveAll(DBTable::Free);
372       delete path;
373       delete archive;
374    }
375
376    String GetName()
377    {
378       return path;   // TOFIX
379    }
380
381    DBTable GetDBTable(char * apath, OpenOptions options)
382    {
383       DBTable dbTbl;
384       for(dbTbl = dbTables.first; dbTbl; dbTbl = dbTbl.next)
385          if(!strcmp(apath, dbTbl.apath))
386             break;
387       if(!dbTbl)
388       {
389          char build[MAX_LOCATION];
390          ArchiveDir dirTable = archive.OpenDirectory(apath, FileStats { }, replace);
391          delete dirTable;
392          dbTbl = DBTable { this, apath = CopyString(apath) };
393          strcpy(build, apath);
394          PathCat(build, "fields");
395          dbTbl.apathFields = CopyString(build);
396          dbTables.Add(dbTbl);
397
398          dbTbl.dir = archive.OpenDirectory(apath, null, readOnlyDir);
399
400          RowsCountFileEdit(archive, apath, init, &dbTbl.allocatedRowsCount, &dbTbl.deletedRowsCount, &dbTbl.rowsCountPosition);
401          dbTbl.rowsCount = dbTbl.allocatedRowsCount - dbTbl.deletedRowsCount;
402
403          {
404             int c;
405             File f = ((EDBArchive)archive).f;
406             EDBArchiveDir dir = (EDBArchiveDir) dbTbl.dir;
407             uint position;
408             EAREntry entry { };
409             uint allocatedCount = dbTbl.allocatedRowsCount;
410             f.Seek(dir.position, start);
411             dbTbl.rowPositionsSize = dbTbl.allocatedRowsCount;
412             dbTbl.rowPositions = new0 uint[Max(1,dbTbl.rowPositionsSize)];
413             position = dir.first;
414
415             while(true)
416             {
417                char fileName[MAX_FILENAME] = "";
418                uint number;
419                f.Seek(position, start);
420                f.Read(entry, sizeof(EAREntry), 1);
421                f.Read(fileName, 1, entry.nameLen);
422                fileName[entry.nameLen] = '\0';
423                number = atoi(fileName);
424                if(number && number <= allocatedCount)
425                   dbTbl.rowPositions[number - 1] = position;
426                if(entry.next)
427                   position = entry.next;
428                else
429                   break;
430             }
431          }
432
433          if(options.type == tablesList)
434          {
435             dbTbl.fields.Add(EDBField
436                {
437                   tbl = dbTbl,
438                   name = CopyString("Name"),
439                   type = class(String),
440                   length = 0,
441                   num = 1
442                });
443             dbTbl.fieldsCount = 1;
444          }
445          else if(options.type == fieldsList)
446          {
447             dbTbl.fields.Add(EDBField
448                {
449                   tbl = dbTbl,
450                   name = CopyString("Name"),
451                   type = class(String),
452                   length = 0,
453                   num = 1
454                });
455             dbTbl.fields.Add(EDBField
456                {
457                   tbl = dbTbl,
458                   name = CopyString("Type"),
459                   type = class(Class),
460                   length = 0,
461                   num = 2
462                });
463             dbTbl.fields.Add(EDBField
464                {
465                   tbl = dbTbl,
466                   name = CopyString("Length"),
467                   type = class(int),
468                   length = 0,
469                   num = 3
470                });
471             dbTbl.fieldsCount = 3;
472          }
473          else
474          {
475             int num, rowsCount, deletedRowsCount;
476             DBTable fieldsTable = dbTbl.fieldsTable;
477             if(!fieldsTable) fieldsTable = dbTbl.fieldsTable = GetDBTable(dbTbl.apathFields, { fieldsList });
478             RowsCountFileEdit(archive, dbTbl.apathFields, init, &rowsCount, &deletedRowsCount, &dbTbl.fieldsTable.rowsCountPosition /*fieldsCountPosition*/);
479
480             for(num = 1; num <= rowsCount; num++)          // TOFIX in future a field position might have been deleted
481             {
482                EDBField fld { tbl = dbTbl, num = num };
483                incref fld;
484                fld.Read();
485                dbTbl.fields.Add(fld);
486             }
487             dbTbl.fieldsCount = rowsCount;
488          }
489       }
490       return dbTbl;
491    }
492
493    uint ObjectsCount(ObjectType type)
494    {
495       // TODO
496       return 0;
497    }
498
499    bool RenameObject(ObjectType type, const String name, const String rename)
500    {
501       // TODO
502       return false;
503    }
504
505    bool DeleteObject(ObjectType type, const String name)
506    {
507       // TODO
508       return false;
509    }
510
511    Table OpenTable(const String name, OpenOptions options)
512    {
513       EDBTable tbl = null;
514       char apath[MAX_LOCATION] = "";
515       switch(options.type)
516       {
517          case viewRows:
518             break;
519          case processesList:
520             break;
521          case databasesList:
522             {
523                // get the table for databasesList using a temporary archive of a db with a table databases filled
524                // with the list of edb files in data source's dirPath
525             }
526             break;
527          case queryRows:
528             break;
529          case tableRows:
530             if(name)
531             {
532                Table tblTables = OpenTable(null, { tablesList });
533                if(tblTables)
534                {
535                   Row rowTables { tblTables };
536                   Field fldTableName = tblTables.FindField("Name");
537                   if(fldTableName)
538                   {
539                      if(!rowTables.Find(fldTableName, first, nil, name))
540                      {
541                         if(options.create == create)
542                         {
543                            strcpy(apath, name);
544                            rowTables.Add();
545                            rowTables.SetData(fldTableName, name);
546                         }
547                         else
548                            Logf($"Table (%s) does not exist.\n", name);
549                      }
550                      else
551                         strcpy(apath, name);
552                   }
553                   delete rowTables;
554                   delete tblTables;
555                }
556                else
557                   Log($"Unable to detect if table exists!\n");
558             }
559             break;
560          case tablesList:
561             strcpy(apath, "-/tables");
562             break;
563          case fieldsList:
564             if(name)
565             {
566                strcpy(apath, name);
567                PathCat(apath, "fields");
568             }
569             break;
570       }
571
572       if(apath[0] && archive)
573       {
574          DBTable dbTable = GetDBTable(apath, options);
575          if(dbTable)
576          {
577             tbl = EDBTable { dbTable = dbTable };
578             LinkTable(tbl);
579          }
580       }
581       return tbl;
582    }
583 }
584
585 static struct IndexBinaryTree : BinaryTree
586 {
587    DBIndex index;
588 };
589
590 static int IndexCompareRows(IndexBinaryTree tree, uint r1, uint r2)
591 {
592    DBIndex index = tree.index;
593    int f;
594    EDBRow row1 = index.row1;
595    EDBRow row2 = index.row2;
596
597    if(index.cache && index.cache._num == r1)
598       row1 = index.cache;
599    else
600    {
601       row1.tbl = index.dbTable;
602       row1.num = r1;
603    }
604
605    if(index.cache && index.cache._num == r2)
606       row2 = index.cache;
607    else
608    {
609       row2.tbl = index.dbTable;
610       row2.num = r2;
611    }
612
613    for(f = 0; f < index.numFields; f++)
614    {
615       Field field = index.fieldIndexes[f].field;
616       int order = (index.fieldIndexes[f].order == ascending) ? 1 : -1;
617       Field memberField = index.fieldIndexes[f].memberField;
618       while(field)
619       {
620          Class type = field.type;
621          int64 data1 = 0, data2 = 0;
622          int fieldResult = 0;
623          if(type.type == structClass)
624          {
625             data1 = (int64)new0 byte[type.structSize];
626             data2 = (int64)new0 byte[type.structSize];
627          }
628          ((bool (*)())(void *)row1.GetData)(row1, field, type, (type.type == structClass) ? (void *)data1 : &data1);
629          ((bool (*)())(void *)row2.GetData)(row2, field, type, (type.type == structClass) ? (void *)data2 : &data2);
630          fieldResult = order * ((int (*)(void *, void *, void *))(void *)type._vTbl[__ecereVMethodID_class_OnCompare])(type,
631             (type.type == systemClass || type.type == bitClass || type.type == enumClass || type.type == unitClass) ? &data1 : (void *)data1,
632             (type.type == systemClass || type.type == bitClass || type.type == enumClass || type.type == unitClass) ? &data2 : (void *)data2);
633          ((void (*)(void *, void *))(void *)type._vTbl[__ecereVMethodID_class_OnFree])(type, (void *)data1);
634          ((void (*)(void *, void *))(void *)type._vTbl[__ecereVMethodID_class_OnFree])(type, (void *)data2);
635          if(field.type.type == structClass)
636          {
637             void * dataPtr = (void *)data1;
638             delete dataPtr;
639             dataPtr = (void *)data2;
640             delete dataPtr;
641          }
642          if(fieldResult)
643          {
644             if(memberField)
645             {
646                row1 = index.row1;
647                row1.tbl = ((EDBTable)index.fieldIndexes[f].memberTable).dbTable;
648                row1.index = ((EDBTable)index.fieldIndexes[f].memberTable).index;
649                row1.num = 0;
650                row1.Find(index.fieldIndexes[f].memberIdField, middle, nil, data1);
651
652                row2 = index.row2;
653                row2.tbl = ((EDBTable)index.fieldIndexes[f].memberTable).dbTable;
654                row2.index = ((EDBTable)index.fieldIndexes[f].memberTable).index;
655                row2.num = 0;
656                row2.Find(index.fieldIndexes[f].memberIdField, middle, nil, data2);
657
658                field = memberField;
659                memberField = null;
660             }
661             else
662                return fieldResult;
663          }
664          else
665             break;
666       }
667    }
668    if(r1 > r2)
669       return 1;
670    else if(r1 < r2)
671       return -1;
672    else
673       return 0;
674 }
675
676 static class DBIndex : struct
677 {
678    DBIndex prev, next;
679    DBTable dbTable;
680    int numFields;
681    FieldIndex * fieldIndexes;
682    EDBRow row1 { };
683    EDBRow row2 { };
684    EDBRow cache;
685    IndexBinaryTree tree
686    {
687       index = this,
688       CompareKey = (void *)IndexCompareRows;
689    };
690    bool init;
691
692    ~DBIndex()
693    {
694       tree.Free();
695       delete fieldIndexes;
696    }
697    void Free() { delete this; }
698 }
699
700 static class EDBTable : Table
701 {
702    DBTable dbTable;
703    DBIndex index;
704
705    Field AddField(const String name, Class type, int length)
706    {
707       EDBDatabase edb = dbTable.db;
708       EDBField field
709       {
710          tbl = dbTable,
711          name = CopyString(name),
712          type = type,
713          length = length
714       };
715       DBTable fieldsTable = dbTable.fieldsTable;
716       incref field;
717       if(!fieldsTable) fieldsTable = dbTable.fieldsTable = edb.GetDBTable(dbTable.apathFields, { fieldsList });
718
719       RowsCountFileEdit(edb.archive, dbTable.apathFields, add, &fieldsTable.allocatedRowsCount, &fieldsTable.deletedRowsCount, &fieldsTable.rowsCountPosition /*dbTable.fieldsCountPosition*/);
720       fieldsTable.rowsCount = fieldsTable.allocatedRowsCount - fieldsTable.deletedRowsCount;
721
722       // What should this be?
723       fieldsTable.rowPositions = renew0 fieldsTable.rowPositions uint[fieldsTable.allocatedRowsCount];
724
725       dbTable.fields.Add(field);
726
727       // Field num starts at index base 1 !
728       field.num = dbTable.fields.count;
729       field.Write();
730       return field;
731    }
732
733    Field FindField(const String name)
734    {
735       Field fld;
736       for(fld = dbTable.fields.first; fld; fld = fld.next)
737          if(!strcmp(fld.name, name))
738             return fld;
739       return null;
740    }
741
742    Field GetFirstField()
743    {
744       return dbTable.fields.first;
745    }
746
747    DriverRow CreateRow()
748    {
749       return EDBRow { tbl = dbTable, index = index, num = 0 };
750    }
751
752    bool GenerateIndex(int count, FieldIndex * fieldIndexes, bool init)
753    {
754       DBIndex index = null;
755       //Time startTime = GetTime();
756       if(count)
757       {
758          for(index = dbTable.indexes.first; index; index = index.next)
759          {
760             if(index.numFields == count)
761             {
762                int c;
763                for(c = 0; c<count; c++)
764                   if(index.fieldIndexes[c].field != fieldIndexes[c].field ||
765                      index.fieldIndexes[c].order != fieldIndexes[c].order ||
766                      index.fieldIndexes[c].memberField != fieldIndexes[c].memberField)
767                      break;
768                if(c == count)
769                   break;
770             }
771          }
772          if(!index)
773          {
774             int c;
775
776             for(c = 0; c<count; c++)
777             {
778                if(!fieldIndexes[c].field)
779                   return false;
780                else
781                {
782                   EDBField field;
783                   for(field = dbTable.fields.first; field; field = field.next)
784                      if(field == fieldIndexes[c].field)
785                         break;
786                   if(!field)
787                      return false;
788                }
789             }
790             index = DBIndex { numFields = count, dbTable = dbTable, init = init };
791             index.fieldIndexes = new FieldIndex[count];
792             memcpy(index.fieldIndexes, fieldIndexes, count * sizeof(FieldIndex));
793             dbTable.indexes.Add(index);
794
795             {
796 #ifdef _DEBUG
797                //char output[10000];
798 #endif
799                char indexName[1024];
800                int c;
801                File f = null;
802                strcpy(indexName, "index_");
803                for(c = 0; c<index.numFields; c++)
804                {
805                   strcat(indexName, index.fieldIndexes[c].field.name);
806                   if(index.fieldIndexes[c].memberField)
807                   {
808                      strcat(indexName, ".");
809                      strcat(indexName, index.fieldIndexes[c].memberField.name);
810                   }
811                   strcat(indexName, (index.fieldIndexes[c].order == ascending) ? "+" : "-");
812                }
813                if(init)
814                   f = dbTable.dir.FileOpen(indexName);
815                if(f)
816                {
817                   ArchiveDir dir = dbTable.db.archive.OpenDirectory(dbTable.apath, FileStats { }, replace);
818                   f.Get(index.tree);
819                   delete f;
820                   if(indexOptions.deleteIndex)
821                      dir.Delete(indexName);
822                   delete dir;
823                }
824                else
825                {
826                   index.cache = EDBRow { tbl = dbTable };
827                   for(c = 1; c<= dbTable.allocatedRowsCount; c++)
828                   {
829                      char file[MAX_FILENAME];
830                      sprintf(file, "%d", c);
831                      index.cache.num = c;
832                      if(dbTable.rowPositions[c - 1])
833                         index.tree.Add(BTNode { c });
834                   }
835                   delete index.cache;
836                }
837 #ifdef _DEBUG
838                /*
839                index.tree.Print(output, inOrder);
840                printf("\n\n%s - %s:\n", dbTable.apath, indexName);
841                puts(output);
842                printf("\n");
843                */
844 #endif
845             }
846          }
847       }
848       this.index = index;
849 #ifndef ECERE_STATIC
850       //Logf("Indexing took %f seconds\n", GetTime() - startTime);
851 #endif
852       return true;
853    }
854
855    String GetName()
856    {
857       return dbTable.apath;
858    }
859
860    uint GetFieldsCount()
861    {
862       return dbTable.fieldsCount;
863    }
864
865    uint GetRowsCount()
866    {
867       return dbTable.rowsCount;
868    }
869 }
870
871 static class EDBField : Field
872 {
873 public:
874    EDBField prev, next;
875    DBTable tbl;
876    property int num
877    {
878       set
879       {
880          num = value;
881          delete apath;
882          delete aname;
883          if(num)
884          {
885             char file[MAX_FILENAME];
886             char build[MAX_LOCATION];
887             sprintf(file, "%d", num);
888             strcpy(build, tbl.apath);
889             PathCat(build, "fields");
890             PathCat(build, file);
891             apath = CopyString(build);
892             aname = CopyString(file);
893          }
894       }
895    }
896    String name;
897    Class type;
898    int length;
899
900    int num;
901    String apath;
902    String aname;
903
904    String GetName() { return name; }
905    Class GetType() { return type; }
906    int GetLength() { return length; }
907    Field GetNext() { return next; }
908    Field GetPrev() { return prev; }
909    Table GetTable() { return (Table)tbl; }
910
911    ~EDBField()
912    {
913       delete apath;
914       delete aname;
915       delete name;
916    }
917
918    void Read()
919    {
920       EDBDatabase edb = tbl.db;
921       if(aname)
922       {
923          File f = edb.archive.FileOpen(apath);
924          if(f)
925          {
926             int numFields;
927             uint offsets[3];
928             f.Get(numFields);
929             f.Read(offsets, sizeof(uint), 3);
930             f.Get(name);
931             f.Get(type);
932             f.Get(length);
933             delete f;
934          }
935          else
936             Log($"Error reading field");
937       }
938    }
939
940    void Write()
941    {
942       int length = 0;
943       TempFile f { };
944       ArchiveDir dir;
945       EDBDatabase edb = tbl.db;
946       int numFields = 3;
947       uint offsets[3];
948       f.Put(numFields);
949       f.Write(offsets, sizeof(uint), numFields);
950       offsets[0] = f.Tell();
951       f.Put(name);
952       offsets[1] = f.Tell();
953       f.Put(type);
954       offsets[2] = f.Tell();
955       f.Put(length);
956       f.Seek(sizeof(uint), start);
957       f.Write(offsets, sizeof(uint), numFields);
958       f.Seek(0, start);
959
960       dir = edb.archive.OpenDirectory(tbl.apathFields, FileStats { }, replace);
961       dir.AddFromFile(aname, f, { size = f.GetSize() }, replace, 0, null, null);
962       delete f;
963       delete dir;
964    }
965
966    void Free() { delete this; }
967 }
968
969 static class EDBRow : DriverRow
970 {
971    DBTable tbl;
972    DBIndex index;
973    BTNode node;
974
975    String aname;
976    int _num;
977
978    property int num
979    {
980       set
981       {
982          _num = value;
983          delete aname;
984          /*if(tbl && _num)
985          {
986             char file[MAX_FILENAME];
987             sprintf(file, "%d", _num);
988             aname = CopyString(file);
989          }*/
990       }
991    }
992
993    ~EDBRow()
994    {
995       delete aname;
996    }
997
998    bool GetData(EDBField field, typed_object & data)
999    {
1000       bool result = false;
1001       EDBDatabase edb = tbl.db;
1002       File f = (_num && tbl.rowPositions[_num - 1]) ? edb.archive.FileOpenAtPosition(tbl.rowPositions[_num - 1]) : null;
1003       if(f && data._class)
1004       {
1005          uint numFields = 0;
1006          f.Seek(0, start);
1007          f.Get(numFields);
1008          if(numFields)
1009          {
1010             uint * offsets = new0 uint[numFields];
1011             f.Read(offsets, sizeof(uint), numFields);
1012             if(((field.num < numFields && offsets[field.num-1] && offsets[field.num-1] != offsets[field.num]) || (field.num == numFields && offsets[field.num-1])))
1013             {
1014                f.Seek(offsets[field.num-1], start);
1015                ((void (*)(void *, void *, void *))(void *)field.type._vTbl[__ecereVMethodID_class_OnUnserialize])(field.type, data, f);
1016                result = true;
1017             }
1018
1019             delete offsets;
1020          }
1021       }
1022       delete f;
1023       return result;
1024    }
1025
1026    bool SetData(EDBField field, typed_object data)
1027    {
1028       EDBDatabase edb = tbl.db;
1029       File f = (_num && tbl.rowPositions[_num - 1]) ? edb.archive.FileOpenAtPosition(tbl.rowPositions[_num - 1]) : null;
1030       if(f)
1031       {
1032          TempFile tf { };
1033          ArchiveDir dirTable;
1034
1035          uint numFields, oldNumFields = 0;
1036          f.Seek(0, start);
1037          f.Get(oldNumFields);
1038          numFields = Max(oldNumFields, tbl.fields.count);
1039
1040          if(numFields)
1041          {
1042             uint * offsets = new0 uint[numFields];
1043             byte * buffer = null;
1044             uint size;
1045             f.Read(offsets, sizeof(uint), oldNumFields);
1046             tf.Put(numFields);
1047
1048             tf.Write(offsets, sizeof(uint), numFields);
1049
1050             size = f.GetSize();
1051             if(field.num-1 < oldNumFields)
1052             {
1053                // We will read right up to to the field we're writing to
1054                // (Size will be from right after the offset table to that offset)
1055                 if(offsets[field.num-1])
1056                   // *** Fields are based at index 1, if we're writing the last field we need to set size to its offset
1057                   // because we will be rewriting over it. ***
1058                   size = offsets[field.num-1];
1059                 else
1060                 {
1061                    int c;
1062                    // If this field does not exist yet (offsets[field.num-1] == 0), we want to write at the end (leave size = f.GetSize()),
1063                    for(c = field.num; c < oldNumFields; c++)
1064                    {
1065                       if(offsets[c])
1066                       {
1067                          size = offsets[c];
1068                          break;
1069                       }
1070                    }
1071                 }
1072             }
1073
1074             // Subtract the header (numFields + offsets table), that's where we're at right now
1075             size -= f.Tell();
1076
1077             if(size)
1078             {
1079                buffer = renew buffer byte[size];
1080                f.Read(buffer, 1, size);
1081                tf.Write(buffer, 1, size);
1082             }
1083
1084             // Update the offset of the field we're writing to
1085             offsets[field.num-1] = tf.Tell();
1086             // Serialize the data we're writing
1087             ((void (*)(void *, void *, void *))(void *)field.type._vTbl[__ecereVMethodID_class_OnSerialize])(field.type, data, tf);
1088
1089             if(field.num < numFields)
1090             {
1091                int c;
1092                int difference = 0;
1093                for(c = field.num; c<numFields; c++)
1094                {
1095                   if(offsets[c])
1096                   {
1097                      difference = tf.Tell() - offsets[c];
1098                      break;
1099                   }
1100                }
1101
1102                if(c < numFields)
1103                {
1104                   int fileSize = f.GetSize();
1105                   f.Seek(offsets[c], start);
1106                   size = fileSize - offsets[c];
1107                   buffer = renew buffer byte[size];
1108                   f.Read(buffer, 1, size);
1109                   tf.Write(buffer, 1, size);
1110                }
1111                for(; c<numFields; c++)
1112                {
1113                   if(offsets[c])
1114                      offsets[c] += difference;
1115                }
1116             }
1117             if(numFields > oldNumFields)
1118             {
1119                int c;
1120                for(c = 0; c < field.num - 1; c++)
1121                {
1122                   if(offsets[c])
1123                      offsets[c] += (numFields - oldNumFields) * sizeof(uint);
1124                }
1125             }
1126
1127             tf.Seek(sizeof(uint), start);
1128             tf.Write(offsets, sizeof(uint), numFields);
1129
1130             delete buffer;
1131             delete offsets;
1132          }
1133
1134          delete f;
1135          dirTable = edb.archive.OpenDirectory(tbl.apath, FileStats { }, replace);
1136          tf.Seek(0, start);
1137          if(!aname)
1138          {
1139             char file[MAX_FILENAME];
1140             sprintf(file, "%d", _num);
1141             aname = CopyString(file);
1142          }
1143          dirTable.AddFromFileAtPosition(tbl.rowPositions[_num - 1], aname, tf, { size = tf.GetSize() }, replace, 0, null, &tbl.rowPositions[_num - 1]);
1144          delete tf;
1145          delete dirTable;
1146
1147          {
1148             DBIndex i;
1149             for(i = tbl.indexes.first; i; i = i.next)
1150             {
1151                BTNode n = (i == index) ? node : null;
1152                int c;
1153                for(c = 0; c<i.numFields; c++)
1154                   if(i.fieldIndexes[c].field == field ||
1155                      i.fieldIndexes[c].memberField == field ||
1156                      i.fieldIndexes[c].memberIdField == field)
1157                      break;
1158                if(c == i.numFields) continue;
1159
1160                if(!n)
1161                   n = i.tree.FindAll(_num);
1162                if(n)
1163                {
1164                   i.tree.Remove(n);
1165                   i.tree.Add(n);
1166                }
1167             }
1168          }
1169       }
1170       return true;
1171    }
1172
1173    bool Nil()
1174    {
1175       return !_num;
1176    }
1177
1178    bool Select(MoveOptions move)
1179    {
1180       EDBDatabase edb = tbl.db;
1181       if(tbl.rowsCount)
1182       {
1183          if(index)
1184          {
1185             switch(move)
1186             {
1187                case first:
1188                   node = index.tree.first;
1189                   num = node ? (uint)node.key : 0;
1190                   return _num != 0;
1191                case next:
1192                   node = node ? node.next : index.tree.first;
1193                   num = node ? (uint)node.key : 0;
1194                   return _num != 0;
1195                case last:
1196                   node = index.tree.last;
1197                   num = node ? (uint)node.key : 0;
1198                   return _num != 0;
1199                case previous:
1200                   node = node ? node.prev : index.tree.last;
1201                   num = node ? (uint)node.key : 0;
1202                   return _num != 0;
1203                case middle:
1204                   node = index.tree.root;
1205                   num = node ? (uint)node.key : 0;
1206                   return _num != 0;
1207                case nil:
1208                   num = 0;
1209                   break;
1210             }
1211          }
1212          else
1213             switch(move)
1214             {
1215                case first:
1216                   num = 0;
1217                case next:
1218                   while(_num < tbl.allocatedRowsCount)
1219                   {
1220                      num = _num + 1;
1221                      if(tbl.rowPositions[_num - 1])
1222                         return true;
1223                   }
1224                   num = 0;
1225                   break;
1226                case last:
1227                   num = tbl.allocatedRowsCount + 1;
1228                case previous:
1229                   while(_num)
1230                   {
1231                      num = _num - 1;
1232                      if(tbl.rowPositions[_num - 1])
1233                         return true;
1234                   }
1235                   break;
1236                case middle:
1237                case nil:
1238                   num = 0;
1239                   break;
1240             }
1241       }
1242       return false;
1243    }
1244
1245    int CompareRowNum(int num, EDBField field, typed_object data)
1246    {
1247       int result = -1;
1248       EDBDatabase edb = tbl.db;
1249       File f;
1250       f = (num && tbl.rowPositions[num - 1]) ? edb.archive.FileOpenAtPosition(tbl.rowPositions[num - 1]) : null;
1251       if(f && data._class)
1252       {
1253          uint numFields = 0;
1254          f.Get(numFields);
1255          if(field.num <= numFields)
1256          {
1257             uint * offsets = new0 uint[numFields];
1258             Class type = field.type;
1259             int64 read = 0;
1260
1261             f.Read(offsets, sizeof(uint), numFields);
1262
1263             if(((field.num < numFields && offsets[field.num-1] && offsets[field.num-1] != offsets[field.num]) || (field.num == numFields && offsets[field.num-1])))
1264             {
1265                f.Seek(offsets[field.num-1], start);
1266                if(type.type == structClass)
1267                   read = (int64)new0 byte[type.structSize];
1268                ((void (*)(void *, void *, void *))(void *)type._vTbl[__ecereVMethodID_class_OnUnserialize])(type, (type.type == structClass) ? (void *)read : &read, f);
1269             }
1270             //if(data._class == type)
1271             {
1272                result = ((int (*)(void *, void *, void *))(void *)type._vTbl[__ecereVMethodID_class_OnCompare])(type,
1273                      (type.type == systemClass || type.type == bitClass || type.type == enumClass || type.type == unitClass) ? &read : (void *)read, data);
1274             }
1275             ((void (*)(void *, void *))(void *)type._vTbl[__ecereVMethodID_class_OnFree])(type, (void *)read);
1276             if(type.type == structClass)
1277             {
1278                void * dataPtr = (void *)read;
1279                delete dataPtr;
1280             }
1281             delete offsets;
1282          }
1283
1284          delete f;
1285       }
1286       return result;
1287    }
1288
1289    int CompareRow(EDBField field, typed_object data)
1290    {
1291       return CompareRowNum(_num, field, data);
1292    }
1293
1294    bool Find(EDBField field, MoveOptions move, MatchOptions match, typed_object data)
1295    {
1296       EDBDatabase edb = tbl.db;
1297       if(tbl == field.tbl)
1298       {
1299          if(tbl.rowsCount)
1300          {
1301             /*if(move == nil)
1302                return false;*/
1303
1304             if(index && index.fieldIndexes[0].field == field && !index.fieldIndexes[0].memberField)
1305             {
1306                BTNode node = this.node, lastNode = null;
1307                int result;
1308                int order = (index.fieldIndexes[0].order == ascending) ? 1 : -1;
1309
1310                if(move == next || move == nil)
1311                {
1312                   if(move == next)
1313                      node = node ? node.next : null;
1314                   else
1315                      move = next;
1316                   result = node ? order * CompareRowNum((uint)node.key, field, data) : 1;
1317                   // We won't find this on our right
1318                   if(result > 0)
1319                   {
1320                      this.node = null;
1321                      num = 0;
1322                      return false;
1323                   }
1324                   // The next row is good
1325                   else if(result == 0)
1326                   {
1327                      this.node = node;
1328                      num = (uint)node.key;
1329                      return true;
1330                   }
1331                }
1332                else if(move == previous)
1333                {
1334                   node = node ? node.prev : null;
1335                   result = node ? order * CompareRowNum((uint)node.key, field, data) : -1;
1336                   // We won't find this on our left
1337                   if(result < 0)
1338                   {
1339                      this.node = null;
1340                      num = 0;
1341                      return false;
1342                   }
1343                   // The previous row is good
1344                   else if(result == 0)
1345                   {
1346                      this.node = node;
1347                      num = (uint)node.key;
1348                      return true;
1349                   }
1350                }
1351
1352                // Go up as high as we need to
1353                if(move == here)
1354                {
1355                   while(node)
1356                   {
1357                      BTNode max = node.maximum, min = node.minimum;
1358                      int maxResult = order * CompareRowNum((uint)max.key, field, data);
1359                      int minResult = order * CompareRowNum((uint)min.key, field, data);
1360                      if(maxResult >= 0 && minResult <= 0) break;
1361                      node = node.parent;
1362                   }
1363                }
1364                else if(move == first || move == next)
1365                {
1366                   if(move == first)
1367                      node = index.tree.first;
1368                   while(node)
1369                   {
1370                      BTNode max = node.maximum;
1371                      result = order * CompareRowNum((uint)max.key, field, data);
1372                      if(result >= 0) break;
1373                      node = node.parent;
1374                   }
1375                }
1376                else if(move == last || move == previous)
1377                {
1378                   node = index.tree.last;
1379                   while(node)
1380                   {
1381                      BTNode min = node.minimum;
1382                      result = order * CompareRowNum((uint)min.key, field, data);
1383                      if(result <= 0) break;
1384                      node = node.parent;
1385                   }
1386                }
1387                else if(move == middle)
1388                   node = index.tree.root;
1389
1390                // Go down
1391                while(node)
1392                {
1393                   result = order * CompareRowNum((uint)node.key, field, data);
1394                   if(!result) break;
1395                   node = (result < 0) ? node.right : node.left;
1396                }
1397
1398                // Look for closest matching row to search point
1399                while(node && !result)
1400                {
1401                   lastNode = node;
1402                   node = (move == last || move == previous) ? node.next : node.prev;
1403                   if(node) result = order * CompareRowNum((uint)node.key, field, data);
1404                }
1405                this.node = lastNode;
1406                num = lastNode ? (uint)lastNode.key : 0;
1407                return lastNode ? true : false;
1408             }
1409             else
1410             {
1411                if(move == first || move == middle)
1412                {
1413                   Select(first);
1414                   move = next;
1415                }
1416                else if(move == last)
1417                {
1418                   Select(last);
1419                   move = previous;
1420                }
1421                else if(move == next)
1422                   Select(next);
1423                else if(move == previous)
1424                   Select(previous);
1425                else if(move == nil)
1426                   move = next;
1427                while(_num)
1428                {
1429                   int result = CompareRow(field, data);
1430                   if(!result)
1431                   {
1432                      return true;
1433                   }
1434                   Select(move);
1435                }
1436             }
1437          }
1438       }
1439       return false;
1440    }
1441
1442    int MultipleCompareRowNum(int num, FieldFindData * findData, int numFindFields)
1443    {
1444       int result = -1;
1445       EDBDatabase edb = tbl.db;
1446       File f;
1447       f = (num && tbl.rowPositions[num - 1]) ? edb.archive.FileOpenAtPosition(tbl.rowPositions[num - 1]) : null;
1448       if(f)
1449       {
1450          uint numFields = 0;
1451          int c;
1452          uint * offsets;
1453          f.Get(numFields);
1454          offsets = new0 uint[numFields];
1455          f.Read(offsets, sizeof(uint), numFields);
1456
1457          for(c = 0; c<numFindFields; c++)
1458          {
1459             EDBField field = (EDBField)findData[c].field;
1460             int order = (!index || index.fieldIndexes[c].order == ascending) ? 1 : -1;
1461
1462             if(field.num <= numFields)
1463             {
1464                Class type = field.type;
1465                int64 read = 0;
1466
1467                if(((field.num < numFields && offsets[field.num-1] && offsets[field.num-1] != offsets[field.num]) || (field.num == numFields && offsets[field.num-1])))
1468                {
1469                   f.Seek(offsets[field.num-1], start);
1470
1471                   if(type.type == structClass)
1472                      read = (int64)new0 byte[type.structSize];
1473                   ((void (*)(void *, void *, void *))(void *)type._vTbl[__ecereVMethodID_class_OnUnserialize])(type, (type.type == structClass) ? (void *)read : &read, f);
1474                }
1475                //if(data._class == type)
1476                {
1477                   result = order * ((int (*)(void *, void *, void *))(void *)type._vTbl[__ecereVMethodID_class_OnCompare])(type,
1478                         (type.type == systemClass || type.type == bitClass || type.type == enumClass || type.type == unitClass) ? &read : (void *)read,
1479                         (type.type == systemClass || type.type == bitClass || type.type == enumClass || type.type == unitClass) ? &findData[c].value.i64 : (void *)findData[c].value.i64);
1480                }
1481                ((void (*)(void *, void *))(void *)type._vTbl[__ecereVMethodID_class_OnFree])(type, (void *)read);
1482                if(type.type == structClass)
1483                {
1484                   void * dataPtr = (void *)read;
1485                   delete dataPtr;
1486                }
1487
1488                if(result) break;
1489             }
1490          }
1491          delete offsets;
1492          delete f;
1493       }
1494       return result;
1495    }
1496
1497    int MultipleCompareRow(FieldFindData * findData, int numFields)
1498    {
1499       return MultipleCompareRowNum(_num, findData, numFields);
1500    }
1501
1502    bool FindMultiple(FieldFindData * findData, MoveOptions move, int numFields)
1503    {
1504       EDBDatabase edb = tbl.db;
1505       int c;
1506       for(c = 0; c<numFields; c++)
1507          if(tbl != ((EDBField)findData[c].field).tbl)
1508             break;
1509       if(c == numFields)
1510       {
1511          if(tbl.rowsCount)
1512          {
1513             bool indexedFind = false;
1514             /*if(move == nil)
1515                return false;*/
1516             if(index)
1517             {
1518                for(c = 0; c<numFields; c++)
1519                {
1520                   if(index.numFields <= c || index.fieldIndexes[c].field != findData[c].field || index.fieldIndexes[c].memberField)
1521                      break;
1522                }
1523                if(c == numFields)
1524                   indexedFind = true;
1525             }
1526             if(indexedFind)
1527             {
1528                BTNode node = this.node, lastNode = null;
1529                int result;
1530
1531                if(move == next || move == nil)
1532                {
1533                   if(move == next)
1534                      node = node ? node.next : null;
1535                   else
1536                      move = next;
1537                   result = node ? MultipleCompareRowNum((uint)node.key, findData, numFields) : 1;
1538                   // We won't find this on our right
1539                   if(result > 0)
1540                   {
1541                      this.node = null;
1542                      num = 0;
1543                      return false;
1544                   }
1545                   // The next row is good
1546                   else if(result == 0)
1547                   {
1548                      this.node = node;
1549                      num = (uint)node.key;
1550                      return true;
1551                   }
1552                }
1553                else if(move == previous)
1554                {
1555                   node = node ? node.prev : null;
1556                   result = node ? MultipleCompareRowNum((uint)node.key, findData, numFields) : -1;
1557                   // We won't find this on our left
1558                   if(result < 0)
1559                   {
1560                      this.node = null;
1561                      num = 0;
1562                      return false;
1563                   }
1564                   // The previous row is good
1565                   else if(result == 0)
1566                   {
1567                      this.node = node;
1568                      num = (uint)node.key;
1569                      return true;
1570                   }
1571                }
1572
1573                // Go up as high as we need to
1574                if(move == first || move == next)
1575                {
1576                   if(move == first)
1577                      node = index.tree.first;
1578                   while(node)
1579                   {
1580                      BTNode max = node.maximum;
1581                      result = MultipleCompareRowNum((uint)max.key, findData, numFields);
1582                      if(result >= 0) break;
1583                      node = node.parent;
1584                   }
1585                }
1586                else if(move == last || move == previous)
1587                {
1588                   node = index.tree.last;
1589                   while(node)
1590                   {
1591                      BTNode min = node.minimum;
1592                      result = MultipleCompareRowNum((uint)min.key, findData, numFields);
1593                      if(result <= 0) break;
1594                      node = node.parent;
1595                   }
1596                }
1597                else if(move == middle)
1598                   node = index.tree.root;
1599
1600                // Go down
1601                while(node)
1602                {
1603                   result = MultipleCompareRowNum((uint)node.key, findData, numFields);
1604                   if(!result) break;
1605                   node = (result < 0) ? node.right : node.left;
1606                }
1607
1608                // Look for closest matching row to search point
1609                while(node && !result)
1610                {
1611                   lastNode = node;
1612                   node = (move == last || move == previous) ? node.next : node.prev;
1613                   if(node) result = MultipleCompareRowNum((uint)node.key, findData, numFields);
1614                }
1615                this.node = lastNode;
1616                num = lastNode ? (uint)lastNode.key : 0;
1617                return lastNode ? true : false;
1618             }
1619             else
1620             {
1621                if(move == first || move == middle)
1622                {
1623                   Select(first);
1624                   move = next;
1625                }
1626                else if(move == last)
1627                {
1628                   Select(last);
1629                   move = previous;
1630                }
1631                else if(move == nil)
1632                   move = next;
1633                else if(!_num)
1634                   Select(first);
1635                while(_num)
1636                {
1637                   int result = MultipleCompareRow(findData, numFields);
1638                   if(!result)
1639                   {
1640                      return true;
1641                   }
1642                   Select(move);
1643                }
1644             }
1645          }
1646       }
1647       return false;
1648    }
1649
1650    bool Synch(DriverRow to)
1651    {
1652       EDBRow rowTo = (EDBRow)to;
1653       if(tbl == rowTo.tbl)
1654       {
1655          num = rowTo._num;
1656          node = rowTo.node;
1657          return true;
1658       }
1659       return false;
1660    }
1661
1662    bool Add(uint64 id)
1663    {
1664       bool reused = tbl.deletedRowsCount != 0;
1665       EDBDatabase edb = tbl.db;
1666       TempFile f;
1667       ArchiveDir dirTable;
1668
1669       RowsCountFileEdit(edb.archive, tbl.apath, reused ? reuse : add, &tbl.allocatedRowsCount, &tbl.deletedRowsCount, &tbl.rowsCountPosition);
1670       tbl.rowsCount = tbl.allocatedRowsCount - tbl.deletedRowsCount;
1671       if(reused)
1672       {
1673          int n;
1674          for(n = 1; n <= tbl.allocatedRowsCount; n++)
1675          {
1676             if(!tbl.rowPositions[n - 1])
1677                break;
1678          }
1679          num = n;
1680       }
1681       else
1682       {
1683          num = tbl.rowsCount;
1684
1685          if(tbl.allocatedRowsCount > tbl.rowPositionsSize)
1686          {
1687             tbl.rowPositionsSize = tbl.allocatedRowsCount + tbl.allocatedRowsCount / 2;
1688             //tbl.rowPositionsSize = tbl.allocatedRowsCount;
1689             tbl.rowPositions = renew0 tbl.rowPositions uint[Max(1, tbl.rowPositionsSize)];
1690          }
1691          tbl.rowPositions[tbl.allocatedRowsCount - 1] = 0;
1692       }
1693
1694       dirTable = edb.archive.OpenDirectory(tbl.apath, FileStats { }, replace);
1695
1696       f = TempFile { };
1697       f.Seek(0, start);
1698
1699       if(!aname)
1700       {
1701          char file[MAX_FILENAME];
1702          sprintf(file, "%d", _num);
1703          aname = CopyString(file);
1704       }
1705       dirTable.AddFromFileAtPosition(tbl.rowPositions[_num - 1], aname, f, { size = f.GetSize() }, replace, 0, null, &tbl.rowPositions[_num - 1]);
1706       delete f;
1707
1708       delete dirTable;
1709
1710       {
1711          DBIndex i;
1712          for(i = tbl.indexes.first; i; i = i.next)
1713          {
1714             BTNode n { _num };
1715             i.tree.Add(n);
1716             if(i == index)
1717                node = n;
1718          }
1719       }
1720       return true;
1721    }
1722
1723    bool Delete()
1724    {
1725       if(_num)
1726       {
1727          EDBDatabase edb = tbl.db;
1728          ArchiveDir dirTable;
1729          BTNode node = this.node;
1730          int oldNum = this._num;
1731
1732          RowsCountFileEdit(edb.archive, tbl.apath, del, &tbl.allocatedRowsCount, &tbl.deletedRowsCount, &tbl.rowsCountPosition);
1733          tbl.rowsCount = tbl.allocatedRowsCount - tbl.deletedRowsCount;
1734          tbl.rowPositions[_num - 1] = 0;
1735
1736          dirTable = edb.archive.OpenDirectory(tbl.apath, FileStats { }, replace);
1737          if(!aname)
1738          {
1739             char file[MAX_FILENAME];
1740             sprintf(file, "%d", _num);
1741             aname = CopyString(file);
1742          }
1743          dirTable.Delete(aname);
1744          delete dirTable;
1745
1746          Select(next);
1747          if(!_num)
1748             Select(last);
1749
1750          {
1751             DBIndex i;
1752             for(i = tbl.indexes.first; i; i = i.next)
1753             {
1754                BTNode n = (i == index) ? node : null;
1755                if(!n)
1756                   n = i.tree.FindAll(oldNum);
1757                if(n)
1758                {
1759                   i.tree.Delete(n);
1760                }
1761             }
1762             node = null;
1763          }
1764          return true;
1765       }
1766       return false;
1767    }
1768
1769    int GetSysID()
1770    {
1771       return _num;
1772    }
1773
1774    bool GoToSysID(int id)
1775    {
1776       if(tbl.allocatedRowsCount >= id && id > 0 && tbl.rowPositions[id - 1])
1777       {
1778          _num = id;
1779
1780          if(index)
1781             node = index.tree.FindAll(id);
1782          return true;
1783       }
1784       return false;
1785    }
1786 }