winamp/Src/nde/Index.cpp
2024-09-24 14:54:57 +02:00

787 lines
18 KiB
C++

/* ---------------------------------------------------------------------------
Nullsoft Database Engine
--------------------
codename: Near Death Experience
--------------------------------------------------------------------------- */
/* ---------------------------------------------------------------------------
Raw Index Class
--------------------------------------------------------------------------- */
#include "nde.h"
#include <stdio.h>
const char *iSign="NDEINDEX";
//---------------------------------------------------------------------------
Index::Index(VFILE *H, unsigned char id, int pos, int type, bool newindex, int nentries, Table *parentTable)
{
Handle = H;
IndexTable = NULL;
MaxSize=0;
locateUpToDate = false;
Id = id;
Position = pos;
// DataType = type;
SecIndex = NULL;
InChain=false;
InInsert=false;
InChainIdx = -1;
pTable = parentTable;
TableHandle = pTable->Handle;
SetGlobalLocateUpToDate(false);
if (!newindex)
{
Vfseek(Handle, (int)strlen(__INDEX_SIGNATURE__), SEEK_SET);
Vfread(&NEntries, sizeof(NEntries), Handle);
}
else
NEntries = nentries;
LoadIndex(newindex);
}
//---------------------------------------------------------------------------
Index::~Index()
{
if (IndexTable)
free(IndexTable);
}
//---------------------------------------------------------------------------
int Index::Insert(int N)
{
return Insert(NULL, N, false);
}
//---------------------------------------------------------------------------
int Index::Insert(Index *parindex, int N, bool localonly)
{
if (InChain) return -1;
Index *p = parindex;
IndexField *f = 0;
locateUpToDate = false;
SetGlobalLocateUpToDate(false);
InInsert=true;
if (N > NEntries) N = NEntries;
if ((NEntries+1) > (int)MaxSize)
{
MaxSize *=2;//+= BLOCK_SIZE;
int *newBlock = (int *)calloc(MaxSize, sizeof(int)*2);
memcpy(newBlock, IndexTable, NEntries*sizeof(int)*2);
free(IndexTable);
IndexTable = newBlock;
}
if (N < NEntries && Id == PRIMARY_INDEX)
{
memmove(IndexTable+(N+1)*2, IndexTable+(N*2), (NEntries-N)*sizeof(int)*2);
NEntries++;
}
else
{
N=NEntries;
NEntries++;
}
Update(N, 0, NULL, localonly);
// Should be always safe to cat the new record since if we are primary index,
// then secondary is sorted, so value will be moved at update
// if we are a secondary index, then an insertion will insert at the end of the primary index anyway
if (!localonly && SecIndex)
{
InChain=true;
int pp = SecIndex->index->Insert(this, N, false);
InChain=false;
IndexTable[N*2+1] = pp == -1 ? N : pp;
if (N < NEntries-1 && Id == PRIMARY_INDEX)
{
int pidx = -1;
if (!parindex)
{
int v = pp;
f = SecIndex;
if (f)
{
while (f->index->SecIndex->index != this)
{
v = f->index->GetCooperative(v);
f = f->index->SecIndex;
}
p = f->index;
pidx = v;
}
}
if (p && pidx != -1)
{
p->SetCooperative(pidx, N);
UpdateMe(p, N, NEntries);
}
}
}
InInsert=false;
return N;
}
//---------------------------------------------------------------------------
void Index::LoadIndex(bool newindex)
{
if (IndexTable)
free(IndexTable);
MaxSize = ((NEntries) / BLOCK_SIZE + 1) * BLOCK_SIZE;
IndexTable = (int *)calloc(MaxSize, sizeof(int)*2);
if (!newindex)
{
Vfseek(Handle, (int)strlen(__INDEX_SIGNATURE__)+4+((NEntries*4*2)+4)*(Position+1), SEEK_SET);
int v = 0;
Vfread(&v, sizeof(v), Handle);
Id = (unsigned char)v;
Vfread(IndexTable, NEntries*2*sizeof(int), Handle);
}
}
//---------------------------------------------------------------------------
int Index::Update(int Idx, int Pos, RecordBase *record, bool localonly)
{
return Update(NULL, -1, Idx, Pos, record, false, localonly);
}
//---------------------------------------------------------------------------
int Index::Update(Index *parindex, int paridx, int Idx, int Pos, RecordBase *record, bool forceLast, bool localonly)
{
if (InChain) return InChainIdx;
int NewIdx=Idx;
int oldSecPtr = IndexTable[Idx*2+1];
if (!forceLast && Id == PRIMARY_INDEX || record == NULL || Idx < NUM_SPECIAL_RECORDS)
{
if (Idx < NEntries && Idx >= 0)
{
IndexTable[Idx*2] = Pos;
if (!localonly && SecIndex && SecIndex->index != this && !InInsert)
{
InChain=true;
InChainIdx = Idx;
SecIndex->index->Update(this, Idx, IndexTable[Idx*2+1], Pos, record, forceLast, false);
InChainIdx = -1;
InChain=false;
}
}
else
{
#ifdef WIN32
MessageBox(NULL, L"Updating outside range", L"Oops", 16);
#else
printf("NDE Error: updating outside range!\n");
#endif
}
}
else
{
if (forceLast)
NewIdx = NEntries;
else
{
if (Pos != Get(Idx) || Id != PRIMARY_INDEX)
{
int state = 0;
NewIdx = FindSortedPlace(record->GetField(Id), Idx, &state, QFIND_ENTIRE_SCOPE);
}
}
if (NewIdx <= NEntries && NewIdx >= NUM_SPECIAL_RECORDS)
{
if (NewIdx != Idx)
{
Index *p = parindex;
IndexField *f = 0;
int pidx = paridx;
NewIdx = MoveIndex(Idx, NewIdx);
if (SecIndex->index != this)
{
if (!parindex)
{
int v = GetCooperative(NewIdx);
f = SecIndex;
if (f)
{
while (f->index->SecIndex->index != this)
{
v = f->index->GetCooperative(v);
f = f->index->SecIndex;
}
p = f->index;
pidx = v;
}
}
if (p)
{
p->SetCooperative(pidx, NewIdx);
UpdateMe(p, NewIdx, Idx);
}
}
}
IndexTable[NewIdx*2] = Pos;
if (!localonly && SecIndex && SecIndex->index != this && !InInsert) // Actually, we should never be InInsert and here, but lets cover our ass
{
InChain=true;
InChainIdx = oldSecPtr;
SecIndex->index->Update(this, NewIdx, oldSecPtr, Pos, record, forceLast, false);
InChainIdx = -1;
InChain=false;
}
}
else
{
#ifdef WIN32
MessageBox(NULL, L"QSort failed and tried to update index outside range", L"Oops", 16);
#else
printf("NDE Error: qsort failed and tried to update index outside range!\n");
#endif
}
}
locateUpToDate = false;
SetGlobalLocateUpToDate(false);
pTable->IndexModified();
return NewIdx;
}
//---------------------------------------------------------------------------
int Index::Get(int Idx)
{
if (Idx < NEntries)
return IndexTable[Idx*2];
else
{
#ifdef WIN32
MessageBox(NULL, L"Requested index outside range", L"Oops", 16);
#else
printf("NDE Error: requested index outside range!\n");
#endif
return -1;
}
}
//---------------------------------------------------------------------------
void Index::Set(int Idx, int P)
{
if (Idx < NEntries)
IndexTable[Idx*2]=P;
else
{
#ifdef WIN32
MessageBox(NULL, L"Updating index outside range", L"Oops", 16);
#else
printf("NDE Error: updating index outside range!\n");
#endif
}
}
//---------------------------------------------------------------------------
void Index::WriteIndex(void)
{
if (Id == PRIMARY_INDEX)
{
Vfseek(Handle, (int)strlen(__INDEX_SIGNATURE__), SEEK_SET);
Vfwrite(&NEntries, sizeof(NEntries), Handle);
}
Vfseek(Handle, (int)strlen(__INDEX_SIGNATURE__)+4+((NEntries*4*2)+4)*(Position+1), SEEK_SET);
int v=(int)Id;
Vfwrite(&v, sizeof(v), Handle);
Vfwrite(IndexTable, NEntries*2*sizeof(int), Handle);
}
//---------------------------------------------------------------------------
int Index::MoveIndex(int idx, int newidx)
{
if (idx == newidx)
return newidx;
int oldPos=IndexTable[idx*2], oldPtr=IndexTable[idx*2+1];
if (NEntries > idx+1)
memmove(IndexTable+idx*2, IndexTable+(idx+1)*2, (NEntries-idx)*sizeof(int)*2);
if (newidx > idx)
newidx--;
if (NEntries > newidx)
memmove(IndexTable+(newidx+1)*2, IndexTable+newidx*2, (NEntries-newidx-1)*sizeof(int)*2);
IndexTable[newidx*2] = oldPos;
IndexTable[newidx*2+1] = oldPtr;
return newidx;
}
//---------------------------------------------------------------------------
void Index::Colaborate(IndexField *secindex)
{
SecIndex = secindex;
for (int i=0;i<NUM_SPECIAL_RECORDS;i++)
/*secindex->index->*/SetCooperative(i, i);
}
//---------------------------------------------------------------------------
void Index::Propagate(void)
{
int i;
if (!SecIndex || SecIndex->ID == PRIMARY_INDEX) return;
SecIndex->index->NEntries=NUM_SPECIAL_RECORDS;
for (i=0;i<NUM_SPECIAL_RECORDS;i++)
{
SecIndex->index->Set(i, Get(i));
SecIndex->index->SetCooperative(i, GetCooperative(i));
}
Scanner *s = pTable->NewScanner();
if (!s)
{
#ifdef WIN32
MessageBox(NULL, L"Failed to create a scanner in reindex", L"Oops", 16);
#else
printf("NDE Error: failed to create a scanner in reindex!\n");
#endif
return;
}
int *coopSave = (int *)calloc(NEntries, sizeof(int));
if (!coopSave)
{
#ifdef WIN32
MessageBox(NULL, L"Alloc failed in reindex", L"Oops", 16);
#else
printf("NDE Error: alloc failed in reindex!\n");
#endif
return;
}
for (i=0;i<NEntries;i++)
{
coopSave[i] = GetCooperative(i);
SetCooperative(i, i);
}
if (SecIndex->index->SecIndex->index->Id != PRIMARY_INDEX)
{
#ifdef WIN32
MessageBox(NULL, L"Propagating existing index", L"Oops", 16);
#else
printf("NDE Error: propagating existing index!\n");
#endif
free(coopSave);
return;
}
s->SetWorkingIndexById(-1);
for (i=NUM_SPECIAL_RECORDS;i<NEntries;i++)
{
Record *rec = s->GetRecord(coopSave[i]);
if (rec)
{
SecIndex->index->NEntries++;
// SecIndex->index->Insert(NULL, i, true);
SecIndex->index->SetCooperative(i, coopSave[i]);
SecIndex->index->Set(i, Get(i));
SecIndex->index->Update(i, SecIndex->index->Get(i), rec, true);
/* SecIndex->index->SetCooperative(i, q);*/
rec->Release();
}
else
{
#ifdef WIN32
MessageBox(NULL, L"Unable to read record in reindex", L"Oops", 16);
#else
printf("NDE Error: unable to read record in reindex!\n");
#endif
}
}
free(coopSave);
if (NEntries != SecIndex->index->NEntries) {
#ifdef WIN32
MessageBox(NULL, L"Secindex->NEntries desynchronized in reindex", L"Oops", 16);
#else
printf("NDE Error: Secindex->NEntries desynchronized in reindex!\n");
#endif
}
pTable->DeleteScanner(s);
}
//---------------------------------------------------------------------------
void Index::SetCooperative(int Idx, int secpos)
{
if (Idx < NEntries && Idx >= 0)
IndexTable[Idx*2+1] = secpos;
else
{
#ifdef WIN32
MessageBox(NULL, L"Cooperative update outside range", L"Oops", 16);
#else
printf("NDE Error: cooperative update outside range!\n");
#endif
}
}
//---------------------------------------------------------------------------
int Index::GetCooperative(int Idx)
{
if (Idx < NEntries && Idx >= 0)
return IndexTable[Idx*2+1];
else
{
#ifdef WIN32
MessageBox(NULL, L"Cooperative request outside range", L"Oops", 16);
#else
printf("NDE Error: cooperative request outside range!\n");
#endif
}
return -1;
}
//---------------------------------------------------------------------------
int Index::NeedFix() {
for (int i=NUM_SPECIAL_RECORDS;i<NEntries;i++) {
if (IndexTable[i*2+1] <= 0) return 1;
}
return 0;
}
//---------------------------------------------------------------------------
void Index::UpdateMe(Index *Me, int NewIdx, int OldIdx)
{
int j=NewIdx > OldIdx ? -1 : 1;
for (int i=min(NewIdx, OldIdx);i<=max(NewIdx, OldIdx)&&i<NEntries;i++)
{
if (i == NewIdx) continue;
int v = GetCooperative(i);
IndexField *f = SecIndex;
while (f->index->SecIndex->index != this)
{
v = f->index->GetCooperative(v);
f = f->index->SecIndex;
}
Me->SetCooperative(v, Me->GetCooperative(v)+j);
}
}
//---------------------------------------------------------------------------
Field *Index::QuickFindField(unsigned char Idx, int Pos)
{
int ThisPos = Pos;
while (ThisPos)
{
Vfseek(TableHandle, ThisPos, SEEK_SET);
Field entry(ThisPos);
uint32_t next_pos;
entry.ReadField(pTable, ThisPos, true, &next_pos);
if (entry.GetFieldId() == Idx)
{
return entry.ReadField(pTable, ThisPos);
}
ThisPos = next_pos;
}
return NULL;
}
//---------------------------------------------------------------------------
// Dynamic qsort (i definitly rule)
int Index::FindSortedPlace(Field *thisField, int curIdx, int *laststate, int start)
{
return FindSortedPlaceEx(thisField, curIdx, laststate, start, COMPARE_MODE_EXACT);
}
//---------------------------------------------------------------------------
// and here again ugly switch
int Index::FindSortedPlaceEx(Field *thisField, int curIdx, int *laststate, int start, int comp_mode)
{
int top=start != QFIND_ENTIRE_SCOPE ? start : NUM_SPECIAL_RECORDS;
int bottom=NEntries-1;
int compEntry = 0;
int cePos = 0;
Field *compField=NULL;
int i = 0;
Field *cfV=NULL;
if (NEntries <= NUM_SPECIAL_RECORDS) return NUM_SPECIAL_RECORDS;
switch(comp_mode)
{
case COMPARE_MODE_EXACT:
while (bottom-top >= 1)
{
compEntry=(bottom-top)/2+top;
if (compEntry == curIdx) compEntry++;
if (compEntry == bottom) break;
cePos = Get(compEntry);
if (!cePos) bottom = compEntry;
else
{
compField = QuickFindField(Id, Get(compEntry));
cfV = compField;
if (!thisField)
{
if (!compField) i = 0;
else i = 1;
}
else i = thisField->Compare(cfV);
switch (i)
{
case 1:
top = compEntry+1;
break;
case -1:
bottom = compEntry-1;
break;
case 0:
*laststate=0;
delete compField;
return compEntry+1;
}
delete compField;
}
}
compEntry=(bottom-top)/2+top;
if (compEntry == curIdx) return curIdx;
compField = QuickFindField(Id, Get(compEntry));
cfV = compField;
if (thisField) *laststate = thisField->Compare(cfV);
else
{
if (!compField) *laststate = 0;
else *laststate = 1;
}
break;
case COMPARE_MODE_CONTAINS:
while (bottom-top >= 1)
{
compEntry=(bottom-top)/2+top;
if (compEntry == curIdx) compEntry++;
if (compEntry == bottom) break;
cePos = Get(compEntry);
if (!cePos) bottom = compEntry;
else
{
compField = QuickFindField(Id, Get(compEntry));
cfV = compField;
if (!thisField)
{
if (!compField) i = 0;
else i = 1;
}
else i = thisField->Contains(cfV);
switch (i)
{
case 1:
top = compEntry+1;
break;
case -1:
bottom = compEntry-1;
break;
case 0:
*laststate=0;
delete compField;
return compEntry+1;
}
if (compField)
{
delete compField;
}
}
}
compEntry=(bottom-top)/2+top;
if (compEntry == curIdx) return curIdx;
compField = QuickFindField(Id, Get(compEntry));
cfV = compField;
if (thisField) *laststate = thisField->Contains(cfV);
else
{
if (!compField) *laststate = 0;
else *laststate = 1;
}
break;
case COMPARE_MODE_STARTS:
while (bottom-top >= 1)
{
compEntry=(bottom-top)/2+top;
if (compEntry == curIdx) compEntry++;
if (compEntry == bottom) break;
cePos = Get(compEntry);
if (!cePos) bottom = compEntry;
else
{
compField = QuickFindField(Id, Get(compEntry));
cfV = compField;
if (!thisField)
{
if (!compField) i = 0;
else i = 1;
}
else i = thisField->Starts(cfV);
switch (i)
{
case 1:
top = compEntry+1;
break;
case -1:
bottom = compEntry-1;
break;
case 0:
*laststate=0;
delete compField;
return compEntry+1;
}
if (compField)
{
delete compField;
}
}
}
compEntry=(bottom-top)/2+top;
if (compEntry == curIdx) return curIdx;
compField = QuickFindField(Id, Get(compEntry));
cfV = compField;
if (thisField) *laststate = thisField->Starts(cfV);
else
{
if (!compField) *laststate = 0;
else *laststate = 1;
}
break;
}
switch (*laststate)
{
case -1:
delete compField;
return compEntry;
case 1:
case 0:
delete compField;
return /*compEntry==NEntries-1 ? compEntry : */compEntry+1;
}
return NUM_SPECIAL_RECORDS; // we're not supposed to be here :/
}
int Index::QuickFind(int Id, Field *field, int start)
{
return QuickFindEx(Id, field, start, COMPARE_MODE_EXACT);
}
//---------------------------------------------------------------------------
int Index::QuickFindEx(int Id, Field *field, int start, int comp_mode)
{
int laststate = 0;
Field *compField=NULL, *cfV=NULL;
int i = FindSortedPlaceEx(field, Id, &laststate, start, comp_mode)-1; // -1 because we don't insert but just search
if (laststate != 0)
return FIELD_NOT_FOUND;
if (i < start)
return FIELD_NOT_FOUND;
switch(comp_mode)
{
case COMPARE_MODE_CONTAINS:
while (--i>=NUM_SPECIAL_RECORDS)
{
compField = QuickFindField(Id, Get(i));
cfV = compField;
if (field->Contains(cfV) != 0)
{
if (compField)
{
delete compField;
}
return i+1;
}
if (compField)
{
delete compField;
}
}
break;
case COMPARE_MODE_EXACT:
while (--i>=NUM_SPECIAL_RECORDS)
{
compField = QuickFindField(Id, Get(i));
cfV = compField;
if (field->Compare(cfV) != 0)
{
if (compField)
{
delete compField;
}
return i+1;
}
if (compField)
{
delete compField;
}
}
break;
case COMPARE_MODE_STARTS:
while (--i>=NUM_SPECIAL_RECORDS)
{
compField = QuickFindField(Id, Get(i));
cfV = compField;
if (field->Starts(cfV) != 0)
{
delete compField;
return i+1;
}
delete compField;
}
break;
}
return i+1;
}
//---------------------------------------------------------------------------
int Index::TranslateIndex(int Pos, Index *index)
{
int v = GetCooperative(Pos);
IndexField *f = SecIndex;
while (f->index != this && f->index != index)
{
v = f->index->GetCooperative(v);
f = f->index->SecIndex;
}
return v;
}
//---------------------------------------------------------------------------
void Index::Delete(int Idx, int Pos, Record *record)
{
Update(NULL, -1, Idx, Pos, record, true, false);
Shrink();
}
//---------------------------------------------------------------------------
void Index::Shrink(void)
{
if (InChain) return;
if (SecIndex && SecIndex->index != this) // Actually, we should never be InInsert and here, but lets cover our ass
{
InChain=true;
SecIndex->index->Shrink();
InChain=false;
}
NEntries--;
}
//---------------------------------------------------------------------------
void Index::SetGlobalLocateUpToDate(bool isUptodate) {
if (!pTable) return;
pTable->SetGlobalLocateUpToDate(isUptodate);
}
unsigned char Index::GetId() {
return Id;
}