/*!
 * \file    OMS_OidHash.cpp
 * \author  MarkusSi
 * \brief   Hash to access the objects stored in the liboms cache via their oid
 */
/*

    ========== licence begin  GPL
    Copyright (c) 2002-2005 SAP AG

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    as published by the Free Software Foundation; either version 2
    of the License, or (at your option) any later version.

    This program 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
    ========== licence end



*/


#include "Oms/OMS_OidHash.hpp"
#include "Oms/OMS_ClassIdEntry.hpp"
#include "Oms/OMS_ContainerInfo.hpp"
#include "Oms/OMS_ObjectContainer.hpp"
#include "Oms/OMS_VarObjInfo.hpp"
#include "Oms/OMS_DumpInterface.hpp"
#include "Oms/OMS_Session.hpp"
#include "Oms/OMS_DbpError.hpp"
#include "SAPDBCommon/SAPDB_MemCopyMove.hpp"
#include "geo573.h"


class OMS_OidHashDumpInfo {
public:
  void*                 m_this;
  OmsObjectContainerPtr m_hashnext;
  OmsObjectId           m_oid;
  tgg91_PageRef         m_objseq;
  unsigned char         m_state;
  unsigned char         m_filler;
  tsp00_Uint4           m_beforeImages;
  tsp00_Uint4           m_containerHandle;
  tsp00_Int4            m_slot;
};

/*----------------------------------------------------------------------*/
// Implementation OMS_OidHash
/*----------------------------------------------------------------------*/

OMS_OidHash::OMS_OidHash () 
  : m_headentries(0), 
    m_mask(0),     // PTS 1118855 
    m_count(0),    // PTS 1118855
    m_maxCount(0), // PTS 1118855
    m_head(NULL),	
    m_headcurr(NULL), 
    m_context(NULL),
    m_maxLen(0)
{
}
// end constructor

/*----------------------------------------------------------------------*/

OMS_OidHash::~OMS_OidHash () 
{
  HashFree();	
  new(this) OMS_OidHash();
}
// end destructor


/*===========================================================================*/
/*! The method removes all objects which belong to dropped containers from   
**  the hash. The corresponding objects containers in the context cache 
**  are returned to the free-list.  */
/*===========================================================================*/
void OMS_OidHash::Clean() 
{
  // removes all objects of dropped containers from OID Hash
  OmsObjectContainerPtr* prev;
  OmsObjectContainerPtr  curr;
  OmsObjectContainerPtr  toFree;
  tsp00_Int4             ix;
  if (m_count > 0) {  // PTS 1120585
    for (ix = 0; ix < m_headentries; ix++) {
      prev = &m_head[ix];
      curr = *prev;
      while (NULL != curr) {
        OMS_ClassIdEntry* containerInfo = curr->GetContainerInfoNoCheck(m_context);
        if (containerInfo->GetContainerInfoPtr()->IsDropped()) {
          DecCount();    // PTS 1118855
          toFree = curr;
          *prev = curr->GetNext();
          curr  = curr->GetNext();

          // PTS 1117571
          if (containerInfo->UseCachedKeys() && containerInfo->IsKeyedObject()) {
            // Delete object out of cached key structure  
            containerInfo->VersionDelKey(toFree, m_context);
          }

          if (toFree->IsNewObject())
          {
            OMS_DETAIL_TRACE(omsTrNewObj, m_context->m_session->m_lcSink,
              "OMS_OidHash::Clean : dec flush: " << toFree->m_oid 
              << ", class: " << containerInfo->GetClassInfoPtr()->GetClassName());
            m_context->DecNewObjectsToFlush();
          }
          containerInfo->chainFree(*m_context, toFree, 19);
        }
        else {
          prev  = curr->GetNextAddr();
          curr  = curr->GetNext();
        }
      }
    }
  }
}


