1
0
Fork 0
arangodb/arangod/Wal/RecoverState.cpp

1694 lines
61 KiB
C++

////////////////////////////////////////////////////////////////////////////////
/// @brief Recovery state
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2014 ArangoDB GmbH, Cologne, Germany
/// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is ArangoDB GmbH, Cologne, Germany
///
/// @author Jan Steemann
/// @author Copyright 2014, ArangoDB GmbH, Cologne, Germany
/// @author Copyright 2011-2013, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////
#include "RecoverState.h"
#include "Basics/FileUtils.h"
#include "Basics/conversions.h"
#include "Basics/files.h"
#include "Basics/Exceptions.h"
#include "VocBase/collection.h"
#include "VocBase/replication-applier.h"
#include "VocBase/voc-shaper.h"
#include "Wal/LogfileManager.h"
#include "Wal/Slots.h"
using namespace triagens::wal;
// -----------------------------------------------------------------------------
// --SECTION-- helper functions
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @brief whether or not a collection is volatile
////////////////////////////////////////////////////////////////////////////////
static inline bool IsVolatile (TRI_transaction_collection_t const* trxCollection) {
return trxCollection->_collection->_collection->_info._isVolatile;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief get the directory for a database
////////////////////////////////////////////////////////////////////////////////
static std::string GetDatabaseDirectory (TRI_server_t* server,
TRI_voc_tick_t databaseId) {
char* idString = TRI_StringUInt64(databaseId);
char* dname = TRI_Concatenate2String("database-", idString);
TRI_FreeString(TRI_CORE_MEM_ZONE, idString);
char* filename = TRI_Concatenate2File(server->_databasePath, dname);
TRI_FreeString(TRI_CORE_MEM_ZONE, dname);
std::string result(filename);
TRI_FreeString(TRI_CORE_MEM_ZONE, filename);
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief get the directory for a collection
////////////////////////////////////////////////////////////////////////////////
static std::string GetCollectionDirectory (TRI_vocbase_t* vocbase,
TRI_voc_cid_t collectionId) {
char* dirname = TRI_GetDirectoryCollection(vocbase->_path,
"empty", // does not matter
TRI_COL_TYPE_DOCUMENT, // does not matter
collectionId);
std::string result(dirname);
TRI_FreeString(TRI_CORE_MEM_ZONE, dirname);
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief wait until a database directory disappears
////////////////////////////////////////////////////////////////////////////////
static int WaitForDeletion (TRI_server_t* server,
TRI_voc_tick_t databaseId,
int statusCode) {
std::string const result = GetDatabaseDirectory(server, databaseId);
int iterations = 0;
// wait for at most 30 seconds for the directory to be removed
while (TRI_IsDirectory(result.c_str())) {
if (iterations == 0) {
LOG_TRACE("waiting for deletion of database directory '%s', called with status code %d",
result.c_str(),
statusCode);
if (statusCode != TRI_ERROR_FORBIDDEN &&
(statusCode == TRI_ERROR_ARANGO_DATABASE_NOT_FOUND ||
statusCode != TRI_ERROR_NO_ERROR)) {
LOG_WARNING("forcefully deleting database directory '%s'", result.c_str());
TRI_RemoveDirectory(result.c_str());
}
}
else if (iterations >= 30 * 10) {
LOG_WARNING("unable to remove database directory '%s'", result.c_str());
return TRI_ERROR_INTERNAL;
}
if (iterations == 5 * 10) {
LOG_INFO("waiting for deletion of database directory '%s'", result.c_str());
}
++iterations;
usleep(100000);
}
return TRI_ERROR_NO_ERROR;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief wait until a collection directory disappears
////////////////////////////////////////////////////////////////////////////////
static int WaitForDeletion (TRI_vocbase_t* vocbase,
TRI_voc_cid_t collectionId,
int statusCode) {
std::string const result = GetCollectionDirectory(vocbase, collectionId);
int iterations = 0;
// wait for at most 30 seconds for the directory to be removed
while (TRI_IsDirectory(result.c_str())) {
if (iterations == 0) {
LOG_TRACE("waiting for deletion of collection directory '%s', called with status code %d",
result.c_str(),
statusCode);
if (statusCode != TRI_ERROR_FORBIDDEN &&
(statusCode == TRI_ERROR_ARANGO_COLLECTION_NOT_FOUND ||
statusCode != TRI_ERROR_NO_ERROR)) {
LOG_WARNING("forcefully deleting collection directory '%s'", result.c_str());
TRI_RemoveDirectory(result.c_str());
}
}
else if (iterations >= 30 * 10) {
LOG_WARNING("unable to remove collection directory '%s'", result.c_str());
return TRI_ERROR_INTERNAL;
}
if (iterations == 5 * 10) {
LOG_INFO("waiting for deletion of collection directory '%s'", result.c_str());
}
++iterations;
usleep(100000);
}
return TRI_ERROR_NO_ERROR;
}
// -----------------------------------------------------------------------------
// --SECTION-- constructors / destructors
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @brief creates the recover state
////////////////////////////////////////////////////////////////////////////////
RecoverState::RecoverState (TRI_server_t* server,
bool ignoreRecoveryErrors)
: server(server),
failedTransactions(),
remoteTransactions(),
remoteTransactionCollections(),
remoteTransactionDatabases(),
lastTick(0),
logfilesToProcess(),
openedCollections(),
openedDatabases(),
runningRemoteTransactions(),
emptyLogfiles(),
policy(TRI_DOC_UPDATE_ONLY_IF_NEWER, 0, nullptr),
ignoreRecoveryErrors(ignoreRecoveryErrors),
errorCount(0) {
}
////////////////////////////////////////////////////////////////////////////////
/// @brief destroys the recover state
////////////////////////////////////////////////////////////////////////////////
RecoverState::~RecoverState () {
releaseResources();
// free running remote transactions
for (auto it = runningRemoteTransactions.begin(); it != runningRemoteTransactions.end(); ++it) {
auto trx = (*it).second;
delete trx;
}
runningRemoteTransactions.clear();
}
// -----------------------------------------------------------------------------
// --SECTION-- public functions
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @brief release opened collections and databases so they can be shut down
/// etc.
////////////////////////////////////////////////////////////////////////////////
void RecoverState::releaseResources () {
// hand over running remote transactions to the applier
for (auto it = runningRemoteTransactions.begin(); it != runningRemoteTransactions.end(); ++it) {
auto* trx = (*it).second;
TRI_vocbase_t* vocbase = trx->vocbase();
TRI_ASSERT(vocbase != nullptr);
auto* applier = vocbase->_replicationApplier;
TRI_ASSERT(applier != nullptr);
applier->addRemoteTransaction(trx);
}
// reset trx counter as we're moving transactions from this thread to a potential other
triagens::arango::TransactionBase::setNumbers(0, 0);
runningRemoteTransactions.clear();
// release all collections
for (auto it = openedCollections.begin(); it != openedCollections.end(); ++it) {
TRI_vocbase_col_t* collection = (*it).second;
TRI_ReleaseCollectionVocBase(collection->_vocbase, collection);
}
openedCollections.clear();
// release all databases
for (auto it = openedDatabases.begin(); it != openedDatabases.end(); ++it) {
TRI_vocbase_t* vocbase = (*it).second;
TRI_ReleaseDatabaseServer(server, vocbase);
}
openedDatabases.clear();
}
////////////////////////////////////////////////////////////////////////////////
/// @brief gets a database (and inserts it into the cache if not in it)
////////////////////////////////////////////////////////////////////////////////
TRI_vocbase_t* RecoverState::useDatabase (TRI_voc_tick_t databaseId) {
auto it = openedDatabases.find(databaseId);
if (it != openedDatabases.end()) {
return (*it).second;
}
TRI_vocbase_t* vocbase = TRI_UseDatabaseByIdServer(server, databaseId);
if (vocbase == nullptr) {
return nullptr;
}
openedDatabases.insert(it, std::make_pair(databaseId, vocbase));
return vocbase;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief release a database (so it can be dropped)
////////////////////////////////////////////////////////////////////////////////
TRI_vocbase_t* RecoverState::releaseDatabase (TRI_voc_tick_t databaseId) {
auto it = openedDatabases.find(databaseId);
if (it == openedDatabases.end()) {
return nullptr;
}
TRI_vocbase_t* vocbase = (*it).second;
TRI_ASSERT(vocbase != nullptr);
// release all collections we ourselves have opened for this database
auto it2 = openedCollections.begin();
while (it2 != openedCollections.end()) {
TRI_vocbase_col_t* collection = (*it2).second;
TRI_ASSERT(collection != nullptr);
if (collection->_vocbase->_id == databaseId) {
// correct database, now release the collection
TRI_ASSERT(vocbase == collection->_vocbase);
TRI_ReleaseCollectionVocBase(vocbase, collection);
// get new iterator position
it2 = openedCollections.erase(it2);
}
else {
// collection not found, advance in the loop
++it2;
}
}
TRI_ReleaseDatabaseServer(server, vocbase);
openedDatabases.erase(databaseId);
return vocbase;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief release a collection (so it can be dropped)
////////////////////////////////////////////////////////////////////////////////
TRI_vocbase_col_t* RecoverState::releaseCollection (TRI_voc_cid_t collectionId) {
auto it = openedCollections.find(collectionId);
if (it == openedCollections.end()) {
return nullptr;
}
TRI_vocbase_col_t* collection = (*it).second;
TRI_ASSERT(collection != nullptr);
TRI_ReleaseCollectionVocBase(collection->_vocbase, collection);
openedCollections.erase(collectionId);
return collection;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief gets a collection (and inserts it into the cache if not in it)
////////////////////////////////////////////////////////////////////////////////
TRI_vocbase_col_t* RecoverState::useCollection (TRI_vocbase_t* vocbase,
TRI_voc_cid_t collectionId,
int& res) {
auto it = openedCollections.find(collectionId);
if (it != openedCollections.end()) {
res = TRI_ERROR_NO_ERROR;
return (*it).second;
}
TRI_set_errno(TRI_ERROR_NO_ERROR);
TRI_vocbase_col_status_e status; // ignored here
TRI_vocbase_col_t* collection = TRI_UseCollectionByIdVocBase(vocbase, collectionId, status);
if (collection == nullptr) {
res = TRI_errno();
if (res == TRI_ERROR_ARANGO_CORRUPTED_COLLECTION) {
LOG_WARNING("unable to open collection %llu. Please check the logs above for errors.",
(unsigned long long) collectionId);
}
return nullptr;
}
TRI_document_collection_t* document = collection->_collection;
TRI_ASSERT(document != nullptr);
// disable secondary indexes for the moment
document->useSecondaryIndexes(false);
openedCollections.insert(it, std::make_pair(collectionId, collection));
res = TRI_ERROR_NO_ERROR;
return collection;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief looks up a collection
/// the collection will be opened after this call and inserted into a local
/// cache for faster lookups
/// returns nullptr if the collection does not exist
////////////////////////////////////////////////////////////////////////////////
TRI_document_collection_t* RecoverState::getCollection (TRI_voc_tick_t databaseId,
TRI_voc_cid_t collectionId) {
TRI_vocbase_t* vocbase = useDatabase(databaseId);
if (vocbase == nullptr) {
LOG_TRACE("database %llu not found", (unsigned long long) databaseId);
return nullptr;
}
int res;
TRI_vocbase_col_t* collection = useCollection(vocbase, collectionId, res);
if (collection == nullptr) {
LOG_TRACE("collection %llu of database %llu not found", (unsigned long long) collectionId, (unsigned long long) databaseId);
return nullptr;
}
TRI_document_collection_t* document = collection->_collection;
TRI_ASSERT(document != nullptr);
return document;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief executes an operation in a remote transaction
////////////////////////////////////////////////////////////////////////////////
int RecoverState::executeRemoteOperation (TRI_voc_tick_t databaseId,
TRI_voc_cid_t collectionId,
TRI_voc_tid_t transactionId,
TRI_df_marker_t const* marker,
TRI_voc_fid_t fid,
std::function<int(RemoteTransactionType*, Marker*)> func) {
auto it = remoteTransactions.find(transactionId);
if (it == remoteTransactions.end()) {
LOG_WARNING("remote transaction not found: internal error");
return TRI_ERROR_INTERNAL;
}
TRI_voc_tid_t externalId = (*it).second.second;
auto it2 = runningRemoteTransactions.find(externalId);
if (it2 == runningRemoteTransactions.end()) {
LOG_WARNING("remote transaction not found: internal error");
return TRI_ERROR_INTERNAL;
}
auto trx = (*it2).second;
registerRemoteUsage(databaseId, collectionId);
EnvelopeMarker* envelope = nullptr;
int res = TRI_ERROR_INTERNAL;
try {
envelope = new EnvelopeMarker(marker, fid);
// execute the operation
res = func(trx, envelope);
if (res != TRI_ERROR_NO_ERROR) {
THROW_ARANGO_EXCEPTION(res);
}
}
catch (triagens::basics::Exception const& ex) {
res = ex.code();
}
catch (...) {
res = TRI_ERROR_INTERNAL;
}
if (envelope != nullptr) {
delete envelope;
}
return res;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief executes a single operation inside a transaction
////////////////////////////////////////////////////////////////////////////////
int RecoverState::executeSingleOperation (TRI_voc_tick_t databaseId,
TRI_voc_cid_t collectionId,
TRI_df_marker_t const* marker,
TRI_voc_fid_t fid,
std::function<int(SingleWriteTransactionType*, Marker*)> func) {
// first find the correct database
TRI_vocbase_t* vocbase = useDatabase(databaseId);
if (vocbase == nullptr) {
LOG_TRACE("database %llu not found", (unsigned long long) databaseId);
return TRI_ERROR_ARANGO_DATABASE_NOT_FOUND;
}
int res;
TRI_vocbase_col_t* collection = useCollection(vocbase, collectionId, res);
if (collection == nullptr || collection->_collection == nullptr) {
if (res == TRI_ERROR_ARANGO_CORRUPTED_COLLECTION) {
return res;
}
return TRI_ERROR_ARANGO_COLLECTION_NOT_FOUND;
}
TRI_voc_tick_t tickMax = collection->_collection->_tickMax;
if (marker->_tick <= tickMax) {
// already transferred this marker
return TRI_ERROR_NO_ERROR;
}
SingleWriteTransactionType* trx = nullptr;
EnvelopeMarker* envelope = nullptr;
res = TRI_ERROR_INTERNAL;
try {
trx = new SingleWriteTransactionType(new triagens::arango::StandaloneTransactionContext(), vocbase, collectionId);
if (trx == nullptr) {
THROW_ARANGO_EXCEPTION(res);
}
trx->addHint(TRI_TRANSACTION_HINT_NO_BEGIN_MARKER, false);
trx->addHint(TRI_TRANSACTION_HINT_NO_ABORT_MARKER, false);
trx->addHint(TRI_TRANSACTION_HINT_NO_THROTTLING, false);
trx->addHint(TRI_TRANSACTION_HINT_LOCK_NEVER, false);
res = trx->begin();
if (res != TRI_ERROR_NO_ERROR) {
THROW_ARANGO_EXCEPTION(res);
}
envelope = new EnvelopeMarker(marker, fid);
// execute the operation
res = func(trx, envelope);
if (res != TRI_ERROR_NO_ERROR) {
THROW_ARANGO_EXCEPTION(res);
}
// commit the operation
res = trx->commit();
}
catch (triagens::basics::Exception const& ex) {
res = ex.code();
}
catch (...) {
res = TRI_ERROR_INTERNAL;
}
if (envelope != nullptr) {
delete envelope;
}
if (trx != nullptr) {
delete trx;
}
return res;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief callback to handle one marker during recovery
/// this function only builds up state and does not change any data
////////////////////////////////////////////////////////////////////////////////
bool RecoverState::InitialScanMarker (TRI_df_marker_t const* marker,
void* data,
TRI_datafile_t* datafile) {
RecoverState* state = reinterpret_cast<RecoverState*>(data);
TRI_ASSERT(marker != nullptr);
// note the marker's tick
TRI_ASSERT(marker->_tick >= state->lastTick);
if (marker->_tick > state->lastTick) {
state->lastTick = marker->_tick;
}
switch (marker->_type) {
// -----------------------------------------------------------------------------
// transactions
// -----------------------------------------------------------------------------
case TRI_WAL_MARKER_BEGIN_TRANSACTION: {
transaction_begin_marker_t const* m = reinterpret_cast<transaction_begin_marker_t const*>(marker);
// insert this transaction into the list of failed transactions
// we do this because if we don't find a commit marker for this transaction,
// we'll have it in the failed list at the end of the scan and can ignore it
state->failedTransactions.emplace(std::make_pair(m->_transactionId, std::make_pair(m->_databaseId, false)));
break;
}
case TRI_WAL_MARKER_COMMIT_TRANSACTION: {
transaction_commit_marker_t const* m = reinterpret_cast<transaction_commit_marker_t const*>(marker);
// remove this transaction from the list of failed transactions
state->failedTransactions.erase(m->_transactionId);
break;
}
case TRI_WAL_MARKER_ABORT_TRANSACTION: {
// insert this transaction into the list of failed transactions
transaction_abort_marker_t const* m = reinterpret_cast<transaction_abort_marker_t const*>(marker);
auto it = state->failedTransactions.find(m->_transactionId);
if (it != state->failedTransactions.end()) {
// delete previous element if present
state->failedTransactions.erase(m->_transactionId);
}
// and (re-)insert
state->failedTransactions.emplace(std::make_pair(m->_transactionId, std::make_pair(m->_databaseId, true)));
break;
}
case TRI_WAL_MARKER_BEGIN_REMOTE_TRANSACTION: {
transaction_remote_begin_marker_t const* m = reinterpret_cast<transaction_remote_begin_marker_t const*>(marker);
// insert this transaction into the list of remote transactions
state->remoteTransactions.emplace(std::make_pair(m->_transactionId, std::make_pair(m->_databaseId, m->_externalId)));
break;
}
case TRI_WAL_MARKER_COMMIT_REMOTE_TRANSACTION: {
transaction_remote_commit_marker_t const* m = reinterpret_cast<transaction_remote_commit_marker_t const*>(marker);
// remove this transaction from the list of remote transactions
state->remoteTransactions.erase(m->_transactionId);
break;
}
case TRI_WAL_MARKER_ABORT_REMOTE_TRANSACTION: {
transaction_remote_abort_marker_t const* m = reinterpret_cast<transaction_remote_abort_marker_t const*>(marker);
// insert this transaction into the list of failed transactions
// the transaction is treated the same as a regular local transaction that is aborted
auto it = state->failedTransactions.find(m->_transactionId);
if (it == state->failedTransactions.end()) {
// insert the transaction into the list of failed transactions
state->failedTransactions.emplace(std::make_pair(m->_transactionId, std::make_pair(m->_databaseId, false)));
}
// remove this transaction from the list of remote transactions
state->remoteTransactions.erase(m->_transactionId);
break;
}
/*
// -----------------------------------------------------------------------------
// create markers
// -----------------------------------------------------------------------------
case TRI_WAL_MARKER_CREATE_COLLECTION: {
collection_create_marker_t const* m = reinterpret_cast<collection_create_marker_t const*>(marker);
// undo a potential drop marker discovered before for the same collection
state->droppedCollections.erase(m->_collectionId);
break;
}
case TRI_WAL_MARKER_CREATE_DATABASE: {
database_create_marker_t const* m = reinterpret_cast<database_create_marker_t const*>(marker);
// undo a potential drop marker discovered before for the same database
state->droppedDatabases.erase(m->_databaseId);
break;
}
case TRI_WAL_MARKER_CREATE_INDEX: {
// ignored
break;
}
// -----------------------------------------------------------------------------
// drop markers
// -----------------------------------------------------------------------------
*/
case TRI_WAL_MARKER_DROP_COLLECTION: {
collection_drop_marker_t const* m = reinterpret_cast<collection_drop_marker_t const*>(marker);
// note that the collection was dropped and doesn't need to be recovered
state->droppedIds.insert(m->_collectionId);
break;
}
/*
case TRI_WAL_MARKER_DROP_DATABASE: {
database_drop_marker_t const* m = reinterpret_cast<database_drop_marker_t const*>(marker);
// note that the database was dropped and doesn't need to be recovered
state->droppedDatabases.insert(m->_databaseId);
break;
}
case TRI_WAL_MARKER_DROP_INDEX: {
// ignored
break;
}
*/
}
return true;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief callback to replay one marker during recovery
/// this function modifies indexes etc.
////////////////////////////////////////////////////////////////////////////////
bool RecoverState::ReplayMarker (TRI_df_marker_t const* marker,
void* data,
TRI_datafile_t* datafile) {
RecoverState* state = reinterpret_cast<RecoverState*>(data);
#ifdef TRI_ENABLE_FAILURE_TESTS
LOG_TRACE("replaying marker of type %s", TRI_NameMarkerDatafile(marker));
#endif
switch (marker->_type) {
// -----------------------------------------------------------------------------
// attributes and shapes
// -----------------------------------------------------------------------------
case TRI_WAL_MARKER_ATTRIBUTE: {
// re-insert the attribute into the shaper
attribute_marker_t const* m = reinterpret_cast<attribute_marker_t const*>(marker);
TRI_voc_cid_t collectionId = m->_collectionId;
TRI_voc_tick_t databaseId = m->_databaseId;
if (state->isDropped(databaseId, collectionId)) {
return true;
}
int res = state->executeSingleOperation(databaseId, collectionId, marker, datafile->_fid, [&](SingleWriteTransactionType* trx, Marker* envelope) -> int {
TRI_document_collection_t* document = trx->documentCollection();
// re-insert the attribute
int res = TRI_InsertAttributeVocShaper(document->getShaper(), marker, false);
return res;
});
if (res != TRI_ERROR_NO_ERROR &&
res != TRI_ERROR_ARANGO_DATABASE_NOT_FOUND &&
res != TRI_ERROR_ARANGO_COLLECTION_NOT_FOUND) {
LOG_WARNING("could not apply attribute marker: %s", TRI_errno_string(res));
++state->errorCount;
return state->canContinue();
}
break;
}
case TRI_WAL_MARKER_SHAPE: {
// re-insert the shape into the shaper
shape_marker_t const* m = reinterpret_cast<shape_marker_t const*>(marker);
TRI_voc_cid_t collectionId = m->_collectionId;
TRI_voc_tick_t databaseId = m->_databaseId;
if (state->isDropped(databaseId, collectionId)) {
return true;
}
int res = state->executeSingleOperation(databaseId, collectionId, marker, datafile->_fid, [&](SingleWriteTransactionType* trx, Marker* envelope) -> int {
TRI_document_collection_t* document = trx->documentCollection();
// re-insert the shape
int res = TRI_InsertShapeVocShaper(document->getShaper(), marker, false);
return res;
});
if (res != TRI_ERROR_NO_ERROR &&
res != TRI_ERROR_ARANGO_DATABASE_NOT_FOUND &&
res != TRI_ERROR_ARANGO_COLLECTION_NOT_FOUND) {
LOG_WARNING("could not apply shape marker: %s", TRI_errno_string(res));
++state->errorCount;
return state->canContinue();
}
break;
}
// -----------------------------------------------------------------------------
// crud operations
// -----------------------------------------------------------------------------
case TRI_WAL_MARKER_DOCUMENT: {
// re-insert the document into the collection
document_marker_t const* m = reinterpret_cast<document_marker_t const*>(marker);
TRI_voc_cid_t collectionId = m->_collectionId;
TRI_voc_tick_t databaseId = m->_databaseId;
if (state->isDropped(databaseId, collectionId)) {
return true;
}
TRI_voc_tick_t transactionId = m->_transactionId;
if (state->ignoreTransaction(transactionId)) {
// transaction was aborted
return true;
}
char const* base = reinterpret_cast<char const*>(m);
char const* key = base + m->_offsetKey;
TRI_shaped_json_t shaped;
TRI_EXTRACT_SHAPED_JSON_MARKER(shaped, m);
int res = TRI_ERROR_NO_ERROR;
if (state->isRemoteTransaction(transactionId)) {
// remote operation
res = state->executeRemoteOperation(databaseId, collectionId, transactionId, marker, datafile->_fid, [&](RemoteTransactionType* trx, Marker* envelope) -> int {
if (IsVolatile(trx->trxCollection(collectionId))) {
return TRI_ERROR_NO_ERROR;
}
TRI_doc_mptr_copy_t mptr;
int res = TRI_InsertShapedJsonDocumentCollection(trx->trxCollection(collectionId), (TRI_voc_key_t) key, m->_revisionId, envelope, &mptr, &shaped, nullptr, false, false, true);
if (res == TRI_ERROR_ARANGO_UNIQUE_CONSTRAINT_VIOLATED) {
state->policy.setExpectedRevision(m->_revisionId);
res = TRI_UpdateShapedJsonDocumentCollection(trx->trxCollection(collectionId), (TRI_voc_key_t) key, m->_revisionId, envelope, &mptr, &shaped, &state->policy, false, false);
}
return res;
});
}
else if (! state->isUsedByRemoteTransaction(collectionId)) {
// local operation
res = state->executeSingleOperation(databaseId, collectionId, marker, datafile->_fid, [&](SingleWriteTransactionType* trx, Marker* envelope) -> int {
if (IsVolatile(trx->trxCollection())) {
return TRI_ERROR_NO_ERROR;
}
TRI_doc_mptr_copy_t mptr;
int res = TRI_InsertShapedJsonDocumentCollection(trx->trxCollection(), (TRI_voc_key_t) key, m->_revisionId, envelope, &mptr, &shaped, nullptr, false, false, true);
if (res == TRI_ERROR_ARANGO_UNIQUE_CONSTRAINT_VIOLATED) {
state->policy.setExpectedRevision(m->_revisionId);
res = TRI_UpdateShapedJsonDocumentCollection(trx->trxCollection(), (TRI_voc_key_t) key, m->_revisionId, envelope, &mptr, &shaped, &state->policy, false, false);
}
return res;
});
}
else {
// ERROR - found a local action for a collection that has an ongoing remote transaction
res = TRI_ERROR_TRANSACTION_INTERNAL;
}
if (res != TRI_ERROR_NO_ERROR &&
res != TRI_ERROR_ARANGO_CONFLICT &&
res != TRI_ERROR_ARANGO_DATABASE_NOT_FOUND &&
res != TRI_ERROR_ARANGO_COLLECTION_NOT_FOUND) {
LOG_WARNING("unable to insert document in collection %llu of database %llu: %s",
(unsigned long long) collectionId,
(unsigned long long) databaseId,
TRI_errno_string(res));
++state->errorCount;
return state->canContinue();
}
break;
}
case TRI_WAL_MARKER_EDGE: {
// re-insert the edge into the collection
edge_marker_t const* m = reinterpret_cast<edge_marker_t const*>(marker);
TRI_voc_cid_t collectionId = m->_collectionId;
TRI_voc_tick_t databaseId = m->_databaseId;
if (state->isDropped(databaseId, collectionId)) {
return true;
}
TRI_voc_tick_t transactionId = m->_transactionId;
if (state->ignoreTransaction(transactionId)) {
return true;
}
char const* base = reinterpret_cast<char const*>(m);
char const* key = base + m->_offsetKey;
TRI_document_edge_t edge;
edge._fromCid = m->_fromCid;
edge._toCid = m->_toCid;
edge._fromKey = const_cast<char*>(base) + m->_offsetFromKey;
edge._toKey = const_cast<char*>(base) + m->_offsetToKey;
TRI_shaped_json_t shaped;
TRI_EXTRACT_SHAPED_JSON_MARKER(shaped, m);
int res = TRI_ERROR_NO_ERROR;
if (state->isRemoteTransaction(transactionId)) {
// remote operation
res = state->executeRemoteOperation(databaseId, collectionId, transactionId, marker, datafile->_fid, [&](RemoteTransactionType* trx, Marker* envelope) -> int {
if (IsVolatile(trx->trxCollection(collectionId))) {
return TRI_ERROR_NO_ERROR;
}
TRI_doc_mptr_copy_t mptr;
int res = TRI_InsertShapedJsonDocumentCollection(trx->trxCollection(collectionId), (TRI_voc_key_t) key, m->_revisionId, envelope, &mptr, &shaped, &edge, false, false, true);
if (res == TRI_ERROR_ARANGO_UNIQUE_CONSTRAINT_VIOLATED) {
state->policy.setExpectedRevision(m->_revisionId);
res = TRI_UpdateShapedJsonDocumentCollection(trx->trxCollection(collectionId), (TRI_voc_key_t) key, m->_revisionId, envelope, &mptr, &shaped, &state->policy, false, false);
}
return res;
});
}
else if (! state->isUsedByRemoteTransaction(collectionId)) {
// local operation
res = state->executeSingleOperation(databaseId, collectionId, marker, datafile->_fid, [&](SingleWriteTransactionType* trx, Marker* envelope) -> int {
if (IsVolatile(trx->trxCollection())) {
return TRI_ERROR_NO_ERROR;
}
TRI_doc_mptr_copy_t mptr;
int res = TRI_InsertShapedJsonDocumentCollection(trx->trxCollection(), (TRI_voc_key_t) key, m->_revisionId, envelope, &mptr, &shaped, &edge, false, false, true);
if (res == TRI_ERROR_ARANGO_UNIQUE_CONSTRAINT_VIOLATED) {
state->policy.setExpectedRevision(m->_revisionId);
res = TRI_UpdateShapedJsonDocumentCollection(trx->trxCollection(), (TRI_voc_key_t) key, m->_revisionId, envelope, &mptr, &shaped, &state->policy, false, false);
}
return res;
});
}
else {
// ERROR - found a local action for a collection that has an ongoing remote transaction
res = TRI_ERROR_TRANSACTION_INTERNAL;
}
if (res != TRI_ERROR_NO_ERROR &&
res != TRI_ERROR_ARANGO_CONFLICT &&
res != TRI_ERROR_ARANGO_DATABASE_NOT_FOUND &&
res != TRI_ERROR_ARANGO_COLLECTION_NOT_FOUND) {
LOG_WARNING("unable to insert edge in collection %llu of database %llu: %s",
(unsigned long long) collectionId,
(unsigned long long) databaseId,
TRI_errno_string(res));
++state->errorCount;
return state->canContinue();
}
break;
}
case TRI_WAL_MARKER_REMOVE: {
// re-apply the remove operation
remove_marker_t const* m = reinterpret_cast<remove_marker_t const*>(marker);
TRI_voc_cid_t collectionId = m->_collectionId;
TRI_voc_tick_t databaseId = m->_databaseId;
if (state->isDropped(databaseId, collectionId)) {
return true;
}
TRI_voc_tick_t transactionId = m->_transactionId;
if (state->ignoreTransaction(transactionId)) {
return true;
}
char const* base = reinterpret_cast<char const*>(m);
char const* key = base + sizeof(remove_marker_t);
int res = TRI_ERROR_NO_ERROR;
if (state->isRemoteTransaction(transactionId)) {
// remote operation
res = state->executeRemoteOperation(databaseId, collectionId, transactionId, marker, datafile->_fid, [&](RemoteTransactionType* trx, Marker* envelope) -> int {
if (IsVolatile(trx->trxCollection(collectionId))) {
return TRI_ERROR_NO_ERROR;
}
// remove the document and ignore any potential errors
state->policy.setExpectedRevision(m->_revisionId);
TRI_RemoveShapedJsonDocumentCollection(trx->trxCollection(collectionId), (TRI_voc_key_t) key, m->_revisionId, envelope, &state->policy, false, false);
return TRI_ERROR_NO_ERROR;
});
}
else if (! state->isUsedByRemoteTransaction(collectionId)) {
// local operation
res = state->executeSingleOperation(databaseId, collectionId, marker, datafile->_fid, [&](SingleWriteTransactionType* trx, Marker* envelope) -> int {
if (IsVolatile(trx->trxCollection())) {
return TRI_ERROR_NO_ERROR;
}
// remove the document and ignore any potential errors
state->policy.setExpectedRevision(m->_revisionId);
TRI_RemoveShapedJsonDocumentCollection(trx->trxCollection(), (TRI_voc_key_t) key, m->_revisionId, envelope, &state->policy, false, false);
return TRI_ERROR_NO_ERROR;
});
}
else {
// ERROR - found a local action for a collection that has an ongoing remote transaction
res = TRI_ERROR_TRANSACTION_INTERNAL;
}
if (res != TRI_ERROR_NO_ERROR &&
res != TRI_ERROR_ARANGO_CONFLICT &&
res != TRI_ERROR_ARANGO_DATABASE_NOT_FOUND &&
res != TRI_ERROR_ARANGO_COLLECTION_NOT_FOUND) {
LOG_WARNING("unable to remove document in collection %llu of database %llu: %s",
(unsigned long long) collectionId,
(unsigned long long) databaseId,
TRI_errno_string(res));
++state->errorCount;
return state->canContinue();
}
break;
}
// -----------------------------------------------------------------------------
// transactions
// -----------------------------------------------------------------------------
case TRI_WAL_MARKER_BEGIN_REMOTE_TRANSACTION: {
transaction_remote_begin_marker_t const* m = reinterpret_cast<transaction_remote_begin_marker_t const*>(marker);
TRI_voc_tick_t databaseId = m->_databaseId;
TRI_voc_tid_t externalId = m->_externalId;
// start a remote transaction
if (state->isDropped(databaseId)) {
return true;
}
TRI_vocbase_t* vocbase = state->useDatabase(databaseId);
if (vocbase == nullptr) {
LOG_WARNING("cannot start remote transaction in database %llu: %s",
(unsigned long long) databaseId,
TRI_errno_string(TRI_ERROR_ARANGO_DATABASE_NOT_FOUND));
}
auto trx = new RemoteTransactionType(state->server, vocbase, externalId);
if (trx == nullptr) {
LOG_WARNING("unable to start transaction: %s", TRI_errno_string(TRI_ERROR_OUT_OF_MEMORY));
++state->errorCount;
return state->canContinue();
}
trx->addHint(TRI_TRANSACTION_HINT_NO_BEGIN_MARKER, true);
int res = trx->begin();
if (res != TRI_ERROR_NO_ERROR) {
LOG_WARNING("unable to start transaction: %s", TRI_errno_string(TRI_ERROR_OUT_OF_MEMORY));
delete trx;
++state->errorCount;
return state->canContinue();
}
state->runningRemoteTransactions.emplace(std::make_pair(m->_externalId, trx));
break;
}
case TRI_WAL_MARKER_RENAME_COLLECTION: {
collection_rename_marker_t const* m = reinterpret_cast<collection_rename_marker_t const*>(marker);
TRI_voc_cid_t collectionId = m->_collectionId;
TRI_voc_tick_t databaseId = m->_databaseId;
if (state->isDropped(databaseId)) {
return true;
}
TRI_vocbase_t* vocbase = state->useDatabase(databaseId);
if (vocbase == nullptr) {
// if the underlying database is gone, we can go on
LOG_TRACE("cannot open database %llu", (unsigned long long) databaseId);
return true;
}
TRI_vocbase_col_t* collection = state->releaseCollection(collectionId);
if (collection == nullptr) {
collection = TRI_LookupCollectionByIdVocBase(vocbase, collectionId);
}
if (collection == nullptr) {
// if the underlying collection is gone, we can go on
LOG_TRACE("cannot open collection %llu", (unsigned long long) collectionId);
return true;
}
char const* name = reinterpret_cast<char const*>(m) + sizeof(collection_rename_marker_t);
// check if other collection exist with target name
TRI_vocbase_col_t* other = TRI_LookupCollectionByNameVocBase(vocbase, name);
if (other != nullptr) {
TRI_voc_cid_t otherCid = other->_cid;
state->releaseCollection(otherCid);
int statusCode = TRI_DropCollectionVocBase(vocbase, other, false);
WaitForDeletion(vocbase, otherCid, statusCode);
}
int res = TRI_RenameCollectionVocBase(vocbase, collection, name, true, false);
if (res != TRI_ERROR_NO_ERROR) {
LOG_WARNING("cannot rename collection collection %llu in database %llu: %s",
(unsigned long long) collectionId,
(unsigned long long) databaseId,
TRI_errno_string(res));
++state->errorCount;
return state->canContinue();
}
break;
}
case TRI_WAL_MARKER_CHANGE_COLLECTION: {
collection_change_marker_t const* m = reinterpret_cast<collection_change_marker_t const*>(marker);
TRI_voc_cid_t collectionId = m->_collectionId;
TRI_voc_tick_t databaseId = m->_databaseId;
if (state->isDropped(databaseId)) {
return true;
}
TRI_vocbase_t* vocbase = state->useDatabase(databaseId);
if (vocbase == nullptr) {
// if the underlying database is gone, we can go on
LOG_TRACE("cannot open database %llu", (unsigned long long) databaseId);
return true;
}
TRI_document_collection_t* document = state->getCollection(databaseId, collectionId);
if (document == nullptr) {
// if the underlying collection is gone, we can go on
LOG_TRACE("cannot change properties of collection %llu in database %llu: %s",
(unsigned long long) collectionId,
(unsigned long long) databaseId,
TRI_errno_string(TRI_ERROR_ARANGO_COLLECTION_NOT_FOUND));
return true;
}
char const* properties = reinterpret_cast<char const*>(m) + sizeof(collection_change_marker_t);
TRI_json_t* json = triagens::basics::JsonHelper::fromString(properties);
if (! TRI_IsObjectJson(json)) {
if (json != nullptr) {
TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, json);
}
LOG_WARNING("cannot unpack collection properties for collection %llu in database %llu",
(unsigned long long) collectionId,
(unsigned long long) databaseId);
++state->errorCount;
return state->canContinue();
}
TRI_json_t const* value;
TRI_col_info_t parameters;
parameters._doCompact = true;
parameters._waitForSync = vocbase->_settings.defaultWaitForSync;
parameters._maximalSize = vocbase->_settings.defaultMaximalSize;
value = TRI_LookupObjectJson(json, "doCompact");
if (TRI_IsBooleanJson(value)) {
parameters._doCompact = value->_value._boolean;
}
value = TRI_LookupObjectJson(json, "waitForSync");
if (TRI_IsBooleanJson(value)) {
parameters._waitForSync = value->_value._boolean;
}
value = TRI_LookupObjectJson(json, "maximalSize");
if (TRI_IsNumberJson(value)) {
parameters._maximalSize = static_cast<TRI_voc_size_t>(value->_value._number);
}
int res = TRI_UpdateCollectionInfo(vocbase, document, &parameters, vocbase->_settings.forceSyncProperties);
if (res != TRI_ERROR_NO_ERROR) {
LOG_WARNING("cannot change collection properties for collection %llu in database %llu: %s",
(unsigned long long) collectionId,
(unsigned long long) databaseId,
TRI_errno_string(res));
++state->errorCount;
return state->canContinue();
}
break;
}
// -----------------------------------------------------------------------------
// create operations
// -----------------------------------------------------------------------------
case TRI_WAL_MARKER_CREATE_INDEX: {
index_create_marker_t const* m = reinterpret_cast<index_create_marker_t const*>(marker);
TRI_voc_cid_t collectionId = m->_collectionId;
TRI_voc_tick_t databaseId = m->_databaseId;
TRI_idx_iid_t indexId = m->_indexId;
if (state->isDropped(databaseId, collectionId)) {
return true;
}
TRI_vocbase_t* vocbase = state->useDatabase(databaseId);
if (vocbase == nullptr) {
// if the underlying database is gone, we can go on
LOG_TRACE("cannot create index for collection %llu in database %llu: %s",
(unsigned long long) collectionId,
(unsigned long long) databaseId,
TRI_errno_string(TRI_ERROR_ARANGO_DATABASE_NOT_FOUND));
return true;
}
TRI_document_collection_t* document = state->getCollection(databaseId, collectionId);
if (document == nullptr) {
// if the underlying collection is gone, we can go on
LOG_TRACE("cannot create index for collection %llu in database %llu: %s",
(unsigned long long) collectionId,
(unsigned long long) databaseId,
TRI_errno_string(TRI_ERROR_ARANGO_COLLECTION_NOT_FOUND));
return true;
}
char const* properties = reinterpret_cast<char const*>(m) + sizeof(index_create_marker_t);
TRI_json_t* json = triagens::basics::JsonHelper::fromString(properties);
if (! TRI_IsObjectJson(json)) {
if (json != nullptr) {
TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, json);
}
LOG_WARNING("cannot unpack index properties for index %llu, collection %llu in database %llu",
(unsigned long long) indexId,
(unsigned long long) collectionId,
(unsigned long long) databaseId);
++state->errorCount;
return state->canContinue();
}
if (! TRI_IsObjectJson(json)) {
TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, json);
LOG_WARNING("cannot unpack index properties for index %llu, collection %llu in database %llu",
(unsigned long long) indexId,
(unsigned long long) collectionId,
(unsigned long long) databaseId);
++state->errorCount;
return state->canContinue();
}
// fake transaction to satisfy assertions
triagens::arango::TransactionBase trx(true);
std::string collectionDirectory = GetCollectionDirectory(vocbase, collectionId);
char* idString = TRI_StringUInt64(indexId);
char* indexName = TRI_Concatenate3String("index-", idString, ".json");
TRI_FreeString(TRI_CORE_MEM_ZONE, idString);
char* filename = TRI_Concatenate2File(collectionDirectory.c_str(), indexName);
TRI_FreeString(TRI_CORE_MEM_ZONE, indexName);
bool ok = TRI_SaveJson(filename, json, vocbase->_settings.forceSyncProperties);
TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, json);
if (! ok) {
TRI_FreeString(TRI_CORE_MEM_ZONE, filename);
LOG_WARNING("cannot create index %llu, collection %llu in database %llu",
(unsigned long long) indexId,
(unsigned long long) collectionId,
(unsigned long long) databaseId);
++state->errorCount;
return state->canContinue();
}
else {
TRI_PushBackVectorString(&document->_indexFiles, filename);
}
break;
}
case TRI_WAL_MARKER_CREATE_COLLECTION: {
collection_create_marker_t const* m = reinterpret_cast<collection_create_marker_t const*>(marker);
TRI_voc_cid_t collectionId = m->_collectionId;
TRI_voc_tick_t databaseId = m->_databaseId;
// remove the drop marker
state->droppedCollections.erase(collectionId);
if (state->isDropped(databaseId)) {
return true;
}
TRI_vocbase_t* vocbase = state->useDatabase(databaseId);
if (vocbase == nullptr) {
// if the underlying database is gone, we can go on
LOG_TRACE("cannot open database %llu", (unsigned long long) databaseId);
return true;
}
TRI_vocbase_col_t* collection = state->releaseCollection(collectionId);
if (collection == nullptr) {
collection = TRI_LookupCollectionByIdVocBase(vocbase, collectionId);
}
if (collection != nullptr) {
// drop an existing collection
int statusCode = TRI_DropCollectionVocBase(vocbase, collection, false);
WaitForDeletion(vocbase, collectionId, statusCode);
}
char const* properties = reinterpret_cast<char const*>(m) + sizeof(collection_create_marker_t);
TRI_json_t* json = triagens::basics::JsonHelper::fromString(properties);
if (! TRI_IsObjectJson(json)) {
if (json != nullptr) {
TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, json);
}
LOG_WARNING("cannot unpack collection properties for collection %llu in database %llu",
(unsigned long long) collectionId,
(unsigned long long) databaseId);
++state->errorCount;
return state->canContinue();
}
// check if there is another collection with the same name as the one that we attempt to create
TRI_json_t const* name = TRI_LookupObjectJson(json, "name");
if (TRI_IsStringJson(name)) {
collection = TRI_LookupCollectionByNameVocBase(vocbase, name->_value._string.data);
if (collection != nullptr && ! TRI_IsSystemNameCollection(name->_value._string.data)) {
// if yes, delete it
TRI_voc_cid_t otherCid = collection->_cid;
state->releaseCollection(otherCid);
int statusCode = TRI_DropCollectionVocBase(vocbase, collection, false);
WaitForDeletion(vocbase, otherCid, statusCode);
}
}
TRI_col_info_t info;
memset(&info, 0, sizeof(TRI_col_info_t));
TRI_FromJsonCollectionInfo(&info, json);
TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, json);
// fake transaction to satisfy assertions
triagens::arango::TransactionBase trx(true);
WaitForDeletion(vocbase, collectionId, TRI_ERROR_ARANGO_COLLECTION_NOT_FOUND);
if (state->willBeDropped(collectionId)) {
// in case we detect that this collection is going to be deleted anyway, set
// the sync properties to false temporarily
bool oldSync = vocbase->_settings.forceSyncProperties;
vocbase->_settings.forceSyncProperties = false;
collection = TRI_CreateCollectionVocBase(vocbase, &info, collectionId, false);
vocbase->_settings.forceSyncProperties = oldSync;
}
else {
// collection will be kept
collection = TRI_CreateCollectionVocBase(vocbase, &info, collectionId, false);
}
TRI_FreeCollectionInfoOptions(&info);
if (collection == nullptr) {
LOG_WARNING("cannot create collection %llu in database %llu",
(unsigned long long) collectionId,
(unsigned long long) databaseId);
++state->errorCount;
return state->canContinue();
}
break;
}
case TRI_WAL_MARKER_CREATE_DATABASE: {
database_create_marker_t const* m = reinterpret_cast<database_create_marker_t const*>(marker);
TRI_voc_tick_t databaseId = m->_databaseId;
// remove the drop marker
state->droppedDatabases.erase(databaseId);
TRI_vocbase_t* vocbase = state->releaseDatabase(databaseId);
if (vocbase != nullptr) {
// remove already existing database
int statusCode = TRI_DropByIdDatabaseServer(state->server, databaseId, false, false);
WaitForDeletion(state->server, databaseId, statusCode);
}
char const* properties = reinterpret_cast<char const*>(m) + sizeof(database_create_marker_t);
TRI_json_t* json = triagens::basics::JsonHelper::fromString(properties);
if (! TRI_IsObjectJson(json)) {
if (json != nullptr) {
TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, json);
}
LOG_WARNING("cannot unpack database properties for database %llu", (unsigned long long) databaseId);
++state->errorCount;
return state->canContinue();
}
TRI_json_t const* nameValue = TRI_LookupObjectJson(json, "name");
if (! TRI_IsStringJson(nameValue)) {
TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, json);
LOG_WARNING("cannot unpack database properties for database %llu", (unsigned long long) databaseId);
++state->errorCount;
return state->canContinue();
}
std::string nameString(nameValue->_value._string.data);
TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, json);
// remove already existing database with same name
vocbase = TRI_LookupDatabaseByNameServer(state->server, nameString.c_str());
if (vocbase != nullptr) {
TRI_voc_tick_t otherId = vocbase->_id;
state->releaseDatabase(otherId);
int statusCode = TRI_DropDatabaseServer(state->server, nameString.c_str(), false, false);
WaitForDeletion(state->server, otherId, statusCode);
}
TRI_vocbase_defaults_t defaults;
TRI_GetDatabaseDefaultsServer(state->server, &defaults);
// fake transaction to satisfy assertions
triagens::arango::TransactionBase trx(true);
vocbase = nullptr;
WaitForDeletion(state->server, databaseId, TRI_ERROR_ARANGO_DATABASE_NOT_FOUND);
int res = TRI_CreateDatabaseServer(state->server, databaseId, nameString.c_str(), &defaults, &vocbase, false);
if (res != TRI_ERROR_NO_ERROR) {
LOG_WARNING("cannot create database %llu: %s", (unsigned long long) databaseId, TRI_errno_string(res));
++state->errorCount;
return state->canContinue();
}
break;
}
// -----------------------------------------------------------------------------
// drop operations
// -----------------------------------------------------------------------------
case TRI_WAL_MARKER_DROP_INDEX: {
index_drop_marker_t const* m = reinterpret_cast<index_drop_marker_t const*>(marker);
TRI_voc_cid_t collectionId = m->_collectionId;
TRI_voc_tick_t databaseId = m->_databaseId;
TRI_idx_iid_t indexId = m->_indexId;
if (state->isDropped(databaseId, collectionId)) {
return true;
}
TRI_vocbase_t* vocbase = state->useDatabase(databaseId);
if (vocbase == nullptr) {
// if the underlying database is gone, we can go on
LOG_TRACE("cannot open database %llu", (unsigned long long) databaseId);
return true;
}
TRI_document_collection_t* document = state->getCollection(databaseId, collectionId);
if (document == nullptr) {
// if the underlying collection gone, we can go on
return true;
}
// fake transaction to satisfy assertions
triagens::arango::TransactionBase trx(true);
// ignore any potential error returned by this call
TRI_DropIndexDocumentCollection(document, indexId, false);
TRI_RemoveFileIndexCollection(document, indexId);
// additionally remove the index file
std::string collectionDirectory = GetCollectionDirectory(vocbase, collectionId);
char* idString = TRI_StringUInt64(indexId);
char* indexName = TRI_Concatenate3String("index-", idString, ".json");
TRI_FreeString(TRI_CORE_MEM_ZONE, idString);
char* filename = TRI_Concatenate2File(collectionDirectory.c_str(), indexName);
TRI_FreeString(TRI_CORE_MEM_ZONE, indexName);
TRI_UnlinkFile(filename);
TRI_FreeString(TRI_CORE_MEM_ZONE, filename);
break;
}
case TRI_WAL_MARKER_DROP_COLLECTION: {
collection_drop_marker_t const* m = reinterpret_cast<collection_drop_marker_t const*>(marker);
TRI_voc_cid_t collectionId = m->_collectionId;
TRI_voc_tick_t databaseId = m->_databaseId;
// insert the drop marker
state->droppedCollections.insert(collectionId);
TRI_vocbase_t* vocbase = state->useDatabase(databaseId);
if (vocbase == nullptr) {
// database already deleted - do nothing
return true;
}
// ignore any potential error returned by this call
TRI_vocbase_col_t* collection = state->releaseCollection(collectionId);
if (collection == nullptr) {
collection = TRI_LookupCollectionByIdVocBase(vocbase, collectionId);
}
if (collection != nullptr) {
// fake transaction to satisfy assertions
triagens::arango::TransactionBase trx(true);
int statusCode = TRI_DropCollectionVocBase(vocbase, collection, false);
WaitForDeletion(vocbase, collectionId, statusCode);
}
break;
}
case TRI_WAL_MARKER_DROP_DATABASE: {
database_drop_marker_t const* m = reinterpret_cast<database_drop_marker_t const*>(marker);
TRI_voc_tick_t databaseId = m->_databaseId;
// insert the drop marker
state->droppedDatabases.insert(databaseId);
TRI_vocbase_t* vocbase = state->releaseDatabase(databaseId);
if (vocbase != nullptr) {
// fake transaction to satisfy assertions
triagens::arango::TransactionBase trx(true);
// ignore any potential error returned by this call
TRI_DropByIdDatabaseServer(state->server, databaseId, false, false);
}
break;
}
}
return true;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief replay a single logfile
////////////////////////////////////////////////////////////////////////////////
int RecoverState::replayLogfile (Logfile* logfile,
int number) {
int const n = static_cast<int>(logfilesToProcess.size());
LOG_INFO("replaying WAL logfile '%s' (%d of %d)",
logfile->filename().c_str(), number + 1, n);
if (! TRI_IterateDatafile(logfile->df(), &RecoverState::ReplayMarker, static_cast<void*>(this))) {
LOG_WARNING("WAL inspection failed when scanning logfile '%s'", logfile->filename().c_str());
return TRI_ERROR_ARANGO_RECOVERY;
}
return TRI_ERROR_NO_ERROR;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief replay all logfiles
////////////////////////////////////////////////////////////////////////////////
int RecoverState::replayLogfiles () {
droppedCollections.clear();
droppedDatabases.clear();
int i = 0;
for (auto& it : logfilesToProcess) {
TRI_ASSERT(it != nullptr);
int res = replayLogfile(it, i++);
if (res != TRI_ERROR_NO_ERROR) {
return res;
}
}
return TRI_ERROR_NO_ERROR;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief abort open transactions
////////////////////////////////////////////////////////////////////////////////
int RecoverState::abortOpenTransactions () {
if (failedTransactions.empty()) {
// nothing to do
return TRI_ERROR_NO_ERROR;
}
LOG_TRACE("writing abort markers for still open transactions");
int res = TRI_ERROR_NO_ERROR;
try {
// write abort markers for all transactions
for (auto it = failedTransactions.begin(); it != failedTransactions.end(); ++it) {
TRI_voc_tid_t transactionId = (*it).first;
if ((*it).second.second) {
// already handled
continue;
}
TRI_voc_tick_t databaseId = (*it).second.first;
AbortTransactionMarker marker(databaseId, transactionId);
SlotInfoCopy slotInfo = triagens::wal::LogfileManager::instance()->allocateAndWrite(marker.mem(), marker.size(), false);
if (slotInfo.errorCode != TRI_ERROR_NO_ERROR) {
THROW_ARANGO_EXCEPTION(slotInfo.errorCode);
}
}
}
catch (triagens::basics::Exception const& ex) {
res = ex.code();
}
catch (...) {
res = TRI_ERROR_INTERNAL;
}
return res;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief remove all empty logfiles found during logfile inspection
////////////////////////////////////////////////////////////////////////////////
int RecoverState::removeEmptyLogfiles () {
if (emptyLogfiles.empty()) {
return TRI_ERROR_NO_ERROR;
}
LOG_TRACE("removing empty WAL logfiles");
for (auto it = emptyLogfiles.begin(); it != emptyLogfiles.end(); ++it) {
auto filename = (*it);
if (basics::FileUtils::remove(filename, 0)) {
LOG_TRACE("removing empty WAL logfile '%s'", filename.c_str());
}
}
emptyLogfiles.clear();
return TRI_ERROR_NO_ERROR;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief fill the secondary indexes of all collections used in recovery
////////////////////////////////////////////////////////////////////////////////
int RecoverState::fillIndexes () {
// fake transaction to allow populating the secondary indexes
triagens::arango::TransactionBase trx(true);
// release all collections
for (auto it = openedCollections.begin(); it != openedCollections.end(); ++it) {
TRI_vocbase_col_t* collection = (*it).second;
TRI_document_collection_t* document = collection->_collection;
TRI_ASSERT(document != nullptr);
// activate secondary indexes
document->useSecondaryIndexes(true);
int res = TRI_FillIndexesDocumentCollection(collection, document);
if (res != TRI_ERROR_NO_ERROR) {
return res;
}
}
return TRI_ERROR_NO_ERROR;
}
// -----------------------------------------------------------------------------
// --SECTION-- END-OF-FILE
// -----------------------------------------------------------------------------
// Local Variables:
// mode: outline-minor
// outline-regexp: "/// @brief\\|/// {@inheritDoc}\\|/// @page\\|// --SECTION--\\|/// @\\}"
// End: