/* Copyright (C) 2014 InfiniDB, Inc.

   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; version 2 of
   the License.

   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., 51 Franklin Street, Fifth Floor, Boston,
   MA 02110-1301, USA. */

/*
 * $Id: we_rbmetawriter.cpp 4737 2013-08-14 20:45:46Z bwilkinson $
 */

#include "we_rbmetawriter.h"

#include <cerrno>
#include <iostream>
#include <sstream>
#include <unistd.h>
#include <boost/filesystem/path.hpp>

#include "we_config.h"
#include "we_convertor.h"
#include "we_define.h"
#include "we_log.h"
#include "we_bulkrollbackmgr.h"
#include "idbcompress.h"
using namespace compress;
using namespace execplan;
#include "IDBDataFile.h"
#include "IDBFileSystem.h"
#include "IDBPolicy.h"
using namespace idbdatafile;

namespace
{
const char* DATA_DIR_SUFFIX = "_data";
const char* TMP_FILE_SUFFIX = ".tmp";

const char* VERSION3_REC = "# VERSION: 3";
const int VERSION3_REC_LEN = 12;
const char* VERSION4_REC = "# VERSION: 4";
const int VERSION4_REC_LEN = 12;
const char* COLUMN1_REC = "COLUM1";  // HWM extent for a DBRoot
const int COLUMN1_REC_LEN = 6;
const char* COLUMN2_REC = "COLUM2";  // Placeholder for empty DBRoot
const int COLUMN2_REC_LEN = 6;
const char* DSTORE1_REC = "DSTOR1";  // HWM extent for a DBRoot
const int DSTORE1_REC_LEN = 6;
const char* DSTORE2_REC = "DSTOR2";  // Placeholder for empty DBRoot
const int DSTORE2_REC_LEN = 6;

//--------------------------------------------------------------------------
// Local Function that prints contents of an RBChunkInfo object
//--------------------------------------------------------------------------
std::ostream& operator<<(std::ostream& os, const WriteEngine::RBChunkInfo& chk)
{
  os << "OID-" << chk.fOid << "; DBRoot-" << chk.fDbRoot << "; Part-" << chk.fPartition << "; Seg-"
     << chk.fSegment << "; HWM-" << chk.fHwm;

  return os;
}
}  // namespace

