The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.
// -*- Mode: C++ -*-

//          GiSTnode.cpp
//
// Copyright (c) 1996, Regents of the University of California
// $Header: /cvsroot/Tree-M/GiST/GiSTnode.cpp,v 1.1 2001/05/06 00:45:52 root Exp $

#include <string.h>
#include "GiST.h"

const int ALLOC_INCR=32;

extern "C" int 
GiSTintcmp(const void *x, const void *y)
{
	int i=*(int *)x;
	int j=*(int *)y;
	return(i-j);
}

int 
GiSTnode::Size() const
{
	int size=GIST_PAGE_HEADER_SIZE+sizeof(GiSTlte);
	int fixlen=FixedLength();

	if(fixlen) size+=numEntries*(fixlen+sizeof(GiSTpage));
	else for(int i=0; i<numEntries; i++)
		size+=(sizeof(GiSTlte)+sizeof(GiSTpage)+entries[i]->CompressedLength());
	return size;
}

GiSTnode::GiSTnode()
{
	packedNode=NULL;
	sibling=0;
	level=0;
	numEntries=0;
	maxEntries=0;
	entries=NULL;
	tree=NULL;
}

GiSTnode::GiSTnode(const GiSTnode &node)
{
	maxEntries=node.maxEntries;
	numEntries=node.numEntries;
	level=node.level;
	sibling=node.sibling;
	tree=node.tree;
	if (node.packedNode) {
		packedNode=new char[tree->Store()->PageSize()];
		memcpy(packedNode, node.packedNode, tree->Store()->PageSize());
	}
	else packedNode=NULL;
	if(maxEntries) entries=new GiSTentryptr[maxEntries];
	else entries=NULL;
	for(int i=0; i<numEntries; i++) {
		entries[i]=(GiSTentry*) node.entries[i]->Copy();
		((GiSTentry *)entries[i])->SetNode(this);
	}
	path=node.path;
}

void 
GiSTnode::Expand(int index)
{
	int newMaxEntries=maxEntries+ALLOC_INCR;
	if(newMaxEntries<index+1) newMaxEntries=index+1+ALLOC_INCR;
	GiSTentryptr *newEntries=new GiSTentryptr[newMaxEntries];
	int i;

	for(i=numEntries; i<newMaxEntries; i++) newEntries[i]=NULL;
	for(i=0; i<numEntries; i++) newEntries[i]=entries[i];
	if(entries!=NULL) delete entries;
	entries=newEntries;
	maxEntries=newMaxEntries;
}

GiSTentryptr& 
GiSTnode::operator[](int index)
{
	assert(index>=0);
	if (index>=maxEntries) Expand(index);
	return entries[index];
}

const GiSTentryptr& 
GiSTnode::operator[](int index) const
{
	static GiSTentryptr e;

	assert(index>=0);
	if(index>=maxEntries) return e;
	return entries[index];
}

void 
GiSTnode::InsertBefore(const GiSTentry& entry, int index)
{
	assert(index<=NumEntries());
	GiSTentry *e=(GiSTentry*) entry.Copy();

	e->SetLevel(Level());
	e->SetPosition(index);
	e->SetNode(this);
	// Move everything else over
	for(int i=NumEntries(); i>index; i--) {
		GiSTentry *e=(*this)[i-1];
		e->SetPosition(i);
		(*this)[i]=e;
	}
	// Stick the entry in
	(*this)[index]=e;
	// Bump up the count
	numEntries++;
}

void 
GiSTnode::Insert(const GiSTentry& entry)
{
	// Find out where to insert it
	int i;

	for(i=0; i<NumEntries(); i++) if((*this)[i]->Compare(entry)>0) break;
	// Do the deed
	InsertBefore(entry, i);
}

void 
GiSTnode::DeleteEntry(int index)
{
	int j;

	assert(index<numEntries);
	// free up the memory in the entry to be deleted
	if(entries[index].Ptr()) delete entries[index].Ptr();
	// Remove the entry
	for(j=index; j<numEntries-1; j++) {
		entries[j]=entries[j+1];
		entries[j]->SetPosition(j);
	}
	numEntries--;
}

