/**************************************************************************
 * Source Id :
 *
 * $Id: dbase.cpp,v 1.5 2001/04/20 10:15:01 georgetasker Exp $
 *-------------------------------------------------------------------------
 * Project Notes :
 *
 *  Diamond Base
 *  ============
 *      A solid database implementation, spurred on by the continuing
 *  Metal (Lead) Base saga.
 *
 *  Project Team :
 *        A. Davison
 *        K. Lentin
 *        D. Platt
 *
 *	Project Commenced : 05-02-1993
 *
 *-------------------------------------------------------------------------
 *  Module Notes :
 *
 *		Diamond Base Object : this object is the main database which will
 *	interact with client code via a transaction protocol
 *
 *
 *
 *  Original Author : Andy
 *
 **************************************************************************/

// FOR NOW::
// For compilers that support precompilation, includes "wx.h".
#include "wx/wxprec.h"

#ifdef __BORLANDC__
    #pragma hdrstop
#endif

#if defined(__BORLAND_C__) || defined(_MSC_VER)
#include <strstrea.h>
#else 
#include <strstream.h>
#endif

#include <diamond/dbconf.h>

#if HAVE_MALLOC_H && !MALLOC_H_INCLUDED
extern "C" {
#include <malloc.h>
}
#define MALLOC_H_INCLUDED 1
#endif
#include <diamond/dbase.h>
#include <diamond/nserver.h>
#include <diamond/dbobj.h>
#include <diamond/btree.h>
#include <diamond/diarel.h>
#include <diamond/version.h>

//const int MAX_REG = 100;

// The global diamondbase object is defined here.

diamondBase theDiamondBase;

//
// Provides a way of accessing the DiamondBase methods
// via a function
//
int DIA_EXPORT diaLoadConfig(const char *configName)
{
	return theDiamondBase.loadConfig(configName);
}

const char*
DIA_EXPORT diamondBase::verStr(void) const
{
	return "$Id: dbase.cpp,v 1.5 2001/04/20 10:15:01 georgetasker Exp $";
}

//----------------------------------------------------
// Diamond Base constructor
//
//
//	Provide this with the name of the database description
// file.

diamondBase::diamondBase()
{
	// Startup the database.
	// Make all the arrays point to nothing.

	int i;
	for(i=0;i<MAX_REG;i++)
		regInfo[i] = 0;
	for (i=0; i<MAX_DB_INFO; i++)
		dbList[i].theDbObject = 0;
}


//---------------------------------------
//  Attach a user class to the
// database server.

dbError diamondBase::attach(dbString& name, DIA_object* resBuffer, int32& refId, dbObjData* & objData)
{

	int32		nameId;		// The ID of the name, returned by the nameserver.
	dbString	path;	    // The path for the relation.
	dbString	prefix;
	int			cpyDbNum=-1;	// The dbList number of a user class which
								// already is the requested relation.
	int			newRegNum=-1;	// The registration no. for the new user class.
	int			newDbNum=-1;	// New spot in the dbList
	int			lastDbNum=-1;	// Oldest unused dbObj
	int			i;

	// Is the database alive

	if (!nsOpen) {
		refId = -2;
		return dbErr(db_notopen, "Database config file missing or empty");
	}

	// First, is this relation already open ?

	path = queryName(name,nameId);
	prefix = name;

	// Find a blank registration spot
	for (i=0; i < MAX_REG; i++)
		if (!regInfo[i])
		{
			newRegNum = i;
			break;
		}

	// We couldn't find one. Invalidate refId and return an error.

	if (newRegNum == -1)
	{
		refId = newRegNum;
		return dbErr(db_toomany, "attachments");
	}

	// Search all the dbObj classes attached
	time_t lastTime = 0;
	for(i=0;i<MAX_DB_INFO;i++)
	{
		// There is an object there
		if (dbList[i].theDbObject)
		{
			// Is it the one we want? cpyDbNum says which one to copy
			if (dbList[i].nameId == nameId) {
				cpyDbNum = i;	// Found a reference to this relation.
								// Remember it.
			}

			// See if this is the oldest detached dbObj
			if (dbList[i].detachTime && (lastTime == 0 || dbList[i].detachTime < lastTime))
			{
				// This is a detached dbObj which is older than any other
				lastTime = dbList[i].detachTime;
				lastDbNum = i;
			}
		}
		else
			if (newDbNum == -1)
				newDbNum = i; // Remember the location of the first empty slot.
	}

	// Now:
	// cpyDbNum holds the spot (if any) where this dbObj is already present
	// lastDbNum holds the spot of the oldest detached dbObj
	// newDbNum holds the spot of a free slot

	// cerr << "Already:" << cpyDbNum << "   Oldest:"<<lastDbNum << "   Free:"<<newDbNum << endl;

	if (cpyDbNum == -1)	// Couldn't find an instance of this relation
	{
		// Ok. First we need to create a dbObject for this relation.
		dbObject* dbObj = new dbObject(path,prefix);

		//
		// Check to see if we were successful creating the
		// dbObject.
		//
		if (dbObj->status != db_ok) {
			return dbObj->status;
		}

		if (newDbNum != -1) // Got a spare spot?
			cpyDbNum = newDbNum; // Put it here
		else
		{
			// No spare spot, nuke an old one.
			// NB refCount will be 0 otherwise no detach time
			// We delete the oldest detached one

			delete dbList[lastDbNum].theDbObject;
			cpyDbNum = lastDbNum; // And put the new one here
		}
		// Now cpyDbNum points where to put it, so store it
		dbList[cpyDbNum].theDbObject = dbObj;
	}

	// We create a new regInfo for each registration, whether the dbObj was
	// there or not.
	regInfo[newRegNum] = new dbRegInfo(resBuffer,
									   cpyDbNum);

	// Now there is one more reference
	dbList[cpyDbNum].theDbObject->usageCount++;
	objData = dbList[cpyDbNum].theDbObject->getObjData();

	// It hasn't been detached.
	dbList[cpyDbNum].detachTime = 0;

	// And who it is.
	dbList[cpyDbNum].nameId = nameId;

	// Clean up.
	//delete path;
	refId = newRegNum;	// return the registration number for this user class.
	return dbErr(db_ok);
}

