//
//  File:   hdbRepos.cpp
//
//  Legal Notice:   This file is part of the HadronZoo C++ Class Library. Copyright 2025 HadronZoo Project (http://www.hadronzoo.com)
//
//  The HadronZoo C++ Class Library is free software: You can redistribute it, and/or modify it under the terms of the GNU Lesser General Public License, as published by the Free
//  Software Foundation, either version 3 of the License, or any later version.
//
//  The HadronZoo C++ Class Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
//  A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public License along with the HadronZoo C++ Class Library. If not, see http://www.gnu.org/licenses.
//
//
//  Implimentation of the HadronZoo Proprietary Database Suite
//
#include <iostream>
#include <fstream>
#include <cstdio>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "hzBasedefs.h"
#include "hzString.h"
#include "hzChars.h"
#include "hzChain.h"
#include "hzDate.h"
#include "hzTextproc.h"
#include "hzCodec.h"
#include "hzDocument.h"
#include "hzDirectory.h"
#include "hzDatabase.h"
#include "hzDelta.h"
#include "hzProcess.h"
using namespace std ;
/*
**  Variables
*/
extern  hzDeltaClient*  _hzGlobal_DeltaClient ;     //  Total of current delta clients
/*
**  Prototypes
*/
uint32_t    Datatype2Size       (hdbBasetype eType) ;
const char* _hds_showinitstate  (hdbIniStat nState) ;
void        _hdb_ck_initstate   (const hzString& objName, hdbIniStat eActual, hdbIniStat eExpect) ;
/*
**  Chain block bucket regime
*/
/*
**  hdbObjRepos::_cache_chain Functions
*/
void    hdbObjRepos::_cache::Clear  (void)
{
    //  Clear cache
    //
    //  Arguments:  None
    //  Returns:    None
    m_Chain.Clear() ;
}
hdbObjRepos::_c_blk*    hdbObjRepos::_cache::_findBlock (uint32_t objId)
{
    //  Find the address of the chain block for the given object id. This is done by a binary chop on the chain of blocks.
    //
    //  Argument:   objId   The object id
    //
    //  Returns:    Pointer to the block or NULL if the object does not exist
    _hzfunc("_hz_xchain::_findBlock") ;
    _c_blk*     pBloc ;     //  Current chain block
    uint32_t    nMax ;      //  Number of blocks -1
    uint32_t    nDiv ;      //  Binary chop divider
    uint32_t    nPos ;      //  Starting position
    uint32_t    found ;     //  Number found/limit checker
    if (!this)
        Fatal("No Instance\n") ;
    if (!objId)             return 0 ;
    if (!m_Chain.Count())   return 0 ;
    if (m_Chain.Count() == 1)
        return m_Chain[0] ;
    nMax = m_Chain.Count() - 1 ;
    for (found = 2 ; found < nMax ; found *= 2) ;
    nDiv = found / 2 ;
    nPos = found - 1 ;
    for (;;)
    {
        if (nPos > nMax)
        {
            if (!nDiv)
                break ;
            nPos -= nDiv ;
            nDiv /= 2 ;
            continue ;
        }
        pBloc = m_Chain[nPos] ;
        if (objId > pBloc->m_nHi)
        {
            //  Go higher
            if (!nDiv)
                break ;
            nPos += nDiv ;
            nDiv /= 2 ;
            continue ;
        }
        if (objId < pBloc->m_nLo)
        {
            //  Go lower
            if (!nDiv)
                break ;
            nPos -= nDiv ;
            nDiv /= 2 ;
            continue ;
        }
        found = nPos ;
        return m_Chain[found] ;
    }
    return 0 ;
}
/*
**  hdbObjRepos::_cache Functions
*/
hzEcode hdbObjRepos::_cache::Init   (const hdbClass* pClass)
{
    //  Initialize the _cache. Create the _hz_xchain instance
    //
    //  Argument:   pClass  The data class
    //
    //  Returns:    E_OK
    //
    //  Exits:      E_ARGUMENT  If no class is supplied
    //              E_NOINIT    If supplied class is not initialized
    //              E_DUPLICATE If Cache is already set to a class
    if (!pClass)            hzexit(E_ARGUMENT, "No class supplied") ;
    if (!pClass->IsInit())  hzexit(E_NOINIT, "Class not initialized") ;
    if (m_pClass)           hzexit(E_DUPLICATE, "Cache already has a data class") ;
    m_pClass = pClass ;
    return E_OK ;
}
hzEcode hdbObjRepos::_cache::FetchEDO       (hzChain& edo, uint32_t objId)
{
    //  Fetch the EDO indicated by the supplied object id, into the supplied chain
    //
    //  Arguments:  1)  edo     hzChain as EDO recepticle
    //              2)  objId   The object id
    //
    //  Returns:    E_NOTFOUND  If an EDO of the id is not found
    //              E_OK        Operation successful
    _hzfunc("hdbObjRepos::_cache::FetchEDO") ;
    xbufIter    zi ;        //  Node data iterator
    xbufIter    edoStart ;  //  Start of EDO marker
    xbufIter    edoMark ;   //  Tail of EDO marker
    _c_blk*     pBloc ;     //  Current chain block
    uint32_t    nLen ;      //  EDO length
    uint32_t    nId ;       //  EDO object id
    if (!this)
        Fatal("No Instance\n") ;
    if (!objId)
        return hzerr(E_NOTFOUND, "Illegal object ID (0)") ;
    edo.Clear() ;
    pBloc = _findBlock(objId) ;
    if (!pBloc)
        return hzerr(E_NOTFOUND, "No block identified for object %u", objId) ;
    //  Now iterate EDOs in block. Read the length and ids from serial integers and iterate until either an EDO of the object id is found or surpassed
    zi = pBloc->m_edo_space ;
    for (; !zi.eof() ;)
    {
        //  Read EDO length and id
        edoStart = zi ;
        ReadSerialUINT32(nLen, zi) ;
        edoMark = zi ;
        ReadSerialUINT32(nId, zi) ;
        //threadLog("EDO id %u len %u oset %u\n", nId, nLen, zi._oset()) ;
        if (nId < objId)
            { zi = edoMark ; zi += nLen ; continue ; }
        if (nId > objId)
            return E_NOTFOUND ;
        //  EDO found, advance marker to the end
        edoMark += nLen ;
        break ;
    }
    //  Copy the EDO to the chain
    for (zi = edoStart ; !zi.eof() && zi != edoMark ; zi++)
    {
        edo.AddByte(*zi) ;
    }
    
    return E_OK ;
}
hzEcode hdbObjRepos::_cache::CommitEDO  (const hzChain& edo, uint32_t objId, bool bLoad)
{
    //  Commit an EDO to the cache.
    //
    //  If the supplied object Id is 0, the EDO is a new object so the commit is an INSERT - in which case the object will be placed at the end, and the id issued in respect of it,
    //  will be the highest thusfar issues. If the supplied object id is non-zero, the commit is an UPDATE and it must be of an object that already exists and has not been deleted.
    //
    //  In an UPDATE, the supplied EDO replaces the existing EDO. The supplied EDO may be shorter, longer, or the same size as the existing EDO. If the same size, the existing EDO
    //  is overwritten, otherwise the cache block is recreated. All data up to the existing EDO is copied to a new block, the supplied EDO is then added to the new block, then all
    //  data after the existing EDO is copied over - then the new block replaces the old.
    //
    //  Arguments:  1)  edo     EDO to commit (supplied as hzChain)
    //              2)  objId   The object id
    //
    //  Returns:    E_CORRUPPT  If the supplied object id is non-zero but no cache block could be identified.
    //              E_NOTFOUND  If the supplied object id addresses a deleted object
    //              E_OK        Operation successful
    _hzfunc("hdbObjRepos::_cache::CommitEDO") ;
    hzXbuf      newBuf ;    //  New buffer (result of insert)
    chIter      ei ;        //  EDO iterator
    xbufIter    zi ;        //  Iterator
    xbufIter    xi ;        //  Reserve iterator
    _c_blk*     pBloc ;     //  Current chain block
    uint32_t    nLen ;      //  EDO length
    uint32_t    nId ;       //  EDO object id
    //  Validate
    if (!this)      hzexit(E_CORRUPT, "No repository instance") ;
    if (!m_pClass)  hzexit(E_NOINIT, "Cache not initialized") ;
    /*  Diags
    hzChain err ;
    err << "EDO is [ " ;
    for (ei = edo ; !ei.eof() ; ei++)
    {
        err.Printf("%02x ", (uchar) *ei) ;
    }
    err << "]\n" ;
    threadLog(err) ;
    threadLog("Commiting EDO objId %d\n", objId) ;
    */
    if (!objId)
        hzexit(E_ARGUMENT, "Invalid object ID") ;
    //  Virgin cache
    if (!m_Chain.Count())
    {
        pBloc = new _c_blk() ;
        m_Chain.Add(pBloc) ;
    }
    //if (!objId || objId > m_nTopId)
    if (objId > m_nTopId)
    {
        //  EDO is taken to be new so objId is set at current highest + 1. Note this is not the EDO population as some EDOs may have been deleted. The position of the new EDO will
        //  be at the end of the last block in the chain.
        pBloc = m_Chain[m_Chain.Count()-1] ;
        if (pBloc->m_edo_space.Size() > 4000)
        {
            pBloc = new _c_blk() ;
            m_Chain.Add(pBloc) ;
            pBloc->m_nLo = objId ;
            threadLog("Allocating new block %p low %u\n", pBloc, pBloc->m_nLo) ;
        }
        pBloc->m_edo_space += edo ;
        pBloc->m_nHi = objId ;
        m_nEDO++ ;
        m_nTopId++ ;
        return E_OK ;
    }
    //  objId supplied so looking for an existing EDO to replace. If not found this is an error
    pBloc = _findBlock(objId) ;
    if (!pBloc)
        return hzerr(E_CORRUPT, "No block located") ;
    //  Now iterate EDOs in block. Read the length and ids from serial integers and iterate until either an EDO of the object id is found or surpassed
    //zi.SetPosn(pBloc, pBloc->m_nOsetEDO) ;
    if (objId > pBloc->m_nHi)
    {
        //  New object will be the highest
        pBloc->m_edo_space += edo ;
        pBloc->m_nHi = objId ;
        m_nEDO++ ;
    }
    else if (objId < pBloc->m_nLo)
    {
        //  New object will be the lowest
        newBuf += edo ;
        newBuf += pBloc->m_edo_space ;
        pBloc->m_edo_space = newBuf ;
        pBloc->m_nLo = objId ;
        m_nEDO++ ;
    }
    else
    {
        //  New object is in-between so iterate to the correct position
        zi = pBloc->m_edo_space ;
        for (; !zi.eof() ;)
        {
            xi = zi ;
            ReadSerialUINT32(nLen, zi) ;
            ReadSerialUINT32(nId, zi) ;
            if (nId > objId)
                return E_NOTFOUND ;
            if (nId < objId)
            {
                xi += nLen ;
                zi = xi ;
                continue ;
            }
            break ;
        }
        if (nId > objId)
        {
            //  The object id does not exist in the block. In normal operation this is an error since object ids are non-recurrent. During the initial delta load (bLoad=true), this
            //  is allowed since deltas are not necessarily in order.
            if (!bLoad)
                return E_NOTFOUND ;
            //  xi is the position of the new edo
            for (zi = pBloc->m_edo_space ; zi != xi ; zi++)
            {
                newBuf.AddByte(*zi) ;
            }
            newBuf += edo ;
            for (; !xi.eof() ; xi++)
            {
                newBuf.AddByte(*zi) ;
            }
            pBloc->m_edo_space = newBuf ;
        }
        else
        {
            //  xi marks the existing object. If the size is the same, just overwrite
            if (nLen == edo.Size())
            {
                for (ei = edo ; !ei.eof() ; ei++)
                {
                    xi = *ei ;
                    xi++ ;
                }
            }
            else
            {
                //  Size not the same. Write everything up to xi to newBuf, write edo to newBuf, write everything north of xi + nLen to newBuf, make newBuf the new edo_space.
                for (zi = pBloc->m_edo_space ; zi != xi ; zi++)
                {
                    newBuf.AddByte(*zi) ;
                }
                newBuf += edo ;
                for (xi += nLen ; !xi.eof() ; xi++)
                {
                    newBuf.AddByte(*zi) ;
                }
                pBloc->m_edo_space = newBuf ;
            }
        }
    }
    return E_OK ;
}
void    hdbObjRepos::_cache::Show   (hzChain& Z, bool bDetail) const
{
    //  Show node content
    _hzfunc("hdbObjRepos::_cache::Show") ;
    xbufIter    zi ;        //  X-buf iterator
    _c_blk*     pBloc ;     //  Current chain block
    uint32_t    n ;         //  Block counter
    uint32_t    byte ;      //  Byte
    for (n = 0 ; n < m_Chain.Count() ; n++)
    {
        pBloc = m_Chain[n] ;
        Z.Printf("Node %u: Lo %u Hi %u Size %u\n", n, pBloc->m_nLo, pBloc->m_nHi, pBloc->m_edo_space.Size()) ;
        if (bDetail)
        {
            for (zi = pBloc->m_edo_space ; !zi.eof() ; zi++)
            {
                byte = *zi & 0xff ;
                Z.Printf("%02x,", byte) ;
            }
            Z.AddByte(CHAR_NL) ;
        }
    }
}
/*
**  hdbObjRepos Functions
*/
hdbObjRepos::hdbObjRepos    (hdbADP& adp)
{
    m_pADP = &adp ;
    m_pBR_Delta = 0 ;
    m_pBR_Datum = 0 ;
    m_pMain = 0 ;
    m_nSeqId = m_nPopulation = 0 ;
    m_DeltaId = 0 ;
    m_bBinaries = false ;
    m_eReposInit = HDB_CLASS_INIT_NONE ;
}
hdbObjRepos::~hdbObjRepos   (void)
{
    if (m_pMain)
        m_pMain->Clear() ;
    delete m_pMain ;
}
hzEcode hdbObjRepos::InitStart  (const hdbClass* pNative, const hzString& name, const hzString& workdir, hdbReposMode eMode)
{
    //  Begin repository initialization sequence. This function sets the repository native class, names the repository and the working directory (location of data files). Once this
    //  function has completed, it may be followed by calls to InitMbrIndex() to add member-wise indexes to the repository. Lastly InitDone() is called to complete the process.
    //
    //  Arguments:  1)  pNative The data class
    //              2)  name    The repository name
    //              3)  workdir The operaional directory
    //              4)  bCache  Use RAM Primacy
    //
    //  Returns:    E_ARGUMENT  If no data class is supplied
    //              E_NOINIT    If no class members have been defined
    //              E_INITDUP   If this is a repeat call
    //              E_DUPLICATE If the cache already exists
    //              E_OK        If the operation was successful
    _hzfunc("hdbObjRepos::InitStart") ;
    //const hdbMember*  pMbr ;      //  Named class member
    //uint32_t          nIndex ;    //  Member iterator
    hzEcode             rc ;        //  Return code
    if (!this)
        hzexit(E_CORRUPT, "No hdbObjRepos instance") ;
    //  Check init state and state of supplied class
    _hdb_ck_initstate(name, m_eReposInit, HDB_CLASS_INIT_NONE) ;
    //  Check data class has been supplied and is initialized
    if (!pNative)
        return hzerr(E_ARGUMENT, "No data class supplied") ;
    if (!pNative->IsInit())
        return hzerr(E_NOINIT, "Supplied class (%s) is not initialized", pNative->txtName()) ;
    if (pNative->HasBinaries())
    {
        if (eMode == HDB_REPOS_CACHE)
        {
            hzwarn(E_TYPE, "Ignoring CACHE mode, using DUAL") ;
            eMode = HDB_REPOS_DUAL ;
        }
    }
    //  Ensure Repository Name is Unique
    if (!name)
        return hzerr(E_ARGUMENT, "No name supplied") ;
    if (m_pADP->GetObjRepos(name))
        return hzerr(E_DUPLICATE, "Repository %s already exists", *name) ;
    //  Ensure working directory is given and operational
    if (!workdir)
        return hzerr(E_ARGUMENT, "No working directory supplied for object cache %s", *name) ;
    rc = AssertDir(*workdir, 0777) ;
    if (rc != E_OK)
        return hzerr(rc, "Cannot assert working directory %s for object cache %s\n", *workdir, *name) ;
    //  Proceed with initialization
    m_pClass = pNative ;
    m_Name = name ;
    m_Workdir = workdir ;
    if (eMode == HDB_REPOS_HARD || eMode == HDB_REPOS_DUAL)
    {
        //  Set up the member data binary datum repostory
        if (pNative->HasBinaries())
        {
            m_nameBR_Datum = m_Name + "_br_datum" ;
            m_pBR_Datum = new hdbBinRepos(*m_pADP) ;
            rc = m_pBR_Datum->Init(m_nameBR_Datum, m_Workdir) ;
            if (rc != E_OK)
                return hzerr(rc, "Failed to initialize binary data store: Repos %s, Datum BR (%s), workdir %s\n", *m_Name, *m_nameBR_Datum, *m_Workdir) ;
        }
        //  Set up the default binary datum repostory
        if (!m_pBR_Delta)
        {
            m_nameBR_Delta = m_Name + "_br_delta" ;
            m_pBR_Delta = new hdbBinRepos(*m_pADP) ;
            rc = m_pBR_Delta->Init(m_nameBR_Delta, m_Workdir) ;
            if (rc != E_OK)
                return hzerr(rc, "Failed to initialize binary data store %s (%s)\n", *m_nameBR_Delta, *m_Workdir) ;
        }
    }
    
    //  Set up RAM Primacy cache if applicable
    if (eMode == HDB_REPOS_CACHE || eMode == HDB_REPOS_DUAL)
    {
        m_pathCD = m_Workdir ;
        m_pathCD += "/" ;
        m_pathCD += m_Name ;
        m_pathCD += ".cache" ;
        m_pMain = new _cache() ;
        m_pMain->Init(m_pClass) ;
    }
    //  Set init state
    m_eReposInit = HDB_REPOS_INIT_PROG ;
    //  Insert the repository
    m_pADP->RegisterObjRepos(this) ;
    return E_OK ;
}
hzEcode hdbObjRepos::InitMbrIndex   (const hdbMember* pMbr, bool bUnique)
{
    //  Add an index based on the supplied member name. Find the member in the class and from the datatype, this will determine which sort of index
    //  should be set up.
    //
    //  Arguments:  1)  mbrName Name of member index shall apply to
    //              2)  bUnique Flag if value uniqness applies
    //
    //  Returns:    E_NOINIT    If the cache initialization sequence has not been started
    //              E_ARGUMENT  If the member name is not supplied
    //              E_SEQUENCE  If the cache initialization has been completed by InitDone()
    //              E_NOTFOUND  If the named member is not found in the cache class
    //              E_TYPE      If the named member is of a type that cannot accept an index
    //              E_OK        If the index is successfully created
    _hzfunc("hdbObjRepos::InitMbrIndex") ;
    //const hdbMember*  pMbr ;      //  Named class member
    hdbIndex*           pIdx ;      //  The index to be added
    hzString            iname ;     //  Index name of the form repos::member
    hzEcode             rc = E_OK ; //  Return code
    //  Check init state
    _hdb_ck_initstate(m_Name, m_eReposInit, HDB_REPOS_INIT_PROG) ;
    if (!m_pClass)  return hzerr(E_NOINIT, "No cache class set up") ;
    if (!pMbr)      return hzerr(E_ARGUMENT, "No member supplied") ;
    if (pMbr->Class() != m_pClass)
        return hzerr(E_NOTFOUND, "No such member as %s in class %s\n", pMbr->txtName(), m_pClass->txtType()) ;
    if (pMbr->Posn() < 0 || pMbr->Posn() >= m_pClass->MbrCount())
        return hzerr(E_NOTFOUND, "Member %s has no defined position within the class\n", pMbr->txtName()) ;
    //  Member's datatype will determine the type of index
    pIdx = 0 ;
    switch  (pMbr->Basetype())
    {
    case BASETYPE_EMADDR:
    case BASETYPE_URL:      pIdx = new hdbIndexUkey() ;
                            break ;
    case BASETYPE_STRING:
    case BASETYPE_IPADDR:
    case BASETYPE_TIME:
    case BASETYPE_SDATE:
    case BASETYPE_XDATE:
    case BASETYPE_DOUBLE:
    case BASETYPE_INT64:
    case BASETYPE_INT32:
    case BASETYPE_UINT64:
    case BASETYPE_UINT32:
    case BASETYPE_UINT16:
    case BASETYPE_UBYTE:    pIdx = new hdbIndexUkey() ;
                            break ;
    case BASETYPE_INT16:
    case BASETYPE_BYTE:
    case BASETYPE_ENUM:     pIdx = new hdbIndexEnum() ;
                            break ;
    case BASETYPE_TEXT:
    case BASETYPE_TXTDOC:   pIdx = new hdbIndexText() ;
                            break ;
    case BASETYPE_CLASS:
    case BASETYPE_BINARY:   hzerr(E_TYPE, "Invalid member type. No Index allowed") ;
                            rc = E_TYPE ;
                            break ;
    default:
        break ;
    }
    m_mapIndex.Insert(pMbr->DeltaId(), pIdx) ;
    m_eReposInit = HDB_REPOS_INIT_PROG ;
    return rc ;
}
hzEcode hdbObjRepos::InitMbrIndex   (const hzString& mbrName, bool bUnique)
{
    //  Add an index based on the supplied member name. Find the member in the class and from the datatype, this will determine which sort of index
    //  should be set up.
    //
    //  Arguments:  1)  mbrName Name of member index shall apply to
    //              2)  bUnique Flag if value uniqness applies
    //
    //  Returns:    E_NOINIT    If the cache initialization sequence has not been started
    //              E_ARGUMENT  If the member name is not supplied
    //              E_SEQUENCE  If the cache initialization has been completed by InitDone()
    //              E_NOTFOUND  If the named member is not found in the cache class
    //              E_TYPE      If the named member is of a type that cannot accept an index
    //              E_OK        If the index is successfully created
    _hzfunc("hdbObjRepos::InitMbrIndex") ;
    const hdbMember*    pMbr ;      //  Named class member
    //  Check init state
    _hdb_ck_initstate(m_Name, m_eReposInit, HDB_REPOS_INIT_PROG) ;
    if (!m_pClass)  return hzerr(E_NOINIT, "No cache class set up") ;
    if (!mbrName)   return hzerr(E_ARGUMENT, "No member name supplied") ;
    pMbr = m_pClass->GetMember(mbrName) ;
    if (!pMbr)
        return hzerr(E_NOTFOUND, "No such member as %s in class %s\n", *mbrName, m_pClass->txtType()) ;
    return InitMbrIndex(pMbr, bUnique) ;
}
hzEcode hdbObjRepos::InitMbrRepos   (const hzString& mbrName, const hzString& reposName)
{
    //  By default subclass data objects are embedded within host class data objects. This function directs the repository to hold subclass data objects in another repository. This
    //  is done on a per-member basis and only applies to member with of a CLASS data type.
    //
    //  Arguments:  1)  mbrName The member name. The named member must exist and be of a CLASS data type.
    //              2)  pRepos  Pointer to the target repository
    //
    //  Returns:    E_NOTFOUND  If the named member does not exist.
    //              E_TYPE      If the named member is not of a BINARY data type
    //              E_OK        If the operation was successful.
    _hzfunc("hdbObjRepos::InitMbrRepos") ;
    const hdbObjRepos*  pRepos ;    //  External repository
    const hdbMember*    pMbr ;      //  Named class member
    _hdb_ck_initstate(m_Name, m_eReposInit, HDB_REPOS_INIT_PROG) ;
    pMbr = m_pClass->GetMember(mbrName) ;
    if (!pMbr)
        return hzerr(E_NOTFOUND, "No such member as %s in class %s\n", *mbrName, m_pClass->txtType()) ;
    if (pMbr->Basetype() != BASETYPE_BINARY && pMbr->Basetype() != BASETYPE_TEXT && pMbr->Basetype() != BASETYPE_TXTDOC)
        return hzerr(E_TYPE, "Member %s has non binary base type", *mbrName) ;
    if (!reposName)
        return hzerr(E_ARGUMENT, "No Binary Repository named") ;
    //  A pre-existing hdbObjRepos has been named so the member will use this.
    pRepos = m_pADP->GetObjRepos(reposName) ;
    if (!pRepos)
        return hzerr(E_NOTFOUND, "Repository %s does not exist", *reposName) ;
    m_mapRepos.Insert(pMbr->Posn(), pRepos) ;
    return E_OK ;
}
hzEcode hdbObjRepos::InitDone   (void)
{
    //  Complete repository initialization.
    //
    //  Deal with files if the working directory has been supplied. If files bearing the repository name exists in the stated working directory, these are assumed to be data files
    //  and will be read in to populate the repository. Delta files begin with a header which must match the class definition. This is checked before loading the rest of the data.
    //  If a file of the cache's name does not exist in the working directory, it will be created and a header will be written.
    //
    //  If a backup directory has been specified and a file of the cache's name exists in this directory, the header will be checked and assuming this is OK, the length of the file
    //  will aslo be checked (should match with that in the work directory)
    //
    //  Arguments:  None
    //
    //  Returns:    E_INITFAIL  If Repository has no native data class, or the native data class has no members and/or no description
    //              E_WRITEFAIL If the data file cannot be opened in write mode
    _hzfunc("hdbObjRepos::InitDone") ;
    const hdbMember*    pMbr ;      //  Member
    ifstream            is ;        //  For reading in working data file
    ofstream            os ;        //  For writing in working data file
    FSTAT               fs ;        //  File status
    hzChain             E ;         //  Existing class description header from data file
    hzAtom              atom ;      //  For setting member values
    hdbIndex*           pIdx ;      //  The index to be added
    hdbIndexUkey*       pIdxU ;     //  The index to be added
    hzString            strDesc ;   //  Temp string holding memeber data
    uint32_t            nLine ;     //  Line number for reporting file errors
    uint32_t            mbrNo ;     //  Member number
    char*               lineBuf ;   //  For getline
    hzEcode             rc ;        //  Return code
    //  Check initialization and if there are some members added
    _hdb_ck_initstate(m_Name, m_eReposInit, HDB_REPOS_INIT_PROG) ;
    if (!m_pClass)
        return hzerr(E_INITFAIL, "Repository %s: No data class", *m_Name) ;
    if (!m_pClass->MbrCount())
        return hzerr(E_INITFAIL, "Data class %s: No members!", *m_Name) ;
    //  Create the XML Class Description
    //  m_pClass->DescClass(X, 0) ;
    //  if (!X.Size())
    //      return hzerr(E_INITFAIL, "Data class %s: Failed to write description", *m_Name) ;
    //  threadLog("Called with name %s and workdir %s\n", *m_Name, *m_Workdir) ;
    if (m_pathCD)
    {
        //  Cache and cache delta applies
        if (lstat(*m_pathCD, &fs) < 0)
        {
            //  Working data file does not exist or is empty. Create a new one from the class description as per C++ calls
            os.open(*m_pathCD) ;
            if (os.fail())
                return hzerr(E_WRITEFAIL, "Data class %s Cannot open data file %s in write mode", *m_Name, *m_pathCD) ;
            os << m_pClass->Desc() ;
            if (os.fail())
            {
                os.close() ;
                os.clear() ;
                hzerr(rc, "Data class %s Cannot write class description to data file %s", *m_Name, *m_pathCD) ;
                return rc ;
            }
            os.flush() ;
            os.close() ;
            os.clear() ;
        }
        //  Working data file does exist and has content so read it in. Start with header
        is.open(*m_pathCD) ;
        if (is.fail())
            return hzerr(E_OPENFAIL, "Class %s data file %s exists but cannot be read in", *m_Name, *m_pathCD) ;
        lineBuf = new char[512] ;
        for (nLine = 1 ;; nLine++)
        {
            is.getline(lineBuf, 500) ;
            if (!lineBuf[0])
                break ;
            E << lineBuf ;
            E.AddByte(CHAR_NL) ;
            if (!strcmp(lineBuf, "</class>"))
                break ;
        }
        is.close() ;
        delete lineBuf ;
        //  Compare class description header from file to that of the class
        strDesc = E ;
        if (strDesc != m_pClass->Desc())
            hzerr(E_FORMAT, "Format error in data file %s. Existing description \n[\n%s\n]\nNew\n[\n%s\n]\n", *m_pathCD, *strDesc, *m_pClass->Desc()) ;
    }
    //  Initialize all allocated indexes
    for (mbrNo = 0 ; mbrNo < m_pClass->MbrCount() ; mbrNo++)
    {
        pMbr = m_pClass->GetMember(mbrNo) ;
        threadLog("Initializing member %s:%s\n", m_pClass->txtName(), pMbr->txtName()) ;
        //  Check if the member has an associated index
        pIdx = m_mapIndex[pMbr->DeltaId()] ;
        if (!pIdx)
            continue ;
        if (pIdx->Whatami() == HZINDEX_UKEY)
        {
            pIdxU = (hdbIndexUkey*) pIdx ;
            rc = pIdxU->Init(this, pMbr->strName(), pMbr->Basetype()) ;
            if (rc != E_OK)
                return hzerr(rc, "Failed to initialize unique-key index %s (%s)\n", *m_Name, pMbr->txtName()) ;
        }
    }
    //  Init matrix
    if (rc != E_OK)
        threadLog("Failed to init repos %s\n", *m_Name) ;
    else
    {
        threadLog("Complete init repos %s\n", *m_Name) ;
        m_eReposInit = HDB_REPOS_INIT_DONE ;
    }
    return rc ;
}
const hdbObjRepos*  hdbObjRepos::ObjRepos   (const hdbMember* pMbr) const
{
    //  Locate the external repository associated with a subclass member. This will either be the repository itself or that specified during initialization InitMbrRepos()
    //
    //  Argument:   pMbr    Data class member
    //
    //  Returns:    Pointer to the external repository if found, 0 otherwise
    _hzfunc("hdbObjRepos::ObjRepos") ;
    const hdbObjRepos*  pR ;    //  Binary repos
    if (!m_pClass)      { hzerr(E_NOINIT, "No data class") ; return 0 ; }
    if (!pMbr)          { hzerr(E_NOINIT, "No member supplied") ; return 0 ; }
    if (pMbr->Class() != m_pClass)
        { hzerr(E_CORRUPT, "Member %s not in class %s", pMbr->txtName(), m_pClass->txtName()) ; return 0 ; }
    if (pMbr->Basetype() != BASETYPE_CLASS)
        { hzerr(E_TYPE, "Member %s is not BINARY or TXTDOC", pMbr->txtName()) ; return 0 ; }
    pR = m_mapRepos[pMbr->Posn()] ;
    if (!pR)
        return this ;
    return pR ;
}
/*
**  Open Repository. Support functions
*/
hzEcode hdbObjRepos::_loadCache (void)
{
    //  Load whole object deltas
    _hzfunc("hdbObjRepos::_loadCache") ;
    hdbObject           currObj ;       //  Current object
    hzChain             mlText ;        //  For gathering text content accross several lines
    hzChain             edo ;           //  For EDO commital
    uint32_t            objId ;         //  Object id
    hzEcode             rc = E_OK ;     //  Return code
    //  Check init state
    _hdb_ck_initstate(m_Name, m_eReposInit, HDB_REPOS_INIT_DONE) ;
    //  Init data object container
    rc = currObj.Init(m_pClass) ;
    if (rc != E_OK)
        return hzerr(E_NOINIT, "Could not init object") ;
    //  Check if RAM Primacy is deployed
    if (!m_pMain)
        return E_OK ;
    if (!m_pBR_Delta)
        return hzerr(E_NOINIT, "No binary repos for whole object deltas") ;
    threadLog("CALLED on repos %s, binary repos  %s\n", *m_Name, *m_nameBR_Delta) ;
    for (objId = 1 ; objId < m_pBR_Delta->Count() && rc == E_OK ; objId++)
    {
        rc = m_pBR_Delta->Fetch(mlText, objId) ;
        if (rc != E_OK)
        {
            threadLog("Could not fetch object %u, err=%s\n", objId, Err2Txt(rc)) ;
            continue ;
        }
        rc = currObj.ImportDelta(mlText) ;
        if (rc != E_OK)
            hzerr(rc, "Could not load full delta") ;
        else
        {
            rc = currObj.Integrity() ;
            if (rc != E_OK)
                hzerr(rc, "Case 1 Cannot Insert Object (Integrity fails)") ;
            else
            {
                rc = currObj.ExportEDO(edo) ;
                if (rc != E_OK)
                    hzerr(rc, "Could not export EDO") ;
                else
                {
                    rc = m_pMain->CommitEDO(edo, currObj.GetObjId(), true) ;
                    if (rc != E_OK)
                        hzerr(rc, "Could not commit EDO") ;
                    else
                    {
                        rc = _updateIdx(currObj) ;
                        if (rc != E_OK)
                            hzerr(rc, "Could not update indexes") ;
                    }
                }
            }
        }
        currObj.Clear() ;
        mlText.Clear() ;
    }
    m_nSeqId = m_nPopulation = m_pMain->Count() ;
    threadLog("Population of objects in cache %s is %d. Status is %s\n", txtName(), Count(), Err2Txt(rc)) ;
    return rc ;
}
hzEcode hdbObjRepos::_loadDeltas    (void)
{
    //  Load data from delta files.
    //
    //  Loads both whole object and member deltas. Note that member deltas are ignored unless the host object exists within the repository. 
    _hzfunc("hdbObjRepos::_loadDeltas") ;
    ifstream            is ;            //  For reading in working data file
    hdbObject           currObj ;       //  Current object
    hzChain             mlText ;        //  For gathering text content accross several lines
    hzChain             Y ;             //  For writing class description header from data file
    hzChain             edo ;           //  For EDO commital
    hzAtom              atom ;          //  For setting member values
    _atomval            av ;            //  Atom value
    const hdbMember*    pMbr ;          //  Member
    hdbIndex*           pIdx ;          //  Index pointer
    hdbIndexUkey*       pIdxU ;         //  Index pointer
    hdbIndexEnum*       pIdxE ;         //  Index pointer
    char*               lineBuf ;       //  For getline
    char*               j ;             //  For buffer iteration
    hzString            tmpStr ;        //  Temp string holding memeber data
    hzDomain            dom ;           //  Temp domain
    hzEmaddr            ema ;           //  Temp email addr
    hzUrl               url ;           //  Temp URL
    uint32_t            nLine ;         //  Line number for reporting file errors
    uint32_t            reposId ;       //  Repos id
    uint32_t            classId ;       //  Class id
    uint32_t            objId ;         //  Object id
    uint32_t            lastObjId ;     //  Last object id
    uint32_t            novals ;        //  Number of member values
    uint32_t            mbrNo ;         //  Member number
    hzEcode             rc = E_OK ;     //  Return code
    //  Check init state
    _hdb_ck_initstate(m_Name, m_eReposInit, HDB_REPOS_INIT_DONE) ;
    //  Init data object container
    rc = currObj.Init(m_pClass) ;
    if (rc != E_OK)
        return hzerr(E_NOINIT, "Could not init object") ;
    //  Check if RAM Primacy is deployed
    if (!m_pMain)
        return E_OK ;
    threadLog("CALLED on repos %s, delta file %s\n", *m_Name, *m_pathCD) ;
    //  Allocate line buffer
    lineBuf = new char[2048] ;
    reposId = classId = objId = lastObjId = 0 ;
    //  Bypass header
    is.open(*m_pathCD) ;
    for (nLine = 1 ;; nLine++)
    {
        is.getline(lineBuf, 2040) ;
        if (!is.gcount())
            break ;
        if (is.gcount() == 2040)
            { rc = hzerr(E_RANGE, "Line %u exceeds buffer", nLine) ; break ; }
        lineBuf[is.gcount()] = 0 ;
        Y << lineBuf ;
        Y.AddByte(CHAR_NL) ;
        if (!strcmp(lineBuf, "</class>"))
            break ;
    }
    //  Now read in the rest of the data (in delta notation)
    threadLog("LOAD on repos %s, workpath %s\n", *m_Name, *m_pathCD) ;
    lastObjId = 0 ;
    novals = 0 ;
    for (; rc == E_OK ; nLine++)
    {
        if (!(nLine % 1000))
            threadLog("Processing line %u\n", nLine) ;
        is.getline(lineBuf, 2040) ;
        if (!is.gcount())
        {
            threadLog("Terminating on line %u\n", nLine) ;
            break ;
        }
        if (is.gcount() == 2040)
            { rc = hzerr(E_RANGE, "Line %u exceeds buffer", nLine) ; break ; }
        lineBuf[is.gcount()] = 0 ;
        if (lineBuf[0] == CHAR_AT)
        {
            if (!memcmp(lineBuf, "@obj", 4))
            {
                //  Whole form delta
                mlText << lineBuf ;
                mlText.AddByte(CHAR_NL) ;
                for (;; nLine++)
                {
                    is.getline(lineBuf, 2040) ;
                    if (!is.gcount())
                        break ;
                    lineBuf[is.gcount()] = 0 ;
                    mlText << lineBuf ;
                    mlText.AddByte(CHAR_NL) ;
                    if (lineBuf[0] == '}' && lineBuf[1] == 0)
                        break ;
                }
                rc = currObj.ImportDelta(mlText) ;
                if (rc != E_OK)
                    hzerr(rc, "Could not load full delta") ;
                else
                {
                    rc = currObj.Integrity() ;
                    if (rc != E_OK)
                        hzerr(rc, "Cannot Insert Object (Integrity fails)") ;
                    else
                    {
                        rc = currObj.ExportEDO(edo) ;
                        if (rc != E_OK)
                            hzerr(rc, "Could not export EDO") ;
                        else
                        {
                            rc = m_pMain->CommitEDO(edo, currObj.GetObjId(), true) ;
                            if (rc != E_OK)
                                hzerr(rc, "Could not commit EDO") ;
                            else
                            {
                                rc = _updateIdx(currObj) ;
                                if (rc != E_OK)
                                    hzerr(rc, "Could not update indexes") ;
                            }
                        }
                    }
                }
                currObj.Clear() ;
                mlText.Clear() ;
                novals = 0 ;
                continue ;
            }
            //  Handle partial deltas
            j = lineBuf + 1 ;
            //  Gather repos id
            if (*j == 'r')
            {
                for (j++, reposId = 0 ; IsDigit(*j) ; j++)
                    { reposId *= 10 ; reposId += *j - '0' ; }
                if (*j != CHAR_PERIOD)
                    { rc = hzerr(E_FORMAT, "File %s Line %d: Period expected after repos ID", *m_pathCD, nLine) ; break ; }
                j++ ;
            }
            //  Gather class id
            if (*j == 'c')
            {
                for (j++, classId = 0 ; IsDigit(*j) ; j++)
                    { classId *= 10 ; classId += *j - '0' ; }
                if (*j != CHAR_PERIOD)
                    { rc = hzerr(E_FORMAT, "File %s Line %d: Period expected after class ID", *m_pathCD, nLine) ; break ; }
                j++ ;
            }
            //  Gather and act on the object id in the delta
            if (*j == 'o')
            {
                for (j++, objId = 0 ; IsDigit(*j) ; j++)
                    { objId *= 10 ; objId += *j - '0' ; }
                if (!lastObjId)
                    lastObjId = objId ;
                if (*j != CHAR_PERIOD)
                    { rc = hzerr(E_FORMAT, "File %s Line %d: Period and member ID expected after object ID", *m_pathCD, nLine) ; break ; }
                j++ ;
                if (lastObjId && objId != lastObjId)
                {
                    //  Now dealing with another object, so write out the EDO for the existing object, and start a new one
                    //  currObj.ExportDelta(J) ;
                    
                    rc = currObj.Integrity() ;
                    if (rc != E_OK)
                    {
                        hzerr(rc, "Case 2 Cannot Insert Object - Integrity fails") ;
                        break ;
                    }
                    currObj.SetObjId(lastObjId) ;
                    rc = currObj.ExportEDO(edo) ;
                    if (rc != E_OK)
                        hzerr(rc, "Could not export EDO") ;
                    else
                    {
                        rc = m_pMain->CommitEDO(edo, lastObjId, true) ;
                        if (rc != E_OK)
                            hzerr(rc, "Could not commit EDO") ;
                    }
                    if (rc != E_OK)
                        break ;
                    currObj.Clear() ;
                    lastObjId = objId ;
                    novals = 0 ;
                    //threadLog("Cleared object: ID %u\n", objId) ;
                }
            }
            //  Gather and act on the member ID in the delta
            if (*j == 'm')
            {
                for (j++, mbrNo = 0 ; IsDigit(*j) ; j++)
                    { mbrNo *= 10 ; mbrNo += *j - '0' ; }
                pMbr = m_pClass->GetMember(mbrNo) ;
                if (!pMbr)
                {
                    rc = hzerr(E_FORMAT, "File %s Line %d: Member %d does not exist in class %s\n", *m_pathCD, nLine, mbrNo, m_pClass->txtType()) ;
                    break ;
                }
                //threadLog("Doing member %s\n", pMbr->txtName()) ;
            }
            //  Get member values
            if (*j == CHAR_EQUAL)
            {
                //  Read in value (multi-line if applicable)
                tmpStr.Clear() ;
                j++ ;
                if (*j == CHAR_SQOPEN && j[1] == 0)
                {
                    //  Multi-line value. Loop with getline until a line is found with a ']' by itself.
                    mlText.Clear() ;
                    for (;;)
                    {
                        is.getline(lineBuf, 2040) ;
                        if (!is.gcount())
                            break ;
                        lineBuf[is.gcount()] = 0 ;
                        if (lineBuf[0] == CHAR_SQCLOSE && lineBuf[1] == 0)
                            break ;
                        mlText.Append(lineBuf, is.gcount()) ;
                    }
                    if (!mlText.Size())
                        continue ;
                    tmpStr = mlText ;
                }
                else
                {
                    tmpStr = j ;
                }
                novals++ ;
                if (!tmpStr)
                    continue ;
                //  Set the member value in the object
                rc = atom.SetValue(pMbr->Basetype(), tmpStr) ;
                if (rc != E_OK)
                {
                    threadLog("ERROR: Object %d member %s value %s. Err=%s\n", objId, pMbr->txtName(), *tmpStr, Err2Txt(rc)) ;
                    break ;
                }
                //  if (pMbr->Basetype() == BASETYPE_XDATE)
                //  threadLog("DELTA: Object %d member %s=%s (%s)\n", objId, pMbr->txtName(), atom.Show(), j) ;
                rc = currObj.SetValue(pMbr, atom) ;
                if (rc != E_OK)
                {
                    threadLog("ERROR - Could not set member %s\n", pMbr->txtName()) ;
                    break ;
                }
                pIdx = m_mapIndex[pMbr->DeltaId()] ;
                if (pIdx)
                {
                    if (pIdx->Whatami() == HZINDEX_UKEY)
                    {
                        pIdxU = (hdbIndexUkey*) pIdx ;
                        rc = pIdxU->Insert(atom, objId) ;
                        if (rc != E_OK)
                        {
                            threadLog("Mbr %s Failed on UKEY index insert. Atom %s Err=%s\n", pMbr->txtName(), atom.Show(), Err2Txt(rc)) ;
                            break ;
                        }
                    }
                    if (pIdx->Whatami() == HZINDEX_ENUM)
                    {
                        pIdxE = (hdbIndexEnum*) pIdx ;
                        rc = pIdxE->Insert(objId, atom) ;
                        if (rc != E_OK)
                        {
                            threadLog("Mbr %s Failed on ENUM index insert. Atom %s\n", pMbr->txtName(), atom.Show()) ;
                        }
                    }
                }
                atom.Clear() ;
            }
            continue ;
        }
        hzerr(E_SYNTAX, "File %s Line %d [%s]: Unknown instruction. Only @obj:, @del:, and @obj: allowed\n", *m_pathCD, nLine, lineBuf) ;
        rc = E_SYNTAX ;
        break ;
    }
    is.close() ;
    threadLog("Delta file closed\n") ;
    if (novals && rc == E_OK)
    {
        //  Commit the last EDO
        rc = currObj.Integrity() ;
        if (rc != E_OK)
            hzerr(rc, "Case 3 Cannot Insert Object - Integrity fails") ;
        else
        {
            currObj.SetObjId(objId) ;
            rc = currObj.ExportEDO(edo) ;
            if (rc != E_OK)
                hzerr(rc, "Could not export EDO") ;
            else
            {
                rc = m_pMain->CommitEDO(edo, objId, true) ;
                if (rc != E_OK)
                    hzerr(rc, "Could not commit EDO") ;
            }
        }
    }
    m_nSeqId = m_nPopulation = m_pMain->Count() ;
    threadLog("Population of objects in cache %s is %d. Delta file %s. Status is %s\n", txtName(), Count(), *m_pathCD, Err2Txt(rc)) ;
    delete [] lineBuf ;
    return rc ;
}
hzEcode hdbObjRepos::Open   (void)
{
    //  Open the data object repository, i.e. ready it for data operations. Depending on repository configuration, this action will do the following:-
    //
    //      Open the binary repository for Whole Object Deltas. This is done in all cases.
    //      This action restores RAM Primacy components to the last known data state.
    //
    //  Now deal with files if the working directory has been supplied. If a file of the cache's name exists in the working directory, this is assumed
    //  to be the data file and will be read in to populate the cache. The file must begin with a header which must match the class definition so this
    //  is checked before loading the remaining data. If a file of the cache's name does not exist in the working directory, it will be created and a
    //  header will be written.
    //
    //  If a backup directory has been specified and a file of the cache's name exists in this directory, the header will be checked and assuming this
    //  is OK, the length of the file will aslo be checked (should match with that in the work directory)
    //
    //  Arguments:  None
    _hzfunc("hdbObjRepos::Open") ;
    hzEcode     rc ;        //  Return code
    if (!this)
        Fatal("No instance") ;
    _hdb_ck_initstate(m_Name, m_eReposInit, HDB_REPOS_INIT_DONE) ;
    //if (!m_Workdir)
    //  return hzerr(E_NOINIT, "No workdir") ;
    if (m_pBR_Datum)
    {
        //  Only if native class has BINARY/TXTDOC members
        rc = m_pBR_Datum->Open() ;
        if (rc != E_OK)
            return hzerr(E_NOINIT, "Could not open mbr datum binary repos") ;
    }
    if (m_pBR_Delta)
    {
        rc = m_pBR_Delta->Open() ;
        if (rc != E_OK)
            return hzerr(E_NOINIT, "Could not open delta binary repos") ;
        rc = _loadCache() ;
        threadLog("Whole Object Deltas loaded - Status %s\n", Err2Txt(rc)) ;
    }
    rc = _loadDeltas() ;
    threadLog("Deltas loaded\n") ;
    //  Now open file for writing
    if (rc == E_OK)
    {
        m_osDelta.open(*m_pathCD, ios::app) ;
        if (m_osDelta.fail())
            return hzerr(E_WRITEFAIL, "Class %s Cannot open data file %s in write mode", *m_Name, *m_pathCD) ;
        m_eReposInit = HDB_REPOS_OPEN ;
    }
    threadLog("Population of objects in repos %s is %d. Delta file %s. Status %s\n", txtName(), Count(), *m_pathCD, Err2Txt(rc)) ;
    //threadLog("Repos %s: Pop %d\n", *m_Name, Count()) ;
    return rc ;
}
hzEcode hdbObjRepos::Clear  (void)
{
    //  Destroys all data in the Ram table and re-initializes everything.
    //
    //  Arguments:  None
    _hzfunc("hdbObjRepos::Clear") ;
    _hdb_ck_initstate(m_Name, m_eReposInit, HDB_REPOS_OPEN) ;
    if (m_pMain)
        m_pMain->Clear() ;
    //  if (m_pAuxA)    m_pAuxA->Clear() ;
    //  if (m_pAuxB)    m_pAuxB->Clear() ;
    return E_OK ;
}
hzEcode hdbObjRepos::_updateIdx (const hdbObject& obj)
{
    //  Supprt function. Updates the repository indexes on behalf of Insert() and _loadDeltas()
    _hzfunc("hdbObjRepos::_updateIdx") ;
    const hdbMember*    pMbr ;          //  Member pointer
    hdbIndex*           pIdx ;          //  Index pointer
    hdbIndexUkey*       pIdxU ;         //  Index pointer
    hdbIndexEnum*       pIdxE ;         //  Index pointer
    hzAtom              atom ;          //  Atom from member of populating object
    uint32_t            objId ;         //  Object id
    uint32_t            mbrNo ;         //  Member number
    hzEcode             rc = E_OK ;     //  Return value
    hzEcode             ic = E_OK ;     //  Return value
    objId = obj.GetObjId() ;
    if (!objId)
        return hzerr(E_NOTFOUND, "No object id") ;
    for (mbrNo = 0 ; mbrNo < m_pClass->MbrCount() ; mbrNo++)
    {
        //  Get member and value
        pMbr = m_pClass->GetMember(mbrNo) ;
        obj.GetValue(atom, pMbr) ;
        if (atom.IsNull())
            continue ;
        //  Check if member has an index
        pIdx = m_mapIndex[pMbr->DeltaId()] ;
        if (!pIdx)
            continue ;
        //  Update index
        if (pIdx->Whatami() == HZINDEX_UKEY)
        {
            pIdxU = (hdbIndexUkey*) pIdx ;
            rc = pIdxU->Insert(atom, objId) ;
        }
        if (pIdx->Whatami() == HZINDEX_ENUM)
        {
            pIdxE = (hdbIndexEnum*) pIdx ;
            rc = pIdxE->Insert(objId, atom) ;
        }
        if (rc != E_OK)
        {
            threadLog("UKEY idx insert of %s returned err=%s\n", atom.Show(), Err2Txt(ic)) ;
            break ;
        }
    }
    return rc ;
}
hzEcode hdbObjRepos::Insert (uint32_t& objId, const hdbObject& theObj)
{
    //  The INSERT operation adds a new data object to a repository and creates a new object id in respect of it. If any indexes apply, these are updated accordingly.
    //
    //  Note that in INSERT operations, the supplied object is expected to be an entirely new, and whole, object of the repository native data class. All subclass objects will have
    //  been added to the native class data object, prior to this function call. Because the supplied data object is entirely new, a whole object delta is generated.
    //
    //  In all cases the new data object is committed to a binary datum repository as a whole object delta. If RAM Primacy applies, the new data object is also written as an EDO to
    //  repository cache. The whole object delta is produced by calling ExportDelta() on the supplied object. The EDO is produced by calling ExportEDO() on the supplied object.
    //
    //  If the object has populated BINARY or TXTDOC members, their values are committed to the applicable binary datum repository before the delta and EDO exports. This is so that
    //  the values are assigned datum ids - which are the only representation the values will have in the delta and in the EDO.
    //
    //  Arguments:  1)  objId   The object id that will be assigned by this operation
    //              2)  pObj    Pointer to object to be inserted
    //
    //  Returns:    E_NOINIT    If either the cache or the supplied object is not initialized
    //              E_ARGUMENT  If the object is not supplied
    //              E_TYPE      If the object is not of the same data class as the cache
    //              E_DUPLICATE If the object cannot be inserted because one or more members violate uniqueness
    //              E_WRITEFAIL If the object deltas cannot be written
    //              E_OK        If the insert operation was successful
    _hzfunc("hdbObjRepos::Insert") ;
    const hdbMember*    pMbr ;      //  Member pointer
    hzChain         Z ;             //  For building output to data file
    hzChain         theChain ;      //  For extracting atom data stored as chains
    hzChain         edo ;           //  For EDO export and commital
    hzChain         delta ;         //  For delta export
    hzAtom          atom ;          //  Atom from member of populating object
    _atomval        av ;            //  Atomic value
    hdbIndex*       pIdx ;          //  Index pointer
    hdbIndexUkey*   pIdxU ;         //  Index pointer
    //hdbIndexEnum* pIdxE ;         //  Index pointer
    hzString        strVal ;        //  Temp string
    uint32_t        mbrNo ;         //  Member number
    hzEcode         rc = E_OK ;     //  Return value
    //  Check Init state
    if (!this)
        Fatal("No Object\n") ;
    objId = 0 ;
    _hdb_ck_initstate(m_Name, m_eReposInit, HDB_REPOS_OPEN) ;
    if (!theObj.Class())
        return hzerr(E_NOINIT, "Supplied object is not initialized") ;
    //  Check object class is the same as the host class or contains a sub-class that is the same as the sub-class
    if (theObj.Class() != m_pClass)
    {
        if (!m_pADP->IsSubClass(m_pClass, theObj.Class()))
        {
            hzerr(E_TYPE, "Supplied object class %s not compatible with this cache class %s", theObj.Classname(), m_pClass->txtType()) ;
            return E_TYPE ;
        }
    } 
    //  For an INSERT the object id (in the supplied object), must be 0
    if (theObj.GetObjId() != 0)
        return hzerr(E_RANGE, "Supplied object has an object id of %u", theObj.GetObjId()) ;
    /*
    **  Check members to see if any of them require unique values. Any that do will have an unique key index. If the member value already exists, abort the INSERT.
    */
    for (mbrNo = 0 ; mbrNo < m_pClass->MbrCount() ; mbrNo++)
    {
        pMbr = m_pClass->GetMember(mbrNo) ;
        pIdx = m_mapIndex[pMbr->DeltaId()] ;
        if (!pIdx)
            continue ;
        if (pIdx->Whatami() == HZINDEX_UKEY)
        {
            pIdxU = (hdbIndexUkey*) pIdx ;
            rc = theObj.GetValue(atom, pMbr) ;
            //atom.SetValue(pMbr->Basetype(), theObj.m_Values[mbrNo]) ;
            if (rc != E_OK)
                return hzerr(rc, "%s: Could not select on index for member %s", *m_Name, pMbr->txtName()) ;
            if (objId)
                return hzerr(E_DUPLICATE, "%s: Got objId of %d for member %s", *m_Name, objId, pMbr->txtName()) ;
        }
    }
    /*
    **  The new object does not conflict with an existing one and so insertation can proceed. The objId is assigned as the number of existing objects + 1
    */
    //  Issue the object id
    objId = ++m_nSeqId ;
    theObj.SetObjId(objId) ;
    //  Commit BINARY/TXTDOC values
    if (m_pBR_Datum)
    {
        rc = theObj.CommitBinaries(m_pBR_Datum) ;
        if (rc != E_OK)
            return hzerr(rc, "Could not commit binaries") ;
    }
    //  Export whole object delta
    rc = theObj.ExportDelta(delta) ;
    if (rc != E_OK)
        return hzerr(rc, "Could not export whole object delta") ;
    //  Update the delta file
    m_osDelta << delta ;
    m_osDelta.flush() ;
    //  Commit whole object delta to binary datum repository
    
    if (m_pMain)
    {
        //  Commit EDO
        rc = theObj.ExportEDO(edo) ;
        if (rc != E_OK)
            return hzerr(rc, "Could not export EDO") ;
        //  Commit EDO to cache
        rc = m_pMain->CommitEDO(edo, objId, false) ;
        if (rc != E_OK)
            return hzerr(rc, "Could not commit EDO") ;
    }
    //  Update indexes
    rc = _updateIdx(theObj) ;
    if (rc != E_OK)
        return hzerr(rc, "Could not update indexes") ;
    return rc ;
}
hzEcode hdbObjRepos::Delete (uint32_t objId)
{
    //  Mark as deleted, the object found at the supplied address. Write out an object deletion to the working and backup data files is they apply.
    //
    //  Arguments:  1)  objId   The target object id
    _hzfunc("hdbObjRepos::Delete") ;
    _hdb_ck_initstate(m_Name, m_eReposInit, HDB_REPOS_OPEN) ;
    //  if (!mx->m_Objects.Exists(objId))
    //      return E_NOTFOUND ;
    //if (m_bDeletes)
        //return E_DELETE ;
    //  pOld = (char*) mx->m_Objects[objId] ;
    //  mx->m_Objects.Delete(objId) ;
    return E_OK ;
}
hzEcode hdbObjRepos::Update (hdbObject& obj, uint32_t objId)
{
    //  Overwrite the object found at the supplied address, with the supplied object and update any affected indexes accordingly
    //
    //  Arguments:  1)  obj     The new version of the data object
    //              2)  objId   The object id of the original version
    //
    //              DEPRECATED
    _hzfunc("hdbObjRepos::Update") ;
    uint32_t    nIndex ;        //  Member and index iterator
    _hdb_ck_initstate(m_Name, m_eReposInit, HDB_REPOS_OPEN) ;
    if (objId >= m_pMain->Count())
        return E_RANGE ;
    //  Now go thru object's class members filling in the allocated fixed length space
    for (nIndex = 0 ;; nIndex++)
    {
    }
    return E_OK ;
}
hzEcode hdbObjRepos::Fetch  (hdbObject& obj, uint32_t objId) const
{
    //  Fetch populates the supplied object recipticle (hdbObject instance) with the object identified by the supplied object id.
    //
    //  The supplied recepticle is cleared by this function. If the supplied object id is invalid, the recepticle is left blank
    //
    //  Arguments:  1)  obj     The object
    //              2)  objId   The object id to fetch
    //
    //  Returns:    E_NOTFOUND  The requested object does not exist or has been deleted
    //              E_OK        Operation success
    _hzfunc("hdbObjRepos::Fetch") ;
    hzChain     edo ;           //  EDO fetched from repos and passed to object
    hzEcode     rc = E_OK ;     //  Return code
    //  Check init state and that supplied object is of the same class as the cache
    _hdb_ck_initstate(m_Name, m_eReposInit, HDB_REPOS_OPEN) ;
    //  Wrong data class?
    if (obj.Class() != m_pClass)
        hzexit(E_TYPE, "Repository %s is of class %s. Supplied object is of class %s", *m_Name, Classname(), obj.Classname()) ;
    //  Clear all object members
    obj.Clear() ;
    //  Object id out of range?
    if (objId > m_pMain->Count())
        return hzerr(E_NOTFOUND, "Beyond Range %u\n", m_pMain->Count()) ;
    //  Fetch data object
    if (m_pMain)
    {
        //  Fetch EDO
        rc = m_pMain->FetchEDO(edo, objId) ;
        if (rc != E_OK)
        {
            threadLog("FetchEDO failed\n") ;
            return rc ;
        }
        rc = obj.ImportEDO(edo) ;
        if (rc != E_OK)
        {
            threadLog("ImportEDO failed\n") ;
            return rc ;
        }
        rc = obj.Integrity() ;
        if (rc != E_OK)
        {
            hzChain     err ;       //  Error
            chIter      ei ;        //  EDO iterator
            err << "EDO is [ " ;
            for (ei = edo ; !ei.eof() ; ei++)
            {
                err.Printf("%02x ", (uchar) *ei) ;
            }
            err << "]\n" ;
            threadLog(err) ;
            threadLog("Integrity check failed err=%s\n", Err2Txt(rc)) ;
        }
        return rc ;
    }
    //  Fetch data object as whole object delta
    return rc ;
}
hzEcode hdbObjRepos::Exists (uint32_t& objId, const hdbMember* pMbr, const hzAtom& value)
{
    //  Identify a single object by matching on the supplied member name and value.
    //
    //  Arguments:  1)  objId   The object id identified by the operation (0 if not object found)
    //              2)  pMbr    The member to be tested
    //              3)  value   The value the member must have to identify the object
    //
    //  Returns@    E_NOINIT    If the object cache is not initialized
    //              E_CORRUPT   If the member does not exist within the cache data class
    //              E_NODATA    If the member is not indexed
    //              E_OK        If no errors occured
    _hzfunc("hdbObjRepos::Exists()") ;
    hdbIndex*       pIdx ;      //  Index pointer
    hdbIndexUkey*   pIdxU ;     //  Index pointer
    hzEcode         rc ;        //  Return code
    //  No instance?
    if (!this)
        hzexit(E_CORRUPT, "No instance") ;
    threadLog("Called on member %s value %s\n", pMbr->txtName(), value.Show()) ;
    //  Check init state
    _hdb_ck_initstate(m_Name, m_eReposInit, HDB_REPOS_OPEN) ;
    if (!m_pClass)  return hzerr(E_NOINIT, "No cache class set up") ;
    if (!pMbr)      return hzerr(E_ARGUMENT, "No member supplied") ;
    objId = 0 ;
    //  Check for index
    pIdx = m_mapIndex[pMbr->DeltaId()] ;
    if (!pIdx)
        return hzerr(E_NODATA, "No index on member %s in class %s", pMbr->txtName(), m_pClass->txtName()) ;
    //atom.SetValue(pMbr->Basetype(), value) ;
    if (pIdx->Whatami() != HZINDEX_UKEY)
        return hzerr(E_TYPE, "Wrong index on member %s in class %s", pMbr->txtName(), m_pClass->txtName()) ;
    pIdxU = (hdbIndexUkey*) pIdx ;
    rc = pIdxU->Select(objId, value) ;
    if (rc != E_OK)
        return hzerr(rc, "ERROR: Index selection on member %s in class %s", pMbr->txtName(), m_pClass->txtName()) ;
    return rc ;
}
#if 0
hzEcode hdbObjRepos::Fetchbin   (hzAtom& atom, const hzString& member, uint32_t objId)
{
    //  Fetch the actual binary content from a document/binary member. This is a two step process. Firstly in the object element itself, we have the
    //  address of the binary which will reside in a hdbBinCron or hdbBinStore instance. The Second part is lifting the actual value (reading in the
    //  binary)
    //
    //  Arguments:  1)  atom    The atom to be set to the member's value.
    //              2)  member  The name of member
    //              3)  objId   The id of the data object
    //
    //  Returns:    E_RANGE     If the object id is invalid
    //              E_TYPE      If the member is not BASETYPE_BINARY or BASETYPE_TXTDOC
    //              E_OK        If the operation was successful
    _hzfunc("hdbObjRepos::Fetchbin") ;
    const hdbMember*    pMbr ;          //  Member pointer
    const hdbBinRepos*  pRepos ;        //  Applicabe binary datum repository
    hzChain         Z ;             //  Chain to recieve the binary from the hdbBinCron
    _atomval        av ;            //  Atom value
    uint32_t        nSlot ;         //  Set by AssignSlot
    uint32_t        mbrNo ;         //  Member position
    uint32_t        addr ;          //  String number
    hzEcode         rc = E_OK ;     //  Return from hdbBinCron::Fetch
    atom.Clear() ;
    _hdb_ck_initstate(m_Name, m_eReposInit, HDB_REPOS_OPEN) ;
    pMbr = m_pClass->GetMember(member) ;
    if (!pMbr)
        return hzerr(E_CORRUPT, "No class member of %s in class %s", *member, m_pClass->txtType()) ;
    mbrNo = pMbr->Posn() ;
    if (objId < 1 || objId > m_pMain->Count())
        return E_RANGE ;
    //  And check member is a document or a binary
    if (pMbr->Basetype() != BASETYPE_BINARY && pMbr->Basetype() != BASETYPE_TXTDOC)
        return E_TYPE ;
    //  Grab the binary address from this object element
    m_pMain->AssignSlot(nSlot, objId, 0) ;
    m_pMain->GetVal(av, objId, mbrNo) ;
    addr = av.m_uInt32 ;
    //  Grab the binary value
    //rc = m_Binaries[mbrNo]->Fetch(Z, addr) ;
    pRepos = (const hdbBinRepos*) m_pStores[mbrNo] ;
    rc = pRepos->Fetch(Z, addr) ;
    if (rc == E_OK)
        atom.SetValue(pMbr->Basetype(), Z) ;
    return rc ;
}
#endif
hzEcode hdbObjRepos::GetBinary  (hzChain& Z, const hdbMember* pMbr, uint32_t objId) const
{
    //  Populate the supplied chain with the binary object held by the named member in the identified data object.
    //
    //  This assumes the named data class member holds binary objects and that the supplied data object id is valid. As object repositories do not directly hold
    //  binary values, the member value will be the address of the binary object residing in a separate binary repository. The address is then used in a Fetch()
    //  on the binary repository.
    //
    //  Arguments:  1)  atom    The atom to be set to the member's value.
    //              2)  member  The name of member
    //              3)  objId   The id of the data object
    //
    //  Returns:    E_RANGE     If the object id is invalid
    //              E_TYPE      If the member is not BASETYPE_BINARY or BASETYPE_TXTDOC
    //              E_OK        If the operation was successful
    _hzfunc("hdbObjRepos::GetBinary") ;
    //  Exit if cache not open
    _hdb_ck_initstate(m_Name, m_eReposInit, HDB_REPOS_OPEN) ;
    if (!pMbr)
        return hzerr(E_ARGUMENT, "No member supplied") ;
    if (pMbr->Class() != m_pClass)
        return hzerr(E_CORRUPT, "No class member of %s in class %s", pMbr->txtName(), m_pClass->txtType()) ;
    //  And check member is a document or a binary
    if (pMbr->Basetype() != BASETYPE_BINARY && pMbr->Basetype() != BASETYPE_TXTDOC)
        return E_TYPE ;
    //  Grab the binary value
    return m_pBR_Datum->Fetch(Z, objId) ;
}
#if 0
hzEcode hdbObjRepos::Fetchlist  (hzVect<uint32_t>& items, const hzString& member, uint32_t objId)
{
    //  For members that amount to lists (of either atomic values or class instances), fetch the list into the supplied vector (of uint32_t). The vector
    //  will then be used to iterate through real values for the member, stored in one of the auxillary RAM tables.
    //
    //  Arguments:  1)  items   The vector of items (uint32_t) which will serve as addresses
    //              2)  member  The class member (which must be defined as supporting multiple values)
    //              3)  objId   The data object id
    //
    //  Returns:    E_NOINIT    If the hdbObjRepos is not fully initialized
    //              E_CORRUPT   If the named member does not exist
    //              E_RANGE     If the member is not a list (has max pop of 1)
    //              E_NOTFOUND  If the object id does not exist in the repository
    //              E_OK        If init state is full, member is a list and object exists - even if the member has no values.
    _hzfunc("hdbObjRepos::Fetchlist") ;
    const hdbMember*    pMbr ;      //  Member pointer
    items.Clear() ;
    _hdb_ck_initstate(m_Name, m_eReposInit, HDB_REPOS_OPEN) ;
    pMbr = m_pClass->GetMember(member) ;
    if (!pMbr)
        return hzerr(E_CORRUPT, "No class member of %s in class %s", *member, m_pClass->txtType()) ;
    if (pMbr->MaxPop() == 1)
        return E_RANGE ;
    if (objId < 1 || objId > m_pMain->Count())
        return E_NOTFOUND ;
    //  if (!mx->m_Lists[pMbr->Posn()])
    //      return E_TYPE ;
    return E_OK ;
}
#endif
hzEcode hdbObjRepos::Select (hdbIdset& result, const char* cpSQL) const
{
    //  Select data objects according to the supplied search criteria (arg 2), and populate the supplied hdbIdset with the object ids.
    //
    //  The parse process is rudimentary, so SQL-esce rather than strict SQL. Each search criteria term will produce an idset result. OR and AND operations are applied where there
    //  are multiple terms. If these operations are not stated, AND is assumed.
    //
    //  Arguments:  1)  result  The bitmap of object ids identified by the select operation
    //              2)  cpSql   The SQL-esce search criteria
    _hzfunc("hdbObjRepos::Select_a") ;
    hzEcode     rc = E_OK ;
    //  Check init state
    _hdb_ck_initstate(m_Name, m_eReposInit, HDB_REPOS_OPEN) ;
    //  Clear result idset
    result.Clear() ;
    //  Do the Select
    //  if (!pExp->Parse(cpSQL))
    //      hzerr(E_PARSE) ;
    return rc ;
}