// Ryzom - MMORPG Framework
// Copyright (C) 2010 Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
#include "../pd_lib/pds_common.h"
#include "pds_database.h"
#include "pds_table.h"
#include "pds_type.h"
#include "database_adapter.h"
#include "db_manager.h"
#include "pd_lib/pd_server_utils.h"
#include "pd_lib/reference_builder.h"
#include "pd_lib/delta_builder.h"
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
using namespace NLMISC;
using namespace NLNET;
// Hosted by db_manager.cpp
extern NLMISC::CVariable DeltaUpdateRate;
/*
* Constructor
*/
CDatabase::CDatabase(uint32 id) : _Reference(id)
{
clear();
_State.Id = id;
}
/*
* Destructor
*/
CDatabase::~CDatabase()
{
PDS_FULL_DEBUG("delete()");
clear();
}
/*
* Massive Database clear
*/
void CDatabase::clear()
{
uint i;
for (i=0; i<_Types.size(); ++i)
{
if (_Types[i] != NULL)
delete _Types[i];
_Types[i] = NULL;
}
_Types.clear();
for (i=0; i<_Tables.size(); ++i)
{
if (_Tables[i] != NULL)
delete _Tables[i];
_Tables[i] = NULL;
}
_Tables.clear();
_Init = false;
//_ObjectList.clear();
_SetMap.clear();
_State.Name.clear();
_ServiceId.set(0xffff);
}
/*
* Init database
* \param xmlStream is the xml description of the database
*/
bool CDatabase::init()
{
PDS_DEBUG("init()");
// clear all before doing anything
clear();
const CDatabaseNode& db = _Description.getDatabaseNode();
_State.Name = db.Name;
uint i;
for (i=0; iinit(this, db.Types[i]))
{
PDS_WARNING("init(): failed to init type '%d'", i);
return false;
}
if (type->getId() != i)
{
PDS_WARNING("init(): failed to init type '%d'", i);
return false;
}
_Types.push_back(type);
}
for (i=0; iinit(this, db.Tables[i]) || table->getId() != i)
{
PDS_WARNING("init(): failed to init table '%d'", i);
return false;
}
_Tables.push_back(table);
}
for (i=0; i<_Tables.size(); ++i)
{
CTable* table = _Tables[i];
if (!table->buildColumns())
{
PDS_WARNING("init(): failed to build table '%d' columns", i);
return false;
}
}
for (i=0; i<_Tables.size(); ++i)
{
_Tables[i]->postInit();
}
_Init = true;
if (!notifyNewReference(false))
{
_Init = false;
return false;
}
// save description in log dir
std::string logDir = RY_PDS::CPDSLib::getLogDirectory(_State.Id);
if (!CFile::isExists(logDir) || !CFile::isDirectory(logDir))
{
if (!CFile::createDirectoryTree(logDir))
{
PDS_WARNING("init(): failed to create log root directory '%s'", logDir.c_str());
}
if (!CFile::setRWAccess(logDir))
{
PDS_WARNING("init(): failed, can't set RW access to directory '%s'", logDir.c_str());
}
}
// CTimestamp initDate;
// initDate.setToCurrent();
// _Description.saveDescription(logDir + initDate.toString() + ".description");
PDS_DEBUG("init() successful");
return true;
}
/*
* Checkup database
*/
bool CDatabase::checkup()
{
PDS_DEBUG("checkup()");
if (!initialised())
{
PDS_WARNING("checkup(): database not initialised");
return false;
}
uint i;
bool dbOk = true;
for (i=0; i<_Types.size(); ++i)
{
if (_Types[i] == NULL || !_Types[i]->initialised())
{
PDS_WARNING("checkup(): type '%d' not initialised", i);
continue;
}
}
for (i=0; i<_Tables.size(); ++i)
{
if (_Tables[i] == NULL || !_Tables[i]->initialised())
{
PDS_WARNING("checkup(): table '%d' not initialised", i);
continue;
}
CTable* table = _Tables[i];
const std::vector &columns = table->getColumns();
const std::vector &attributes = table->getAttributes();
uint j;
// walk through attributes
for (j=0; jinitialised())
{
PDS_WARNING("checkup(): attribute '%s:%d' not initialised", table->getName().c_str(), i);
dbOk = false;
continue;
}
const CAttribute* attribute = attributes[j];
if (attribute->getParent() != table)
{
PDS_WARNING("checkup(): attribute '%s:%s' doesn't point table '%d'", table->getName().c_str(), attribute->getName().c_str(), i);
dbOk = false;
}
uint k;
for (k=attribute->getOffset(); kgetOffset()+attribute->getColumns(); ++k)
{
if (k >= columns.size() || !columns[k].initialised())
{
PDS_WARNING("checkup(): attribute '%s:%s' points to not initialised column '%d'", table->getName().c_str(), attribute->getName().c_str(), k);
dbOk = false;
}
else if (columns[k].getParent() != attribute)
{
PDS_WARNING("checkup(): attribute '%s:%s' points to column '%s' that doesn't point back", table->getName().c_str(), attribute->getName().c_str(), columns[k].getName().c_str());
dbOk = false;
}
else
{
const CColumn &column = columns[k];
}
}
}
// walk through columns
for (j=0; jgetName().c_str(), j);
dbOk = false;
continue;
}
}
}
if (dbOk)
{
PDS_DEBUG("checkup(): Database is correct");
}
PDS_DEBUG("checkup() finished");
return true;
}
/*
* Initialise internal timestamps
*/
void CDatabase::initTimestamps()
{
// initialise update and creation timestamps to now
_CreationTimestamp.setToCurrent();
_MinuteUpdateTimestamp = _CreationTimestamp;
_HourUpdateTimestamp = _CreationTimestamp;
_DayUpdateTimestamp = _CreationTimestamp;
}
/*
* Get value as a string
* \param path is of the form '[tableindex|tablename].[$key|index].attrib1.attrib2'
*/
string CDatabase::getValue(const CLocatePath::TLocatePath &path)
{
if (path.size() < 3)
{
PDS_WARNING("getValue(): path is too short");
return "";
}
uint node = 0;
// select table
const CTable* table;
uint tableId;
if (sscanf(path[node].Name.c_str(), "%d", &tableId) == 1)
table = getTable(tableId);
else
table = getTable(path[node].Name);
if (table == NULL)
{
PDS_WARNING("getValue(): unable to select table '%s'", path[node].Name.c_str());
return "";
}
++node;
// select row
RY_PDS::CObjectIndex object;
if (path[node].Name[0] == '$')
{
uint64 key;
if (sscanf(path[node].Name.c_str()+1, "%"NL_I64"X", &key) != 1)
{
PDS_WARNING("getValue(): unable to select mapped row '%s'", path[node].Name.c_str());
return "";
}
object = table->getMappedRow(key);
}
else
{
RY_PDS::TRowIndex row;
if (sscanf(path[node].Name.c_str(), "%u", &row) != 1)
{
PDS_WARNING("getValue(): unable to select row '%s'", path[node].Name.c_str());
return "";
}
object = RY_PDS::CObjectIndex((RY_PDS::TTableIndex)table->getId(), row);
}
if (!object.isValid() || !isAllocated(object))
{
PDS_WARNING("getValue(): object '%s' not accessible", path[node].Name.c_str());
return "";
}
++node;
const CTable* subTable = table;
// browse through row
while (node < path.size())
{
const CAttribute* attribute = subTable->getAttribute(path[node].Name);
if (attribute == NULL)
{
PDS_WARNING("getValue(): '%s' is not an attribute of '%s'", path[node].Name.c_str(), subTable->getName().c_str());
return "";
}
++node;
}
return "";
}
/*
* Get Table
*/
const CTable* CDatabase::getTable(const std::string &name) const
{
uint i;
for (i=0; i<_Tables.size(); ++i)
if (_Tables[i] != NULL && _Tables[i]->getName() == name)
return _Tables[i];
return NULL;
}
/*
* Get Type
*/
const CType* CDatabase::getType(const std::string &name) const
{
uint i;
for (i=0; i<_Types.size(); ++i)
if (_Types[i] != NULL && _Types[i]->getName() == name)
return _Types[i];
return NULL;
}
/*
* Get Attribute
*/
const CAttribute* CDatabase::getAttribute(uint32 tableId, uint32 attributeId) const
{
const CTable* table = getTable(tableId);
if (table == NULL)
return NULL;
return table->getAttribute(attributeId);
}
/*
* Get Column
*/
const CColumn* CDatabase::getColumn(uint32 tableId, uint32 columnId) const
{
const CTable* table = getTable(tableId);
if (table == NULL)
return NULL;
return table->getColumn(columnId);
}
/*
* Allocate a row in a table
* \param index is the table/row to allocate
* Return true if succeded
*/
bool CDatabase::allocate(const RY_PDS::CObjectIndex &index)
{
H_AUTO(PDS_Database_allocate);
if (!initialised())
{
PDS_WARNING("allocate(): database not initialised");
return false;
}
if (!index.isValid())
{
PDS_WARNING("allocate(): index '%s' is not valid", index.toString().c_str());
return false;
}
CTable *table = getNonConstTable(index.table());
if (table == NULL || !table->initialised())
{
PDS_WARNING("allocate(): table '%d' is not initialised", index.table());
return false;
}
bool success = table->allocate(index.row());
if (success)
PDS_FULL_DEBUG("allocated '%s' successfully", index.toString().c_str());
return success;
}
/*
* Deallocate a row in a table
* \param index is the table/row to deallocate
* Return true if succeded
*/
bool CDatabase::deallocate(const RY_PDS::CObjectIndex &index)
{
H_AUTO(PDS_Database_deallocate);
if (!initialised())
{
PDS_WARNING("deallocate(): database not initialised");
return false;
}
if (!index.isValid())
{
PDS_WARNING("deallocate(): index '%s' is not valid", index.toString().c_str());
return false;
}
CTable *table = getNonConstTable(index.table());
if (table == NULL || !table->initialised())
{
PDS_WARNING("deallocate(): table '%d' is not initialised", index.table());
return false;
}
bool success = table->deallocate(index.row());
if (success)
PDS_FULL_DEBUG("deallocated '%s' successfully", index.toString().c_str());
return success;
}
/*
* Tells if an object is allocated
* \param object is the object index to test
*/
bool CDatabase::isAllocated(const RY_PDS::CObjectIndex &index) const
{
const CTable* table = getTable(index.table());
return table != NULL && table->isAllocated(index.row());
}
/*
* Map a row in a table
* \param index is the table/row to allocate
* \param key is the 64 bits row key
* Return true if succeded
*/
bool CDatabase::mapRow(const RY_PDS::CObjectIndex &index, uint64 key)
{
if (!initialised())
{
PDS_WARNING("mapRow(): database not initialised");
return false;
}
if (!index.isValid())
{
PDS_WARNING("mapRow(): index '%s' is not valid", index.toString().c_str());
return false;
}
CTable *table = getNonConstTable(index.table());
if (table == NULL || !table->initialised())
{
PDS_WARNING("deallocate(): table '%d' is not initialised", index.table());
return false;
}
bool success = table->mapRow(index, key);
if (success)
PDS_FULL_DEBUG("mapped '%016"NL_I64"X' to '%s' successfully", key, index.toString().c_str());
return success;
}
/*
* Unmap a row in a table
* \param tableIndex is the table to find row
* \param key is the 64 bits row key
* Return true if succeded
*/
bool CDatabase::unmapRow(RY_PDS::TTableIndex tableIndex, uint64 key)
{
if (!initialised())
{
PDS_WARNING("unmapRow(): database not initialised");
return false;
}
CTable *table = getNonConstTable(tableIndex);
if (table == NULL || !table->initialised())
{
PDS_WARNING("deallocate(): table '%d' is not initialised", tableIndex);
return false;
}
bool success = table->unmapRow(key);
if (success)
PDS_FULL_DEBUG("unmapped '%016"NL_I64"X' successfully", key);
return success;
}
/**
* Get a mapped row
* \param tableIndex is the table in which the row is mapped
* \param key is the 64 bits row key
* Return a valid CObjectIndex if success
*/
RY_PDS::CObjectIndex CDatabase::getMappedRow(RY_PDS::TTableIndex tableIndex, uint64 key) const
{
if (!initialised())
{
PDS_WARNING("getMappedRow(): database not initialised");
return RY_PDS::CObjectIndex();
}
const CTable *table = getTable(tableIndex);
if (table == NULL || !table->initialised())
{
PDS_WARNING("getMappedRow(): table '%d' is not initialised", tableIndex);
return RY_PDS::CObjectIndex();
}
return table->getMappedRow(key);
}
/**
* Release a row in a table
* \param index is the table/row to release
* Return true if succeded
*/
bool CDatabase::release(const RY_PDS::CObjectIndex &index)
{
if (!initialised())
{
PDS_WARNING("release(): database not initialised");
return false;
}
if (!index.isValid())
{
PDS_WARNING("release(): index '%s' is not valid", index.toString().c_str());
return false;
}
CTable *table = getNonConstTable(index.table());
if (table == NULL || !table->initialised())
{
PDS_WARNING("release(): table '%d' is not initialised", index.table());
return false;
}
bool success = table->release(index.row());
if (success)
PDS_FULL_DEBUG("released '%s' successfully", index.toString().c_str());
return success;
}
/*
* Release all rows in all table
* Typically, the client disconnected, there is no need to keep rows
*/
bool CDatabase::releaseAll()
{
if (!initialised())
{
PDS_WARNING("releaseAll(): database not initialised");
return false;
}
uint i;
for (i=0; i<_Tables.size(); ++i)
{
if (_Tables[i] != NULL && _Tables[i]->initialised())
{
_Tables[i]->releaseAll();
}
}
return true;
}
//
// Set method
//
bool CDatabase::set(RY_PDS::TTableIndex table, RY_PDS::TRowIndex row, RY_PDS::TColumnIndex column, uint datasize, const void* dataptr)
{
H_AUTO(PDS_Database_set);
if (!initialised())
{
PDS_WARNING("set(): database not initialised");
return false;
}
CTable* _table = getNonConstTable(table);
if (_table == NULL)
{
PDS_WARNING("set(): table '%d' is NULL", table);
return false;
}
bool success = _table->set(row, column, datasize, dataptr);
if (success)
PDS_FULL_DEBUG("set '%s' column '%d' successfully", RY_PDS::CObjectIndex(table, row).toString().c_str(), column);
return success;
}
/*
* Set an object parent
*/
bool CDatabase::setParent(RY_PDS::TTableIndex table, RY_PDS::TRowIndex row, RY_PDS::TColumnIndex column, const RY_PDS::CObjectIndex &parent)
{
H_AUTO(PDS_Database_setParent);
if (!initialised())
{
PDS_WARNING("set(): database not initialised");
return false;
}
CTable* _table = getNonConstTable(table);
if (_table == NULL)
{
PDS_WARNING("set(): table '%d' is NULL", table);
return false;
}
bool success = _table->setParent(row, column, parent);
if (success)
PDS_FULL_DEBUG("set '%s' column '%d' successfully", RY_PDS::CObjectIndex(table, row).toString().c_str(), column);
return success;
}
//
// Get method
//
bool CDatabase::get(RY_PDS::TTableIndex table, RY_PDS::TRowIndex row, RY_PDS::TColumnIndex column, uint& datasize, void* dataptr, TDataType &type)
{
if (!initialised())
{
PDS_WARNING("set(): database not initialised");
return false;
}
CTable* _table = getNonConstTable(table);
if (_table == NULL)
{
PDS_WARNING("set(): table '%d' is NULL", table);
return false;
}
bool success = _table->get(row, column, datasize, dataptr, type);
if (success)
PDS_FULL_DEBUG("get '%s' column '%d' successfully", RY_PDS::CObjectIndex(table, row).toString().c_str(), column);
return success;
}
/*
* Display database
*/
void CDatabase::display(NLMISC::CLog* log, bool displayHeader) const
{
if (!initialised())
{
log->displayNL("Database not initialised");
}
log->displayNL("Database '%s'", _State.Name.c_str());
log->displayNL("%d types, %d tables", _Types.size(), _Tables.size());
uint i;
log->displayNL(" %-36s | %-15s | %-2s | %s", "TypeId/Name", "DataTypeId/Name", "Sz", "IsIndex");
for (i=0; i<_Types.size(); ++i)
{
if (_Types[i] == NULL)
log->display("** Type %d not initialised", i);
else
_Types[i]->display(log);
}
bool first = true;
for (i=0; i<_Tables.size(); ++i)
{
if (_Tables[i] == NULL)
{
log->display("** Table %d not initialised", i);
}
else
{
_Tables[i]->display(log, false, first);
first = false;
}
}
}
/*
* Dump database content and info of an object to xml
*/
void CDatabase::dumpToXml(const RY_PDS::CObjectIndex& index, NLMISC::IStream& xml, sint expandDepth)
{
if (xml.isReading() || !initialised())
return;
CTable* table = getNonConstTable(index.table());
if (table != NULL)
table->dumpToXml(index.row(), xml, expandDepth);
}
/*
* Set value with human readable parameters
*/
bool CDatabase::set(RY_PDS::TTableIndex table, RY_PDS::TRowIndex row, RY_PDS::TColumnIndex column, const std::string& type, const std::string &value)
{
TDataType datatype = getDataTypeFromName(type);
if (!checkDataType(datatype))
return false;
switch (datatype)
{
case PDS_bool:
case PDS_char:
case PDS_uint8:
case PDS_sint8:
{
uint8 data;
NLMISC::fromString(value, data);
return set(table, row, column, sizeof(data), &data);
}
break;
case PDS_ucchar:
case PDS_uint16:
case PDS_sint16:
{
uint16 data;
NLMISC::fromString(value, data);
return set(table, row, column, sizeof(data), &data);
}
break;
case PDS_uint32:
case PDS_sint32:
case PDS_enum:
case PDS_CSheetId:
case PDS_CNodeId:
{
uint32 data;
NLMISC::fromString(value, data);
return set(table, row, column, sizeof(data), &data);
}
break;
case PDS_uint64:
case PDS_sint64:
{
uint64 data;
sscanf(value.c_str(), "%016"NL_I64"d", &data);
return set(table, row, column, sizeof(data), &data);
}
break;
case PDS_CEntityId:
{
CEntityId data;
data.fromString(value.c_str());
return set(table, row, column, sizeof(data), &data);
}
break;
case PDS_float:
{
float data;
NLMISC::fromString(value, data);
return set(table, row, column, sizeof(data), &data);
}
break;
case PDS_double:
{
double data;
NLMISC::fromString(value, data);
return set(table, row, column, sizeof(data), &data);
}
break;
case PDS_Index:
{
RY_PDS::CObjectIndex data;
data.fromString(value.c_str());
return set(table, row, column, sizeof(data), &data);
}
break;
case PDS_dimension:
{
uint32 data;
NLMISC::fromString(value, data);
return set(table, row, column, sizeof(data), &data);
}
break;
}
return false;
}
/*
* Fetch data
*/
bool CDatabase::fetch(const RY_PDS::CObjectIndex& index, RY_PDS::CPData &data, bool fetchIndex)
{
H_AUTO(PDS_Database_fetch);
if (!initialised())
{
PDS_WARNING("set(): database not initialised");
return false;
}
CTable* table = getNonConstTable(index.table());
if (table == NULL)
{
PDS_WARNING("fetch(): unable to get table '%d'", index.table());
return false;
}
bool success = table->fetch(index.row(), data, fetchIndex);
if (success)
PDS_FULL_DEBUG("fetch '%s' successfully", index.toString().c_str());
return success;
}
/*
* Build index allocators
* One per table
*/
bool CDatabase::buildIndexAllocators(std::vector &allocators)
{
PDS_DEBUG("buildIndexAllocators()");
if (!initialised())
{
PDS_WARNING("buildIndexAllocators(): database not initialised");
return false;
}
allocators.clear();
allocators.resize(_Tables.size());
uint i;
for (i=0; i<_Tables.size(); ++i)
if (_Tables[i] != NULL && _Tables[i]->initialised())
_Tables[i]->buildIndexAllocator(allocators[i]);
return true;
}
/*
* Rebuild forwardrefs from backrefs
*/
bool CDatabase::rebuildForwardRefs()
{
if (!initialised())
{
PDS_WARNING("rebuildForwardRefs(): database not initialised");
return false;
}
// clear up object list
_SetMap.clear();
// the rebuild forwardrefs
uint i;
for (i=0; i<_Tables.size(); ++i)
{
if (_Tables[i] != NULL && _Tables[i]->initialised())
{
if (!_Tables[i]->resetForwardRefs())
{
PDS_WARNING("rebuildForwardRefs(): failed to resetForwardRefs(), abort");
return false;
}
}
}
for (i=0; i<_Tables.size(); ++i)
{
if (_Tables[i] != NULL && _Tables[i]->initialised())
{
if (!_Tables[i]->rebuildForwardRefs())
{
PDS_WARNING("rebuildForwardRefs(): failed to rebuildForwardRefs(), abort");
return false;
}
}
}
for (i=0; i<_Tables.size(); ++i)
{
if (_Tables[i] != NULL && _Tables[i]->initialised())
{
if (!_Tables[i]->fixForwardRefs())
{
PDS_WARNING("rebuildForwardRefs(): failed to fixForwardRefs(), abort");
return false;
}
}
}
return true;
}
/*
* Rebuild table maps
*/
bool CDatabase::rebuildTableMaps()
{
if (!initialised())
{
PDS_WARNING("rebuildTableMaps(): database not initialised");
return false;
}
uint i;
for (i=0; i<_Tables.size(); ++i)
{
if (_Tables[i] != NULL && _Tables[i]->initialised())
{
_Tables[i]->buildRowMapper();
}
}
return true;
}
/*
* Reset dirty lists
*/
bool CDatabase::resetDirtyTags()
{
if (!initialised())
{
PDS_WARNING("resetDirtyLists(): database not initialised");
return false;
}
uint i;
for (i=0; i<_Tables.size(); ++i)
{
if (_Tables[i] != NULL && _Tables[i]->initialised())
{
_Tables[i]->resetDirtyTags();
}
}
return true;
}
/*
* Reset and rebuild all maps, references, lists...
*/
bool CDatabase::rebuildVolatileData()
{
PDS_DEBUG("rebuildVolatileData()");
if (!initialised())
{
PDS_WARNING("rebuildVolatileData(): database not initialised");
return false;
}
// force database to notify a new reference is up to date
notifyNewReference();
if (!rebuildTableMaps())
{
PDS_WARNING("rebuildVolatileData(): failed to rebuildTableMaps()");
return false;
}
if (!rebuildForwardRefs())
{
PDS_WARNING("rebuildVolatileData(): failed to rebuildForwardRefs()");
return false;
}
/*
if (!resetDirtyTags())
{
PDS_WARNING("rebuildVolatileData(): failed to resetDirtyTags()");
return false;
}
*/
// preload data from reference files
uint i;
for (i=0; i<_Tables.size(); ++i)
{
if (_Tables[i] != NULL && _Tables[i]->initialised())
{
if (!_Tables[i]->preloadRefFiles())
{
PDS_WARNING("rebuildVolatileData(): failed to preloadRefFiles() for table '%d'", i);
return false;
}
}
}
// load string manager
// if (!_StringManager.load(_Reference.getPath()))
// {
// PDS_WARNING("rebuildVolatileData(): failed to load string manager");
// }
return true;
}
/*
* Adapt database to new description
* \param description is the latest xml description of the database
* \returns true is adaptation succeded
*/
CDatabase* CDatabase::adapt(const string& description)
{
PDS_DEBUG("adapt()");
if (!initialised())
{
PDS_WARNING("adapt(): failed, database not initialised");
return NULL;
}
// get 'From' HashKey
CHashKey hash1 = _Description.getHashKey();
// get 'Into' HashKey
CHashKey hash2 = getSHA1((const uint8*)(description.c_str()), (uint32)description.size());
// same hash, ok go on
if (hash1 == hash2)
return this;
// build a clean reference if needed
if (!isReferenceUpToDate())
{
if (!buildReference())
{
PDS_WARNING("adapt(): failed to buildReference()");
return NULL;
}
}
// backup old reference
if (!_Reference.save(_Reference.getPath()+"ref"))
{
PDS_WARNING("adapt(): failed to backup reference index");
}
// create a new destination database
CDatabase* into = new CDatabase(_State.Id);
if (!into->createFromScratch(description))
{
PDS_WARNING("adapt(): failed to create new reference");
delete into;
return NULL;
}
CDatabaseAdapter adapter;
if (!adapter.build(this, into))
{
PDS_WARNING("adapt(): failed to build() adapter");
delete into;
return NULL;
}
if (!adapter.buildContent())
{
PDS_WARNING("adapt(): adapter failed to buildContent()");
delete into;
return NULL;
}
// rebuild volatile data
// that is all non persistant data that can be found from persistant data
if (!into->rebuildVolatileData())
{
PDS_WARNING("adapt(): failed to rebuildVolatileData()");
delete into;
return NULL;
}
// init timestamps as database is ready to run
into->initTimestamps();
return into;
}
/*
* Load previous database state
*/
bool CDatabase::loadState()
{
PDS_DEBUG("loadState()");
if (initialised())
{
PDS_WARNING("loadState(): cannot loadState(), database is already initialised");
return false;
}
// check directory exists
std::string directory = _Reference.getNominalRootPath();
if (!CFile::isExists(directory) || !CFile::isDirectory(directory))
{
return false;
}
// load database current state (or previous state if current state corrupted)
if (!_State.load(_Reference) && !_State.load(_Reference, true))
{
return false;
}
// get last valid reference
if (!_Reference.load())
{
return false;
}
if (_Reference.Index != _State.CurrentIndex)
{
PDS_WARNING("loadState(): failed, Reference and State files have different current index", (_Reference.getPath()+"description.xml").c_str());
return false;
}
// load description
if (!_Description.loadDescriptionFile(_Reference.getPath()+"description.xml"))
{
PDS_WARNING("loadState(): failed to load description file '%s'", (_Reference.getPath()+"description.xml").c_str());
return false;
}
// and init!
if (!init())
{
PDS_WARNING("loadState(): failed to init db '%d'", _State.Id);
return false;
}
PDS_DEBUG("loadState(): database '%d' init ok", _State.Id);
// check reference is up to date
if (!isReferenceUpToDate())
{
if (!buildReference())
{
PDS_WARNING("loadState(): failed to buildReference()");
return false;
}
}
// rebuild volatile data
// that is all non persistant data that can be found from persistant data
if (!rebuildVolatileData())
{
PDS_WARNING("loadState(): failed to rebuildVolatileData()");
return false;
}
// init timestamps as database is ready to run
initTimestamps();
return true;
}
/*
* Check if reference is still the same
* Reinit reference if reference changed
*/
bool CDatabase::checkReferenceChange()
{
CRefIndex ref(_State.Id);
// load current reference
if (!ref.load())
{
PDS_WARNING("checkReferenceChange(): failed to load reference for database '%d', no change assumed", _State.Id);
return false;
}
// check current database reference and current up to date reference
if (ref.Index != _Reference.Index)
{
// check nothing went wrong
if (ref.Index < _Reference.Index || ref.Timestamp <= _Reference.Timestamp)
{
PDS_WARNING("checkReferenceChange(): index changed (new index %d), but is lower than previous index (%d), no change assumed", ref.Index, _Reference.Index);
return false;
}
// affect new reference
_Reference = ref;
notifyNewReference(false);
return true;
}
return false;
}
/*
* Check if reference is up to date
* Returns true if reference is the latest valid database image
*/
bool CDatabase::isReferenceUpToDate()
{
PDS_DEBUG("isReferenceUpToDate()");
if (!initialised())
{
PDS_WARNING("isReferenceUpToDate(): failed, database is not initialised");
return false;
}
vector files;
NLMISC::CPath::getPathContent(_Reference.getRootPath()+"hours", false, false, true, files);
NLMISC::CPath::getPathContent(_Reference.getRootPath()+"minutes", false, false, true, files);
NLMISC::CPath::getPathContent(_Reference.getRootPath()+"seconds", false, false, true, files);
bool upToDate = true;
uint i;
for (i=0; i _State.EndTimestamp)
{
CFile::moveFile((files[i]+".disabled").c_str(), files[i].c_str());
continue;
}
// compare file timestamp (in name) to current reference timestamp
if (_Reference.Timestamp <= timestamp && timestamp <= _State.EndTimestamp)
upToDate = false;
}
}
if (upToDate)
PDS_DEBUG("isReferenceUpToDate(): database is up to date");
return upToDate;
}
/*
* Build a up to date reference
*/
bool CDatabase::buildReference()
{
PDS_DEBUG("buildReference()");
CRefIndex prev = _Reference;
CRefIndex next(_State.Id);
if (!next.buildNext())
{
PDS_WARNING("buildReference(): failed to build next ref");
return false;
}
PDS_DEBUG("buildReference(): building from reference '%08X' to reference '%08X'", prev.Index, next.Index);
// build reference but clamp to last valid state (
next.Timestamp = _State.EndTimestamp;
_Reference = next;
// setup reference
// this will release all references owned by the internal table buffers
// so we can copy/modify reference files without problem
if (!CReferenceBuilder::build(prev, next))
{
PDS_WARNING("buildReference(): failed to build next reference");
return false;
}
notifyNewReference();
PDS_DEBUG("buildReference(): new reference is up to date");
return true;
}
/*
* Create new database from scratch, setup everything needed (references, etc.)
* \param description is the xml database description
*/
bool CDatabase::createFromScratch(const string& description)
{
PDS_DEBUG("createFromScratch()");
// In order to create a new database from scratch, directory must no exist
if (CFile::isExists(_Reference.getNominalRootPath()))
{
PDS_WARNING("createFromScratch(): failed, directory '%s' already exists for database '%d', manual cleanup is needed to proceed", _Reference.getRootPath().c_str(), _Reference.DatabaseId);
return false;
}
// build a brand new reference
if (!_Reference.buildNext())
{
PDS_WARNING("createFromScratch(): failed, unable to init reference");
return false;
}
if (initialised())
{
PDS_WARNING("createFromScratch(): failed, database is initialised");
return false;
}
if (!_Description.loadDescription((uint8*)description.c_str()))
{
PDS_WARNING("createFromScratch(): failed to load description");
return false;
}
if (!init())
{
PDS_WARNING("createFromScratch(): failed to init() database");
return false;
}
// // save description
// if (!_Description.saveDescription(_Reference.getPath() + "description.xml"))
// {
// PDS_WARNING("createFromScratch(): failed to save description");
// return false;
// }
// init timestamps as database is ready to run
initTimestamps();
// a new reference is valid
notifyNewReference();
return true;
}
/*
* Build the delta files and purge all dirty rows in tables
*/
bool CDatabase::buildDelta(const CTimestamp& starttime, const CTimestamp& endtime)
{
H_AUTO(PDS_Database_buildDelta);
if (!initialised())
{
PDS_WARNING("buildDelta(): failed, database is not initialised");
return false;
}
uint i;
for (i=0; i<_Tables.size(); ++i)
{
if (!_Tables[i]->buildDelta(starttime, endtime))
{
PDS_WARNING("buildDelta(): failed to buildDelta() for table '%d' '%s'", i, _Tables[i]->getName().c_str());
return false;
}
}
// std::string logDir = RY_PDS::CPDSLib::getLogDirectory(_State.Id);
// if (!CFile::isExists(logDir) || !CFile::isDirectory(logDir))
// {
// if (!CFile::createDirectoryTree(logDir))
// {
// PDS_WARNING("buildDelta(): failed to create log root directory '%s'", logDir.c_str());
// }
//
// if (!CFile::setRWAccess(logDir))
// {
// PDS_WARNING("buildDelta(): failed, can't set RW access to directory '%s'", logDir.c_str());
// }
// }
// // save string manager logs
// if (!_StringManager.logEmpty())
// {
// COFile smf;
// COXml smxml;
// std::string smfilename = logDir + endtime.toString()+".string_log";
// if (!smf.open(smfilename) || !smxml.init(&smf) || !_StringManager.storeLog(smxml))
// {
// PDS_WARNING("buildDelta(): failed to build string manager log file '%s'", smfilename.c_str());
// }
// }
// save straight logs
if (!_LogQueue.empty())
{
// std::string logfilename = logDir + endtime.toString()+"_0000.pd_log";
// COFile logf;
// if (logf.open(logfilename))
// {
// try
// {
// logf.serialCont(_LogQueue);
// }
// catch (const Exception& e)
// {
// PDS_WARNING("buildDelta(): exception occured while saving straight log : %s", e.what());
// }
// }
// else
// {
// PDS_WARNING("buildDelta(): failed to build log file '%s'", logfilename.c_str());
// }
//
_LogQueue.clear();
}
// State file swapping
std::string statePath = _Reference.getRootPath();
std::string stateName = CDatabaseState::fileName();
if (CFile::fileExists(statePath+stateName) &&
!CFile::copyFile(statePath+"previous_"+stateName, statePath+stateName, false))
{
PDS_WARNING("buildDelta(): failed copy state file to backup previous_state");
}
// setup state timestamp
_State.EndTimestamp = endtime;
_State.save(_Reference);
return true;
}
/*
* Flush database from released rows
*/
bool CDatabase::flushReleased()
{
if (!initialised())
{
PDS_WARNING("flushReleased(): failed, database is not initialised");
return false;
}
uint i;
for (i=0; i<_Tables.size(); ++i)
{
if (!_Tables[i]->flushReleased())
{
PDS_WARNING("flushReleased(): failed to flushReleased() for table '%d' '%s'", i, _Tables[i]->getName().c_str());
return false;
}
}
return true;
}
/*
* Notify a new reference is ready
*/
bool CDatabase::notifyNewReference(bool validateRef)
{
if (!initialised())
{
PDS_WARNING("notifyNewReference(): failed, database is not initialised");
return false;
}
if (validateRef)
_Reference.setAsValidRef();
_State.CurrentIndex = _Reference.Index;
uint i;
for (i=0; i<_Tables.size(); ++i)
_Tables[i]->notifyNewReference(_Reference);
return true;
}
/*
* Receive update
*/
void CDatabase::receiveUpdate(uint32 id)
{
// add to acknowledged updates
_ReceivedUpdates.push_back(id);
_State.LastUpdateId = id;
}
/*
* Flush updates
*/
void CDatabase::flushUpdates(std::vector& acknowledged)
{
// copy acknowledge and flush
acknowledged = _ReceivedUpdates;
_ReceivedUpdates.clear();
}
/**
* Get Update Queue for id
* May return NULL if message was already received
*/
RY_PDS::CDbMessageQueue* CDatabase::getUpdateMessageQueue(uint32 updateId)
{
if (updateId != 0 && updateId <= _State.LastUpdateId)
return NULL;
_LogQueue.push_back(RY_PDS::CUpdateLog());
RY_PDS::CUpdateLog& ulog = _LogQueue.back();
ulog.UpdateId = updateId;
ulog.createUpdates();
ulog.StartStamp.setToCurrent();
ulog.EndStamp.setToCurrent();
return ulog.getUpdates();
}
CVariable MinuteUpdateRate("pds", "MinuteUpdateRate", "Number of seconds between two minute updates", 60, 0, true);
CVariable HourUpdateRate("pds", "HourUpdateRate", "Number of seconds between two hour updates", 3600, 0, true);
CVariable ReferenceUpdateRate("pds", "ReferenceUpdateRate", "Number of seconds between two reference builds", 86400, 0, true);
CVariable KeepSecondsAtMinuteUpdate("pds", "KeepSecondsAtMinuteUpdate", "Number of seconds to keep (in number of minute updates)", 2, 0, true);
CVariable KeepMinutesAtHourUpdate("pds", "KeepMinutesAtHourUpdate", "Number of minutes to keep (in number of hours updates)", 2, 0, true);
CVariable KeepHoursAtReferenceUpdate("pds", "KeepHoursAtReferenceUpdate", "Number of hours to keep (in number of daily updates)", 2, 0, true);
/*
* Send Delta/Reference build commands
*/
bool CDatabase::sendBuildCommands(const CTimestamp& current)
{
checkUpdateRates();
while (current - _MinuteUpdateTimestamp >= MinuteUpdateRate)
{
CTimestamp start = _MinuteUpdateTimestamp;
CTimestamp end = start + MinuteUpdateRate;
CTimestamp keep = end - MinuteUpdateRate*KeepSecondsAtMinuteUpdate;
string outputPath = _Reference.getMinutesUpdatePath();
string hoursUpdatePath = _Reference.getHoursUpdatePath();
string minutesUpdatePath = _Reference.getMinutesUpdatePath();
string secondsUpdatePath = _Reference.getSecondsUpdatePath();
string mintimestamp = start.toString();
string maxtimestamp = end.toString();
CDeltaBuilder::TDelta type = CDeltaBuilder::Minute;
string keeptimestamp = keep.toString();
CMessage& msgdelta = CDbManager::addTask("RB_GEN_DELTA", NULL, NULL);
msgdelta.serial(outputPath);
msgdelta.serial(hoursUpdatePath);
msgdelta.serial(minutesUpdatePath);
msgdelta.serial(secondsUpdatePath);
msgdelta.serial(mintimestamp);
msgdelta.serial(maxtimestamp);
msgdelta.serialEnum(type);
msgdelta.serial(keeptimestamp);
_MinuteUpdateTimestamp = end;
}
while (current - _HourUpdateTimestamp >= HourUpdateRate)
{
CTimestamp start = _HourUpdateTimestamp;
CTimestamp end = start + HourUpdateRate;
CTimestamp keep = end - HourUpdateRate*KeepMinutesAtHourUpdate;
string outputPath = _Reference.getHoursUpdatePath();
string hoursUpdatePath = _Reference.getHoursUpdatePath();
string minutesUpdatePath = _Reference.getMinutesUpdatePath();
string secondsUpdatePath = _Reference.getSecondsUpdatePath();
string mintimestamp = start.toString();
string maxtimestamp = end.toString();
CDeltaBuilder::TDelta type = CDeltaBuilder::Hour;
string keeptimestamp = keep.toString();
CMessage& msgdelta = CDbManager::addTask("RB_GEN_DELTA", NULL, NULL);
msgdelta.serial(outputPath);
msgdelta.serial(hoursUpdatePath);
msgdelta.serial(minutesUpdatePath);
msgdelta.serial(secondsUpdatePath);
msgdelta.serial(mintimestamp);
msgdelta.serial(maxtimestamp);
msgdelta.serialEnum(type);
msgdelta.serial(keeptimestamp);
_HourUpdateTimestamp = end;
}
while (current - _DayUpdateTimestamp >= ReferenceUpdateRate)
{
CTimestamp start = _DayUpdateTimestamp;
CTimestamp end = start + ReferenceUpdateRate;
CTimestamp keep = end - ReferenceUpdateRate*KeepHoursAtReferenceUpdate;
CRefIndex* next = new CRefIndex(_State.Id);
*next = _Reference;
next->buildNext();
string rootRefPath = _Reference.getRootPath();
string previousReferencePath = _Reference.getPath();
string nextReferencePath = next->getPath();
string logUpdatePath = _Reference.getLogPath();
string hoursUpdatePath = _Reference.getHoursUpdatePath();
string minutesUpdatePath = _Reference.getMinutesUpdatePath();
string secondsUpdatePath = _Reference.getSecondsUpdatePath();
string mintimestamp = start.toString();
string maxtimestamp = end.toString();
string keeptimestamp = keep.toString();
CMessage& msgref = CDbManager::addTask("RB_GEN_REF", this, (void*)next);
msgref.serial(rootRefPath);
msgref.serial(previousReferencePath);
msgref.serial(nextReferencePath);
msgref.serial(hoursUpdatePath);
msgref.serial(minutesUpdatePath);
msgref.serial(secondsUpdatePath);
msgref.serial(logUpdatePath);
msgref.serial(mintimestamp);
msgref.serial(maxtimestamp);
msgref.serial(keeptimestamp);
_DayUpdateTimestamp = end;
}
return true;
}
/*
* Task ran successfully
*/
void CDatabase::taskSuccessful(void* arg)
{
// when reference is up to date, notify new reference
CRefIndex* ref = (CRefIndex*)arg;
_Reference = *ref;
notifyNewReference(true);
delete ref;
}
/*
* Task failed!
*/
void CDatabase::taskFailed(void* arg)
{
// ok, nothing to do in this case...
}
/*
* Static Check for update rates
*/
void CDatabase::checkUpdateRates()
{
uint deltaRate = DeltaUpdateRate;
uint minuteRate = MinuteUpdateRate;
// minute rate must be a multiple of delta rate
if ((minuteRate % deltaRate) != 0)
{
minuteRate = deltaRate*(minuteRate/deltaRate + 1);
nlwarning("CDatabase::checkUpdateRate(): MinuteUpdateRate is not a multiple of DeltaUpdateRate! Rounded to %d seconds", minuteRate);
MinuteUpdateRate = minuteRate;
}
uint hourRate = HourUpdateRate;
// hour rate must be a multiple of minute rate
if ((hourRate % minuteRate) != 0)
{
hourRate = minuteRate*(hourRate/minuteRate + 1);
nlwarning("CDatabase::checkUpdateRate(): HourUpdateRate is not a multiple of MinuteUpdateRate! Rounded to %d seconds", hourRate);
HourUpdateRate = hourRate;
}
uint referenceRate = ReferenceUpdateRate;
// reference rate must be a multiple of hour rate
if ((referenceRate % hourRate) != 0)
{
referenceRate = hourRate*(referenceRate/hourRate + 1);
nlwarning("CDatabase::checkUpdateRate(): ReferenceUpdateRate is not a multiple of HourUpdateRate! Rounded to %d seconds", referenceRate);
ReferenceUpdateRate = referenceRate;
}
}
/*
* Serialise SheetId String Mapper
*/
void CDatabase::serialSheetIdStringMapper(NLMISC::IStream& f)
{
// serial mapper
_SheetIdStringMapper.serial(f);
if (f.isReading())
{
// if mapper is read, save it now in reference path
std::string refPath = _Reference.getPath();
COFile ofile;
if (!ofile.open(refPath+"sheetid_map.bin"))
{
PDS_WARNING("serialSheetIdStringMapper(): failed to open reference sheetid_map file '%s' for save", (refPath+"sheetid_map.bin").c_str());
return;
}
_SheetIdStringMapper.serial(ofile);
}
}
/*
* Get Table Index from name
*/
RY_PDS::TTableIndex CDatabase::getTableIndex(const std::string& tableName) const
{
const CTable* table = getTable(tableName);
return (table == NULL) ? RY_PDS::INVALID_TABLE_INDEX : (RY_PDS::TTableIndex)table->getId();
}
/*
* Get Table Index from name
*/
std::string CDatabase::getTableName(RY_PDS::TTableIndex index) const
{
if (index >= _Tables.size() || _Tables[index] == NULL)
return "invalid";
return _Tables[index]->getName();
}
/*
* Search object in database using its key
* \param key is the 64 bits row key to search through all tables
*/
bool CDatabase::searchObjectIndex(uint64 key, std::set& indexes) const
{
indexes.clear();
uint i;
for (i=0; i<_Tables.size(); ++i)
{
if (_Tables[i] == NULL || !_Tables[i]->initialised() || !_Tables[i]->isMapped())
continue;
RY_PDS::CObjectIndex index = _Tables[i]->getMappedRow(key);
if (index.isValid())
indexes.insert(index);
}
return !indexes.empty();
}