// The destructor. Makes sure all memory is cleaned up

diamondBase::~diamondBase()
{
	int i;
	for(i=0;i<MAX_REG;i++)
	{
		delete regInfo[i];
		regInfo[i]=0;
	}
	for (i=0; i<MAX_DB_INFO; i++)
		delete dbList[i].theDbObject;
}

// Return the current diamondBase version string

const char* diamondBase::version(const bool full) const
{
	strstream s;
	s << "DiamondBase Version " << VERSION_MAJOR << "." << VERSION_MINOR
	  << "." << PATCH_LEVEL << PATCH_STATUS << "  Released: "
	  << RELEASE_DATE << endl;

	if (full)
	{
		s << "diamondBase: " << verStr() << endl;
		s << "TNameServer: " << TNameServer::verStr() << endl;
		bTree b;
		s << "bTree: " << b.verStr() << endl;
		s << "recServer: " << b.recServer::verStr() << endl;
		s << "cache: " << b.cache::verStr() << endl;
		// diaRel can't be instantiated.
		s << "diaRel: " << diaVerStr() << endl;
		bucket bu(0,0,0,LEAF,0,0,0);
		s << "bucket: " << bu.verStr() << endl;
		dbRegInfo ri(0,0);
		s << "dbRegInfo: " << ri.verStr() << endl;
		dbQueryInfo qi(0,0);
		s << "dbQueryInfo: " << qi.verStr() << endl;
		dbObject dbo;
		s << "dbObject: " << dbo.verStr() << endl;
		dbString str;
		s << "dbString: " << str.verStr() << endl;
	}
	return s.str();
}

//--------------------------------------------
// Perform some transaction, given by the
// id argument, with the dbObject defined
// in the regInfo structure.