void
GiSTnode::DeleteBulk(int vec[], int veclen)
{
	int i;

	qsort(vec, veclen, sizeof(int), GiSTintcmp);
	for(i=veclen-1; i>=0; i--) DeleteEntry(vec[i]);
}

void 
GiSTnode::Pack(char *page) const
{
	// Pack the header
	GiSTheader *h=(GiSTheader *) page;

	h->level=Level();
	h->numEntries=NumEntries();
	h->sibling=Sibling();

	int fixlen=FixedLength();
	GiSTlte *ltable=(GiSTlte *)(page+tree->Store()->PageSize());
	GiSTlte ltptr=GIST_PAGE_HEADER_SIZE;

	for(int i=0; i<numEntries; i++) {
		GiSTcompressedEntry compressedEntry=(*this)[i]->Compress();

		if(fixlen) assert(fixlen==compressedEntry.keyLen);
		// Copy the entry onto the page
		if(compressedEntry.keyLen>0) memcpy(page+ltptr, compressedEntry.key, compressedEntry.keyLen);
		memcpy(page+ltptr+compressedEntry.keyLen, &compressedEntry.ptr, sizeof(GiSTpage));
		// Be tidy
		if(compressedEntry.key) delete compressedEntry.key;
		// Enter a pointer to the entry in the line table
		if(!fixlen) *--ltable=ltptr;
		ltptr+=compressedEntry.keyLen+sizeof(GiSTpage);
	}
	// Store extra line table entry so we know last entry's length
	*--ltable=ltptr;
}

void 
GiSTnode::Unpack(const char *page)
{
	const GiSTheader *h=(const GiSTheader *) page;

	Reset();
	SetLevel(h->level);
	SetSibling(h->sibling);
	if(!packedNode) packedNode=new char[tree->Store()->PageSize()];
	memcpy(packedNode, page, tree->Store()->PageSize());
	Expand(h->numEntries);
	SetNumEntries(h->numEntries);
	for(int i=0; i<h->numEntries; i++) {
		GiSTcompressedEntry tmpentry=Entry(i);
		GiSTentry *e=CreateEntry();

		e->SetLevel(Level());
		e->SetPosition(i);
		e->SetNode(this);
		e->Decompress(tmpentry);
		// be tidy
		if(tmpentry.key) delete tmpentry.key;
		// Append the body with the entry
		entries[i]=e;
	}
}

// SearchMinPenalty returns where a new entry should be inserted.
GiSTpage 
GiSTnode::SearchMinPenalty(const GiSTentry &newEntry) const
{
	GiSTentry *minEntry=NULL;
	GiSTpenalty *minPenalty=NULL;

	for(int i=0; i<numEntries; i++) {
		GiSTentry *e=(*this)[i];
		assert(e->Node()==this);
		GiSTpenalty *penalty=e->Penalty(newEntry);

		if(minEntry==NULL||(*penalty)<(*minPenalty)) {
			minEntry=e;
			if(minPenalty) delete minPenalty;
			minPenalty=penalty;
		}
		else delete penalty;
	}
	delete minPenalty;
	return minEntry->Ptr();
}

void 
GiSTnode::Coalesce(const GiSTnode &source, const GiSTentry& entry) // entry is the entry in the parent node that points to this
{
	// Coalesce by one-by-one insertion
	//   Take each entry from the source node
	//   and insert it into this node.
	for(int i=0; i<source.numEntries; i++) {
		GiSTentry& e=source[i];

		InsertBefore(e, NumEntries());
	}
}