/*===========================================================================*/
/*! The method removes all objects from the hash. The corresponding objects 
**  containers in the context cache are returned to the free-list. 
**  An adaptation of the hash size is executed if necessary.  */
/*===========================================================================*/
void OMS_OidHash::Clear() 
{ 
  tsp00_Int4  ix;
  tsp00_Int4 listCount  = 0;
  tsp00_Int4 entryCount = 0;
  OmsObjectContainerPtr curr;

  if (m_count > 0) {  // PTS 1120585
    for (ix = 0; ix < m_headentries; ++ix) {
      curr = m_head[ix];
      if (NULL != curr) {
        ++listCount;
        do {
          ++entryCount;
          if (curr->VarObjFlag()) {
            REINTERPRET_CAST(OMS_VarObjInfo*, &curr->m_pobj)->freeVarObj(m_context);
          }
#ifdef USE_SYSTEM_ALLOC_CO13
          OmsObjectContainerPtr toFree = curr;
          OMS_ClassIdEntry* containerInfo = curr->GetContainerInfoNoCheck(m_context);
          curr  = curr->GetNext();
          containerInfo->chainFree(*m_context, toFree, 20);
#else
          curr  = curr->GetNext();
#endif
        }
        while (NULL != curr);
      }
      m_head[ix] = NULL;
    }
  }

  // PTS 1117571
  // reset search structure for cached keys
  m_context->m_containerDir.ClearCachedKeys(m_context);

  ResetCount(); // PTS 1118855
}


/*===========================================================================*/
/*! The method creates an empty hash with the given size for the context s.
**  For restrictions on the size see method (see OMS_OidHash::HashInit) 
**  \param c Pointer to the context
**  \param sz Intended size (number of buckets) of the hash array */
/*===========================================================================*/
void OMS_OidHash::Create (OMS_Context* c, const tsp00_Int4 sz) 
{
  m_context = c;
  HashInit(sz);
}


/*===========================================================================*/
/*! The method checks that there is no loop in the current hash chain starting 
**  at the given object. 
**  \param curr object container to start check from
**  \return Number of entries found in this chain starting from the given object
**  \exception DbpError (error code -9) A loop is found.*/ 
/*===========================================================================*/
int OMS_OidHash::CheckChain(OmsObjectContainerPtr curr) 
{
  int cnt = 1;
  OmsObjectContainerPtr p = curr->GetNext();
  while (p != NULL) {
    ++cnt;
    if (p == curr) {
      throw DbpError (DbpError::DB_ERROR, -9, "Loop in Oms Cache", __MY_FILE__, __LINE__); 
    }
    p = p->GetNext();
  }
  return cnt;
}

/*===========================================================================*/

void OMS_OidHash::Dump(OMS_DumpInterface& dumpObj) const
{
  OMS_OidHashDumpInfo oidHashDumpInfo;

  dumpObj.SetDumpLabel(LABEL_OMS_OID_CACHE);
  if (m_count > 0) {  // PTS 1120585
    for (tsp00_Int4 ix = 0; ix < m_headentries; ix++) {
      oidHashDumpInfo.m_slot = ix;
      OmsObjectContainerPtr curr = m_head[ix];
      while (NULL != curr)
      {
        SAPDB_MemCopyNoCheck(&oidHashDumpInfo.m_hashnext, curr,
          sizeof(oidHashDumpInfo) -
          sizeof(oidHashDumpInfo.m_this) -
          sizeof(oidHashDumpInfo.m_slot));
        dumpObj.Dump(&oidHashDumpInfo, sizeof(oidHashDumpInfo));
        curr = curr->GetNext();
      }
    }
  }
}


/*===========================================================================*/
/*! The method checks that there are no loop in any hash chains and if the 
**  number of objects in the hash equals the value stored in the member m_count.
**  If an error is found an exception -9 is thrown. */ 
/*===========================================================================*/
void OMS_OidHash::HashCheck() 
{
  int sum = 0;
  for (tsp00_Int4 ix = 0; ix < m_headentries; ix++) {
    if (m_head[ix] != NULL) {
      int cnt = CheckChain (m_head[ix]);
      sum += cnt;
    }
  }
  if (sum != m_count){
    throw DbpError (DbpError::DB_ERROR, -9, "Number of entries does not match member m_count", __MY_FILE__, __LINE__); 
  }
}

/*===========================================================================*/

inline void OMS_OidHash::HashFree () 
{
  if (NULL != m_context)
  {
    m_context->deallocate(m_head);
  }
}