dbError
diamondBase::trans(const int32 refId, const transId id, const int32 idx)
{
	// Did the nameserver fail to open.
	if (refId == -2)
		return dbErr(db_notopen, "Database config file missing or empty");

	// Check the refId is valid
	if (refId < 0 || refId > MAX_REG)
		return dbErr(db_range);

	// Make sure it's in use. Don't use db_nfound 'cos the transaction
	// itself may. Don't want to confuse anybody.
	if (!regInfo[refId])
		return dbErr(db_range);

	// These 3 just make the code below look much better.
	// Note than in some cases below, this copy of queryId is not used,
	// but the original is. This is because the action concerned modifies
	// it.
	DIA_object* theObj = regInfo[refId]->buffer;
	dbObject* theDbObject = dbList[regInfo[refId]->dbListId].theDbObject;
	int32 queryId = regInfo[refId]->queryId;

	// For most situations, not having a query is an error.
	if (queryId == -1)
		switch(id)
		{
			case ti_first:
			case ti_seekFirst:
			case ti_end:
			case ti_next:
			case ti_peekNext:
			case ti_prev:
			case ti_peekPrev:
			case ti_last:
			case ti_seekLast:
			case ti_find:
			case ti_seek:
			case ti_extract:
			case ti_write:
				return dbErr(db_noquery);
			default:
				break;
		}

	// One for each transaction type. Most are self explanatory, see dbObj
	switch(id)
	{
		case ti_add:
			return theDbObject->add(*theObj);

		case ti_del:
			return theDbObject->del(*theObj);

		// A first is merely a seekFirst followed by a next
		case ti_seekFirst:
		case ti_first:
		{
			dbError err = theDbObject->qSeekFirst(queryId, *theObj);
			if (id == ti_seekFirst)
				return err;
			if (err == db_ok)
				err = theDbObject->qNext(queryId, *theObj);
			return err;
		}

		// Start a new query
		case ti_begin:
		{
			if (queryId != -1)
			{
				// If we already have a query, end it now
				dbError err;
				// This queryId not removed 'cos it's a reference
				err = theDbObject->qEnd(regInfo[refId]->queryId);
				if (err != db_ok || err != db_noquery)
					return err;
			}
			// This queryId not removed 'cos it's a reference
			return theDbObject->qBegin(idx, regInfo[refId]->queryId);
		}
		case ti_write:
			return theDbObject->qWrite(queryId, *theObj);

//		case ti_writeRec:
//			return theDbObject->qWriteRec(queryId, *theObj, idx);

		case ti_end:
			// This queryId not removed 'cos it's a reference
			return theDbObject->qEnd(regInfo[refId]->queryId);

		case ti_peekNext:
			return theDbObject->qPeekNext(queryId, *theObj);

		case ti_next:
			return theDbObject->qNext(queryId, *theObj);

		case ti_peekPrev:
			return theDbObject->qPeekPrev(queryId, *theObj);

		case ti_prev:
			return theDbObject->qPrev(queryId, *theObj);

		case ti_seekLast:
		case ti_last:
		{
			dbError err = theDbObject->qSeekLast(queryId, *theObj);
			if (id == ti_seekLast)
				return err;
			if (err == db_ok)
				err = theDbObject->qPrev(queryId, *theObj);
			return err;
		}

		// All these are the same. A find/extract is a seek then a next
		// find/extract differ only in whether a lock is set
		case ti_seek:
		case ti_find:
		case ti_extract:
		{
			dbError err1 = theDbObject->qSeek(queryId, *theObj);
			if (err1 == db_eof)
				err1 = db_nfound;

			if (id == ti_seek || (id==ti_extract && err1 == db_nfound))
				return dbErr(err1);
			dbError err2 = theDbObject->qNext(queryId, *theObj, (bool)(id==ti_extract));
			if (err1 == db_ok)
				return err2;
			return err1;
		}

		// get creates a query, does a find and then removes the query
		case ti_get:
		{
			int32 tempQid;
			dbError err;

			// Start a query
			err = theDbObject->qBegin(idx, tempQid);

			// If it failed bail out
			if (err != db_ok)
				return err;

			// Search for the object.
			err = theDbObject->qSeek(tempQid, *theObj);

			// If that fails (including not found), remove the query and go
			if (err != db_ok)
			{
				theDbObject->qEnd(tempQid);
				if (err == db_eof)
					return dbError(db_nfound);
				else
					return err;
			}

			// Otherwise take the one we want
			err = theDbObject->qNext(tempQid, *theObj, false);

			// If that fails, remove the query and go
			if (err != db_ok)
			{
				theDbObject->qEnd(tempQid);
				if (err == db_eof)
					return dbError(db_nfound);
				else
					return err;
			}

			// Otherwise end the query and return. NB this is not combined
			// with the above call to qEnd because of the different treatment
			// of the returned error. In the above call, it is ignored.
			return theDbObject->qEnd(tempQid);
		}

		// put creates a query, does a write and then removes the query
		case ti_put:
		{
			int32 tempQid;
			dbError err;

			// Start a query
			err = theDbObject->qBegin(idx, tempQid);

			// If it failed bail out
			if (err != db_ok)
				return err;

			// Otherwise write the data
			err = theDbObject->qWrite(tempQid, *theObj);

			if (err == db_nfound || err == db_dup)
			{
				err = theDbObject->add(*theObj);
			}

			// If that fails, remove the query and go
			if (err != db_ok)
			{
				theDbObject->qEnd(tempQid);
				return err;
			}

			// Otherwise end the query and return. NB this is not combined
			// with the above call to qEnd because of the different treatment
			// of the returned error. In the above call, it is ignored.
			return theDbObject->qEnd(tempQid);
		}

		case ti_flush:
			theDbObject->flush();
			break;

		case ti_stats:
			theDbObject->stats();
			break;

		case ti_lock:
		case ti_unlock:
			return theDbObject->lock( (id==ti_lock)?true:false,idx? true:false);

		default:
			return dbErr(db_unimp);
	}

	return dbErr(db_ok);
}

