mirror of
https://github.com/mariadb-corporation/mariadb-columnstore-engine.git
synced 2025-04-18 21:44:02 +03:00
Intro* INSERT statements could face a non-existant block when MCOL-498 feature is enabled. writeRow() guard blocks was supposed to proactively create empty blocks. The pre-patch logic failed when first value in the block has been removed by DELETE and this overwrites the whole valid block with empty magics. This patch moves proactive creation logic into allocRowId().
2905 lines
99 KiB
C++
2905 lines
99 KiB
C++
/* 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_fileop.cpp 4737 2013-08-14 20:45:46Z bwilkinson $
|
|
|
|
#include "config.h"
|
|
#include <unistd.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <sstream>
|
|
#include <iostream>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <stdexcept>
|
|
#if defined(__FreeBSD__)
|
|
#include <sys/param.h>
|
|
#include <sys/mount.h>
|
|
#elif !defined(_MSC_VER)
|
|
#include <sys/vfs.h>
|
|
#endif
|
|
#include <boost/filesystem/operations.hpp>
|
|
#include <boost/filesystem/path.hpp>
|
|
#include <boost/scoped_array.hpp>
|
|
using namespace std;
|
|
|
|
#include "we_fileop.h"
|
|
#include "we_convertor.h"
|
|
#include "we_log.h"
|
|
#include "we_config.h"
|
|
#include "we_stats.h"
|
|
#include "we_simplesyslog.h"
|
|
|
|
#include "idbcompress.h"
|
|
using namespace compress;
|
|
|
|
#include "messagelog.h"
|
|
using namespace logging;
|
|
|
|
#include "IDBDataFile.h"
|
|
#include "IDBFileSystem.h"
|
|
#include "IDBPolicy.h"
|
|
using namespace idbdatafile;
|
|
|
|
namespace WriteEngine
|
|
{
|
|
|
|
/*static*/ boost::mutex FileOp::m_createDbRootMutexes;
|
|
/*static*/ boost::mutex FileOp::m_mkdirMutex;
|
|
/*static*/ std::map<int, boost::mutex*> FileOp::m_DbRootAddExtentMutexes;
|
|
// in 1 call to fwrite(), during initialization
|
|
|
|
//StopWatch timer;
|
|
|
|
/**
|
|
* Constructor
|
|
*/
|
|
FileOp::FileOp(bool doAlloc) : m_compressionType(0),
|
|
m_transId((TxnID)INVALID_NUM), m_buffer(0)
|
|
{
|
|
if (doAlloc)
|
|
{
|
|
m_buffer = new char[DEFAULT_BUFSIZ];
|
|
memset(m_buffer, '\0', DEFAULT_BUFSIZ);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Default Destructor
|
|
*/
|
|
FileOp::~FileOp()
|
|
{
|
|
if (m_buffer)
|
|
{
|
|
delete [] m_buffer;
|
|
}
|
|
|
|
m_buffer = 0;
|
|
}
|
|
|
|
/***********************************************************
|
|
* DESCRIPTION:
|
|
* Close a file
|
|
* PARAMETERS:
|
|
* pFile - file handle
|
|
* RETURN:
|
|
* none
|
|
***********************************************************/
|
|
void FileOp::closeFile( IDBDataFile* pFile ) const
|
|
{
|
|
delete pFile;
|
|
}
|
|
|
|
/***********************************************************
|
|
* DESCRIPTION:
|
|
* Create directory
|
|
* Function uses mutex lock to prevent thread contention trying to create
|
|
* 2 subdirectories in the same directory at the same time.
|
|
* PARAMETERS:
|
|
* dirName - directory name
|
|
* mode - create mode
|
|
* RETURN:
|
|
* NO_ERROR if success, otherwise if fail
|
|
***********************************************************/
|
|
int FileOp::createDir( const char* dirName, mode_t mode ) const
|
|
{
|
|
boost::mutex::scoped_lock lk(m_mkdirMutex);
|
|
int rc = IDBPolicy::mkdir( dirName );
|
|
|
|
if ( rc != 0 )
|
|
{
|
|
int errRc = errno;
|
|
|
|
if (errRc == EEXIST)
|
|
return NO_ERROR; // ignore "File exists" error
|
|
|
|
if ( getLogger() )
|
|
{
|
|
std::ostringstream oss;
|
|
std::string errnoMsg;
|
|
Convertor::mapErrnoToString(errRc, errnoMsg);
|
|
oss << "Error creating directory " << dirName << "; err-" <<
|
|
errRc << "; " << errnoMsg;
|
|
getLogger()->logMsg( oss.str(), ERR_DIR_CREATE, MSGLVL_ERROR );
|
|
}
|
|
|
|
return ERR_DIR_CREATE;
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
/***********************************************************
|
|
* DESCRIPTION:
|
|
* Create the "first" segment file for a column with a fixed file size
|
|
* Note: the file is created in binary mode
|
|
* PARAMETERS:
|
|
* fileName - file name with complete path
|
|
* numOfBlock - the total number of blocks to be initialized (written out)
|
|
* compressionType - Compression Type
|
|
* emptyVal - empty value used to initialize column values
|
|
* width - width of column in bytes
|
|
* dbRoot - DBRoot of column file we are creating
|
|
* RETURN:
|
|
* NO_ERROR if success
|
|
* ERR_FILE_EXIST if file exists
|
|
* ERR_FILE_CREATE if can not create the file
|
|
***********************************************************/
|
|
int FileOp::createFile( const char* fileName, int numOfBlock,
|
|
uint64_t emptyVal, int width,
|
|
uint16_t dbRoot )
|
|
{
|
|
IDBDataFile* pFile =
|
|
IDBDataFile::open(
|
|
IDBPolicy::getType( fileName, IDBPolicy::WRITEENG ),
|
|
fileName,
|
|
"w+b",
|
|
IDBDataFile::USE_VBUF,
|
|
width);
|
|
int rc = 0;
|
|
|
|
if ( pFile != NULL )
|
|
{
|
|
|
|
// Initialize the contents of the extent.
|
|
if (m_compressionType)
|
|
{
|
|
rc = initAbbrevCompColumnExtent( pFile,
|
|
dbRoot,
|
|
numOfBlock,
|
|
emptyVal,
|
|
width );
|
|
}
|
|
else
|
|
{
|
|
rc = initColumnExtent( pFile,
|
|
dbRoot,
|
|
numOfBlock,
|
|
emptyVal,
|
|
width,
|
|
true, // new file
|
|
false, // don't expand; add new extent
|
|
true ); // add abbreviated extent
|
|
}
|
|
|
|
closeFile( pFile );
|
|
}
|
|
else
|
|
return ERR_FILE_CREATE;
|
|
|
|
return rc;
|
|
}
|
|
|
|
/***********************************************************
|
|
* DESCRIPTION:
|
|
* Create the "first" segment file for a column with a fixed file size
|
|
* Note: the file is created in binary mode
|
|
* PARAMETERS:
|
|
* fid - OID of the column file to be created
|
|
* allocSize (out) - number of blocks allocated to the first extent
|
|
* dbRoot - DBRoot where file is to be located
|
|
* partition- Starting partition number for segment file path
|
|
* compressionType - Compression type
|
|
* colDataType - the column data type
|
|
* emptyVal - designated "empty" value for this OID
|
|
* width - width of column in bytes
|
|
* RETURN:
|
|
* NO_ERROR if success
|
|
* ERR_FILE_EXIST if file exists
|
|
* ERR_FILE_CREATE if can not create the file
|
|
***********************************************************/
|
|
int FileOp::createFile(FID fid,
|
|
int& allocSize,
|
|
uint16_t dbRoot,
|
|
uint32_t partition,
|
|
execplan::CalpontSystemCatalog::ColDataType colDataType,
|
|
uint64_t emptyVal,
|
|
int width)
|
|
{
|
|
//std::cout << "Creating file oid: " << fid <<
|
|
// "; compress: " << m_compressionType << std::endl;
|
|
char fileName[FILE_NAME_SIZE];
|
|
int rc;
|
|
|
|
uint16_t segment = 0; // should always be 0 when starting a new column
|
|
RETURN_ON_ERROR( ( rc = oid2FileName( fid, fileName, true,
|
|
dbRoot, partition, segment ) ) );
|
|
|
|
//@Bug 3196
|
|
if ( exists( fileName ) )
|
|
return ERR_FILE_EXIST;
|
|
|
|
// allocatColExtent() treats dbRoot and partition as in/out
|
|
// arguments, so we need to pass in a non-const variable.
|
|
uint16_t dbRootx = dbRoot;
|
|
uint32_t partitionx = partition;
|
|
|
|
// Since we are creating a new column OID, we know partition
|
|
// and segment are 0, so we ignore their output values.
|
|
//timer.start( "allocateColExtent" );
|
|
|
|
BRM::LBID_t startLbid;
|
|
uint32_t startBlock;
|
|
RETURN_ON_ERROR( BRMWrapper::getInstance()->allocateColExtentExactFile(
|
|
(const OID)fid, (uint32_t)width, dbRootx, partitionx, segment, colDataType,
|
|
startLbid, allocSize, startBlock) );
|
|
|
|
// We allocate a full extent from BRM, but only write an abbreviated 256K
|
|
// rows to disk for 1st extent, to conserve disk usage for small tables.
|
|
// One exception here is if we have rolled off partition 0, and we are
|
|
// adding a column to an existing table, then we are adding a column
|
|
// whose first partition is not 0. In this case, we know we are not
|
|
// dealing with a small table, so we init a full extent for 1st extent.
|
|
int totalSize = 0;
|
|
|
|
if (partition == 0)
|
|
totalSize = (INITIAL_EXTENT_ROWS_TO_DISK / BYTE_PER_BLOCK) * width;
|
|
else
|
|
totalSize = allocSize; // full extent if starting partition > 0
|
|
|
|
// Note we can't pass full file name to isDiskSpaceAvail() because the
|
|
// file does not exist yet, but passing DBRoot directory should suffice.
|
|
if ( !isDiskSpaceAvail(Config::getDBRootByNum(dbRoot), totalSize) )
|
|
{
|
|
return ERR_FILE_DISK_SPACE;
|
|
}
|
|
|
|
//timer.stop( "allocateColExtent" );
|
|
|
|
return createFile( fileName, totalSize, emptyVal, width, dbRoot );
|
|
}
|
|
|
|
/***********************************************************
|
|
* DESCRIPTION:
|
|
* Delete a file
|
|
* PARAMETERS:
|
|
* fileName - file name with complete path
|
|
* RETURN:
|
|
* NO_ERROR if success
|
|
* ERR_FILE_NOT_EXIST if file does not exist
|
|
* ERR_FILE_DELETE if can not delete a file
|
|
***********************************************************/
|
|
int FileOp::deleteFile( const char* fileName ) const
|
|
{
|
|
if ( !exists( fileName ) )
|
|
return ERR_FILE_NOT_EXIST;
|
|
|
|
return ( IDBPolicy::remove( fileName ) == -1 ) ? ERR_FILE_DELETE : NO_ERROR;
|
|
}
|
|
|
|
/***********************************************************
|
|
* DESCRIPTION:
|
|
* Deletes all the segment or dictionary store files associated with the
|
|
* specified fid.
|
|
* PARAMETERS:
|
|
* fid - OID of the column being deleted.
|
|
* RETURN:
|
|
* NO_ERROR if success
|
|
* ERR_DM_CONVERT_OID if error occurs converting OID to file name
|
|
***********************************************************/
|
|
int FileOp::deleteFile( FID fid ) const
|
|
{
|
|
char tempFileName[FILE_NAME_SIZE];
|
|
char oidDirName [FILE_NAME_SIZE];
|
|
char dbDir [MAX_DB_DIR_LEVEL][MAX_DB_DIR_NAME_SIZE];
|
|
|
|
RETURN_ON_ERROR((Convertor::oid2FileName(
|
|
fid, tempFileName, dbDir, 0, 0)));
|
|
sprintf(oidDirName, "%s/%s/%s/%s",
|
|
dbDir[0], dbDir[1], dbDir[2], dbDir[3]);
|
|
//std::cout << "Deleting files for OID " << fid <<
|
|
// "; dirpath: " << oidDirName << std::endl;
|
|
//need check return code.
|
|
RETURN_ON_ERROR(BRMWrapper::getInstance()->deleteOid(fid));
|
|
|
|
std::vector<std::string> dbRootPathList;
|
|
Config::getDBRootPathList( dbRootPathList );
|
|
|
|
int rc;
|
|
|
|
for (unsigned i = 0; i < dbRootPathList.size(); i++)
|
|
{
|
|
char rootOidDirName[FILE_NAME_SIZE];
|
|
rc = snprintf(rootOidDirName, FILE_NAME_SIZE, "%s/%s",
|
|
dbRootPathList[i].c_str(), oidDirName);
|
|
|
|
if ( rc == FILE_NAME_SIZE || IDBPolicy::remove( rootOidDirName ) != 0 )
|
|
{
|
|
ostringstream oss;
|
|
oss << "Unable to remove " << rootOidDirName;
|
|
throw std::runtime_error( oss.str() );
|
|
}
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
/***********************************************************
|
|
* DESCRIPTION:
|
|
* Deletes all the segment or dictionary store files associated with the
|
|
* specified fid.
|
|
* PARAMETERS:
|
|
* fid - OIDs of the column/dictionary being deleted.
|
|
* RETURN:
|
|
* NO_ERROR if success
|
|
* ERR_DM_CONVERT_OID if error occurs converting OID to file name
|
|
***********************************************************/
|
|
int FileOp::deleteFiles( const std::vector<int32_t>& fids ) const
|
|
{
|
|
char tempFileName[FILE_NAME_SIZE];
|
|
char oidDirName [FILE_NAME_SIZE];
|
|
char dbDir [MAX_DB_DIR_LEVEL][MAX_DB_DIR_NAME_SIZE];
|
|
std::vector<std::string> dbRootPathList;
|
|
Config::getDBRootPathList( dbRootPathList );
|
|
int rc;
|
|
|
|
for ( unsigned n = 0; n < fids.size(); n++ )
|
|
{
|
|
RETURN_ON_ERROR((Convertor::oid2FileName(
|
|
fids[n], tempFileName, dbDir, 0, 0)));
|
|
sprintf(oidDirName, "%s/%s/%s/%s",
|
|
dbDir[0], dbDir[1], dbDir[2], dbDir[3]);
|
|
//std::cout << "Deleting files for OID " << fid <<
|
|
// "; dirpath: " << oidDirName << std::endl;
|
|
|
|
for (unsigned i = 0; i < dbRootPathList.size(); i++)
|
|
{
|
|
char rootOidDirName[FILE_NAME_SIZE];
|
|
rc = snprintf(rootOidDirName, FILE_NAME_SIZE, "%s/%s", dbRootPathList[i].c_str(),
|
|
oidDirName);
|
|
|
|
if ( rc == FILE_NAME_SIZE || IDBPolicy::remove( rootOidDirName ) != 0 )
|
|
{
|
|
ostringstream oss;
|
|
oss << "Unable to remove " << rootOidDirName;
|
|
throw std::runtime_error( oss.str() );
|
|
}
|
|
}
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
/***********************************************************
|
|
* DESCRIPTION:
|
|
* Deletes all the segment or dictionary store files associated with the
|
|
* specified fid and partition.
|
|
* PARAMETERS:
|
|
* fids - OIDs of the column/dictionary being deleted.
|
|
* partition - the partition number
|
|
* RETURN:
|
|
* NO_ERROR if success
|
|
* ERR_DM_CONVERT_OID if error occurs converting OID to file name
|
|
***********************************************************/
|
|
int FileOp::deletePartitions( const std::vector<OID>& fids,
|
|
const std::vector<BRM::PartitionInfo>& partitions ) const
|
|
{
|
|
char tempFileName[FILE_NAME_SIZE];
|
|
char oidDirName [FILE_NAME_SIZE];
|
|
char dbDir [MAX_DB_DIR_LEVEL][MAX_DB_DIR_NAME_SIZE];
|
|
char rootOidDirName[FILE_NAME_SIZE];
|
|
char partitionDirName[FILE_NAME_SIZE];
|
|
int rcd, rcp;
|
|
|
|
for (uint32_t i = 0; i < partitions.size(); i++)
|
|
{
|
|
RETURN_ON_ERROR((Convertor::oid2FileName(
|
|
partitions[i].oid, tempFileName, dbDir,
|
|
partitions[i].lp.pp, partitions[i].lp.seg)));
|
|
sprintf(oidDirName, "%s/%s/%s/%s/%s",
|
|
dbDir[0], dbDir[1], dbDir[2], dbDir[3], dbDir[4]);
|
|
// config expects dbroot starting from 0
|
|
std::string rt( Config::getDBRootByNum(partitions[i].lp.dbroot) );
|
|
rcd = snprintf(rootOidDirName, FILE_NAME_SIZE, "%s/%s",
|
|
rt.c_str(), tempFileName);
|
|
rcp = snprintf(partitionDirName, FILE_NAME_SIZE, "%s/%s",
|
|
rt.c_str(), oidDirName);
|
|
|
|
if ( rcd == FILE_NAME_SIZE || rcp == FILE_NAME_SIZE
|
|
|| IDBPolicy::remove( rootOidDirName ) != 0 )
|
|
{
|
|
ostringstream oss;
|
|
oss << "Unable to remove " << rootOidDirName;
|
|
throw std::runtime_error( oss.str() );
|
|
}
|
|
|
|
list<string> dircontents;
|
|
|
|
if ( IDBPolicy::listDirectory( partitionDirName, dircontents ) == 0 )
|
|
{
|
|
// the directory exists, now check if empty
|
|
if ( dircontents.size() == 0 )
|
|
{
|
|
// empty directory
|
|
if ( IDBPolicy::remove( partitionDirName ) != 0 )
|
|
{
|
|
ostringstream oss;
|
|
oss << "Unable to remove " << rootOidDirName;
|
|
throw std::runtime_error( oss.str() );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
/***********************************************************
|
|
* DESCRIPTION:
|
|
* Deletes the specified db segment file.
|
|
* PARAMETERS:
|
|
* fid - column OID of file to be deleted.
|
|
* dbRoot - DBRoot associated with segment file
|
|
* partition - partition number of associated segment file
|
|
* segment - segment number of associated segment file
|
|
* RETURN:
|
|
* NO_ERROR if success
|
|
***********************************************************/
|
|
int FileOp::deleteFile( FID fid, uint16_t dbRoot,
|
|
uint32_t partition, uint16_t segment ) const
|
|
{
|
|
char fileName[FILE_NAME_SIZE];
|
|
|
|
RETURN_ON_ERROR( getFileName( fid, fileName,
|
|
dbRoot, partition, segment) );
|
|
|
|
return ( deleteFile( fileName ) );
|
|
}
|
|
|
|
/***********************************************************
|
|
* DESCRIPTION:
|
|
* Check whether a file exists or not
|
|
* PARAMETERS:
|
|
* fileName - file name with complete path
|
|
* RETURN:
|
|
* true if exists, false otherwise
|
|
***********************************************************/
|
|
bool FileOp::exists( const char* fileName ) const
|
|
{
|
|
return IDBPolicy::exists( fileName );
|
|
}
|
|
|
|
/***********************************************************
|
|
* DESCRIPTION:
|
|
* Check whether a file exists or not
|
|
* PARAMETERS:
|
|
* fid - OID of file to be checked
|
|
* dbRoot - DBRoot associated with segment file
|
|
* partition - partition number of associated segment file
|
|
* segment - segment number of associated segment file
|
|
* RETURN:
|
|
* true if exists, false otherwise
|
|
***********************************************************/
|
|
bool FileOp::exists( FID fid, uint16_t dbRoot,
|
|
uint32_t partition, uint16_t segment ) const
|
|
{
|
|
char fileName[FILE_NAME_SIZE];
|
|
|
|
if (getFileName(fid, fileName, dbRoot, partition,
|
|
segment) != NO_ERROR)
|
|
return false;
|
|
|
|
return exists( fileName );
|
|
}
|
|
|
|
/***********************************************************
|
|
* DESCRIPTION:
|
|
* Check whether an OID directory exists or not
|
|
* PARAMETERS:
|
|
* fid - column or dictionary store OID
|
|
* RETURN:
|
|
* true if exists, false otherwise
|
|
***********************************************************/
|
|
bool FileOp::existsOIDDir( FID fid ) const
|
|
{
|
|
char fileName[FILE_NAME_SIZE];
|
|
|
|
if (oid2DirName( fid, fileName ) != NO_ERROR)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return exists( fileName );
|
|
}
|
|
|
|
/***********************************************************
|
|
* DESCRIPTION:
|
|
* Adds an extent to the specified column OID and DBRoot.
|
|
* Function uses ExtentMap to add the extent and determine which
|
|
* specific column segment file the extent is to be added to. If
|
|
* the applicable column segment file does not exist, it is created.
|
|
* If this is the very first file for the specified DBRoot, then the
|
|
* partition and segment number must be specified, else the selected
|
|
* partition and segment numbers are returned. This method tries to
|
|
* optimize full extents creation skiping disk space
|
|
* preallocation(if activated).
|
|
* PARAMETERS:
|
|
* oid - OID of the column to be extended
|
|
* emptyVal - Empty value to be used for oid
|
|
* width - Width of the column (in bytes)
|
|
* hwm - The HWM (or fbo) of the column segment file where the new
|
|
* extent begins.
|
|
* startLbid - The starting LBID for the new extent
|
|
* allocSize - Number of blocks allocated to the extent.
|
|
* dbRoot - The DBRoot of the file with the new extent.
|
|
* partition - The partition number of the file with the new extent.
|
|
* segment - The segment number of the file with the new extent.
|
|
* segFile - The name of the relevant column segment file.
|
|
* pFile - IDBDataFile ptr to the file where the extent is added.
|
|
* newFile - Indicates if the extent was added to a new or existing file
|
|
* hdrs - Contents of the headers if file is compressed.
|
|
* RETURN:
|
|
* NO_ERROR if success
|
|
* else the applicable error code is returned
|
|
***********************************************************/
|
|
int FileOp::extendFile(
|
|
OID oid,
|
|
uint64_t emptyVal,
|
|
int width,
|
|
HWM hwm,
|
|
BRM::LBID_t startLbid,
|
|
int allocSize,
|
|
uint16_t dbRoot,
|
|
uint32_t partition,
|
|
uint16_t segment,
|
|
std::string& segFile,
|
|
IDBDataFile*& pFile,
|
|
bool& newFile,
|
|
char* hdrs)
|
|
{
|
|
int rc = NO_ERROR;
|
|
pFile = 0;
|
|
segFile.clear();
|
|
newFile = false;
|
|
char fileName[FILE_NAME_SIZE];
|
|
|
|
// If starting hwm or fbo is 0 then this is the first extent of a new file,
|
|
// else we are adding an extent to an existing segment file
|
|
if (hwm > 0) // db segment file should exist
|
|
{
|
|
RETURN_ON_ERROR( oid2FileName(oid, fileName, false,
|
|
dbRoot, partition, segment) );
|
|
segFile = fileName;
|
|
|
|
if (!exists(fileName))
|
|
{
|
|
ostringstream oss;
|
|
oss << "oid: " << oid << " with path " << segFile;
|
|
logging::Message::Args args;
|
|
args.add("File not found ");
|
|
args.add(oss.str());
|
|
args.add("");
|
|
args.add("");
|
|
SimpleSysLog::instance()->logMsg(args,
|
|
logging::LOG_TYPE_ERROR,
|
|
logging::M0001);
|
|
return ERR_FILE_NOT_EXIST;
|
|
}
|
|
|
|
pFile = openFile( oid, dbRoot, partition, segment, segFile, "r+b" );//old file
|
|
|
|
if (pFile == 0)
|
|
{
|
|
ostringstream oss;
|
|
oss << "oid: " << oid << " with path " << segFile;
|
|
logging::Message::Args args;
|
|
args.add("Error opening file ");
|
|
args.add(oss.str());
|
|
args.add("");
|
|
args.add("");
|
|
SimpleSysLog::instance()->logMsg(args,
|
|
logging::LOG_TYPE_ERROR,
|
|
logging::M0001);
|
|
return ERR_FILE_OPEN;
|
|
}
|
|
|
|
if ( isDebug(DEBUG_1) && getLogger() )
|
|
{
|
|
std::ostringstream oss;
|
|
oss << "Opening existing column file (extendFile)" <<
|
|
": OID-" << oid <<
|
|
"; DBRoot-" << dbRoot <<
|
|
"; part-" << partition <<
|
|
"; seg-" << segment <<
|
|
"; LBID-" << startLbid <<
|
|
"; hwm-" << hwm <<
|
|
"; file-" << segFile;
|
|
getLogger()->logMsg( oss.str(), MSGLVL_INFO2 );
|
|
}
|
|
|
|
// @bug 5349: check that new extent's fbo is not past current EOF
|
|
if (m_compressionType)
|
|
{
|
|
char hdrsIn[ compress::IDBCompressInterface::HDR_BUF_LEN * 2 ];
|
|
RETURN_ON_ERROR( readHeaders(pFile, hdrsIn) );
|
|
|
|
IDBCompressInterface compressor;
|
|
unsigned int ptrCount = compressor.getPtrCount(hdrsIn);
|
|
unsigned int chunkIndex = 0;
|
|
unsigned int blockOffsetWithinChunk = 0;
|
|
compressor.locateBlock((hwm - 1), chunkIndex, blockOffsetWithinChunk);
|
|
|
|
//std::ostringstream oss1;
|
|
//oss1 << "Extending compressed column file"<<
|
|
// ": OID-" << oid <<
|
|
// "; LBID-" << startLbid <<
|
|
// "; fbo-" << hwm <<
|
|
// "; file-" << segFile <<
|
|
// "; chkidx-" << chunkIndex<<
|
|
// "; numPtrs-"<< ptrCount;
|
|
//getLogger()->logMsg( oss1.str(), MSGLVL_INFO2 );
|
|
|
|
if (chunkIndex >= ptrCount)
|
|
{
|
|
ostringstream oss;
|
|
oss << "oid: " << oid << " with path " << segFile <<
|
|
"; new extent fbo " << hwm << "; number of "
|
|
"compressed chunks " << ptrCount <<
|
|
"; chunkIndex " << chunkIndex;
|
|
logging::Message::Args args;
|
|
args.add("compressed");
|
|
args.add(oss.str());
|
|
SimpleSysLog::instance()->logMsg(args,
|
|
logging::LOG_TYPE_ERROR,
|
|
logging::M0103);
|
|
|
|
// Expand the partial extent to full with emptyVal
|
|
// Since fillCompColumnExtentEmptyChunks() messes with the
|
|
// file on disk, we need to close it and reopen after or
|
|
// the cache isn't updated.
|
|
if ((pFile))
|
|
closeFile( pFile );
|
|
|
|
pFile = NULL;
|
|
string failedTask; // For return error message, if any.
|
|
rc = FileOp::fillCompColumnExtentEmptyChunks(oid,
|
|
width,
|
|
emptyVal,
|
|
dbRoot,
|
|
partition,
|
|
segment,
|
|
hwm,
|
|
segFile,
|
|
failedTask);
|
|
|
|
if (rc != NO_ERROR)
|
|
{
|
|
if (getLogger())
|
|
{
|
|
std::ostringstream oss;
|
|
oss << "FileOp::extendFile: error padding partial compressed extent for " <<
|
|
"column OID-" << oid <<
|
|
"; DBRoot-" << dbRoot <<
|
|
"; part-" << partition <<
|
|
"; seg-" << segment <<
|
|
"; hwm-" << hwm <<
|
|
" " << failedTask.c_str();
|
|
getLogger()->logMsg( oss.str(), rc, MSGLVL_CRITICAL );
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
pFile = openFile( oid, dbRoot, partition, segment, segFile, "r+b" ); // modified file
|
|
}
|
|
|
|
// Get the latest file header for the caller. If a partial extent was filled out,
|
|
// this will be different than when we first read the headers.
|
|
if (hdrs)
|
|
{
|
|
RETURN_ON_ERROR( readHeaders(pFile, hdrs) );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
long long fileSize;
|
|
RETURN_ON_ERROR( getFileSize(pFile, fileSize) );
|
|
long long calculatedFileSize = ((long long)hwm) * BYTE_PER_BLOCK;
|
|
|
|
//std::ostringstream oss2;
|
|
//oss2 << "Extending uncompressed column file"<<
|
|
// ": OID-" << oid <<
|
|
// "; LBID-" << startLbid <<
|
|
// "; fbo-" << hwm <<
|
|
// "; file-" << segFile <<
|
|
// "; filesize-"<<fileSize;
|
|
//getLogger()->logMsg( oss2.str(), MSGLVL_INFO2 );
|
|
|
|
if (calculatedFileSize > fileSize)
|
|
{
|
|
ostringstream oss;
|
|
oss << "oid: " << oid << " with path " << segFile <<
|
|
"; new extent fbo " << hwm << "; file size (bytes) " <<
|
|
fileSize;
|
|
logging::Message::Args args;
|
|
args.add("uncompressed");
|
|
args.add(oss.str());
|
|
SimpleSysLog::instance()->logMsg(args,
|
|
logging::LOG_TYPE_ERROR,
|
|
logging::M0103);
|
|
// Expand the partial extent to full with emptyVal
|
|
// This generally won't ever happen, as uncompressed files
|
|
// are created with full extents.
|
|
rc = FileOp::expandAbbrevColumnExtent( pFile, dbRoot,
|
|
emptyVal, width);
|
|
|
|
if (rc != NO_ERROR)
|
|
{
|
|
if (getLogger())
|
|
{
|
|
std::ostringstream oss;
|
|
oss << "FileOp::extendFile: error padding partial uncompressed extent for " <<
|
|
"column OID-" << oid <<
|
|
"; DBRoot-" << dbRoot <<
|
|
"; part-" << partition <<
|
|
"; seg-" << segment <<
|
|
"; hwm-" << hwm;
|
|
getLogger()->logMsg( oss.str(), rc, MSGLVL_CRITICAL );
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else // db segment file should not exist
|
|
{
|
|
RETURN_ON_ERROR( oid2FileName(oid, fileName, true,
|
|
dbRoot, partition, segment) );
|
|
segFile = fileName;
|
|
|
|
// if obsolete file exists, "w+b" will truncate and write over
|
|
pFile = openFile( fileName, "w+b" );//new file
|
|
|
|
if (pFile == 0)
|
|
return ERR_FILE_CREATE;
|
|
|
|
newFile = true;
|
|
|
|
if ( isDebug(DEBUG_1) && getLogger() )
|
|
{
|
|
std::ostringstream oss;
|
|
oss << "Opening new column file" <<
|
|
": OID-" << oid <<
|
|
"; DBRoot-" << dbRoot <<
|
|
"; part-" << partition <<
|
|
"; seg-" << segment <<
|
|
"; LBID-" << startLbid <<
|
|
"; hwm-" << hwm <<
|
|
"; file-" << segFile;
|
|
getLogger()->logMsg( oss.str(), MSGLVL_INFO2 );
|
|
}
|
|
|
|
if ((m_compressionType) && (hdrs))
|
|
{
|
|
IDBCompressInterface compressor;
|
|
compressor.initHdr(hdrs, m_compressionType);
|
|
}
|
|
}
|
|
|
|
#ifdef _MSC_VER
|
|
|
|
//Need to call the win version with a dir, not a file
|
|
if (!isDiskSpaceAvail(Config::getDBRootByNum(dbRoot), allocSize))
|
|
#else
|
|
if ( !isDiskSpaceAvail(segFile, allocSize) )
|
|
#endif
|
|
{
|
|
return ERR_FILE_DISK_SPACE;
|
|
}
|
|
|
|
// We set to EOF just before we start adding the blocks for the new extent.
|
|
// At one time, I considered changing this to seek to the HWM block, but
|
|
// with compressed files, this is murky; do I find and seek to the chunk
|
|
// containing the HWM block? So I left as-is for now, seeking to EOF.
|
|
rc = setFileOffset(pFile, 0, SEEK_END);
|
|
|
|
if (rc != NO_ERROR)
|
|
return rc;
|
|
|
|
// Initialize the contents of the extent.
|
|
// MCOL-498 optimize full extent creation.
|
|
rc = initColumnExtent( pFile,
|
|
dbRoot,
|
|
allocSize,
|
|
emptyVal,
|
|
width,
|
|
newFile, // new or existing file
|
|
false, // don't expand; new extent
|
|
false, // add full (not abbreviated) extent
|
|
true); // try to optimize extent creation
|
|
|
|
return rc;
|
|
}
|
|
|
|
/***********************************************************
|
|
* DESCRIPTION:
|
|
* Add an extent to the exact segment file specified by
|
|
* the designated OID, DBRoot, partition, and segment.
|
|
* PARAMETERS:
|
|
* oid - OID of the column to be extended
|
|
* emptyVal - Empty value to be used for oid
|
|
* width - Width of the column (in bytes)
|
|
* allocSize - Number of blocks allocated to the extent.
|
|
* dbRoot - The DBRoot of the file with the new extent.
|
|
* partition - The partition number of the file with the new extent.
|
|
* segment - The segment number of the file with the new extent.
|
|
* segFile - The name of the relevant column segment file.
|
|
* startLbid - The starting LBID for the new extent
|
|
* newFile - Indicates if the extent was added to a new or existing file
|
|
* hdrs - Contents of the headers if file is compressed.
|
|
* RETURN:
|
|
* none
|
|
***********************************************************/
|
|
int FileOp::addExtentExactFile(
|
|
OID oid,
|
|
uint64_t emptyVal,
|
|
int width,
|
|
int& allocSize,
|
|
uint16_t dbRoot,
|
|
uint32_t partition,
|
|
uint16_t segment,
|
|
execplan::CalpontSystemCatalog::ColDataType colDataType,
|
|
std::string& segFile,
|
|
BRM::LBID_t& startLbid,
|
|
bool& newFile,
|
|
char* hdrs)
|
|
{
|
|
int rc = NO_ERROR;
|
|
IDBDataFile* pFile = 0;
|
|
segFile.clear();
|
|
newFile = false;
|
|
HWM hwm;
|
|
|
|
// Allocate the new extent in the ExtentMap
|
|
RETURN_ON_ERROR( BRMWrapper::getInstance()->allocateColExtentExactFile(
|
|
oid, width, dbRoot, partition, segment, colDataType, startLbid, allocSize, hwm));
|
|
|
|
// Determine the existence of the "next" segment file, and either open
|
|
// or create the segment file accordingly.
|
|
if (exists(oid, dbRoot, partition, segment))
|
|
{
|
|
pFile = openFile( oid, dbRoot, partition, segment,
|
|
segFile, "r+b" );//old file
|
|
|
|
if (pFile == 0)
|
|
{
|
|
ostringstream oss;
|
|
oss << "oid: " << oid << " with path " << segFile;
|
|
logging::Message::Args args;
|
|
args.add("Error opening file ");
|
|
args.add(oss.str());
|
|
args.add("");
|
|
args.add("");
|
|
SimpleSysLog::instance()->logMsg(args,
|
|
logging::LOG_TYPE_ERROR,
|
|
logging::M0001);
|
|
return ERR_FILE_OPEN;
|
|
}
|
|
|
|
if ( isDebug(DEBUG_1) && getLogger() )
|
|
{
|
|
std::ostringstream oss;
|
|
oss << "Opening existing column file" <<
|
|
": OID-" << oid <<
|
|
"; DBRoot-" << dbRoot <<
|
|
"; part-" << partition <<
|
|
"; seg-" << segment <<
|
|
"; LBID-" << startLbid <<
|
|
"; hwm-" << hwm <<
|
|
"; file-" << segFile;
|
|
getLogger()->logMsg( oss.str(), MSGLVL_INFO2 );
|
|
}
|
|
|
|
if ((m_compressionType) && (hdrs))
|
|
{
|
|
rc = readHeaders(pFile, hdrs);
|
|
|
|
if (rc != NO_ERROR)
|
|
return rc;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
char fileName[FILE_NAME_SIZE];
|
|
RETURN_ON_ERROR( oid2FileName(oid, fileName, true,
|
|
dbRoot, partition, segment) );
|
|
segFile = fileName;
|
|
|
|
pFile = openFile( fileName, "w+b" );//new file
|
|
|
|
if (pFile == 0)
|
|
return ERR_FILE_CREATE;
|
|
|
|
newFile = true;
|
|
|
|
if ( isDebug(DEBUG_1) && getLogger() )
|
|
{
|
|
std::ostringstream oss;
|
|
oss << "Opening new column file" <<
|
|
": OID-" << oid <<
|
|
"; DBRoot-" << dbRoot <<
|
|
"; part-" << partition <<
|
|
"; seg-" << segment <<
|
|
"; LBID-" << startLbid <<
|
|
"; hwm-" << hwm <<
|
|
"; file-" << segFile;
|
|
getLogger()->logMsg( oss.str(), MSGLVL_INFO2 );
|
|
}
|
|
|
|
if ((m_compressionType) && (hdrs))
|
|
{
|
|
IDBCompressInterface compressor;
|
|
compressor.initHdr(hdrs, m_compressionType);
|
|
}
|
|
}
|
|
|
|
#ifdef _MSC_VER
|
|
|
|
//Need to call the win version with a dir, not a file
|
|
if (!isDiskSpaceAvail(Config::getDBRootByNum(dbRoot), allocSize))
|
|
#else
|
|
if ( !isDiskSpaceAvail(segFile, allocSize) )
|
|
#endif
|
|
{
|
|
return ERR_FILE_DISK_SPACE;
|
|
}
|
|
|
|
// We set to EOF just before we start adding the blocks for the new extent.
|
|
// At one time, I considered changing this to seek to the HWM block, but
|
|
// with compressed files, this is murky; do I find and seek to the chunk
|
|
// containing the HWM block? So I left as-is for now, seeking to EOF.
|
|
rc = setFileOffset(pFile, 0, SEEK_END);
|
|
|
|
if (rc != NO_ERROR)
|
|
return rc;
|
|
|
|
// Initialize the contents of the extent.
|
|
// CS doesn't optimize file operations to have a valid
|
|
// segment files with empty magics
|
|
rc = initColumnExtent( pFile,
|
|
dbRoot,
|
|
allocSize,
|
|
emptyVal,
|
|
width,
|
|
newFile, // new or existing file
|
|
false, // don't expand; new extent
|
|
false ); // add full (not abbreviated) extent
|
|
|
|
closeFile( pFile );
|
|
return rc;
|
|
}
|
|
|
|
/***********************************************************
|
|
* DESCRIPTION:
|
|
* Write out (initialize) an extent in a column file.
|
|
* A mutex is used for each DBRoot, to prevent contention between
|
|
* threads, because if multiple threads are creating extents on
|
|
* the same DBRoot at the same time, the extents can become
|
|
* fragmented. It is best to only create one extent at a time
|
|
* on each DBRoot.
|
|
* This function can be used to initialize an entirely new extent, or
|
|
* to finish initializing an extent that has already been started.
|
|
* nBlocks controls how many 8192-byte blocks are to be written out.
|
|
* If bOptExtension is set then method first checks config for
|
|
* DBRootX.Prealloc. If it is disabled then it skips disk space
|
|
* preallocation.
|
|
* PARAMETERS:
|
|
* pFile (in) - IDBDataFile* of column segment file to be written to
|
|
* dbRoot (in) - DBRoot of pFile
|
|
* nBlocks (in) - number of blocks to be written for an extent
|
|
* emptyVal(in) - empty value to be used for column data values
|
|
* width (in) - width of the applicable column
|
|
* bNewFile(in) - are we adding an extent to a new file, in which case
|
|
* headers will be included "if" it is a compressed file.
|
|
* bExpandExtent (in) - Expand existing extent, or initialize a new one
|
|
* bAbbrevExtent(in) - if creating new extent, is it an abbreviated extent
|
|
* bOptExtension(in) - skip full extent preallocation.
|
|
* RETURN:
|
|
* returns ERR_FILE_WRITE if an error occurs,
|
|
* else returns NO_ERROR.
|
|
***********************************************************/
|
|
int FileOp::initColumnExtent(
|
|
IDBDataFile* pFile,
|
|
uint16_t dbRoot,
|
|
int nBlocks,
|
|
uint64_t emptyVal,
|
|
int width,
|
|
bool bNewFile,
|
|
bool bExpandExtent,
|
|
bool bAbbrevExtent,
|
|
bool bOptExtension)
|
|
{
|
|
if ((bNewFile) && (m_compressionType))
|
|
{
|
|
char hdrs[IDBCompressInterface::HDR_BUF_LEN * 2];
|
|
IDBCompressInterface compressor;
|
|
compressor.initHdr(hdrs, m_compressionType);
|
|
|
|
if (bAbbrevExtent)
|
|
compressor.setBlockCount(hdrs, nBlocks);
|
|
|
|
RETURN_ON_ERROR(writeHeaders(pFile, hdrs));
|
|
}
|
|
|
|
// @bug5769 Don't initialize extents or truncate db files on HDFS
|
|
if (idbdatafile::IDBPolicy::useHdfs())
|
|
{
|
|
//@Bug 3219. update the compression header after the extent is expanded.
|
|
if ((!bNewFile) && (m_compressionType) && (bExpandExtent))
|
|
{
|
|
updateColumnExtent(pFile, nBlocks);
|
|
}
|
|
|
|
// @bug 2378. Synchronize here to avoid write buffer pile up too much,
|
|
// which could cause controllernode to timeout later when it needs to
|
|
// save a snapshot.
|
|
pFile->flush();
|
|
}
|
|
else
|
|
{
|
|
// Create vector of mutexes used to serialize extent access per DBRoot
|
|
initDbRootExtentMutexes( );
|
|
|
|
// MCOL-498 Skip the huge preallocations if the option is set
|
|
// for the dbroot. This check is skiped for abbreviated extent.
|
|
// IMO it is better to check bool then to call a function.
|
|
if ( bOptExtension )
|
|
{
|
|
bOptExtension = (idbdatafile::IDBPolicy::PreallocSpace(dbRoot))
|
|
? bOptExtension : false;
|
|
}
|
|
// Reduce number of blocks allocated for abbreviated extents thus
|
|
// CS writes less when creates a new table. This couldn't be zero
|
|
// b/c Snappy compressed file format doesn't tolerate empty files.
|
|
int realNBlocks = ( bOptExtension && nBlocks <= MAX_INITIAL_EXTENT_BLOCKS_TO_DISK ) ? 3 : nBlocks;
|
|
|
|
// Determine the number of blocks in each call to fwrite(), and the
|
|
// number of fwrite() calls to make, based on this. In other words,
|
|
// we put a cap on the "writeSize" so that we don't allocate and write
|
|
// an entire extent at once for the 64M row extents. If we are
|
|
// expanding an abbreviated 64M extent, we may not have an even
|
|
// multiple of MAX_NBLOCKS to write; remWriteSize is the number of
|
|
// blocks above and beyond loopCount*MAX_NBLOCKS.
|
|
int writeSize = realNBlocks * BYTE_PER_BLOCK; // 1M and 8M row extent size
|
|
int loopCount = 1;
|
|
int remWriteSize = 0;
|
|
|
|
if (realNBlocks > MAX_NBLOCKS) // 64M row extent size
|
|
{
|
|
writeSize = MAX_NBLOCKS * BYTE_PER_BLOCK;
|
|
loopCount = realNBlocks / MAX_NBLOCKS;
|
|
remWriteSize = realNBlocks - (loopCount * MAX_NBLOCKS);
|
|
}
|
|
|
|
// Allocate a buffer, initialize it, and use it to create the extent
|
|
idbassert(dbRoot > 0);
|
|
#ifdef PROFILE
|
|
|
|
if (bExpandExtent)
|
|
Stats::startParseEvent(WE_STATS_WAIT_TO_EXPAND_COL_EXTENT);
|
|
else
|
|
Stats::startParseEvent(WE_STATS_WAIT_TO_CREATE_COL_EXTENT);
|
|
|
|
#endif
|
|
boost::mutex::scoped_lock lk(*m_DbRootAddExtentMutexes[dbRoot]);
|
|
#ifdef PROFILE
|
|
|
|
if (bExpandExtent)
|
|
Stats::stopParseEvent(WE_STATS_WAIT_TO_EXPAND_COL_EXTENT);
|
|
else
|
|
Stats::stopParseEvent(WE_STATS_WAIT_TO_CREATE_COL_EXTENT);
|
|
#endif
|
|
// Skip space preallocation if configured so
|
|
// fallback to sequential write otherwise.
|
|
// Couldn't avoid preallocation for full extents,
|
|
// e.g. ADD COLUMN DDL b/c CS has to fill the file
|
|
// with empty magics.
|
|
if ( !bOptExtension )
|
|
{
|
|
#ifdef PROFILE
|
|
Stats::startParseEvent(WE_STATS_INIT_COL_EXTENT);
|
|
#endif
|
|
// Allocate buffer, store it in scoped_array to insure it's deletion.
|
|
// Create scope {...} to manage deletion of writeBuf.
|
|
{
|
|
|
|
unsigned char* writeBuf = new unsigned char[writeSize];
|
|
boost::scoped_array<unsigned char> writeBufPtr( writeBuf );
|
|
|
|
setEmptyBuf( writeBuf, writeSize, emptyVal, width );
|
|
|
|
#ifdef PROFILE
|
|
Stats::stopParseEvent(WE_STATS_INIT_COL_EXTENT);
|
|
|
|
if (bExpandExtent)
|
|
Stats::startParseEvent(WE_STATS_EXPAND_COL_EXTENT);
|
|
else
|
|
Stats::startParseEvent(WE_STATS_CREATE_COL_EXTENT);
|
|
|
|
#endif
|
|
|
|
//std::ostringstream oss;
|
|
//oss << "initColExtent: width-" << width <<
|
|
//"; loopCount-" << loopCount <<
|
|
//"; writeSize-" << writeSize;
|
|
//std::cout << oss.str() << std::endl;
|
|
if (remWriteSize > 0)
|
|
{
|
|
if ( pFile->write( writeBuf, remWriteSize ) != remWriteSize )
|
|
{
|
|
return ERR_FILE_WRITE;
|
|
}
|
|
}
|
|
|
|
for (int j = 0; j < loopCount; j++)
|
|
{
|
|
if ( pFile->write( writeBuf, writeSize ) != writeSize )
|
|
{
|
|
return ERR_FILE_WRITE;
|
|
}
|
|
}
|
|
}
|
|
|
|
//@Bug 3219. update the compression header after the extent is expanded.
|
|
if ((!bNewFile) && (m_compressionType) && (bExpandExtent))
|
|
{
|
|
updateColumnExtent(pFile, nBlocks);
|
|
}
|
|
|
|
// @bug 2378. Synchronize here to avoid write buffer pile up too much,
|
|
// which could cause controllernode to timeout later when it needs to
|
|
// save a snapshot.
|
|
pFile->flush();
|
|
|
|
#ifdef PROFILE
|
|
if (bExpandExtent)
|
|
Stats::stopParseEvent(WE_STATS_EXPAND_COL_EXTENT);
|
|
else
|
|
Stats::stopParseEvent(WE_STATS_CREATE_COL_EXTENT);
|
|
#endif
|
|
|
|
}
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
/***********************************************************
|
|
* DESCRIPTION:
|
|
* Write (initialize) an abbreviated compressed extent in a column file.
|
|
* nBlocks controls how many 8192-byte blocks are to be written out.
|
|
* PARAMETERS:
|
|
* pFile (in) - IDBDataFile* of column segment file to be written to
|
|
* dbRoot (in) - DBRoot of pFile
|
|
* nBlocks (in) - number of blocks to be written for an extent
|
|
* emptyVal(in) - empty value to be used for column data values
|
|
* width (in) - width of the applicable column
|
|
* RETURN:
|
|
* returns ERR_FILE_WRITE or ERR_FILE_SEEK if an error occurs,
|
|
* else returns NO_ERROR.
|
|
***********************************************************/
|
|
int FileOp::initAbbrevCompColumnExtent(
|
|
IDBDataFile* pFile,
|
|
uint16_t dbRoot,
|
|
int nBlocks,
|
|
uint64_t emptyVal,
|
|
int width)
|
|
{
|
|
// Reserve disk space for optimized abbreviated extent
|
|
int rc = initColumnExtent( pFile,
|
|
dbRoot,
|
|
nBlocks,
|
|
emptyVal,
|
|
width,
|
|
true, // new file
|
|
false, // don't expand; add new extent
|
|
true, // add abbreviated extent
|
|
true); // optimize the initial extent
|
|
if (rc != NO_ERROR)
|
|
{
|
|
return rc;
|
|
}
|
|
|
|
#ifdef PROFILE
|
|
Stats::startParseEvent(WE_STATS_COMPRESS_COL_INIT_ABBREV_EXT);
|
|
#endif
|
|
|
|
char hdrs[IDBCompressInterface::HDR_BUF_LEN * 2];
|
|
rc = writeInitialCompColumnChunk( pFile,
|
|
nBlocks,
|
|
INITIAL_EXTENT_ROWS_TO_DISK,
|
|
emptyVal,
|
|
width,
|
|
hdrs );
|
|
|
|
if (rc != NO_ERROR)
|
|
{
|
|
return rc;
|
|
}
|
|
|
|
#ifdef PROFILE
|
|
Stats::stopParseEvent(WE_STATS_COMPRESS_COL_INIT_ABBREV_EXT);
|
|
#endif
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
/***********************************************************
|
|
* DESCRIPTION:
|
|
* Write (initialize) the first extent in a compressed db file.
|
|
* PARAMETERS:
|
|
* pFile - IDBDataFile* of column segment file to be written to
|
|
* nBlocksAllocated - number of blocks allocated to the extent; should be
|
|
* enough blocks for a full extent, unless it's the abbreviated extent
|
|
* nRows - number of rows to initialize, or write out to the file
|
|
* emptyVal - empty value to be used for column data values
|
|
* width - width of the applicable column (in bytes)
|
|
* hdrs - (in/out) chunk pointer headers
|
|
* RETURN:
|
|
* returns NO_ERROR on success.
|
|
***********************************************************/
|
|
int FileOp::writeInitialCompColumnChunk(
|
|
IDBDataFile* pFile,
|
|
int nBlocksAllocated,
|
|
int nRows,
|
|
uint64_t emptyVal,
|
|
int width,
|
|
char* hdrs)
|
|
{
|
|
const int INPUT_BUFFER_SIZE = nRows * width;
|
|
char* toBeCompressedInput = new char[INPUT_BUFFER_SIZE];
|
|
unsigned int userPaddingBytes = Config::getNumCompressedPadBlks() *
|
|
BYTE_PER_BLOCK;
|
|
const int OUTPUT_BUFFER_SIZE = IDBCompressInterface::maxCompressedSize(INPUT_BUFFER_SIZE) +
|
|
userPaddingBytes;
|
|
unsigned char* compressedOutput = new unsigned char[OUTPUT_BUFFER_SIZE];
|
|
unsigned int outputLen = OUTPUT_BUFFER_SIZE;
|
|
boost::scoped_array<char> toBeCompressedInputPtr( toBeCompressedInput );
|
|
boost::scoped_array<unsigned char> compressedOutputPtr(compressedOutput);
|
|
|
|
setEmptyBuf( (unsigned char*)toBeCompressedInput,
|
|
INPUT_BUFFER_SIZE, emptyVal, width);
|
|
|
|
// Compress an initialized abbreviated extent
|
|
IDBCompressInterface compressor( userPaddingBytes );
|
|
int rc = compressor.compressBlock(toBeCompressedInput,
|
|
INPUT_BUFFER_SIZE, compressedOutput, outputLen );
|
|
|
|
if (rc != 0)
|
|
{
|
|
return ERR_COMP_COMPRESS;
|
|
}
|
|
|
|
// Round up the compressed chunk size
|
|
rc = compressor.padCompressedChunks( compressedOutput,
|
|
outputLen, OUTPUT_BUFFER_SIZE );
|
|
|
|
if (rc != 0)
|
|
{
|
|
return ERR_COMP_PAD_DATA;
|
|
}
|
|
|
|
// std::cout << "Uncompressed rowCount: " << nRows <<
|
|
// "; colWidth: " << width <<
|
|
// "; uncompByteCnt: " << INPUT_BUFFER_SIZE <<
|
|
// "; blkAllocCnt: " << nBlocksAllocated <<
|
|
// "; compressedByteCnt: " << outputLen << std::endl;
|
|
|
|
compressor.initHdr(hdrs, m_compressionType);
|
|
compressor.setBlockCount(hdrs, nBlocksAllocated);
|
|
|
|
// Store compression pointers in the header
|
|
std::vector<uint64_t> ptrs;
|
|
ptrs.push_back( IDBCompressInterface::HDR_BUF_LEN * 2 );
|
|
ptrs.push_back( outputLen + (IDBCompressInterface::HDR_BUF_LEN * 2) );
|
|
compressor.storePtrs(ptrs, hdrs);
|
|
|
|
RETURN_ON_ERROR( writeHeaders(pFile, hdrs) );
|
|
|
|
// Write the compressed data
|
|
if ( pFile->write( compressedOutput, outputLen ) != outputLen )
|
|
{
|
|
return ERR_FILE_WRITE;
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
/***********************************************************
|
|
* DESCRIPTION:
|
|
* Fill specified compressed extent with empty value chunks.
|
|
* PARAMETERS:
|
|
* oid - OID for relevant column
|
|
* colWidth - width in bytes of this column
|
|
* emptyVal - empty value to be used in filling empty chunks
|
|
* dbRoot - DBRoot of extent to be filled
|
|
* partition - partition of extent to be filled
|
|
* segment - segment file number of extent to be filled
|
|
* hwm - proposed new HWM of filled in extent
|
|
* segFile - (out) name of updated segment file
|
|
* failedTask - (out) if error occurs, this is the task that failed
|
|
* RETURN:
|
|
* returns NO_ERROR if success.
|
|
***********************************************************/
|
|
int FileOp::fillCompColumnExtentEmptyChunks(OID oid,
|
|
int colWidth,
|
|
uint64_t emptyVal,
|
|
uint16_t dbRoot,
|
|
uint32_t partition,
|
|
uint16_t segment,
|
|
HWM hwm,
|
|
std::string& segFile,
|
|
std::string& failedTask)
|
|
{
|
|
int rc = NO_ERROR;
|
|
segFile.clear();
|
|
failedTask.clear();
|
|
|
|
// Open the file and read the headers with the compression chunk pointers
|
|
// @bug 5572 - HDFS usage: incorporate *.tmp file backup flag
|
|
IDBDataFile* pFile = openFile( oid, dbRoot, partition, segment, segFile,
|
|
"r+b", DEFAULT_COLSIZ, true );
|
|
|
|
if (!pFile)
|
|
{
|
|
failedTask = "Opening file";
|
|
ostringstream oss;
|
|
oss << "oid: " << oid << " with path " << segFile;
|
|
logging::Message::Args args;
|
|
args.add("Error opening file ");
|
|
args.add(oss.str());
|
|
args.add("");
|
|
args.add("");
|
|
SimpleSysLog::instance()->logMsg(args,
|
|
logging::LOG_TYPE_ERROR,
|
|
logging::M0001);
|
|
return ERR_FILE_OPEN;
|
|
}
|
|
|
|
char hdrs[ IDBCompressInterface::HDR_BUF_LEN * 2 ];
|
|
rc = readHeaders( pFile, hdrs );
|
|
|
|
if (rc != NO_ERROR)
|
|
{
|
|
failedTask = "Reading headers";
|
|
closeFile ( pFile );
|
|
return rc;
|
|
}
|
|
|
|
int userPadBytes = Config::getNumCompressedPadBlks() * BYTE_PER_BLOCK;
|
|
IDBCompressInterface compressor( userPadBytes );
|
|
CompChunkPtrList chunkPtrs;
|
|
int rcComp = compressor.getPtrList( hdrs, chunkPtrs );
|
|
|
|
if (rcComp != 0)
|
|
{
|
|
failedTask = "Getting header ptrs";
|
|
closeFile ( pFile );
|
|
return ERR_COMP_PARSE_HDRS;
|
|
}
|
|
|
|
// Nothing to do if the proposed HWM is < the current block count
|
|
uint64_t blkCount = compressor.getBlockCount(hdrs);
|
|
|
|
if (blkCount > (hwm + 1))
|
|
{
|
|
closeFile ( pFile );
|
|
return NO_ERROR;
|
|
}
|
|
|
|
const unsigned int ROWS_PER_EXTENT =
|
|
BRMWrapper::getInstance()->getInstance()->getExtentRows();
|
|
const unsigned int ROWS_PER_CHUNK =
|
|
IDBCompressInterface::UNCOMPRESSED_INBUF_LEN / colWidth;
|
|
const unsigned int CHUNKS_PER_EXTENT = ROWS_PER_EXTENT / ROWS_PER_CHUNK;
|
|
|
|
// If this is an abbreviated extent, we first expand to a full extent
|
|
// @bug 4340 - support moving the DBRoot with a single abbrev extent
|
|
if ( (chunkPtrs.size() == 1) &&
|
|
((blkCount * BYTE_PER_BLOCK) ==
|
|
(uint64_t)(INITIAL_EXTENT_ROWS_TO_DISK * colWidth)) )
|
|
{
|
|
if ( getLogger() )
|
|
{
|
|
std::ostringstream oss;
|
|
oss << "Converting abbreviated partial extent to full extent for" <<
|
|
": OID-" << oid <<
|
|
"; DBRoot-" << dbRoot <<
|
|
"; part-" << partition <<
|
|
"; seg-" << segment <<
|
|
"; file-" << segFile <<
|
|
"; wid-" << colWidth <<
|
|
"; oldBlkCnt-" << blkCount <<
|
|
"; newBlkCnt-" <<
|
|
((ROWS_PER_EXTENT * colWidth) / BYTE_PER_BLOCK);
|
|
getLogger()->logMsg( oss.str(), MSGLVL_INFO2 );
|
|
}
|
|
|
|
off64_t endHdrsOffset = pFile->tell();
|
|
rc = expandAbbrevColumnExtent( pFile, dbRoot, emptyVal, colWidth );
|
|
|
|
if (rc != NO_ERROR)
|
|
{
|
|
failedTask = "Expanding abbreviated extent";
|
|
closeFile ( pFile );
|
|
return rc;
|
|
}
|
|
|
|
CompChunkPtr chunkOutPtr;
|
|
rc = expandAbbrevColumnChunk( pFile, emptyVal, colWidth,
|
|
chunkPtrs[0], chunkOutPtr );
|
|
|
|
if (rc != NO_ERROR)
|
|
{
|
|
failedTask = "Expanding abbreviated chunk";
|
|
closeFile ( pFile );
|
|
return rc;
|
|
}
|
|
|
|
chunkPtrs[0] = chunkOutPtr; // update chunkPtrs with new chunk size
|
|
|
|
rc = setFileOffset( pFile, endHdrsOffset );
|
|
|
|
if (rc != NO_ERROR)
|
|
{
|
|
failedTask = "Positioning file to end of headers";
|
|
closeFile ( pFile );
|
|
return rc;
|
|
}
|
|
|
|
// Update block count to reflect a full extent
|
|
blkCount = (ROWS_PER_EXTENT * colWidth) / BYTE_PER_BLOCK;
|
|
compressor.setBlockCount( hdrs, blkCount );
|
|
}
|
|
|
|
// Calculate the number of empty chunks we need to add to fill this extent
|
|
unsigned numChunksToFill = 0;
|
|
ldiv_t ldivResult = ldiv(chunkPtrs.size(), CHUNKS_PER_EXTENT);
|
|
|
|
if (ldivResult.rem != 0)
|
|
{
|
|
numChunksToFill = CHUNKS_PER_EXTENT - ldivResult.rem;
|
|
}
|
|
|
|
#if 0
|
|
std::cout << "Number of allocated blocks: " <<
|
|
compressor.getBlockCount(hdrs) << std::endl;
|
|
std::cout << "Pointer Header Size (in bytes): " <<
|
|
(compressor.getHdrSize(hdrs) -
|
|
IDBCompressInterface::HDR_BUF_LEN) << std::endl;
|
|
std::cout << "Chunk Pointers (offset,length): " << std::endl;
|
|
|
|
for (unsigned k = 0; k < chunkPtrs.size(); k++)
|
|
{
|
|
std::cout << " " << k << ". " << chunkPtrs[k].first <<
|
|
" , " << chunkPtrs[k].second << std::endl;
|
|
}
|
|
|
|
std::cout << std::endl;
|
|
std::cout << "Number of chunks to fill in: " << numChunksToFill <<
|
|
std::endl << std::endl;
|
|
#endif
|
|
|
|
off64_t endOffset = 0;
|
|
|
|
// Fill in or add necessary remaining empty chunks
|
|
if (numChunksToFill > 0)
|
|
{
|
|
const int IN_BUF_LEN = IDBCompressInterface::UNCOMPRESSED_INBUF_LEN;
|
|
const int OUT_BUF_LEN = IDBCompressInterface::maxCompressedSize(IN_BUF_LEN) + userPadBytes;
|
|
|
|
// Allocate buffer, and store in scoped_array to insure it's deletion.
|
|
// Create scope {...} to manage deletion of buffers
|
|
{
|
|
char* toBeCompressedBuf = new char [ IN_BUF_LEN ];
|
|
unsigned char* compressedBuf = new unsigned char[ OUT_BUF_LEN ];
|
|
boost::scoped_array<char> toBeCompressedInputPtr(toBeCompressedBuf);
|
|
boost::scoped_array<unsigned char>
|
|
compressedOutputPtr(compressedBuf);
|
|
|
|
// Compress and then pad the compressed chunk
|
|
setEmptyBuf( (unsigned char*)toBeCompressedBuf,
|
|
IN_BUF_LEN, emptyVal, colWidth );
|
|
unsigned int outputLen = OUT_BUF_LEN;
|
|
rcComp = compressor.compressBlock( toBeCompressedBuf,
|
|
IN_BUF_LEN, compressedBuf, outputLen );
|
|
|
|
if (rcComp != 0)
|
|
{
|
|
failedTask = "Compressing chunk";
|
|
closeFile ( pFile );
|
|
return ERR_COMP_COMPRESS;
|
|
}
|
|
|
|
toBeCompressedInputPtr.reset(); // release memory
|
|
|
|
rcComp = compressor.padCompressedChunks( compressedBuf,
|
|
outputLen, OUT_BUF_LEN );
|
|
|
|
if (rcComp != 0)
|
|
{
|
|
failedTask = "Padding compressed chunk";
|
|
closeFile ( pFile );
|
|
return ERR_COMP_PAD_DATA;
|
|
}
|
|
|
|
// Position file to write empty chunks; default to end of headers
|
|
// in case there are no chunks listed in the header
|
|
off64_t startOffset = pFile->tell();
|
|
|
|
if (chunkPtrs.size() > 0)
|
|
{
|
|
startOffset = chunkPtrs[chunkPtrs.size() - 1].first +
|
|
chunkPtrs[chunkPtrs.size() - 1].second;
|
|
rc = setFileOffset( pFile, startOffset );
|
|
|
|
if (rc != NO_ERROR)
|
|
{
|
|
failedTask = "Positioning file to begin filling chunks";
|
|
closeFile ( pFile );
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
// Write chunks needed to fill out the current extent, add chunk ptr
|
|
for (unsigned k = 0; k < numChunksToFill; k++)
|
|
{
|
|
rc = writeFile( pFile,
|
|
(unsigned char*)compressedBuf,
|
|
outputLen );
|
|
|
|
if (rc != NO_ERROR)
|
|
{
|
|
failedTask = "Writing a chunk";
|
|
closeFile ( pFile );
|
|
return rc;
|
|
}
|
|
|
|
CompChunkPtr compChunk( startOffset, outputLen );
|
|
chunkPtrs.push_back( compChunk );
|
|
startOffset = pFile->tell();
|
|
}
|
|
} // end of scope for boost scoped array pointers
|
|
|
|
endOffset = pFile->tell();
|
|
|
|
// Update the compressed chunk pointers in the header
|
|
std::vector<uint64_t> ptrs;
|
|
|
|
for (unsigned i = 0; i < chunkPtrs.size(); i++)
|
|
{
|
|
ptrs.push_back( chunkPtrs[i].first );
|
|
}
|
|
|
|
ptrs.push_back( chunkPtrs[chunkPtrs.size() - 1].first +
|
|
chunkPtrs[chunkPtrs.size() - 1].second );
|
|
compressor.storePtrs( ptrs, hdrs );
|
|
|
|
rc = writeHeaders( pFile, hdrs );
|
|
|
|
if (rc != NO_ERROR)
|
|
{
|
|
failedTask = "Writing headers";
|
|
closeFile ( pFile );
|
|
return rc;
|
|
}
|
|
} // end of "numChunksToFill > 0"
|
|
else
|
|
{
|
|
// if no chunks to add, then set endOffset to truncate the db file
|
|
// strictly based on the chunks that are already in the file
|
|
if (chunkPtrs.size() > 0)
|
|
{
|
|
endOffset = chunkPtrs[chunkPtrs.size() - 1].first +
|
|
chunkPtrs[chunkPtrs.size() - 1].second;
|
|
}
|
|
}
|
|
|
|
// Truncate the file to release unused space for the extent we just filled
|
|
if (endOffset > 0)
|
|
{
|
|
rc = truncateFile(pFile, endOffset);
|
|
|
|
if (rc != NO_ERROR)
|
|
{
|
|
failedTask = "Truncating file";
|
|
closeFile ( pFile );
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
closeFile ( pFile );
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
/***********************************************************
|
|
* DESCRIPTION:
|
|
* Expand first chunk in pFile from an abbreviated chunk for an abbreviated
|
|
* extent to a full compressed chunk for a full extent.
|
|
* PARAMETERS:
|
|
* pFile - file to be updated
|
|
* colWidth - width in bytes of this column
|
|
* emptyVal - empty value to be used in filling empty chunks
|
|
* chunkInPtr - chunk pointer referencing first (abbrev) chunk
|
|
* chunkOutPtr- (out) updated chunk ptr referencing first (full) chunk
|
|
* RETURN:
|
|
* returns NO_ERROR if success.
|
|
***********************************************************/
|
|
int FileOp::expandAbbrevColumnChunk(
|
|
IDBDataFile* pFile,
|
|
uint64_t emptyVal,
|
|
int colWidth,
|
|
const CompChunkPtr& chunkInPtr,
|
|
CompChunkPtr& chunkOutPtr )
|
|
{
|
|
int userPadBytes = Config::getNumCompressedPadBlks() * BYTE_PER_BLOCK;
|
|
const int IN_BUF_LEN = IDBCompressInterface::UNCOMPRESSED_INBUF_LEN;
|
|
const int OUT_BUF_LEN = IDBCompressInterface::maxCompressedSize(IN_BUF_LEN) + userPadBytes;
|
|
|
|
char* toBeCompressedBuf = new char[ IN_BUF_LEN ];
|
|
boost::scoped_array<char> toBeCompressedPtr(toBeCompressedBuf);
|
|
|
|
setEmptyBuf( (unsigned char*)toBeCompressedBuf,
|
|
IN_BUF_LEN, emptyVal, colWidth );
|
|
|
|
RETURN_ON_ERROR( setFileOffset(pFile, chunkInPtr.first, SEEK_SET) );
|
|
|
|
char* compressedInBuf = new char[ chunkInPtr.second ];
|
|
boost::scoped_array<char> compressedInBufPtr(compressedInBuf);
|
|
RETURN_ON_ERROR( readFile(pFile, (unsigned char*)compressedInBuf,
|
|
chunkInPtr.second) );
|
|
|
|
// Uncompress an "abbreviated" chunk into our 4MB buffer
|
|
unsigned int outputLen = IN_BUF_LEN;
|
|
IDBCompressInterface compressor( userPadBytes );
|
|
int rc = compressor.uncompressBlock(
|
|
compressedInBuf,
|
|
chunkInPtr.second,
|
|
(unsigned char*)toBeCompressedBuf,
|
|
outputLen);
|
|
|
|
if (rc != 0)
|
|
{
|
|
return ERR_COMP_UNCOMPRESS;
|
|
}
|
|
|
|
compressedInBufPtr.reset(); // release memory
|
|
|
|
RETURN_ON_ERROR( setFileOffset(pFile, chunkInPtr.first, SEEK_SET) );
|
|
|
|
unsigned char* compressedOutBuf = new unsigned char[ OUT_BUF_LEN ];
|
|
boost::scoped_array<unsigned char> compressedOutBufPtr(compressedOutBuf);
|
|
|
|
// Compress the data we just read, as a "full" 4MB chunk
|
|
outputLen = OUT_BUF_LEN;
|
|
rc = compressor.compressBlock(
|
|
reinterpret_cast<char*>(toBeCompressedBuf),
|
|
IN_BUF_LEN,
|
|
compressedOutBuf,
|
|
outputLen );
|
|
|
|
if (rc != 0)
|
|
{
|
|
return ERR_COMP_COMPRESS;
|
|
}
|
|
|
|
// Round up the compressed chunk size
|
|
rc = compressor.padCompressedChunks( compressedOutBuf,
|
|
outputLen, OUT_BUF_LEN );
|
|
|
|
if (rc != 0)
|
|
{
|
|
return ERR_COMP_PAD_DATA;
|
|
}
|
|
|
|
RETURN_ON_ERROR( writeFile(pFile, compressedOutBuf, outputLen) );
|
|
|
|
chunkOutPtr.first = chunkInPtr.first;
|
|
chunkOutPtr.second = outputLen;
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
/***********************************************************
|
|
* DESCRIPTION:
|
|
* Write headers to a compressed column file.
|
|
* PARAMETERS:
|
|
* pFile (in) - IDBDataFile* of column segment file to be written to
|
|
* hdr (in) - header pointers to be written
|
|
* RETURN:
|
|
* returns ERR_FILE_WRITE or ERR_FILE_SEEK if an error occurs,
|
|
* else returns NO_ERROR.
|
|
***********************************************************/
|
|
int FileOp::writeHeaders(IDBDataFile* pFile, const char* hdr) const
|
|
{
|
|
RETURN_ON_ERROR( setFileOffset(pFile, 0, SEEK_SET) );
|
|
|
|
// Write the headers
|
|
if (pFile->write( hdr, IDBCompressInterface::HDR_BUF_LEN * 2 ) != IDBCompressInterface::HDR_BUF_LEN * 2)
|
|
{
|
|
return ERR_FILE_WRITE;
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
/***********************************************************
|
|
* DESCRIPTION:
|
|
* Write headers to a compressed column or dictionary file.
|
|
* PARAMETERS:
|
|
* pFile (in) - IDBDataFile* of column segment file to be written to
|
|
* controlHdr (in) - control header to be written
|
|
* pointerHdr (in) - pointer header to be written
|
|
* ptrHdrSize (in) - size (in bytes) of pointer header
|
|
* RETURN:
|
|
* returns ERR_FILE_WRITE or ERR_FILE_SEEK if an error occurs,
|
|
* else returns NO_ERROR.
|
|
***********************************************************/
|
|
int FileOp::writeHeaders(IDBDataFile* pFile, const char* controlHdr,
|
|
const char* pointerHdr, uint64_t ptrHdrSize) const
|
|
{
|
|
RETURN_ON_ERROR( setFileOffset(pFile, 0, SEEK_SET) );
|
|
|
|
// Write the control header
|
|
if (pFile->write( controlHdr, IDBCompressInterface::HDR_BUF_LEN ) != IDBCompressInterface::HDR_BUF_LEN)
|
|
{
|
|
return ERR_FILE_WRITE;
|
|
}
|
|
|
|
// Write the pointer header
|
|
if (pFile->write( pointerHdr, ptrHdrSize ) != (ssize_t) ptrHdrSize)
|
|
{
|
|
return ERR_FILE_WRITE;
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
/***********************************************************
|
|
* DESCRIPTION:
|
|
* Write out (initialize) an extent in a dictionary store file.
|
|
* A mutex is used for each DBRoot, to prevent contention between
|
|
* threads, because if multiple threads are creating extents on
|
|
* the same DBRoot at the same time, the extents can become
|
|
* fragmented. It is best to only create one extent at a time
|
|
* on each DBRoot.
|
|
* This function can be used to initialize an entirely new extent, or
|
|
* to finish initializing an extent that has already been started.
|
|
* nBlocks controls how many 8192-byte blocks are to be written out.
|
|
* If bOptExtension is set then method first checks config for
|
|
* DBRootX.Prealloc. If it is disabled then it skips disk space
|
|
* preallocation.
|
|
* PARAMETERS:
|
|
* pFile (in) - IDBDataFile* of column segment file to be written to
|
|
* dbRoot (in) - DBRoot of pFile
|
|
* nBlocks (in) - number of blocks to be written for an extent
|
|
* blockHdrInit(in) - data used to initialize each block
|
|
* blockHdrInitSize(in) - number of bytes in blockHdrInit
|
|
* bExpandExtent (in) - Expand existing extent, or initialize a new one
|
|
* bOptExtension(in) - skip full extent preallocation.
|
|
* RETURN:
|
|
* returns ERR_FILE_WRITE if an error occurs,
|
|
* else returns NO_ERROR.
|
|
***********************************************************/
|
|
int FileOp::initDctnryExtent(
|
|
IDBDataFile* pFile,
|
|
uint16_t dbRoot,
|
|
int nBlocks,
|
|
unsigned char* blockHdrInit,
|
|
int blockHdrInitSize,
|
|
bool bExpandExtent,
|
|
bool bOptExtension )
|
|
{
|
|
// @bug5769 Don't initialize extents or truncate db files on HDFS
|
|
if (idbdatafile::IDBPolicy::useHdfs())
|
|
{
|
|
if (m_compressionType)
|
|
updateDctnryExtent(pFile, nBlocks);
|
|
|
|
// Synchronize to avoid write buffer pile up too much, which could cause
|
|
// controllernode to timeout later when it needs to save a snapshot.
|
|
pFile->flush();
|
|
}
|
|
else
|
|
{
|
|
// Create vector of mutexes used to serialize extent access per DBRoot
|
|
initDbRootExtentMutexes( );
|
|
|
|
// MCOL-498 Skip the huge preallocations if the option is set
|
|
// for the dbroot. This check is skiped for abbreviated extent.
|
|
// IMO it is better to check bool then to call a function.
|
|
// CS uses non-compressed dict files for its system catalog so
|
|
// CS doesn't optimize non-compressed dict creation.
|
|
if ( bOptExtension )
|
|
{
|
|
bOptExtension = (idbdatafile::IDBPolicy::PreallocSpace(dbRoot)
|
|
&& m_compressionType) ? bOptExtension : false;
|
|
}
|
|
// Reduce number of blocks allocated for abbreviated extents thus
|
|
// CS writes less when creates a new table. This couldn't be zero
|
|
// b/c Snappy compressed file format doesn't tolerate empty files.
|
|
int realNBlocks = ( bOptExtension && nBlocks <= MAX_INITIAL_EXTENT_BLOCKS_TO_DISK ) ? 1 : nBlocks;
|
|
|
|
// Determine the number of blocks in each call to fwrite(), and the
|
|
// number of fwrite() calls to make, based on this. In other words,
|
|
// we put a cap on the "writeSize" so that we don't allocate and write
|
|
// an entire extent at once for the 64M row extents. If we are
|
|
// expanding an abbreviated 64M extent, we may not have an even
|
|
// multiple of MAX_NBLOCKS to write; remWriteSize is the number of
|
|
// blocks above and beyond loopCount*MAX_NBLOCKS.
|
|
int writeSize = realNBlocks * BYTE_PER_BLOCK; // 1M and 8M row extent size
|
|
int loopCount = 1;
|
|
int remWriteSize = 0;
|
|
|
|
if (realNBlocks > MAX_NBLOCKS) // 64M row extent size
|
|
{
|
|
writeSize = MAX_NBLOCKS * BYTE_PER_BLOCK;
|
|
loopCount = realNBlocks / MAX_NBLOCKS;
|
|
remWriteSize = realNBlocks - (loopCount * MAX_NBLOCKS);
|
|
}
|
|
|
|
// Allocate a buffer, initialize it, and use it to create the extent
|
|
idbassert(dbRoot > 0);
|
|
|
|
#ifdef PROFILE
|
|
if (bExpandExtent)
|
|
Stats::startParseEvent(WE_STATS_WAIT_TO_EXPAND_DCT_EXTENT);
|
|
else
|
|
Stats::startParseEvent(WE_STATS_WAIT_TO_CREATE_DCT_EXTENT);
|
|
#endif
|
|
|
|
boost::mutex::scoped_lock lk(*m_DbRootAddExtentMutexes[dbRoot]);
|
|
|
|
#ifdef PROFILE
|
|
if (bExpandExtent)
|
|
Stats::stopParseEvent(WE_STATS_WAIT_TO_EXPAND_DCT_EXTENT);
|
|
else
|
|
Stats::stopParseEvent(WE_STATS_WAIT_TO_CREATE_DCT_EXTENT);
|
|
#endif
|
|
// Skip space preallocation if configured so
|
|
// fallback to sequential write otherwise.
|
|
// Couldn't avoid preallocation for full extents,
|
|
// e.g. ADD COLUMN DDL b/c CS has to fill the file
|
|
// with empty magics.
|
|
if ( !bOptExtension )
|
|
{
|
|
// Allocate buffer, and store in scoped_array to insure it's deletion.
|
|
// Create scope {...} to manage deletion of writeBuf.
|
|
{
|
|
#ifdef PROFILE
|
|
Stats::startParseEvent(WE_STATS_INIT_DCT_EXTENT);
|
|
#endif
|
|
|
|
unsigned char* writeBuf = new unsigned char[writeSize];
|
|
boost::scoped_array<unsigned char> writeBufPtr( writeBuf );
|
|
|
|
memset(writeBuf, 0, writeSize);
|
|
|
|
for (int i = 0; i < realNBlocks; i++)
|
|
{
|
|
memcpy( writeBuf + (i * BYTE_PER_BLOCK),
|
|
blockHdrInit,
|
|
blockHdrInitSize );
|
|
}
|
|
|
|
#ifdef PROFILE
|
|
Stats::stopParseEvent(WE_STATS_INIT_DCT_EXTENT);
|
|
|
|
if (bExpandExtent)
|
|
Stats::startParseEvent(WE_STATS_EXPAND_DCT_EXTENT);
|
|
else
|
|
Stats::startParseEvent(WE_STATS_CREATE_DCT_EXTENT);
|
|
#endif
|
|
|
|
if (remWriteSize > 0)
|
|
{
|
|
if (pFile->write( writeBuf, remWriteSize ) != remWriteSize)
|
|
{
|
|
return ERR_FILE_WRITE;
|
|
}
|
|
}
|
|
|
|
for (int j = 0; j < loopCount; j++)
|
|
{
|
|
if (pFile->write( writeBuf, writeSize ) != writeSize)
|
|
{
|
|
return ERR_FILE_WRITE;
|
|
}
|
|
}
|
|
// CS doesn't account flush timings.
|
|
#ifdef PROFILE
|
|
if (bExpandExtent)
|
|
Stats::stopParseEvent(WE_STATS_EXPAND_DCT_EXTENT);
|
|
else
|
|
Stats::stopParseEvent(WE_STATS_CREATE_DCT_EXTENT);
|
|
#endif
|
|
}
|
|
} // preallocation fallback end
|
|
|
|
// MCOL-498 CS has to set a number of blocs in the chunk header
|
|
if ( m_compressionType )
|
|
{
|
|
updateDctnryExtent(pFile, nBlocks);
|
|
}
|
|
pFile->flush();
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
/***********************************************************
|
|
* DESCRIPTION:
|
|
* Create a vector containing the mutexes used to serialize
|
|
* extent creation per DBRoot. Serializing extent creation
|
|
* helps to prevent disk fragmentation.
|
|
***********************************************************/
|
|
/* static */
|
|
void FileOp::initDbRootExtentMutexes( )
|
|
{
|
|
boost::mutex::scoped_lock lk(m_createDbRootMutexes);
|
|
|
|
if ( m_DbRootAddExtentMutexes.size() == 0 )
|
|
{
|
|
std::vector<uint16_t> rootIds;
|
|
Config::getRootIdList( rootIds );
|
|
|
|
for (size_t i = 0; i < rootIds.size(); i++)
|
|
{
|
|
boost::mutex* pM = new boost::mutex;
|
|
m_DbRootAddExtentMutexes[ rootIds[i] ] = pM;
|
|
}
|
|
}
|
|
}
|
|
|
|
/***********************************************************
|
|
* DESCRIPTION:
|
|
* Cleans up memory allocated to the DBRoot extent mutexes. Calling
|
|
* this function is not necessary, but it is provided for completeness,
|
|
* to complement initDbRootExtentMutexes(), and to provide a way to
|
|
* free up memory at the end of program execution.
|
|
***********************************************************/
|
|
/* static */
|
|
void FileOp::removeDbRootExtentMutexes( )
|
|
{
|
|
boost::mutex::scoped_lock lk(m_createDbRootMutexes);
|
|
|
|
std::map<int, boost::mutex*>::iterator k = m_DbRootAddExtentMutexes.begin();
|
|
|
|
while (k != m_DbRootAddExtentMutexes.end() )
|
|
{
|
|
delete k->second;
|
|
++k;
|
|
}
|
|
}
|
|
|
|
/***********************************************************
|
|
* DESCRIPTION:
|
|
* Write out (reinitialize) a partial extent in a column file.
|
|
* A mutex is not used to prevent contention between threads,
|
|
* because the extent should already be in place on disk; so
|
|
* disk fragmentation is not an issue.
|
|
* PARAMETERS:
|
|
* pFile (in) - IDBDataFile* of column segment file to be written to
|
|
* startOffset(in)-file offset where we are to begin writing blocks
|
|
* nBlocks (in) - number of blocks to be written to the extent
|
|
* emptyVal(in) - empty value to be used for column data values
|
|
* width (in) - width of the applicable column
|
|
* RETURN:
|
|
* returns ERR_FILE_WRITE if an error occurs,
|
|
* else returns NO_ERROR.
|
|
***********************************************************/
|
|
int FileOp::reInitPartialColumnExtent(
|
|
IDBDataFile* pFile,
|
|
long long startOffset,
|
|
int nBlocks,
|
|
uint64_t emptyVal,
|
|
int width )
|
|
{
|
|
int rc = setFileOffset( pFile, startOffset, SEEK_SET );
|
|
|
|
if (rc != NO_ERROR)
|
|
return rc;
|
|
|
|
if (nBlocks == 0)
|
|
return NO_ERROR;
|
|
|
|
// Determine the number of blocks in each call to fwrite(), and the
|
|
// number of fwrite() calls to make, based on this. In other words,
|
|
// we put a cap on the "writeSize" so that we don't allocate and write
|
|
// an entire extent at once for the 64M row extents.
|
|
int writeSize = nBlocks * BYTE_PER_BLOCK; // 1M and 8M row extent size
|
|
int loopCount = 0;
|
|
int remainderSize = writeSize;
|
|
|
|
if (nBlocks > MAX_NBLOCKS) // 64M row extent size
|
|
{
|
|
writeSize = MAX_NBLOCKS * BYTE_PER_BLOCK;
|
|
loopCount = nBlocks / MAX_NBLOCKS;
|
|
remainderSize = nBlocks - (loopCount * MAX_NBLOCKS);
|
|
}
|
|
|
|
// Allocate a buffer, initialize it, and use it to initialize the extent
|
|
// Store in scoped_array to insure it's deletion.
|
|
// Create scope {...} to manage deletion of writeBuf.
|
|
{
|
|
unsigned char* writeBuf = new unsigned char[writeSize];
|
|
boost::scoped_array<unsigned char> writeBufPtr( writeBuf );
|
|
|
|
setEmptyBuf( writeBuf, writeSize, emptyVal, width );
|
|
|
|
for (int j = 0; j < loopCount; j++)
|
|
{
|
|
if (pFile->write( writeBuf, writeSize ) != writeSize)
|
|
{
|
|
return ERR_FILE_WRITE;
|
|
}
|
|
}
|
|
|
|
if (remainderSize > 0)
|
|
{
|
|
if (pFile->write( writeBuf, remainderSize ) != remainderSize)
|
|
{
|
|
return ERR_FILE_WRITE;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Synchronize here to avoid write buffer pile up too much, which could
|
|
// cause controllernode to timeout later when it needs to save a snapshot.
|
|
pFile->flush();
|
|
return NO_ERROR;
|
|
}
|
|
|
|
/***********************************************************
|
|
* DESCRIPTION:
|
|
* Write out (reinitialize) a partial extent in a dictionary store file.
|
|
* A mutex is not used to prevent contention between threads,
|
|
* because the extent should already be in place on disk; so
|
|
* disk fragmentation is not an issue.
|
|
* PARAMETERS:
|
|
* pFile (in) - IDBDataFile* of column segment file to be written to
|
|
* startOffset(in)-file offset where we are to begin writing blocks
|
|
* nBlocks (in) - number of blocks to be written to the extent
|
|
* blockHdrInit(in) - data used to initialize each block
|
|
* blockHdrInitSize(in) - number of bytes in blockHdrInit
|
|
* RETURN:
|
|
* returns ERR_FILE_WRITE if an error occurs,
|
|
* else returns NO_ERROR.
|
|
***********************************************************/
|
|
int FileOp::reInitPartialDctnryExtent(
|
|
IDBDataFile* pFile,
|
|
long long startOffset,
|
|
int nBlocks,
|
|
unsigned char* blockHdrInit,
|
|
int blockHdrInitSize )
|
|
{
|
|
int rc = setFileOffset( pFile, startOffset, SEEK_SET );
|
|
|
|
if (rc != NO_ERROR)
|
|
return rc;
|
|
|
|
if (nBlocks == 0)
|
|
return NO_ERROR;
|
|
|
|
// Determine the number of blocks in each call to fwrite(), and the
|
|
// number of fwrite() calls to make, based on this. In other words,
|
|
// we put a cap on the "writeSize" so that we don't allocate and write
|
|
// an entire extent at once for the 64M row extents.
|
|
int writeSize = nBlocks * BYTE_PER_BLOCK; // 1M and 8M row extent size
|
|
int loopCount = 0;
|
|
int remainderSize = writeSize;
|
|
|
|
if (nBlocks > MAX_NBLOCKS) // 64M row extent size
|
|
{
|
|
writeSize = MAX_NBLOCKS * BYTE_PER_BLOCK;
|
|
loopCount = nBlocks / MAX_NBLOCKS;
|
|
remainderSize = nBlocks - (loopCount * MAX_NBLOCKS);
|
|
nBlocks = MAX_NBLOCKS;
|
|
}
|
|
|
|
// Allocate a buffer, initialize it, and use it to initialize the extent
|
|
// Store in scoped_array to insure it's deletion.
|
|
// Create scope {...} to manage deletion of writeBuf.
|
|
{
|
|
unsigned char* writeBuf = new unsigned char[writeSize];
|
|
boost::scoped_array<unsigned char> writeBufPtr( writeBuf );
|
|
|
|
memset(writeBuf, 0, writeSize);
|
|
|
|
for (int i = 0; i < nBlocks; i++)
|
|
{
|
|
memcpy( writeBuf + (i * BYTE_PER_BLOCK),
|
|
blockHdrInit,
|
|
blockHdrInitSize );
|
|
}
|
|
|
|
for (int j = 0; j < loopCount; j++)
|
|
{
|
|
if (pFile->write( writeBuf, writeSize ) != writeSize)
|
|
{
|
|
return ERR_FILE_WRITE;
|
|
}
|
|
}
|
|
|
|
if (remainderSize > 0)
|
|
{
|
|
if (pFile->write( writeBuf, remainderSize ) != remainderSize)
|
|
{
|
|
return ERR_FILE_WRITE;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Synchronize here to avoid write buffer pile up too much, which could
|
|
// cause controllernode to timeout later when it needs to save a snapshot.
|
|
pFile->flush();
|
|
return NO_ERROR;
|
|
}
|
|
|
|
/***********************************************************
|
|
* DESCRIPTION:
|
|
* PARAMETERS:
|
|
* pFile - file handle
|
|
* fileSize (out) - file size in bytes
|
|
* RETURN:
|
|
* error code
|
|
***********************************************************/
|
|
int FileOp::getFileSize( IDBDataFile* pFile, long long& fileSize ) const
|
|
{
|
|
fileSize = 0;
|
|
|
|
if ( pFile == NULL )
|
|
return ERR_FILE_NULL;
|
|
|
|
fileSize = pFile->size();
|
|
|
|
if ( fileSize < 0 )
|
|
{
|
|
fileSize = 0;
|
|
return ERR_FILE_STAT;
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
/***********************************************************
|
|
* DESCRIPTION:
|
|
* Get file size using file id
|
|
* PARAMETERS:
|
|
* fid - column OID
|
|
* dbroot - DBRoot of applicable segment file
|
|
* partition - partition of applicable segment file
|
|
* segment - segment of applicable segment file
|
|
* fileSize (out) - current file size for requested segment file
|
|
* RETURN:
|
|
* NO_ERROR if okay, else an error return code.
|
|
***********************************************************/
|
|
int FileOp::getFileSize( FID fid, uint16_t dbRoot,
|
|
uint32_t partition, uint16_t segment,
|
|
long long& fileSize ) const
|
|
{
|
|
fileSize = 0;
|
|
|
|
char fileName[FILE_NAME_SIZE];
|
|
RETURN_ON_ERROR( getFileName(fid, fileName,
|
|
dbRoot, partition, segment) );
|
|
|
|
fileSize = IDBPolicy::size( fileName );
|
|
|
|
if ( fileSize < 0 )
|
|
{
|
|
fileSize = 0;
|
|
return ERR_FILE_STAT;
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
/***********************************************************
|
|
* DESCRIPTION:
|
|
* Check whether it is a directory
|
|
* PARAMETERS:
|
|
* dirName - directory name
|
|
* RETURN:
|
|
* true if it is, false otherwise
|
|
***********************************************************/
|
|
bool FileOp::isDir( const char* dirName ) const
|
|
{
|
|
return IDBPolicy::isDir( dirName );
|
|
}
|
|
|
|
/***********************************************************
|
|
* DESCRIPTION:
|
|
* Convert an oid to a filename
|
|
* PARAMETERS:
|
|
* fid - fid
|
|
* fullFileName - file name
|
|
* bCreateDir - whether need to create a directory
|
|
* dbRoot - DBRoot where file is to be located; 1->DBRoot1,
|
|
* 2->DBRoot2, etc. If bCreateDir is false, meaning we
|
|
* are not creating the file but only searching for an
|
|
* existing file, then dbRoot can be 0, and oid2FileName
|
|
* will search all the DBRoots for the applicable filename.
|
|
* partition - Partition number to be used in filepath subdirectory
|
|
* segment - Segment number to be used in filename
|
|
* RETURN:
|
|
* NO_ERROR if success, other if fail
|
|
***********************************************************/
|
|
int FileOp::oid2FileName( FID fid,
|
|
char* fullFileName,
|
|
bool bCreateDir,
|
|
uint16_t dbRoot,
|
|
uint32_t partition,
|
|
uint16_t segment) const
|
|
{
|
|
#ifdef SHARED_NOTHING_DEMO_2
|
|
|
|
if (fid >= 10000)
|
|
{
|
|
char root[FILE_NAME_SIZE];
|
|
Config::getSharedNothingRoot(root);
|
|
sprintf(fullFileName, "%s/FILE%d", root, fid);
|
|
return NO_ERROR;
|
|
}
|
|
|
|
#endif
|
|
|
|
// Need this stub to use ColumnOp::writeRow in the unit tests
|
|
#ifdef WITH_UNIT_TESTS
|
|
if (fid == 42)
|
|
{
|
|
sprintf(fullFileName, "./versionbuffer");
|
|
return NO_ERROR;
|
|
}
|
|
#endif
|
|
|
|
/* If is a version buffer file, the format is different. */
|
|
if (fid < 1000)
|
|
{
|
|
/* Get the dbroot #
|
|
* Get the root of that dbroot
|
|
* Add "/versionbuffer.cdf"
|
|
*/
|
|
BRM::DBRM dbrm;
|
|
int _dbroot = dbrm.getDBRootOfVBOID(fid);
|
|
|
|
if (_dbroot < 0)
|
|
return ERR_INVALID_VBOID;
|
|
|
|
snprintf(fullFileName, FILE_NAME_SIZE,
|
|
"%s/versionbuffer.cdf", Config::getDBRootByNum(_dbroot).c_str());
|
|
return NO_ERROR;
|
|
}
|
|
|
|
//Get hashed part of the filename. This is the tail-end of the filename path,
|
|
// excluding the DBRoot.
|
|
char tempFileName[FILE_NAME_SIZE];
|
|
char dbDir[MAX_DB_DIR_LEVEL][MAX_DB_DIR_NAME_SIZE];
|
|
RETURN_ON_ERROR((Convertor::oid2FileName(
|
|
fid, tempFileName, dbDir, partition, segment)));
|
|
|
|
// see if file exists in specified DBRoot; return if found
|
|
if (dbRoot > 0)
|
|
{
|
|
sprintf(fullFileName, "%s/%s", Config::getDBRootByNum(dbRoot).c_str(),
|
|
tempFileName);
|
|
|
|
//std::cout << "oid2FileName() OID: " << fid <<
|
|
// " searching for file: " << fullFileName <<std::endl;
|
|
// if (access(fullFileName, R_OK) == 0) return NO_ERROR;
|
|
//@Bug 5397
|
|
if (IDBPolicy::exists( fullFileName ))
|
|
return NO_ERROR;
|
|
|
|
//file wasn't found, user doesn't want dirs to be created, we're done
|
|
if (!bCreateDir)
|
|
return NO_ERROR;
|
|
|
|
//std::cout << "oid2FileName() OID: " << fid <<
|
|
// " creating file: " << fullFileName <<std::endl;
|
|
}
|
|
else
|
|
{
|
|
//Now try to find the file in each of the DBRoots.
|
|
std::vector<std::string> dbRootPathList;
|
|
Config::getDBRootPathList( dbRootPathList );
|
|
|
|
for (unsigned i = 0; i < dbRootPathList.size(); i++)
|
|
{
|
|
sprintf(fullFileName, "%s/%s", dbRootPathList[i].c_str(),
|
|
tempFileName);
|
|
|
|
//found it, nothing more to do, return
|
|
//if (access(fullFileName, R_OK) == 0) return NO_ERROR;
|
|
//@Bug 5397
|
|
if (IDBPolicy::exists( fullFileName ))
|
|
return NO_ERROR;
|
|
}
|
|
|
|
//file wasn't found, user didn't specify DBRoot so we can't create
|
|
return ERR_FILE_NOT_EXIST;
|
|
}
|
|
|
|
/*
|
|
char dirName[FILE_NAME_SIZE];
|
|
|
|
sprintf( dirName, "%s/%s", Config::getDBRootByNum(dbRoot).c_str(),
|
|
dbDir[0] );
|
|
if( !isDir( dirName ) )
|
|
RETURN_ON_ERROR( createDir( dirName ));
|
|
|
|
sprintf( dirName, "%s/%s", dirName, dbDir[1] );
|
|
if( !isDir( dirName ) )
|
|
RETURN_ON_ERROR( createDir( dirName ));
|
|
|
|
sprintf( dirName, "%s/%s", dirName, dbDir[2] );
|
|
if( !isDir( dirName ) )
|
|
RETURN_ON_ERROR( createDir( dirName ));
|
|
|
|
sprintf( dirName, "%s/%s", dirName, dbDir[3] );
|
|
if( !isDir( dirName ) )
|
|
RETURN_ON_ERROR( createDir( dirName ));
|
|
|
|
sprintf( dirName, "%s/%s", dirName, dbDir[4] );
|
|
if( !isDir( dirName ) )
|
|
RETURN_ON_ERROR( createDir( dirName ));
|
|
*/
|
|
|
|
std::stringstream aDirName;
|
|
|
|
aDirName << Config::getDBRootByNum(dbRoot).c_str() << "/" << dbDir[0];
|
|
|
|
if (!isDir((aDirName.str()).c_str()))
|
|
RETURN_ON_ERROR( createDir((aDirName.str()).c_str()) );
|
|
|
|
aDirName << "/" << dbDir[1];
|
|
|
|
if (!isDir(aDirName.str().c_str()))
|
|
RETURN_ON_ERROR( createDir(aDirName.str().c_str()) );
|
|
|
|
aDirName << "/" << dbDir[2];
|
|
|
|
if (!isDir(aDirName.str().c_str()))
|
|
RETURN_ON_ERROR( createDir(aDirName.str().c_str()) );
|
|
|
|
aDirName << "/" << dbDir[3];
|
|
|
|
if (!isDir(aDirName.str().c_str()))
|
|
RETURN_ON_ERROR( createDir(aDirName.str().c_str()) );
|
|
|
|
aDirName << "/" << dbDir[4];
|
|
|
|
if (!isDir(aDirName.str().c_str()))
|
|
RETURN_ON_ERROR( createDir(aDirName.str().c_str()) );
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
/***********************************************************
|
|
* DESCRIPTION:
|
|
* Search for directory path associated with specified OID.
|
|
* If the OID is a version buffer file, it returns the whole
|
|
* filename.
|
|
* PARAMETERS:
|
|
* fid - (in) OID to search for
|
|
* pFile - (out) OID directory path (including DBRoot) that is found
|
|
* RETURN:
|
|
* NO_ERROR if OID dir path found, else returns ERR_FILE_NOT_EXIST
|
|
***********************************************************/
|
|
int FileOp::oid2DirName( FID fid, char* oidDirName ) const
|
|
{
|
|
char tempFileName[FILE_NAME_SIZE];
|
|
char dbDir[MAX_DB_DIR_LEVEL][MAX_DB_DIR_NAME_SIZE];
|
|
|
|
/* If is a version buffer file, the format is different. */
|
|
if (fid < 1000)
|
|
{
|
|
/* Get the dbroot #
|
|
* Get the root of that dbroot
|
|
*/
|
|
BRM::DBRM dbrm;
|
|
int _dbroot = dbrm.getDBRootOfVBOID(fid);
|
|
|
|
if (_dbroot < 0)
|
|
return ERR_INVALID_VBOID;
|
|
|
|
snprintf(oidDirName, FILE_NAME_SIZE, "%s",
|
|
Config::getDBRootByNum(_dbroot).c_str());
|
|
return NO_ERROR;
|
|
}
|
|
|
|
RETURN_ON_ERROR((Convertor::oid2FileName(
|
|
fid, tempFileName, dbDir, 0, 0)));
|
|
|
|
//Now try to find the directory in each of the DBRoots.
|
|
std::vector<std::string> dbRootPathList;
|
|
Config::getDBRootPathList( dbRootPathList );
|
|
|
|
for (unsigned i = 0; i < dbRootPathList.size(); i++)
|
|
{
|
|
sprintf(oidDirName, "%s/%s/%s/%s/%s",
|
|
dbRootPathList[i].c_str(),
|
|
dbDir[0], dbDir[1], dbDir[2], dbDir[3]);
|
|
|
|
//found it, nothing more to do, return
|
|
//@Bug 5397. use the new way to check
|
|
if (IDBPolicy::exists( oidDirName ))
|
|
return NO_ERROR;
|
|
}
|
|
|
|
return ERR_FILE_NOT_EXIST;
|
|
}
|
|
|
|
/***********************************************************
|
|
* DESCRIPTION:
|
|
* Construct directory path for the specified fid (OID), DBRoot, and
|
|
* partition number. Directory path need not exist, nor is it created.
|
|
* PARAMETERS:
|
|
* fid - (in) OID of interest
|
|
* dbRoot - (in) DBRoot of interest
|
|
* partition - (in) partition of interest
|
|
* dirName - (out) constructed directory path
|
|
* RETURN:
|
|
* NO_ERROR if path is successfully constructed.
|
|
***********************************************************/
|
|
int FileOp::getDirName( FID fid, uint16_t dbRoot,
|
|
uint32_t partition,
|
|
std::string& dirName) const
|
|
{
|
|
char tempFileName[FILE_NAME_SIZE];
|
|
char dbDir[MAX_DB_DIR_LEVEL][MAX_DB_DIR_NAME_SIZE];
|
|
|
|
RETURN_ON_ERROR((Convertor::oid2FileName(
|
|
fid, tempFileName, dbDir, partition, 0)));
|
|
|
|
std::string rootPath = Config::getDBRootByNum( dbRoot );
|
|
std::ostringstream oss;
|
|
oss << rootPath << '/' <<
|
|
dbDir[0] << '/' <<
|
|
dbDir[1] << '/' <<
|
|
dbDir[2] << '/' <<
|
|
dbDir[3] << '/' <<
|
|
dbDir[4];
|
|
dirName = oss.str();
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
/***********************************************************
|
|
* DESCRIPTION:
|
|
* Open a file
|
|
* PARAMETERS:
|
|
* fileName - file name with complete path
|
|
* pFile - file handle
|
|
* RETURN:
|
|
* true if exists, false otherwise
|
|
***********************************************************/
|
|
// @bug 5572 - HDFS usage: add *.tmp file backup flag
|
|
IDBDataFile* FileOp::openFile( const char* fileName,
|
|
const char* mode,
|
|
const int ioColSize,
|
|
bool useTmpSuffix ) const
|
|
{
|
|
IDBDataFile* pFile;
|
|
errno = 0;
|
|
|
|
unsigned opts;
|
|
|
|
if (ioColSize > 0)
|
|
opts = IDBDataFile::USE_VBUF;
|
|
else
|
|
opts = IDBDataFile::USE_NOVBUF;
|
|
|
|
if ((useTmpSuffix) && idbdatafile::IDBPolicy::useHdfs())
|
|
opts |= IDBDataFile::USE_TMPFILE;
|
|
|
|
pFile = IDBDataFile::open(
|
|
IDBPolicy::getType( fileName, IDBPolicy::WRITEENG ),
|
|
fileName,
|
|
mode,
|
|
opts,
|
|
ioColSize );
|
|
|
|
if (pFile == NULL)
|
|
{
|
|
int errRc = errno;
|
|
std::ostringstream oss;
|
|
std::string errnoMsg;
|
|
Convertor::mapErrnoToString(errRc, errnoMsg);
|
|
oss << "FileOp::openFile(): fopen(" << fileName <<
|
|
", " << mode << "): errno = " << errRc <<
|
|
": " << errnoMsg;
|
|
logging::Message::Args args;
|
|
args.add(oss.str());
|
|
SimpleSysLog::instance()->logMsg(args,
|
|
logging::LOG_TYPE_CRITICAL,
|
|
logging::M0006);
|
|
SimpleSysLog::instance()->logMsg(args,
|
|
logging::LOG_TYPE_ERROR,
|
|
logging::M0006);
|
|
}
|
|
|
|
return pFile;
|
|
}
|
|
|
|
// @bug 5572 - HDFS usage: add *.tmp file backup flag
|
|
IDBDataFile* FileOp::openFile( FID fid,
|
|
uint16_t dbRoot,
|
|
uint32_t partition,
|
|
uint16_t segment,
|
|
std::string& segFile,
|
|
const char* mode, int ioColSize,
|
|
bool useTmpSuffix ) const
|
|
{
|
|
char fileName[FILE_NAME_SIZE];
|
|
int rc;
|
|
|
|
//fid2FileName( fileName, fid );
|
|
RETURN_ON_WE_ERROR( ( rc = getFileName( fid, fileName,
|
|
dbRoot, partition, segment ) ), NULL );
|
|
|
|
// disable buffering for versionbuffer file
|
|
if (fid < 1000)
|
|
ioColSize = 0;
|
|
|
|
IDBDataFile* pF = openFile( fileName, mode, ioColSize, useTmpSuffix );
|
|
|
|
segFile = fileName;
|
|
|
|
return pF;
|
|
}
|
|
|
|
/***********************************************************
|
|
* DESCRIPTION:
|
|
* Read a portion of file to a buffer
|
|
* PARAMETERS:
|
|
* pFile - file handle
|
|
* readBuf - read buffer
|
|
* readSize - the size to read
|
|
* RETURN:
|
|
* NO_ERROR if success
|
|
* ERR_FILE_NULL if file handle is NULL
|
|
* ERR_FILE_READ if something wrong in reading the file
|
|
***********************************************************/
|
|
int FileOp::readFile( IDBDataFile* pFile, unsigned char* readBuf,
|
|
int readSize ) const
|
|
{
|
|
if ( pFile != NULL )
|
|
{
|
|
int bc = pFile->read( readBuf, readSize );
|
|
if (bc != readSize)
|
|
{
|
|
// MCOL-498 EOF if a next block is empty
|
|
if (bc == 0)
|
|
{
|
|
return ERR_FILE_EOF;
|
|
}
|
|
return ERR_FILE_READ;
|
|
}
|
|
}
|
|
else
|
|
return ERR_FILE_NULL;
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
/***********************************************************
|
|
* Reads contents of headers from "pFile" and stores into "hdrs".
|
|
***********************************************************/
|
|
int FileOp::readHeaders( IDBDataFile* pFile, char* hdrs ) const
|
|
{
|
|
RETURN_ON_ERROR( setFileOffset(pFile, 0) );
|
|
RETURN_ON_ERROR( readFile( pFile, reinterpret_cast<unsigned char*>(hdrs),
|
|
(IDBCompressInterface::HDR_BUF_LEN * 2) ) );
|
|
IDBCompressInterface compressor;
|
|
int rc = compressor.verifyHdr( hdrs );
|
|
|
|
if (rc != 0)
|
|
{
|
|
return ERR_COMP_VERIFY_HDRS;
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
/***********************************************************
|
|
* Reads contents of headers from "pFile" and stores into "hdr1" and "hdr2".
|
|
***********************************************************/
|
|
int FileOp::readHeaders( IDBDataFile* pFile, char* hdr1, char* hdr2 ) const
|
|
{
|
|
unsigned char* hdrPtr = reinterpret_cast<unsigned char*>(hdr1);
|
|
RETURN_ON_ERROR( setFileOffset(pFile, 0) );
|
|
RETURN_ON_ERROR( readFile( pFile, hdrPtr,
|
|
IDBCompressInterface::HDR_BUF_LEN ));
|
|
|
|
IDBCompressInterface compressor;
|
|
int ptrSecSize = compressor.getHdrSize(hdrPtr) -
|
|
IDBCompressInterface::HDR_BUF_LEN;
|
|
return readFile( pFile, reinterpret_cast<unsigned char*>(hdr2),
|
|
ptrSecSize );
|
|
}
|
|
|
|
/***********************************************************
|
|
* DESCRIPTION: No change Old signature
|
|
* Read a portion of file to a buffer
|
|
* PARAMETERS:
|
|
* pFile - file handle
|
|
* offset - file offset
|
|
* origin - can be SEEK_SET, or SEEK_CUR, or SEEK_END
|
|
* RETURN:
|
|
* NO_ERROR if success
|
|
* ERR_FILE_NULL if file handle is NULL
|
|
* ERR_FILE_SEEK if something wrong in setting the position
|
|
***********************************************************/
|
|
int FileOp::setFileOffset( IDBDataFile* pFile, long long offset, int origin ) const
|
|
{
|
|
int rc;
|
|
long long fboOffset = offset; // workaround solution to pass leakcheck error
|
|
|
|
if ( pFile == NULL )
|
|
return ERR_FILE_NULL;
|
|
|
|
if ( offset < 0 )
|
|
return ERR_FILE_FBO_NEG;
|
|
|
|
rc = pFile->seek( fboOffset, origin );
|
|
|
|
if (rc)
|
|
return ERR_FILE_SEEK;
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
/***********************************************************
|
|
* DESCRIPTION:
|
|
* Read a portion of file to a buffer
|
|
* PARAMETERS:
|
|
* pFile - file handle
|
|
* offset - file offset
|
|
* origin - can be SEEK_SET, or SEEK_CUR, or SEEK_END
|
|
* RETURN:
|
|
* NO_ERROR if success
|
|
* ERR_FILE_NULL if file handle is NULL
|
|
* ERR_FILE_SEEK if something wrong in setting the position
|
|
***********************************************************/
|
|
int FileOp::setFileOffsetBlock( IDBDataFile* pFile, uint64_t lbid, int origin) const
|
|
{
|
|
long long fboOffset = 0;
|
|
int fbo = 0;
|
|
|
|
// only when fboFlag is false, we get in here
|
|
uint16_t dbRoot;
|
|
uint32_t partition;
|
|
uint16_t segment;
|
|
RETURN_ON_ERROR( BRMWrapper::getInstance()->getFboOffset(
|
|
lbid, dbRoot, partition, segment, fbo ) );
|
|
fboOffset = ((long long)fbo) * (long)BYTE_PER_BLOCK;
|
|
|
|
return setFileOffset( pFile, fboOffset, origin );
|
|
}
|
|
|
|
/***********************************************************
|
|
* DESCRIPTION:
|
|
* Truncate file to the specified size.
|
|
* PARAMETERS:
|
|
* pFile - file handle
|
|
* fileSize - size of file in bytes.
|
|
* RETURN:
|
|
* NO_ERROR if success
|
|
* ERR_FILE_NULL if file handle is NULL
|
|
* ERR_FILE_SEEK if something wrong in setting the position
|
|
***********************************************************/
|
|
int FileOp::truncateFile( IDBDataFile* pFile, long long fileSize ) const
|
|
{
|
|
if ( pFile == NULL )
|
|
return ERR_FILE_NULL;
|
|
|
|
if ( pFile->truncate( fileSize ) != 0 )
|
|
return ERR_FILE_TRUNCATE;
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
/***********************************************************
|
|
* DESCRIPTION:
|
|
* Write a buffer to a file at at current location
|
|
* PARAMETERS:
|
|
* pFile - file handle
|
|
* writeBuf - write buffer
|
|
* writeSize - the write size
|
|
* RETURN:
|
|
* NO_ERROR if success
|
|
* ERR_FILE_NULL if file handle is NULL
|
|
* ERR_FILE_WRITE if something wrong in writing to the file
|
|
***********************************************************/
|
|
int FileOp::writeFile( IDBDataFile* pFile, const unsigned char* writeBuf,
|
|
int writeSize ) const
|
|
{
|
|
if ( pFile != NULL )
|
|
{
|
|
if ( pFile->write( writeBuf, writeSize ) != writeSize )
|
|
return ERR_FILE_WRITE;
|
|
}
|
|
else
|
|
return ERR_FILE_NULL;
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
/***********************************************************
|
|
* DESCRIPTION:
|
|
* Determine whether the applicable filesystem has room to add the
|
|
* specified number of blocks (where the blocks contain BYTE_PER_BLOCK
|
|
* bytes).
|
|
* PARAMETERS:
|
|
* fileName - file whose file system is to be checked. Does not have to
|
|
* be a complete file name. Dir path is sufficient.
|
|
* nBlock - number of 8192-byte blocks to be added
|
|
* RETURN:
|
|
* true if there is room for the blocks or it can not be determined;
|
|
* false if file system usage would exceed allowable threshold
|
|
***********************************************************/
|
|
bool FileOp::isDiskSpaceAvail(const std::string& fileName, int nBlocks) const
|
|
{
|
|
bool bSpaceAvail = true;
|
|
|
|
unsigned maxDiskUsage = Config::getMaxFileSystemDiskUsage();
|
|
|
|
if (maxDiskUsage < 100) // 100% means to disable the check
|
|
{
|
|
#ifdef _MSC_VER
|
|
ULARGE_INTEGER freeBytesAvail;
|
|
ULARGE_INTEGER totalBytesAvail;
|
|
|
|
if (GetDiskFreeSpaceEx(fileName.c_str(), &freeBytesAvail,
|
|
&totalBytesAvail, 0) != 0)
|
|
{
|
|
double avail = (double)freeBytesAvail.QuadPart;
|
|
double total = (double)totalBytesAvail.QuadPart;
|
|
double wanted = (double)nBlocks * (double)BYTE_PER_BLOCK;
|
|
|
|
//If we want more than there is, return an error
|
|
if (wanted > avail)
|
|
bSpaceAvail = false;
|
|
//If the remaining bytes would be too few, return an error
|
|
else if ((total - (avail - wanted)) / total * 100.0 > maxDiskUsage)
|
|
bSpaceAvail = false;
|
|
}
|
|
|
|
#else
|
|
struct statfs fStats;
|
|
int rc = statfs( fileName.c_str(), &fStats );
|
|
|
|
if (rc == 0)
|
|
{
|
|
double totalBlocks = fStats.f_blocks;
|
|
double blksToAlloc = (double)(nBlocks * BYTE_PER_BLOCK) /
|
|
fStats.f_bsize;
|
|
double freeBlocks = fStats.f_bavail - blksToAlloc;
|
|
|
|
if ((((totalBlocks - freeBlocks) / totalBlocks) * 100.0) > maxDiskUsage)
|
|
bSpaceAvail = false;
|
|
|
|
//std::cout << "isDiskSpaceAvail" <<
|
|
//": totalBlocks: " << totalBlocks <<
|
|
//"; blkSize: " << fStats.f_bsize <<
|
|
//"; nBlocks: " << nBlocks <<
|
|
//"; freeBlks: " << freeBlocks <<
|
|
//"; pctUsed: " << (((totalBlocks-freeBlocks)/totalBlocks)*100.0) <<
|
|
//"; bAvail: " << bSpaceAvail << std::endl;
|
|
}
|
|
|
|
#endif
|
|
}
|
|
|
|
return bSpaceAvail;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Virtual default functions follow; placeholders for derived class if they want
|
|
// to override (see ColumnOpCompress1 and DctnryCompress1 in /wrapper).
|
|
//------------------------------------------------------------------------------
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Expand current abbreviated extent to a full extent for column segment file
|
|
// associated with pFile. Function leaves fileposition at end of file after
|
|
// extent is expanded.
|
|
//------------------------------------------------------------------------------
|
|
int FileOp::expandAbbrevColumnExtent(
|
|
IDBDataFile* pFile, // FILE ptr to file where abbrev extent is to be expanded
|
|
uint16_t dbRoot, // The DBRoot of the file with the abbreviated extent
|
|
uint64_t emptyVal,// Empty value to be used in expanding the extent
|
|
int width ) // Width of the column (in bytes)
|
|
{
|
|
// Based on extent size, see how many blocks to add to fill the extent
|
|
int blksToAdd = ( ((int)BRMWrapper::getInstance()->getExtentRows() -
|
|
INITIAL_EXTENT_ROWS_TO_DISK) / BYTE_PER_BLOCK ) * width;
|
|
|
|
// Make sure there is enough disk space to expand the extent.
|
|
RETURN_ON_ERROR( setFileOffset( pFile, 0, SEEK_END ) );
|
|
|
|
// TODO-will have to address this DiskSpaceAvail check at some point
|
|
if ( !isDiskSpaceAvail(Config::getDBRootByNum(dbRoot), blksToAdd) )
|
|
{
|
|
return ERR_FILE_DISK_SPACE;
|
|
}
|
|
|
|
// Add blocks to turn the abbreviated extent into a full extent.
|
|
int rc = FileOp::initColumnExtent(pFile, dbRoot, blksToAdd, emptyVal, width,
|
|
false, // existing file
|
|
true, // expand existing extent
|
|
false, // n/a since not adding new extent
|
|
true); // optimize segment file extension
|
|
|
|
return rc;
|
|
}
|
|
|
|
void FileOp::setTransId(const TxnID& transId)
|
|
{
|
|
m_transId = transId;
|
|
}
|
|
|
|
void FileOp::setBulkFlag(bool isBulkLoad)
|
|
{
|
|
m_isBulk = isBulkLoad;
|
|
}
|
|
|
|
int FileOp::flushFile(int rc, std::map<FID, FID>& oids)
|
|
{
|
|
return NO_ERROR;
|
|
}
|
|
|
|
int FileOp::updateColumnExtent(IDBDataFile* pFile, int nBlocks)
|
|
{
|
|
return NO_ERROR;
|
|
}
|
|
|
|
int FileOp::updateDctnryExtent(IDBDataFile* pFile, int nBlocks)
|
|
{
|
|
return NO_ERROR;
|
|
}
|
|
|
|
void FileOp::setFixFlag(bool isFix)
|
|
{
|
|
m_isFix = isFix;
|
|
}
|
|
} //end of namespace
|
|
|