/*===========================================================================*/
/*! The method allocates the memory for an hash array, initializes the hash 
**  array and all other members of the class. 
**  The size of the hash array may vary from the given size in the following
**  cases:
**  - The size is not a power of 2: The used size is the next larger number
**    which is a power of 2.
**  - The size is equal or less than zero: The used size is determined by the
**    constant DEFAULT_OMS_HASH_SIZE.
**  - The size lies outside the interval MINIMAL_OMS_HASH_SIZE and 
**    MAXIMAL_OMS_HASH_SIZE: The corresponding boundary of the interval is used. 
**  \param sz Intended size of the hash array. */
/*===========================================================================*/
inline void OMS_OidHash::HashInit (tsp00_Int4 sz)
{
  // Ensure that sz is a power of 2 and is between MINIMAL_OMS_HASH_SIZE and the 
  // maximal possible size              // PTS 1118855
  if (sz <= 0){
    sz = DEFAULT_OMS_HASH_SIZE;
  }
  else if (sz <= MINIMAL_OMS_HASH_SIZE){
    sz = MINIMAL_OMS_HASH_SIZE;
  }
  else {
    tsp00_Int4 size = (MINIMAL_OMS_HASH_SIZE << 1);
    while (sz > size && size < MAXIMAL_OMS_HASH_SIZE){
      size <<= 1;
    }
    sz = size;
  }

  m_head = REINTERPRET_CAST(OmsObjectContainerPtr*, m_context->allocate(sizeof(OmsObjectContainerPtr) * sz));
  m_headentries = sz;                     // PTS 1110780
  m_mask        = sz - 1;                 // PTS 1118855

  for (tsp00_Int4 ix = 0; ix < sz; ix++)
  {
    m_head[ix] = NULL;
  };
  m_headcurr = NULL;
  m_count    = 0;     // PTS 1118855
  m_maxCount = 0;     // PTS 1118855
  m_maxLen   = 0;

  // PTS 1117571
  // reset search structure for cached keys 
  m_context->m_containerDir.ClearCachedKeys(m_context);
}


/*===========================================================================*/
/*! The method removes the object with the given OID out of the hash. The 
**  object container is not removed from the context cache! After the operation
**  the member OMS_OidHash::m_headcurr is pointing to the bucket from
**  which the object was deleted. 
**  \param key OID of the object to be deleted
**  \param updateKeyCache If parameter equals true, then the key structure,
**         which is used for cached keys, is updated. */
/*===========================================================================*/
bool OMS_OidHash::HashDelete (const OmsObjectId& key, bool updateKeyCache)
{

  OmsObjectContainerPtr* prev = HeadPtr(key);
  OmsObjectContainerPtr  curr = *prev;
  while (curr) {
    if (curr->m_oid == key) {
      if (curr->VarObjFlag()) {
        OMS_VarObjInfo* objInfo;
        objInfo = REINTERPRET_CAST(OMS_VarObjInfo*, (&curr->m_pobj));
        objInfo->freeVarObj(m_context);
      }

      if (updateKeyCache)
      {
        // PTS 1117571
        OMS_ClassIdEntry* containerInfo = curr->GetContainerInfoNoCheck(m_context);
        if (containerInfo->UseCachedKeys() && containerInfo->IsKeyedObject()) {
          // Delete object out of cached key structure  
          containerInfo->VersionDelKey(curr, m_context);
        }
      }

      *prev = curr->GetNext();
      DecCount();   // PTS 1118855
      return true;
    }
    else {
      prev = curr->GetNextAddr();
      curr = curr->GetNext();
    }
  }
#ifdef _ASSERT_OMS
  HashCheck();
#endif
  return false;
};