//----------------
// Similar to trans but returns an integer

int32
diamondBase::intTrans(const int32 refId, const transId id, const int32 idx)
{
	// Did the nameserver fail to open.
	if (refId == -2)
		return dbErr(db_notopen, "Database config file missing or empty");

	// Check the refId is valid
	if (refId < 0 || refId > MAX_REG)
		return dbErr(db_range);

	// Make sure it's in use. Don't use db_nfound 'cos the transaction
	// itself may. Don't want to confuse anybody.
	if (!regInfo[refId])
		return dbErr(db_range);

	dbObject* theDbObject = dbList[regInfo[refId]->dbListId].theDbObject;

	dbErr(db_ok);

	// One for each transaction type. Most are self explanatory, see dbObj
	switch(id)
	{
		case ti_count:
			return theDbObject->recordCount();

		default:
			dbErr(db_unimp);
			return idx-idx-1;	// This stops the unused for idx. Remove
								// it when idx is used.
	}

	//
	// This is technically unreachable, but most compilers don't
	// realise and emit a warning.
	//
#if !defined(__BORLANDC__)
	return -1;
#endif
}

//-----------------------------------------------------
// Detach a user class from the database server.
//
// When detaching a user class, care must be taken
// not to delete the dbObject associated with the
// user class if another user class has a handle on it.
//
// To prevent this the registration space is searched
// for references to the dbObject in question. If one
// is found, the regInfo::saveDbObject() method is called,
// preventing the dbObject from being deleted when the
// regInfo destructor is invoked.
// [KEV: See below]

dbError
diamondBase::detach(const int32 refId)
{
// Kev: I have deleted all this. It's saving the wrong reginfo
// plus since the refcount, we don't need it any more.
#if 0
	// Ok. First figure out whether or not the relation is in use
	// by another user class.

	for(int i=0;i<MAX_REG;i++)
	{
		if ((regInfo[i])&&(i!= refId))  // Don't look at nothing, nor
										// ourselves :-)
			if (regInfo[i]->nameId == regInfo[refId]->nameId)
				regInfo[i]->saveDbObject();
	}
#endif

	// Make sure the reference is valid
	if (refId < 0 || refId > MAX_REG)
		return dbErr(db_range);

	if (!regInfo[refId])
		return dbErr(db_range);

	// First sort out the dbList
	// Decrease the reference count of the dbObject associated with the
	// regInfo in question
	if (!(--dbList[regInfo[refId]->dbListId].theDbObject->usageCount))
	{
		// Nobody else is using this. set a detach time.
		dbList[regInfo[refId]->dbListId].detachTime = time(0);

		// Don't delete anything, leave it hanging around.
		// It will be deleted in attach if its spot is needed.
		// We leave it here to save time when the next one is attached
		// since the usage pattern is often to delete an object then create a
		// new one.
	}
	// Now delete the regInfo structure for this user class.

	dbList[regInfo[refId]->dbListId].theDbObject->qEnd(regInfo[refId]->queryId);
	delete regInfo[refId];

	// And finally set it to zero to signify an empty slot.

	regInfo[refId] = 0;

	return dbErr(db_ok);
}

dbError
diamondBase::eject(const dbString& relName)
{
	// cerr << "Ejecting " << relName << endl;
	int i;
	for (i=0; i<MAX_DB_INFO; i++)
		if (dbList[i].theDbObject)
		{
			dbObject* theDbObj = dbList[i].theDbObject;
			//cerr << "TRY" << i << ":"<<relName<<":"<<theDbObj->prefix<<":"<<endl;
			if (!strcmp(relName,theDbObj->prefix))
			{
				if (theDbObj->usageCount)
				{
					return dbErr(db_alreadyregistered);
				}
				delete theDbObj;
				dbList[i].theDbObject = 0;
				// cerr << "Ejected from position " << i << endl;
				break;
			}
		}
	if (i == MAX_REG)
		return dbErr(db_nfound);
	else
		return dbErr(db_ok);
}