GiSTcompressedEntry 
GiSTnode::Entry(int entryPos) const
{
	// Look up the line table
	GiSTlte keyPhysicalPos, nextKeyPhysicalPos;
	int fixlen=FixedLength();

	if(!fixlen) {
		memcpy(&keyPhysicalPos, packedNode+tree->Store()->PageSize()-(entryPos+1)*sizeof(GiSTlte), sizeof(GiSTlte));
		memcpy(&nextKeyPhysicalPos, packedNode+tree->Store()->PageSize()-(entryPos+2)*sizeof(GiSTlte), sizeof(GiSTlte));
	}
	else {
		keyPhysicalPos=GIST_PAGE_HEADER_SIZE+(sizeof(GiSTpage)+fixlen)*entryPos;
		nextKeyPhysicalPos=keyPhysicalPos+sizeof(GiSTpage)+fixlen;
	}

	// Allocate and set up the return key
	GiSTcompressedEntry entry;

	entry.keyLen=nextKeyPhysicalPos-keyPhysicalPos-sizeof(GiSTpage);
	if(entry.keyLen>0) {
		entry.key=new char[entry.keyLen];
		memcpy(entry.key, packedNode+keyPhysicalPos, entry.keyLen);
	}
	memcpy(&entry.ptr, packedNode+keyPhysicalPos+entry.keyLen, sizeof(GiSTpage));
	return entry;
}

GiSTlist<GiSTentry*> 
GiSTnode::Search(const GiSTpredicate &query) const
{
	GiSTlist<GiSTentry*> list;

	for(int i=0; i<numEntries; i++) {
		GiSTentry *e=(*this)[i];

		if(query.Consistent(*e)) list.Append((GiSTentry*)e->Copy());
	}
	return list;
}

GiSTentry* 
GiSTnode::SearchPtr(GiSTpage ptr) const
{
	for(int i=0; i<numEntries; i++) {
		GiSTentry *e=(*this)[i];

		if(e->Ptr()==ptr) return (GiSTentry*) e->Copy();
	}
	return NULL;
}

#ifdef PRINTING_OBJECTS
void 
GiSTnode::Print(ostream& os) const
{
	os << path << " #Entries: " << NumEntries() << ", ";
	os << "Level " << Level();
	if (IsLeaf()) os << "(Leaf)";
	else os << "(Internal)";
	os << ", Sibling: " << Sibling();
	os << ", Size: " << Size() << "/" << tree->Store()->PageSize() << endl;
	for(int i=0; i<numEntries; i++) (*this)[i]->Print(os);
}
#endif

GiSTpage 
GiSTnode::SearchNeighbors(GiSTpage ptr) const
{
	for(int i=0; i<numEntries; i++)
		if((*this)[i]->Ptr()==ptr) {
			// Is there a right neighbor?
			if(i!=numEntries-1) return (*this)[i+1]->Ptr();
			// Is there a left neighbor?
			if (i!=0) return (*this)[i-1]->Ptr();
			break;
		}
	return 0;
}

GiSTnode* 
GiSTnode::PickSplit()
{
	// Create the right node.  Make it an exact copy of this one.
	GiSTnode *node=(GiSTnode*) Copy();
	int half=numEntries/2;
	int i;

	// Delete the first N/2 entries from the right node.
	node->numEntries=0;
	for(i=0; i<numEntries-half; i++) {
		node->entries[i]=node->entries[i+half];
		node->entries[i]->SetPosition(i);
		node->numEntries++;
	}
	// Delete the last N/2 entries from the left node.
	numEntries=half;
	// Return the right node.
	return node;
}

void 
GiSTnode::Reset()
{
	if(entries!=NULL) {
		for(int i=0; i<numEntries; i++) delete entries[i].Ptr();
		delete entries;
		entries=NULL;
	}
	if (packedNode) {
		delete packedNode;
		packedNode=NULL;
	}
	numEntries=maxEntries=0;
}

GiSTnode::~GiSTnode()
{
	Reset();
}

int 
GiSTnode::IsUnderFull(const GiSTstore &store) const
{
	return Size()<store.PageSize()/2;
}

int 
GiSTnode::IsOverFull(const GiSTstore &store) const
{
	return Size()>store.PageSize();
}