/*===========================================================================*/
/*! The method allocates a new hash array with the given size (see 
**  OMS_OidHash::HashInit for restrictions on the size) and if requested
**  the old entries are rehashed into the new hash array. If the allocation of 
**  the new hash array fails, then the old array is used again. 
**  \param newHeadEntries Intended size of the new hash array
**  \param rehash If this parameter equals false, then no rehashing is done.
**         The rehashing can only be skipped, if the cache is completely  
**         reset afterwards anyway.  
*/
/*===========================================================================*/
void OMS_OidHash::HashResize(int newHeadEntries, bool rehash)
{
  OmsObjectContainerPtr* oldHead        = m_head;
  tsp00_Int4             oldCount       = m_count;
  tsp00_Int4             oldHeadEntries = m_headentries;

  float avgChainLen = 0;  // Only for analysing purpose

  bool error = false;
  try
  {
    // Allocate new hash array
    HashInit(newHeadEntries);
  }
  catch (DbpError &e){
    DbpErrorCallbackInterface *pCBInterface = DbpError::dbpGetCallback();  // PTS 1127871
    if (pCBInterface){
      pCBInterface->dbpCaughtError(e);
    }

    error = true;
  }

  if (error){
    // If allocation of new hash array fails, then use old array again
    m_head        = oldHead;
    m_headentries = oldHeadEntries;
    m_mask        = oldHeadEntries - 1;
    m_count       = oldCount;
    return;
  }

  // Rehash if rehashing is requested and there are entries in the old hash 
  if (rehash && oldCount > 0){
    int nonEmptySlots   = 0;
    m_context->m_session->IncRehash();
    for (tsp00_Int4 slot = 0; slot < oldHeadEntries; ++slot) {
      OmsObjectContainerPtr curr = oldHead[slot];
      if (curr != NULL){
        ++nonEmptySlots;
        while (NULL != curr){
          OmsObjectContainerPtr pInsert = curr;
          curr = curr->GetNext();
          HashInsert(pInsert);
        }
      }
    }
    avgChainLen = (float) oldCount / (float) (nonEmptySlots + 1);
  }

  // Free memory of old hash array
  m_context->deallocate (oldHead);
}


/*===========================================================================*/
/*! The method removes all objects which are not locked. The corresponding  
**  object containers which are stored in the context cache are returned to 
**  the free-list. */
/*===========================================================================*/
void OMS_OidHash::RemoveUnlockedObjFromCache()
{
  tsp00_Int4             ix;
  OmsObjectContainerPtr* prev;
  OmsObjectContainerPtr  curr;
  OmsObjectContainerPtr  toFree;
  OMS_ClassIdEntry*      pContainerInfo;

  if (m_count > 0) {  // PTS 1120585
    for (ix = 0; ix < m_headentries; ix++) {
      prev       = &m_head[ix];
      curr       = m_head[ix];
      m_head[ix] = NULL;
      while (NULL != curr) {
        toFree = curr;
        curr   = curr->GetNext();
        //if ((toFree->LockedFlag()) || (toFree->m_beforeImages != 0))  // PTS 1128108
        if (toFree->LockedFlag()) 
        {
          *prev    = toFree;
          toFree->SetNext(NULL);
          prev     = toFree->GetNextAddr();
        }
        else {
          pContainerInfo = toFree->GetContainerInfo(m_context);

          // PTS 1117571
          if (pContainerInfo->UseCachedKeys() && pContainerInfo->IsKeyedObject()) {
            // Delete object out of cached key structure
            pContainerInfo->VersionDelKey(toFree, m_context);
          }

          DecCount();   // PTS 1118855
          pContainerInfo->chainFree(*m_context, toFree, 21);
        }
      }
    }
  }
}


/*===========================================================================*/
/*! The method resets the hash array. The corresponding object containers are
**  not freed. 
** \param adaptOidHash If this parameter equals true, it is checked whether 
**        a rehash should be done. */
/*===========================================================================*/
void OMS_OidHash::SetEmpty(bool adaptOidHash)
{
  tsp00_Int4 ix;
  if (m_count > 0) {  // PTS 1120585
    for (ix = 0; ix < m_headentries; ++ix) {
      m_head[ix] = NULL;
    }
  }
  ResetCount(adaptOidHash);  // PTS 1118855

  // PTS 1117571
  // reset search structure for cached keys 
  m_context->m_containerDir.ClearCachedKeys(m_context);
}

/*===========================================================================*/
  
void OMS_OidHash::ChangeMaxHashChainLen(int len)
{ 
  m_context->m_session->MaxHashChainLen(len); 
}