//////////////////////////////////////////////////////////////////////////
// Revision History:
//
// $Log: dbase.cpp,v $
// Revision 1.5  2001/04/20 10:15:01  georgetasker
// Cygwin compilation fixes, compilation speed enhancements
// Code modified to get rid of some compiler warnings
//
// Revision 1.4  2001/04/19 21:12:54  georgetasker
// cygwin compilation fixes
//
// Revision 1.3  2001/04/05 01:22:33  georgetasker
// Same fix for Borland as I just did for MSVC so that <strstrea.h> is used if it is either of these two compilers, otherwise use <strstream.h>
//
// Revision 1.1  2001/04/04 12:05:49  aleksandras
// forgotten diamond .cpps
//
// Revision 1.65  1996/08/22  12:42:10  dmb
// Improved file I/O error handling
//
// Revision 1.64  1996/03/05 22:04:29  dmb
// removed ^M.
//
// Revision 1.63  1996/03/05  21:42:35  dmb
// Preliminary modifications to get Dos/Windows 0.4 portability.
//
// Revision 1.62  1996/02/01  21:36:46  dmb
// long->int32
//
// Revision 1.61  1995/10/04  02:12:34  davison
// Changed references to objects to DIA_objects to prevent namespace clashes.
//
// Revision 1.60  1995/06/27 13:27:16  kevinl
// Fixes to repair the gcc 2.7.0 carnage:
// modified for loop local variable scope semantics
// some new errors invented.
// moved some inline friends out of classes (stoopid - mystring)
// Modified the setUnique generated member
//
// Revision 1.59  1995/04/11  05:48:22  kevinl
// Changed time sentinel to 0
//
// Revision 1.58  1994/11/14  00:57:41  darrenp
// Suppressed a borland warning about unreachable code.
//
// Revision 1.57  1994/07/04  12:17:00  kevinl
// Removed __CC conditional compilation
//
// Revision 1.56  1994/05/15  04:55:16  kevinl
// Type fixups. Conversion of char* to dbString
//
// Revision 1.55  1994/04/23  02:40:09  kevinl
// Added intTrans
//
// Revision 1.54  1994/03/17  04:34:45  kevinl
// Converted some spaces to tabs
// Added flush, lock, unlock, eject
//
// Revision 1.53  1994/02/03  05:54:19  kevinl
// Moved include files to diamond/ and fixed HAVE_MALLOC_H
//
// Revision 1.52  1994/01/28  08:13:36  kevinl
// Put now tries to add more often. I think this is correct behaviour.
//
// Revision 1.51  1994/01/28  04:34:14  davison
// No longer delets the path returned by queryName calls.
//
// Revision 1.50  1994/01/24  02:59:29  davison
// theDiamondBase is now defined within the library in this file.
// The constructor no longer takes any poarameters,
// and the name server must be initialised before use with a call
// to loadConfig.
//
// Revision 1.50  1994/01/24  02:59:29  davison
// theDiamondBase is now defined within the library in this file.
// The constructor no longer takes any poarameters,
// and the name server must be initialised before use with a call
// to loadConfig.
//
// Revision 1.49  1994/01/23  04:15:53  kevinl
// ::trans now is aware if ::attach failed.
//
// Revision 1.48  1993/12/23  22:58:48  kevinl
// dbErr uses dbString, autoconf stuff, moved logs, added const and size_t
//
// Revision 1.47  1993/11/12  05:51:34  kevinl
// Mapped db_eof to db_nfound in some places
//
// Revision 1.46  1993/11/04  14:57:43  kevinl
// NumNames becomes nsOpen
//
// Revision 1.45  1993/10/13  13:14:36  kevinl
// Fixed get
//
// Revision 1.44  1993/10/05  07:29:41  kevinl
// Attach now handles dbObjData
//
// Revision 1.43  1993/07/19  11:43:14  kevinl
// Removed all the pesky ifdef'ed break's
//
// Revision 1.42  1993/07/11  10:49:21  kevinl
// dbString version
//
// Revision 1.41  1993/07/11  08:18:12  kevinl
// Stopped the NameServer exiting when the config file is empty/missing
//
// Revision 1.40  1993/06/23  05:21:22  kevinl
// Mallocs are now in angular brackets
//
// Revision 1.39  1993/06/20  13:43:05  kevinl
// Fixed multiple mallocs
//
// Revision 1.38  1993/05/26  01:01:39  kevinl
// MALLOC_H_MISSING
//
// Revision 1.37  1993/05/12  08:16:26  kevinl
// detaxch now kills a query
//
// Revision 1.36  1993/05/12  06:34:12  kevinl
// ti_put now checks for nfound not dup!
//
// Revision 1.35  1993/05/11  14:51:30  kevinl
// Oops, minor spelling error
//
// Revision 1.34  1993/05/11  14:42:54  kevinl
// Version numbers
// Extract does not return anything if not found
//
// Revision 1.33  1993/05/07  13:55:06  kevinl
// Fixed put
//
// Revision 1.32  1993/05/06  04:00:38  kevinl
// SASC define for malloc.h and some extra breaks
//
// Revision 1.31  1993/05/03  01:33:03  kevinl
// Cosmetic (mainly) changes for CC
//
// Revision 1.30  1993/05/01  14:37:13  kevinl
// Got rid of ints
//
// Revision 1.29  1993/04/27  07:14:13  kevinl
// Added put and write
//
// Revision 1.28  1993/04/25  11:06:40  kevinl
// Fixed up some error reporting that was a little out
// Comments
//
// Revision 1.27  1993/04/15  07:58:37  kevinl
// Added ti_del
//
// Revision 1.26  1993/04/15  04:21:52  kevinl
// Moved malloc.h
//
// Revision 1.25  1993/04/11  10:25:16  kevinl
// Fixed up return codes from find
//
// Revision 1.24  1993/04/11  05:49:02  kevinl
// Rationalised find/seek/peek methods etc
//
// Revision 1.23  1993/04/09  13:00:29  kevinl
// Stats can be called from diaRel now.
//
// Revision 1.22  1993/04/08  05:26:40  kevinl
// FGixed some memory leaks
//
// Revision 1.21  1993/04/08  00:28:41  kevinl
// Some range checking on queryId's
//
// Revision 1.20  1993/04/07  07:59:58  kevinl
// qBegin now qEnd's a query if it's active
//
// Revision 1.19  1993/04/07  02:29:21  kevinl
// Changed qEnd to reset qId to -1
//
// Revision 1.18  1993/04/06  04:42:19  kevinl
// Check queryId in trans before passing on...
//
// Revision 1.17  1993/04/06  04:27:44  kevinl
// Fixed trans so qBegin gets correct reference
//
// Revision 1.16  1993/04/06  01:01:55  kevinl
// Put a string in db_toomany
// fixed get transaction
//
// Revision 1.15  1993/04/05  01:06:12  kevinl
// Assignment of dbObj now to correct spot
//
// Revision 1.14  1993/04/05  01:02:49  kevinl
// Incorrect comparison for newDbNum fixed
//
// Revision 1.13  1993/04/05  00:52:39  kevinl
// Checking correct field in attach now for empty slot
//
// Revision 1.12  1993/04/05  00:42:17  kevinl
// replaced << with < in attach
// put in a break in attach
//
// Revision 1.11  1993/04/04  23:57:09  kevinl
// Now cache the dbObj's so they don't duisappear
// Made trans much neater by removing all the ugly indirection
//
// Revision 1.10  1993/04/01  04:23:46  kevinl
// Modified/added transactions
// Now have get, extract, and find
//
// Revision 1.9  1993/03/30  07:13:29  davison
// Um.
//
// Revision 1.8  1993/03/30  03:28:01  davison
// Fixed multiple delete problem.
//
// Revision 1.7  1993/03/29  08:04:43  darrenp
// Fixed multiple deletions of other peoples' data
// Removed bogus error reporting
// Debug malloc library
//
// Revision 1.6  1993/03/28  10:37:55  kevinl
// Modified for dbErr
//
// Revision 1.5  1993/03/28  04:53:59  root
// more error code standardization.
//
// Revision 1.4  1993/03/26  06:15:57  darrenp
// standardised error codes.
//
// Revision 1.3  1993/03/26  05:49:24  darrenp
// Small syntax errors.
//
// Revision 1.2  1993/03/25  23:25:16  davison
// Added a few more transactions
// Added the detach method
//
// Revision 1.1  1993/03/25  22:28:50  davison
// Initial revision
//
//////////////////////////////////////////////////////////////////////////