namespace WriteEngine
{
//------------------------------------------------------------------------------
// Compare function used for set of RBChunkInfo objects.
//------------------------------------------------------------------------------
bool RBChunkInfoCompare::operator()(const RBChunkInfo& lhs, const RBChunkInfo& rhs) const
{
  if (lhs.fOid < rhs.fOid)
  {
    return true;
  }

  if ((lhs.fOid == rhs.fOid) && (lhs.fSegment < rhs.fSegment))
  {
    return true;
  }

  return false;
}

//------------------------------------------------------------------------------
// RBMetaWriter constructor
//------------------------------------------------------------------------------
RBMetaWriter::RBMetaWriter(const std::string& appDesc, Log* logger)
 : fMetaDataFile(NULL), fAppDesc(appDesc), fLog(logger), fCreatedSubDir(false)
{
}

//------------------------------------------------------------------------------
// Initialize this meta data file object using the specified table OID and name.
// We assume the application code calling this function, was able to acquire a
// table lock, meaning if there should happen to be any leftoever metadata files
// from a previous job, they can be deleted.
//------------------------------------------------------------------------------
void RBMetaWriter::init(OID tableOID, const std::string& tableName)
{
  fTableOID = tableOID;
  fTableName = tableName;

  std::vector<uint16_t> dbRoots;
  Config::getRootIdList(dbRoots);

  std::string metaFileName;
  std::ostringstream oss;
  oss << "/" << fTableOID;

  // Delete any files that collide with the file names we are going to need.
  // Construct the filenames; we will use a temporary file name until we are
  // finished creating, at which time we will rename the temp files.
  for (unsigned m = 0; m < dbRoots.size(); m++)
  {
    std::string bulkRollbackPath(Config::getDBRootByNum(dbRoots[m]));
    bulkRollbackPath += '/';
    bulkRollbackPath += DBROOT_BULK_ROLLBACK_SUBDIR;
    metaFileName = bulkRollbackPath;
    metaFileName += oss.str();

    std::string tmpMetaFileName = metaFileName;
    tmpMetaFileName += TMP_FILE_SUFFIX;

    // Delete any files that collide with the filenames we intend to use
    IDBPolicy::remove(metaFileName.c_str());
    IDBPolicy::remove(tmpMetaFileName.c_str());

    // Clear out any data subdirectory
    deleteSubDir(metaFileName);
  }
}

//------------------------------------------------------------------------------
// Saves snapshot of extentmap into a bulk rollback meta data file, for
// use in a bulk rollback.  Function was closely modeled after function
// of similar name in bulk/we_tableinfo.cpp.  API was modified to help
// facilitate its use by DML.
//
// columns - Column vector with information about column in table.
//           Includes information about the initial HWM extent, so that
//           the corresponding HWM chunk can be backed up.
// dctnryStoreOids - Dictionary store OIDs that correspond to columns.
// dbRootHWMInfoVecCol - vector of last local HWM info for each DBRoot
//     (asssigned to current PM) for each column in tblOid.
//------------------------------------------------------------------------------
void RBMetaWriter::saveBulkRollbackMetaData(const std::vector<Column>& columns,
                                            const std::vector<OID>& dctnryStoreOids,
                                            const std::vector<BRM::EmDbRootHWMInfo_v>& dbRootHWMInfoVecCol)
{
  int rc = NO_ERROR;
  bool bOpenedFile = false;

  try
  {
    std::vector<uint16_t> dbRoots;
    Config::getRootIdList(dbRoots);

    // Loop through DBRoot HWMs for this PM
    for (unsigned m = 0; m < dbRoots.size(); m++)
    {
      std::string metaFileName = openMetaFile(dbRoots[m]);
      bOpenedFile = true;
      fCreatedSubDir = false;

      // Loop through the columns in the specified table
      for (size_t i = 0; i < columns.size(); i++)
      {
        const BRM::EmDbRootHWMInfo_v& dbRootHWMInfo = dbRootHWMInfoVecCol[i];

        // Select dbRootHWMInfo that matches DBRoot for this iteration
        unsigned k = 0;

        for (; k < dbRootHWMInfo.size(); k++)
        {
          if (dbRoots[m] == dbRootHWMInfo[k].dbRoot)
            break;
        }

        if (k >= dbRootHWMInfo.size())  // logic error; should not happen
        {
          std::ostringstream oss;
          oss << "Error creating meta file; DBRoot" << dbRoots[m]
              << " listed in Calpont config file, but not in extentmap"
                 " for OID "
              << columns[i].dataFile.oid;
          throw WeException(oss.str(), ERR_INVALID_PARAM);
        }

        uint16_t dbRoot = dbRootHWMInfo[k].dbRoot;
        uint32_t partition = 0;
        uint16_t segment = 0;
        HWM localHWM = 0;
        bool bExtentWithData = false;

        // For empty DBRoot (totalBlocks == 0),
        // leave partition, segment, and HWM set to 0
        if ((dbRootHWMInfo[k].totalBlocks > 0) || (dbRootHWMInfo[k].status == BRM::EXTENTOUTOFSERVICE))
        {
          partition = dbRootHWMInfo[k].partitionNum;
          segment = dbRootHWMInfo[k].segmentNum;
          localHWM = dbRootHWMInfo[k].localHWM;
          bExtentWithData = true;
        }

        // Save column meta-data info to support bulk rollback
        writeColumnMetaData(metaFileName, bExtentWithData, columns[i].dataFile.oid, dbRoot, partition,
                            segment, localHWM, columns[i].colDataType, ColDataTypeStr[columns[i].colDataType],
                            columns[i].colWidth, columns[i].compressionType);

        // Save dctnry store meta-data info to support bulk rollback
        if (dctnryStoreOids[i] > 0)
        {
          std::vector<uint32_t> segList;
          std::string segFileListErrMsg;

          if (bExtentWithData)
          {
            std::string dirName;
            FileOp fileOp(false);
            rc = fileOp.getDirName(dctnryStoreOids[i], dbRoot, partition, dirName);

            if (rc != NO_ERROR)
            {
              WErrorCodes ec;
              std::ostringstream oss;
              oss << "Bulk rollback error constructing path "
                     "for dictionary "
                  << dctnryStoreOids[i] << "; dbRoot-" << dbRoot << "; partition-" << partition << "; "
                  << ec.errorString(rc);

              throw WeException(oss.str(), rc);
            }

            rc = BulkRollbackMgr::getSegFileList(dirName, false, segList, segFileListErrMsg);

            if (rc != NO_ERROR)
            {
              WErrorCodes ec;
              std::ostringstream oss;
              oss << "Bulk rollback error for dictionary " << dctnryStoreOids[i] << "; directory-" << dirName
                  << "; " << segFileListErrMsg << "; " << ec.errorString(rc);

              throw WeException(oss.str(), rc);
            }
          }  // end of "if (bExtentWithData)"

          if (segList.size() == 0)
          {
            writeDictionaryStoreMetaNoDataMarker(columns[i].dataFile.oid, dctnryStoreOids[i], dbRoot,
                                                 partition,
                                                 0,  // segment
                                                 columns[i].compressionType);
          }
          else
          {
            // Loop thru dictionary store seg files for this DBRoot
            for (unsigned int kk = 0; kk < segList.size(); kk++)
            {
              unsigned int segDictionary = segList[kk];

              // check HWM for dictionary store file
              HWM dictHWMStore;
              int extState;
              rc = BRMWrapper::getInstance()->getLocalHWM(dctnryStoreOids[i], partition, segDictionary,
                                                          dictHWMStore, extState);

              if (rc != NO_ERROR)
              {
                WErrorCodes ec;
                std::ostringstream oss;
                oss << "Error getting rollback HWM for "
                       "dictionary file "
                    << dctnryStoreOids[i] << "; partition-" << partition << "; segment-" << segDictionary
                    << "; " << ec.errorString(rc);
                throw WeException(oss.str(), rc);
              }

              writeDictionaryStoreMetaData(columns[i].dataFile.oid, dctnryStoreOids[i], dbRoot, partition,
                                           segDictionary, dictHWMStore, columns[i].compressionType);

            }  // loop thru dictionary store seg files in this DBRoot
          }    // dictionary OID has 1 or more seg files in partition
        }      // if dictionary column

        // For a compressed column, backup the starting HWM chunk if the
        // starting HWM block is not on an empty DBRoot (or outOfSrvc)
        if ((columns[i].compressionType) && (columns[i].dataFile.fDbRoot == dbRootHWMInfo[k].dbRoot) &&
            (dbRootHWMInfo[k].totalBlocks > 0) && (dbRootHWMInfo[k].status != BRM::EXTENTOUTOFSERVICE))
        {
          backupColumnHWMChunk(columns[i].dataFile.oid, columns[i].dataFile.fDbRoot,
                               columns[i].dataFile.fPartition, columns[i].dataFile.fSegment,
                               columns[i].dataFile.hwm);
        }

      }  // End of loop through columns

      // time to dump the string stream to file
      std::string data(fMetaDataStream.str());

      // this is to cover partical writes
      // no need for retry if low layer takes care partial writes.
      const char* p = data.c_str();  // buffer contents
      size_t s = data.size();        // buffer size
      size_t w = 0;                  // total bytes written so far
      ssize_t n = 0;                 // bytes written in one write

      for (int i = 0; i < 10 && w < s; i++)
      {
        n = fMetaDataFile->write(p + w, s - w);

        if (n < 0)
          break;

        w += n;
      }

      if (w != s)
      {
        int errRc = errno;
        std::ostringstream oss;
        oss << "Error writing bulk rollback meta-data file " << metaFileName << "; written/expect:" << w
            << "/" << s << "; err-" << errRc << "; " << strerror(errRc);
        throw WeException(oss.str(), ERR_FILE_WRITE);
      }

      fMetaDataStream.str("");
      closeMetaFile();
      bOpenedFile = false;

    }  // End of loop through DBRoot HWMs for this PM

    renameMetaFile();  // rename meta files from temporary filenames
  }
  catch (WeException& ex)  // catch exception to close file, then rethrow
  {
    if (bOpenedFile)
      closeMetaFile();

    // If any error occurred, then go back and try to delete all meta files.
    // We catch and drop any exception, and return the original exception,
    // since we are already in error-mode at this point.
    try
    {
      deleteFile();
    }
    catch (...)
    {
    }

    throw WeException(ex.what(), ex.errorCode());
  }
}

//------------------------------------------------------------------------------
// Open a meta data file to save info about the specified table OID.
//------------------------------------------------------------------------------
std::string RBMetaWriter::openMetaFile(uint16_t dbRoot)
{
  std::string bulkRollbackPath(Config::getDBRootByNum(dbRoot));
  bulkRollbackPath += '/';
  bulkRollbackPath += DBROOT_BULK_ROLLBACK_SUBDIR;

  if (!IDBPolicy::exists(bulkRollbackPath.c_str()))
  {
    if (IDBPolicy::mkdir(bulkRollbackPath.c_str()) != 0)
    {
      std::ostringstream oss;
      oss << "Error creating bulk rollback directory " << bulkRollbackPath << ";" << std::endl;
      throw WeException(oss.str(), ERR_DIR_CREATE);
    }
  }

  // Open the file
  std::ostringstream oss;
  oss << "/" << fTableOID;
  std::string metaFileName(bulkRollbackPath);
  metaFileName += oss.str();
  fMetaFileNames.insert(make_pair(dbRoot, metaFileName));

  std::string tmpMetaFileName(metaFileName);
  tmpMetaFileName += TMP_FILE_SUFFIX;
  fMetaDataFile = IDBDataFile::open(IDBPolicy::getType(tmpMetaFileName.c_str(), IDBPolicy::WRITEENG),
                                    tmpMetaFileName.c_str(), "wb", 0);

  if (!fMetaDataFile)
  {
    int errRc = errno;
    std::ostringstream oss;
    std::string eMsg;
    Convertor::mapErrnoToString(errRc, eMsg);
    oss << "Error opening bulk rollback file " << tmpMetaFileName << "; " << eMsg;
    throw WeException(oss.str(), ERR_FILE_OPEN);
  }

  {
    std::ostringstream ossChown;
    idbdatafile::IDBFileSystem& fs = IDBPolicy::getFs(tmpMetaFileName.c_str());
    if (chownPath(ossChown, tmpMetaFileName, fs) || chownPath(ossChown, bulkRollbackPath, fs))
    {
      throw WeException(ossChown.str(), ERR_FILE_CHOWN);
    }
  }

  fMetaDataStream << "# VERSION: 4" << std::endl
                  << "# APPLICATION: " << fAppDesc << std::endl
                  << "# PID:    " << ::getpid() << std::endl
                  << "# TABLE:  " << fTableName << std::endl
                  << "# COLUM1: coloid,"
                     "dbroot,part,seg,lastLocalHWM,type,typename,width,comp"
                  << std::endl
                  << "# COLUM2: coloid,"
                     "dbroot,part,seg,type,typename,width,comp"
                  << std::endl
                  << "# DSTOR1: coloid,dctoid,"
                     "dbroot,part,seg,localHWM,comp"
                  << std::endl
                  << "# DSTOR2: coloid,dctoid,"
                     "dbroot,part,seg,comp"
                  << std::endl;

  // Clear out any data subdirectory
  // This is redundant because init() also calls deleteSubDir(), but it can't
  // hurt to call twice.  We "really" want to make sure we start with a clean
  // slate (no leftover backup chunk files from a previous import job).
  deleteSubDir(metaFileName);

  return metaFileName;
}

//------------------------------------------------------------------------------
// Close the currently open "temporary named" meta data file used during
// construction.  We will rename all the meta data files (for the various
// dbroots) to their eventual file names later, in renameMetaFile().
//------------------------------------------------------------------------------
void RBMetaWriter::closeMetaFile()
{
  delete fMetaDataFile;
  fMetaDataFile = NULL;
}

//------------------------------------------------------------------------------
// Rename temporary metafile names to their permanent name, taking file names
// from fMetaFileNames.  In the normal case there will be one file name per
// DBRoot for the local PM we are running on.
//------------------------------------------------------------------------------
void RBMetaWriter::renameMetaFile()
{
  for (std::map<uint16_t, std::string>::const_iterator iter = fMetaFileNames.begin();
       iter != fMetaFileNames.end(); ++iter)
  {
    const std::string& metaFileName = iter->second;

    if (!metaFileName.empty())
    {
      std::string tmpMetaFileName = metaFileName;
      tmpMetaFileName += TMP_FILE_SUFFIX;

      if (IDBPolicy::rename(tmpMetaFileName.c_str(), metaFileName.c_str()))
      {
        int errRc = errno;
        std::ostringstream oss;
        std::string eMsg;
        Convertor::mapErrnoToString(errRc, eMsg);
        oss << "Error renaming meta data file-" << tmpMetaFileName << "; will be deleted; " << eMsg;
        throw WeException(oss.str(), ERR_METADATABKUP_FILE_RENAME);
      }
    }
  }
}

//------------------------------------------------------------------------------
// Delete the meta data files for the specified table OID.  We loop through all
// the DBRoots for the local PM, deleting the applicable meta data files.
// If the call to deleteSubDir() should throw an exception, we might not want
// to consider that a fatal error, but we go ahead and let the exception
// go up the call-stack so that the caller can log the corresponding message.
// The application code can then decide whether they want to consider this
// condition as fatal or not.
//------------------------------------------------------------------------------
void RBMetaWriter::deleteFile()
{
  for (std::map<uint16_t, std::string>::const_iterator iter = fMetaFileNames.begin();
       iter != fMetaFileNames.end(); ++iter)
  {
    const std::string& metaFileName = iter->second;

    if (!metaFileName.empty())
    {
      std::string tmpMetaFileName = metaFileName;
      tmpMetaFileName += TMP_FILE_SUFFIX;

      IDBPolicy::remove(metaFileName.c_str());
      IDBPolicy::remove(tmpMetaFileName.c_str());

      deleteSubDir(metaFileName);  // can throw an exception
    }
  }

  fMetaFileNames.clear();
}

//------------------------------------------------------------------------------
// New version of writeColumnMetaData for Shared-Nothing
//------------------------------------------------------------------------------
void RBMetaWriter::writeColumnMetaData(const std::string& metaFileName, bool withHWM, OID columnOID,
                                       uint16_t dbRoot, uint32_t partition, uint16_t segment,
                                       HWM lastLocalHwm, CalpontSystemCatalog::ColDataType colType,
                                       const std::string& colTypeName, int colWidth, int compressionType)
{
  if (withHWM)
  {
    fMetaDataStream << "COLUM1: " << columnOID << ' ' << dbRoot << ' ' << partition << ' ' << segment << ' '
                    << lastLocalHwm << ' ' << colType << ' ' << colTypeName << ' ' << colWidth;
  }
  else
  {
    fMetaDataStream << "COLUM2: " << columnOID << ' ' << dbRoot << ' ' << partition << ' ' << segment << ' '
                    << colType << ' ' << colTypeName << ' ' << colWidth;
  }

  if (compressionType)
    fMetaDataStream << ' ' << compressionType << ' ';

  fMetaDataStream << std::endl;

  // If column is compressed, then create directory for storing HWM chunks
  if (compressionType)
  {
    if (!fCreatedSubDir)
    {
      // @bug 5572 - Don't need db backup files for HDFS;
      //             use hdfs buffer file
      if (!IDBPolicy::useHdfs())
        createSubDir(metaFileName);
    }
  }
}

//------------------------------------------------------------------------------
// New version of writeDictionaryStoreMetaData for Shared-Nothing.
//------------------------------------------------------------------------------
void RBMetaWriter::writeDictionaryStoreMetaData(OID columnOID, OID dictionaryStoreOID, uint16_t dbRoot,
                                                uint32_t partition, uint16_t segment, HWM localHwm,
                                                int compressionType)
{
  fMetaDataStream << "DSTOR1: " << columnOID << ' ' << dictionaryStoreOID << ' ' << dbRoot << ' ' << partition
                  << ' ' << segment << ' ' << localHwm;

  if (compressionType)
    fMetaDataStream << ' ' << compressionType << ' ';

  fMetaDataStream << std::endl;

  // Save dictionary meta data for later use in backing up the HWM chunks
  if (compressionType)
  {
    RBChunkInfo chunkInfo(dictionaryStoreOID, dbRoot, partition, segment, localHwm);
    fRBChunkDctnrySet.insert(chunkInfo);

    if ((fLog) && (fLog->isDebug(DEBUG_1)))
      printDctnryChunkList(chunkInfo, "after adding ");
  }
}

//------------------------------------------------------------------------------
// New version of writeDictionaryStoreMetaNoDataMarker for Shared-Nothing.
//------------------------------------------------------------------------------
void RBMetaWriter::writeDictionaryStoreMetaNoDataMarker(OID columnOID, OID dictionaryStoreOID,
                                                        uint16_t dbRoot, uint32_t partition, uint16_t segment,
                                                        int compressionType)
{
  fMetaDataStream << "DSTOR2: " << columnOID << ' ' << dictionaryStoreOID << ' ' << dbRoot << ' ' << partition
                  << ' ' << segment;

  if (compressionType)
    fMetaDataStream << ' ' << compressionType << ' ';

  fMetaDataStream << std::endl;
}

//------------------------------------------------------------------------------
// Create the subdirectory we will use to backup data needed for rollback.
//------------------------------------------------------------------------------
void RBMetaWriter::createSubDir(const std::string& metaFileName)
{
  std::string bulkRollbackSubPath(metaFileName);
  bulkRollbackSubPath += DATA_DIR_SUFFIX;

  if (IDBPolicy::mkdir(bulkRollbackSubPath.c_str()) != 0)
  {
    std::ostringstream oss;
    oss << "Error creating bulk rollback data subdirectory " << bulkRollbackSubPath << ";";
    throw WeException(oss.str(), ERR_DIR_CREATE);
  }

  fCreatedSubDir = true;
}

//------------------------------------------------------------------------------
// Delete the subdirectory used to backup data needed for rollback.
//------------------------------------------------------------------------------
void RBMetaWriter::deleteSubDir(const std::string& metaFileName)
{
  std::string bulkRollbackSubPath(metaFileName);
  bulkRollbackSubPath += DATA_DIR_SUFFIX;

  if (IDBPolicy::remove(bulkRollbackSubPath.c_str()) != 0)
  {
    std::ostringstream oss;
    oss << "Error deleting bulk rollback data subdirectory " << bulkRollbackSubPath << ";";
    throw WeException(oss.str(), ERR_FILE_DELETE);
  }
}

//------------------------------------------------------------------------------
// Backup the contents of the HWM chunk for the specified column OID extent,
// so that the chunk is available for bulk rollback.
// This operation is only performed for compressed columns.
//------------------------------------------------------------------------------
void RBMetaWriter::backupColumnHWMChunk(OID columnOID, uint16_t dbRoot, uint32_t partition, uint16_t segment,
                                        HWM startingHWM)
{
  // @bug 5572 - Don't need db backup file for HDFS; we use hdfs buffer file
  if (!IDBPolicy::useHdfs())
  {
    backupHWMChunk(true, columnOID, dbRoot, partition, segment, startingHWM);
  }
}

//------------------------------------------------------------------------------
// Backup the contents of the HWM chunk for the specified dictionary store OID
// extent, so that the chunk is available for bulk rollback.
// This operation is only performed for compressed columns.  Once the chunk is
// saved, we remove that OID, partition, and segment from the internal list
// (fRBChunkDctnrySet) that is maintained.
// Return value indicates whether the specified file needs to be backed up or
// not.
//
// This function MUST be maintained to be thread-safe so that multiple threads
// can concurrently call this function, with each thread managing a different
// dictionary column.
//------------------------------------------------------------------------------
// @bug 5572 - HDFS usage: add return flag to indicate backup status
bool RBMetaWriter::backupDctnryHWMChunk(OID dctnryOID, uint16_t dbRoot, uint32_t partition, uint16_t segment)
{
  bool bBackupApplies = false;

  if (fRBChunkDctnrySet.size() > 0)
  {
    RBChunkInfo chunkInfo(dctnryOID, 0, partition, segment, 0);
    RBChunkInfo chunkInfoFound(0, 0, 0, 0, 0);
    bool bFound = false;

    {
      // Use scoped lock to perform "find"
      boost::mutex::scoped_lock lock(fRBChunkDctnryMutex);

      if ((fLog) && (fLog->isDebug(DEBUG_1)))
        printDctnryChunkList(chunkInfo, "when searching ");

      RBChunkSet::iterator iter = fRBChunkDctnrySet.find(chunkInfo);

      if (iter != fRBChunkDctnrySet.end())
      {
        bFound = true;
        chunkInfoFound = *iter;
      }
    }

    if (bFound)
    {
      if (chunkInfoFound.fPartition == partition)
      {
        // @bug 5572 - Don't need db backup file for HDFS;
        //             we use hdfs buffer file.  Set backup flag
        //             so application knows to use tmp buffer file.
        bBackupApplies = true;

        if (!IDBPolicy::useHdfs())
        {
          backupHWMChunk(false, dctnryOID, dbRoot, partition, segment, chunkInfoFound.fHwm);
        }
      }
      else
      {
        // How could this happen?  Ended up asking for different
        // partition than expected for the first instance of this
        // OID and segment file.  Perhaps initial blockskipping
        // or something caused us to advance to another segment file
        // without ever changing the expected extent.  At any rate
        // we still fall through and delete our entry because we
        // apparently did not end up changing the chunk referenced
        // by this RBChunkInfo object.
      }

      {
        // Use scoped lock to perform "erase"
        boost::mutex::scoped_lock lock(fRBChunkDctnryMutex);
        fRBChunkDctnrySet.erase(chunkInfoFound);

        if ((fLog) && (fLog->isDebug(DEBUG_1)))
          printDctnryChunkList(chunkInfoFound, "after deleting ");
      }
    }
  }

  return bBackupApplies;
}

//------------------------------------------------------------------------------
// Backup entire contents of HWM file for the specified columnOID,dbRoot,etc,
// so that the file is available for bulk rollback.  This function is used for
// HDFS files only.  This operation is only performed for compressed columns.
//
// This function MUST be kept thread-safe in support of backupDctnryHWMChunk().
// See that function description for more details.  This is the reason
// backupHWMChunk() has to have a local FileOp object.  We can't share/reuse
// a FileOp data member variable unless we want to employ a mutex.
//------------------------------------------------------------------------------
// @bug 5572 - Stopped using backupHWMFile().
// Don't need db backup file for HDFS; we use hdfs buffer file
void RBMetaWriter::backupHWMFile(bool bColumnFile,    // is this a column (vs dictionary) file
                                 OID columnOID,       // OID of column or dictionary store
                                 uint16_t dbRoot,     // DB Root for db segment file
                                 uint32_t partition,  // partition for db segment file
                                 uint16_t segment,    // segment for db segment file
                                 HWM startingHWM)     // starting HWM for db segment file
{
  std::string fileType("column");

  if (!bColumnFile)
    fileType = "dictionary";

  FileOp fileOp;  // @bug 4960: to keep thread-safe, we use local FileOp

  // Construct file name for db file to be backed up
  char dbFileName[FILE_NAME_SIZE];
  int rc = fileOp.getFileName(columnOID, dbFileName, dbRoot, partition, segment);

  if (rc != NO_ERROR)
  {
    std::ostringstream oss;
    oss << "Error creating backup " << fileType << " file for OID " << columnOID
        << "; Can't construct file name for DBRoot" << dbRoot << "; partition-" << partition << "; segment-"
        << segment;
    throw WeException(oss.str(), rc);
  }

  // Construct file name for backup copy of db file
  std::ostringstream ossFile;
  ossFile << "/" << columnOID << ".p" << partition << ".s" << segment;
  std::string backupFileName;
  rc = getSubDirPath(dbRoot, backupFileName);

  if (rc != NO_ERROR)
  {
    std::ostringstream oss;
    oss << "Error creating backup " << fileType << " file for OID " << columnOID
        << "; Can't find matching meta file for DBRoot" << dbRoot;
    throw WeException(oss.str(), rc);
  }

  backupFileName += ossFile.str();

  std::string backupFileNameTmp = backupFileName;
  backupFileNameTmp += TMP_FILE_SUFFIX;

  // if ( (fLog) && (fLog->isDebug(DEBUG_1)) )
  if (fLog)
  {
    std::ostringstream oss;
    oss << "Backing up HWM file for " << fileType << " file for OID " << columnOID << "; file-"
        << backupFileNameTmp << "; HWM-" << startingHWM;
    fLog->logMsg(oss.str(), MSGLVL_INFO2);
  }

  // Copy the db file to a temporary name
  IDBFileSystem& fs = IDBPolicy::getFs(backupFileNameTmp.c_str());

  if (!fs.exists(dbFileName))
  {
    std::ostringstream oss;
    oss << "Error creating backup " << fileType << " file for OID " << columnOID
        << "; dbfile does not exist for DBRoot" << dbRoot << "; partition-" << partition << "; segment-"
        << segment;
    throw WeException(oss.str(), ERR_FILE_NOT_EXIST);
  }

  rc = fs.copyFile(dbFileName, backupFileNameTmp.c_str());

  if (rc != 0)
  {
    std::ostringstream oss;
    oss << "Error copying backup for " << fileType << " OID-" << columnOID << "; DBRoot-" << dbRoot
        << "; partition-" << partition << "; segment-" << segment << "; rc-" << rc;

    fs.remove(backupFileNameTmp.c_str());
    throw WeException(oss.str(), ERR_METADATABKUP_COMP_WRITE_BULK_BKUP);
  }

  // Rename temporary named backup file to final name
  rc = fs.rename(backupFileNameTmp.c_str(), backupFileName.c_str());

  if (rc != 0)
  {
    std::ostringstream oss;
    oss << "Error renaming temp backup for " << fileType << " OID-" << columnOID << "; DBRoot-" << dbRoot
        << "; partition-" << partition << "; segment-" << segment << "; rc-" << rc;

    fs.remove(backupFileNameTmp.c_str());
    fs.remove(backupFileName.c_str());
    throw WeException(oss.str(), ERR_METADATABKUP_COMP_RENAME);
  }
}

//------------------------------------------------------------------------------
// Backup the contents of the HWM chunk for the specified columnOID,dbRoot,etc,
// so that the chunk is available for bulk rollback.  This function is used for
// non-hdfs files.  This operation is only performed for compressed columns.
//
// This function MUST be kept thread-safe in support of backupDctnryHWMChunk().
// See that function description for more details.  This is the reason
// backupHWMChunk() has to have a local FileOp object.  We can't share/reuse
// a FileOp data member variable unless we want to employ a mutex.
//------------------------------------------------------------------------------
void RBMetaWriter::backupHWMChunk(bool bColumnFile,    // is this a column (vs dictionary) file
                                  OID columnOID,       // OID of column or dictionary store
                                  uint16_t dbRoot,     // DB Root for db segment file
                                  uint32_t partition,  // partition for db segment file
                                  uint16_t segment,    // segment for db segment file
                                  HWM startingHWM)     // starting HWM for db segment file
{
  std::string fileType("column");

  if (!bColumnFile)
    fileType = "dictionary";

  // Open the applicable database column segment file
  std::string segFile;
  FileOp fileOp;  // @bug 4960: to keep thread-safe, we use local FileOp
  IDBDataFile* dbFile = fileOp.openFile(columnOID, dbRoot, partition, segment, segFile, "rb");

  if (!dbFile)
  {
    std::ostringstream oss;
    oss << "Backup error opening " << fileType << " file for OID-" << columnOID << "; DBRoot-" << dbRoot
        << "; partition-" << partition << "; segment-" << segment;
    throw WeException(oss.str(), ERR_FILE_OPEN);
  }

  // Get the size of the file, so we know where to truncate back to.
  long long fileSizeBytes;
  int rc = fileOp.getFileSize(dbFile, fileSizeBytes);

  if (rc != NO_ERROR)
  {
    WErrorCodes ec;
    std::ostringstream oss;
    oss << "Backup error getting file size for " << fileType << " OID-" << columnOID << "; DBRoot-" << dbRoot
        << "; partition-" << partition << "; segment-" << segment << "; " << ec.errorString(rc);
    fileOp.closeFile(dbFile);
    throw WeException(oss.str(), rc);
  }

  // Read Control header
  char controlHdr[CompressInterface::HDR_BUF_LEN];
  rc = fileOp.readFile(dbFile, (unsigned char*)controlHdr, CompressInterface::HDR_BUF_LEN);

  if (rc != NO_ERROR)
  {
    WErrorCodes ec;
    std::ostringstream oss;
    oss << "Backup error reading " << fileType << " file control hdr for OID-" << columnOID << "; DBRoot-"
        << dbRoot << "; partition-" << partition << "; segment-" << segment << "; " << ec.errorString(rc);
    fileOp.closeFile(dbFile);
    throw WeException(oss.str(), rc);
  }

  int rc1 = compress::CompressInterface::verifyHdr(controlHdr);

  if (rc1 != 0)
  {
    rc = ERR_METADATABKUP_COMP_VERIFY_HDRS;

    WErrorCodes ec;
    std::ostringstream oss;
    oss << "Backup error verifying " << fileType << " file control hdr for OID-" << columnOID << "; DBRoot-"
        << dbRoot << "; partition-" << partition << "; segment-" << segment << "; " << ec.errorString(rc)
        << "; rc: " << rc1;
    fileOp.closeFile(dbFile);
    throw WeException(oss.str(), rc);
  }

  auto compressionType = compress::CompressInterface::getCompressionType(controlHdr);
  std::unique_ptr<compress::CompressInterface> compressor(
      compress::getCompressInterfaceByType(compressionType));

  if (!compressor)
  {
    WErrorCodes ec;
    std::ostringstream oss;
    oss << "Ivalid compression type " << compressionType;
    fileOp.closeFile(dbFile);
    throw WeException(oss.str(), rc);
  }

  // Read Pointer header data
  uint64_t hdrSize = compress::CompressInterface::getHdrSize(controlHdr);
  uint64_t ptrHdrSize = hdrSize - CompressInterface::HDR_BUF_LEN;
  char* pointerHdr = new char[ptrHdrSize];
  rc = fileOp.readFile(dbFile, (unsigned char*)pointerHdr, ptrHdrSize);

  if (rc != NO_ERROR)
  {
    WErrorCodes ec;
    std::ostringstream oss;
    oss << "Backup error reading " << fileType << " file pointer hdr for OID-" << columnOID << "; DBRoot-"
        << dbRoot << "; partition-" << partition << "; segment-" << segment << "; " << ec.errorString(rc);
    delete[] pointerHdr;
    fileOp.closeFile(dbFile);
    throw WeException(oss.str(), rc);
  }

  CompChunkPtrList chunkPtrs;
  rc = compress::CompressInterface::getPtrList(pointerHdr, ptrHdrSize, chunkPtrs);
  delete[] pointerHdr;

  if (rc != 0)
  {
    std::ostringstream oss;
    oss << "Backup error getting " << fileType << " file hdr for OID-" << columnOID << "; DBRoot-" << dbRoot
        << "; partition-" << partition << "; segment-" << segment;
    fileOp.closeFile(dbFile);
    throw WeException(oss.str(), ERR_METADATABKUP_COMP_PARSE_HDRS);
  }

  // Locate HWM chunk
  unsigned int chunkIndex = 0;
  unsigned int blockOffsetWithinChunk = 0;
  unsigned char* buffer = 0;
  uint64_t chunkSize = 0;
  compressor->locateBlock(startingHWM, chunkIndex, blockOffsetWithinChunk);

  if (chunkIndex < chunkPtrs.size())
  {
    chunkSize = chunkPtrs[chunkIndex].second;

    // Read the HWM chunk
    rc = fileOp.setFileOffset(dbFile, chunkPtrs[chunkIndex].first, SEEK_SET);

    if (rc != NO_ERROR)
    {
      WErrorCodes ec;
      std::ostringstream oss;
      oss << "Backup error seeking in " << fileType << " file for OID-" << columnOID << "; DBRoot-" << dbRoot
          << "; partition-" << partition << "; segment-" << segment << "; " << ec.errorString(rc);
      fileOp.closeFile(dbFile);
      throw WeException(oss.str(), rc);
    }

    buffer = new unsigned char[chunkPtrs[chunkIndex].second];
    rc = fileOp.readFile(dbFile, buffer, chunkPtrs[chunkIndex].second);

    if (rc != NO_ERROR)
    {
      WErrorCodes ec;
      std::ostringstream oss;
      oss << "Backup error reading in " << fileType << " file for OID-" << columnOID << "; DBRoot-" << dbRoot
          << "; partition-" << partition << "; segment-" << segment << "; " << ec.errorString(rc);
      delete[] buffer;
      fileOp.closeFile(dbFile);
      throw WeException(oss.str(), rc);
    }
  }
  else if (startingHWM == 0)
  {
    // Okay to proceed.  Empty file with no chunks.  Save 0 length chunk.
  }
  else
  {
    rc = ERR_METADATABKUP_COMP_CHUNK_NOT_FOUND;

    WErrorCodes ec;
    std::ostringstream oss;
    oss << "Backup error for " << fileType << " file for OID-" << columnOID << "; DBRoot-" << dbRoot
        << "; partition-" << partition << "; segment-" << segment << "; hwm-" << startingHWM << "; chunkIdx-"
        << chunkIndex << "; numPtrs-" << chunkPtrs.size() << "; not in hdrPtrs"
        << "; " << ec.errorString(rc);
    fileOp.closeFile(dbFile);
    throw WeException(oss.str(), rc);
  }

  // Backup the HWM chunk
  std::string errMsg;
  rc = writeHWMChunk(bColumnFile, columnOID, dbRoot, partition, segment, buffer, chunkSize, fileSizeBytes,
                     startingHWM, errMsg);

  if (rc != NO_ERROR)
  {
    std::ostringstream oss;
    oss << "Backup error writing backup for " << fileType << " OID-" << columnOID << "; DBRoot-" << dbRoot
        << "; partition-" << partition << "; segment-" << segment << "; " << errMsg;
    delete[] buffer;
    fileOp.closeFile(dbFile);
    throw WeException(oss.str(), rc);
  }

  // Close the applicable database column segment file and free memory
  delete[] buffer;
  fileOp.closeFile(dbFile);
}

//------------------------------------------------------------------------------
// Writes out the specified HWM chunk to disk, in case we need it for bulk
// rollback.  If an error occurs, errMsg will contain the error message.
// This function is careful not to create a corrupt file (should the system
// crash in the middle of writing the file for example).  It's imperative
// that during a failure of any kind, that we not "accidentally" create and
// leave around a corrupt or incomplete HWM backup file that could cause a
// bulk rollback to fail, and eventually corrupt a data base file.
// So this function first creates the HWM backup file to a temp file, and
// after it is successfully created, it is it renamed to the final destination.
// If anything goes wrong, we try to delete any files we were creating.
//
// This function MUST be kept thread-safe in support of backupDctnryHWMChunk().
// See that function description for more details.
//------------------------------------------------------------------------------
int RBMetaWriter::writeHWMChunk(bool bColumnFile,    // is this a column (vs dictionary) file
                                OID columnOID,       // OID of column or dictionary store
                                uint16_t dbRoot,     // dbroot for db segment file
                                uint32_t partition,  // partition for db segment file
                                uint16_t segment,    // segment for db segment file
                                const unsigned char* compressedOutBuf,  // compressed chunk to be written
                                uint64_t chunkSize,                     // number of bytes in compressedOutBuf
                                uint64_t fileSize,                      // size of file in bytes
                                HWM chunkHWM,                           // HWM in the chunk being written
                                std::string& errMsg) const              // error msg if error occurs
{
  std::ostringstream ossFile;
  ossFile << "/" << columnOID << ".p" << partition << ".s" << segment;
  std::string fileName;
  std::string dirPath;
  int rc = getSubDirPath(dbRoot, fileName);

  if (rc != NO_ERROR)
  {
    std::ostringstream oss;
    oss << "Error creating backup file for OID " << columnOID << "; Can't find matching meta file for DBRoot"
        << dbRoot;
    errMsg = oss.str();
    return ERR_METADATABKUP_COMP_OPEN_BULK_BKUP;
  }

  dirPath = fileName;

  fileName += ossFile.str();

  std::string fileNameTmp = fileName;
  fileNameTmp += TMP_FILE_SUFFIX;

  // if ( (fLog) && (fLog->isDebug(DEBUG_1)) )
  if (fLog)
  {
    std::string fileType("column");

    if (!bColumnFile)
      fileType = "dictionary";

    std::ostringstream oss;
    oss << "Backing up HWM chunk for " << fileType << " OID-" << columnOID << "; file-" << fileNameTmp
        << "; HWM-" << chunkHWM << "; bytes-" << chunkSize << "; fileSize-" << fileSize;
    fLog->logMsg(oss.str(), MSGLVL_INFO2);
  }

  IDBDataFile* backupFile = IDBDataFile::open(IDBPolicy::getType(fileNameTmp.c_str(), IDBPolicy::WRITEENG),
                                              fileNameTmp.c_str(), "w+b", 0);

  if (!backupFile)
  {
    int errRc = errno;
    WErrorCodes ec;
    std::ostringstream oss;
    std::string eMsg;
    Convertor::mapErrnoToString(errRc, eMsg);
    oss << ec.errorString(ERR_METADATABKUP_COMP_OPEN_BULK_BKUP) << "; " << eMsg;
    errMsg = oss.str();
    return ERR_METADATABKUP_COMP_OPEN_BULK_BKUP;
  }

  IDBFileSystem& fs = IDBPolicy::getFs(fileNameTmp.c_str());

  // Format of backup compressed chunk file:
  //   8 byte unsigned int carrying chunk size
  //   8 byte unsigned int carrying original file size
  //   N bytes containing compressed chunk
  uint64_t sizeHdr[2];
  sizeHdr[0] = chunkSize;
  sizeHdr[1] = fileSize;
  size_t itemsWritten = backupFile->write(sizeHdr, sizeof(uint64_t) * 2) / (sizeof(uint64_t) * 2);

  if (itemsWritten != 1)
  {
    int errRc = errno;
    WErrorCodes ec;
    std::ostringstream oss;
    std::string eMsg;
    Convertor::mapErrnoToString(errRc, eMsg);
    oss << ec.errorString(ERR_METADATABKUP_COMP_WRITE_BULK_BKUP) << "; " << eMsg;
    errMsg = oss.str();

    delete backupFile;
    fs.remove(fileNameTmp.c_str());
    return ERR_METADATABKUP_COMP_WRITE_BULK_BKUP;
  }

  if (chunkSize > 0)
  {
    itemsWritten = backupFile->write(compressedOutBuf, chunkSize) / chunkSize;

    if (itemsWritten != 1)
    {
      int errRc = errno;
      WErrorCodes ec;
      std::ostringstream oss;
      std::string eMsg;
      Convertor::mapErrnoToString(errRc, eMsg);
      oss << ec.errorString(ERR_METADATABKUP_COMP_WRITE_BULK_BKUP) << "; " << eMsg;
      errMsg = oss.str();

      delete backupFile;
      fs.remove(fileNameTmp.c_str());
      return ERR_METADATABKUP_COMP_WRITE_BULK_BKUP;
    }
  }

  backupFile->flush();
  //   IDBDataFile flush() does a sync where appropriate
  delete backupFile;


  // Rename HWM backup file to final name.
  if (fs.rename(fileNameTmp.c_str(), fileName.c_str()))
  {
    int errRc = errno;
    WErrorCodes ec;
    std::ostringstream oss;
    std::string eMsg;
    Convertor::mapErrnoToString(errRc, eMsg);
    oss << ec.errorString(ERR_METADATABKUP_COMP_RENAME) << "; " << eMsg;
    errMsg = oss.str();

    fs.remove(fileNameTmp.c_str());
    fs.remove(fileName.c_str());
    return ERR_METADATABKUP_COMP_RENAME;
  }

  {
    std::ostringstream ossChown;
    idbdatafile::IDBFileSystem& fs = IDBPolicy::getFs(fileName.c_str());
    if (chownPath(ossChown, fileName, fs) || chownPath(ossChown, dirPath, fs))
    {
      throw WeException(ossChown.str(), ERR_FILE_CHOWN);
    }
  }

  return NO_ERROR;
}
//------------------------------------------------------------------------------
// Returns the directory path to be used for storing any backup data files.
//
// This function MUST be kept thread-safe in support of backupDctnryHWMChunk().
// See that function description for more details.
//------------------------------------------------------------------------------
int RBMetaWriter::getSubDirPath(uint16_t dbRoot, std::string& bulkRollbackSubPath) const
{
  std::map<uint16_t, std::string>::const_iterator iter = fMetaFileNames.find(dbRoot);

  if (iter == fMetaFileNames.end())
  {
    return ERR_INVALID_PARAM;
  }

  bulkRollbackSubPath = iter->second;
  bulkRollbackSubPath += DATA_DIR_SUFFIX;

  return NO_ERROR;
}

//------------------------------------------------------------------------------
// Prints list of compressed dictionary HWM chunks that we are tracking,
// in order to backup to disk as needed, before we start adding rows to a
// previously existing chunk.
//------------------------------------------------------------------------------
void RBMetaWriter::printDctnryChunkList(const RBChunkInfo& rbChk, const char* assocAction)
{
  if (fLog)
  {
    std::ostringstream oss;
    oss << "Dumping metaDictHWMChunks " << assocAction << rbChk << ":";

    if (fRBChunkDctnrySet.size() > 0)
    {
      RBChunkSet::iterator iter = fRBChunkDctnrySet.begin();
      int k = 1;

      while (iter != fRBChunkDctnrySet.end())
      {
        oss << std::endl;
        oss << '\t' << k << ". " << *iter;

        ++k;
        ++iter;
      }
    }
    else
    {
      oss << std::endl;
      oss << '\t' << "Empty list";
    }

    fLog->logMsg(oss.str(), MSGLVL_INFO2);
  }
}

//------------------------------------------------------------------------------
// Verify that specified string represents Version 3 file format
//------------------------------------------------------------------------------
/* static */
bool RBMetaWriter::verifyVersion3(const char* versionRec)
{
  if (strncmp(versionRec, VERSION3_REC, VERSION3_REC_LEN) == 0)
    return true;
  else
    return false;
}

//------------------------------------------------------------------------------
// Verify that specified string represents Version 4 file format
//------------------------------------------------------------------------------
/* static */
bool RBMetaWriter::verifyVersion4(const char* versionRec)
{
  if (strncmp(versionRec, VERSION4_REC, VERSION4_REC_LEN) == 0)
    return true;
  else
    return false;
}

//------------------------------------------------------------------------------
// Verify that specified record type is a Column1 record
//------------------------------------------------------------------------------
/* static */
bool RBMetaWriter::verifyColumn1Rec(const char* recType)
{
  if (strncmp(recType, COLUMN1_REC, COLUMN1_REC_LEN) == 0)
    return true;
  else
    return false;
}

//------------------------------------------------------------------------------
// Verify that specified record type is a Column2 record
//------------------------------------------------------------------------------
/* static */
bool RBMetaWriter::verifyColumn2Rec(const char* recType)
{
  if (strncmp(recType, COLUMN2_REC, COLUMN2_REC_LEN) == 0)
    return true;
  else
    return false;
}

//------------------------------------------------------------------------------
// Verify that specified record type is a DStore1 record
//------------------------------------------------------------------------------
/* static */
bool RBMetaWriter::verifyDStore1Rec(const char* recType)
{
  if (strncmp(recType, DSTORE1_REC, DSTORE1_REC_LEN) == 0)
    return true;
  else
    return false;
}

//------------------------------------------------------------------------------
// Verify that specified record type is a DStore2 record
//------------------------------------------------------------------------------
/* static */
bool RBMetaWriter::verifyDStore2Rec(const char* recType)
{
  if (strncmp(recType, DSTORE2_REC, DSTORE2_REC_LEN) == 0)
    return true;
  else
    return false;
}

}  // namespace WriteEngine
