From e4ce808349bac21b2f547638c330c9679f986358 Mon Sep 17 00:00:00 2001 From: Max Neunhoeffer Date: Fri, 4 Mar 2016 15:16:23 +0100 Subject: [PATCH] Finish revolution of document API, RestHandler still missing. --- arangod/Utils/Transaction.cpp | 177 ++--------------- arangod/Utils/Transaction.h | 11 +- arangod/V8Server/v8-collection.cpp | 42 ++-- arangod/VocBase/document-collection.cpp | 246 +++++++++++++++++++----- arangod/VocBase/document-collection.h | 39 ++-- arangod/VocBase/voc-types.h | 1 + arangod/Wal/DocumentOperation.h | 7 +- 7 files changed, 274 insertions(+), 249 deletions(-) diff --git a/arangod/Utils/Transaction.cpp b/arangod/Utils/Transaction.cpp index d04bb8bf8c..45f26cdd9b 100644 --- a/arangod/Utils/Transaction.cpp +++ b/arangod/Utils/Transaction.cpp @@ -848,7 +848,8 @@ OperationResult Transaction::update(std::string const& collectionName, return updateCoordinator(collectionName, newValue, optionsCopy); } - return updateLocal(collectionName, newValue, optionsCopy); + return modifyLocal(collectionName, newValue, optionsCopy, + TRI_VOC_DOCUMENT_OPERATION_UPDATE); } ////////////////////////////////////////////////////////////////////////////// @@ -909,105 +910,6 @@ OperationResult Transaction::updateCoordinator(std::string const& collectionName return OperationResult(res); } -////////////////////////////////////////////////////////////////////////////// -/// @brief update one or multiple documents in a collection, local -/// the single-document variant of this operation will either succeed or, -/// if it fails, clean up after itself -////////////////////////////////////////////////////////////////////////////// - -OperationResult Transaction::updateLocal(std::string const& collectionName, - VPackSlice const newValue, - OperationOptions& options) { - TRI_voc_cid_t cid = resolver()->getCollectionIdLocal(collectionName); - - if (cid == 0) { - return OperationResult(TRI_ERROR_ARANGO_COLLECTION_NOT_FOUND); - } - - // read expected revision - std::string const key(Transaction::extractKey(newValue)); - TRI_voc_rid_t const expectedRevision - = options.ignoreRevs ? 0 : Transaction::extractRevisionId(newValue); - - // generate a new tick value - TRI_voc_tick_t const revisionId = TRI_NewTickServer(); - // TODO: clean this up - TRI_document_collection_t* document = documentCollection(trxCollection(cid)); - - VPackBuilder builder; - builder.openObject(); - - VPackObjectIterator it(newValue); - while (it.valid()) { - // let all but the system attributes pass - std::string k = it.key().copyString(); - if (k[0] != '_' || - (k != TRI_VOC_ATTRIBUTE_KEY && - k != TRI_VOC_ATTRIBUTE_ID && - k != TRI_VOC_ATTRIBUTE_REV && - k != TRI_VOC_ATTRIBUTE_FROM && - k != TRI_VOC_ATTRIBUTE_TO)) { - builder.add(k, it.value()); - } - it.next(); - } - // finally add (new) _rev attribute - builder.add(TRI_VOC_ATTRIBUTE_REV, VPackValue(std::to_string(revisionId))); - builder.close(); - - VPackSlice sanitized = builder.slice(); - - if (orderDitch(trxCollection(cid)) == nullptr) { - return OperationResult(TRI_ERROR_OUT_OF_MEMORY); - } - - TRI_doc_mptr_t mptr; - TRI_voc_rid_t actualRevision = 0; - TRI_doc_update_policy_t policy(expectedRevision == 0 ? TRI_DOC_UPDATE_LAST_WRITE : TRI_DOC_UPDATE_ERROR, expectedRevision, &actualRevision); - - int res = lock(trxCollection(cid), TRI_TRANSACTION_WRITE); - - if (res != TRI_ERROR_NO_ERROR) { - return OperationResult(res); - } - - VPackBuilder oldBuilder; - { VPackObjectBuilder guard(&oldBuilder); - oldBuilder.add(TRI_VOC_ATTRIBUTE_KEY, VPackValue(key)); - if (expectedRevision != 0) { - oldBuilder.add(TRI_VOC_ATTRIBUTE_REV, - VPackValue(std::to_string(expectedRevision))); - } - } - VPackSlice oldValue = oldBuilder.slice(); - - res = document->update(this, &oldValue, &sanitized, &mptr, &policy, options, !isLocked(document, TRI_TRANSACTION_WRITE)); - - if (res == TRI_ERROR_ARANGO_CONFLICT) { - // still return - VPackBuilder resultBuilder; - buildDocumentIdentity(resultBuilder, cid, key, mptr.revisionId(), ""); - - return OperationResult(resultBuilder.steal(), nullptr, "", - TRI_ERROR_ARANGO_CONFLICT, - options.waitForSync || document->_info.waitForSync()); - } else if (res != TRI_ERROR_NO_ERROR) { - return OperationResult(res); - } - - TRI_ASSERT(mptr.getDataPtr() != nullptr); - - if (options.silent) { - return OperationResult(TRI_ERROR_NO_ERROR); - } - - VPackBuilder resultBuilder; - buildDocumentIdentity(resultBuilder, cid, key, std::to_string(revisionId), std::to_string(actualRevision)); - - return OperationResult(resultBuilder.steal(), nullptr, "", TRI_ERROR_NO_ERROR, - options.waitForSync); -} - ////////////////////////////////////////////////////////////////////////////// /// @brief replace one or multiple documents in a collection /// the single-document variant of this operation will either succeed or, @@ -1030,7 +932,8 @@ OperationResult Transaction::replace(std::string const& collectionName, return replaceCoordinator(collectionName, newValue, optionsCopy); } - return replaceLocal(collectionName, newValue, optionsCopy); + return modifyLocal(collectionName, newValue, optionsCopy, + TRI_VOC_DOCUMENT_OPERATION_REPLACE); } ////////////////////////////////////////////////////////////////////////////// @@ -1101,9 +1004,12 @@ OperationResult Transaction::replaceCoordinator(std::string const& collectionNam /// if it fails, clean up after itself ////////////////////////////////////////////////////////////////////////////// -OperationResult Transaction::replaceLocal(std::string const& collectionName, - VPackSlice const newValue, - OperationOptions& options) { +OperationResult Transaction::modifyLocal( + std::string const& collectionName, + VPackSlice const newValue, + OperationOptions& options, + TRI_voc_document_operation_e operation) { + TRI_voc_cid_t cid = resolver()->getCollectionIdLocal(collectionName); if (cid == 0) { @@ -1113,73 +1019,31 @@ OperationResult Transaction::replaceLocal(std::string const& collectionName, // TODO: clean this up TRI_document_collection_t* document = documentCollection(trxCollection(cid)); - // Replace is a read and a write, let's get the write lock already + // Update/replace are a read and a write, let's get the write lock already // for the read operation: int res = lock(trxCollection(cid), TRI_TRANSACTION_WRITE); - if (res != TRI_ERROR_NO_ERROR) { return OperationResult(res); } - VPackBuilder builder; // used repeatedly in closure VPackBuilder resultBuilder; // building the complete result auto workForOneDocument = [&](VPackSlice const newVal) -> int { - - // read key and expected revision - std::string const key(Transaction::extractKey(newVal)); - TRI_voc_rid_t const expectedRevision - = options.ignoreRevs ? 0 : Transaction::extractRevisionId(newVal); - - // generate a new tick value - TRI_voc_tick_t const revisionId = TRI_NewTickServer(); - - builder.clear(); - { - VPackObjectBuilder b(&builder); - VPackObjectIterator it(newVal); - while (it.valid()) { - // let all but the system attributes pass - std::string k = it.key().copyString(); - if (k[0] != '_' || - (k != TRI_VOC_ATTRIBUTE_KEY && - k != TRI_VOC_ATTRIBUTE_ID && - k != TRI_VOC_ATTRIBUTE_REV && - k != TRI_VOC_ATTRIBUTE_FROM && - k != TRI_VOC_ATTRIBUTE_TO)) { - builder.add(k, it.value()); - } - it.next(); - } - // finally add the (new) _rev attributes - builder.add(TRI_VOC_ATTRIBUTE_REV, VPackValue(std::to_string(revisionId))); - } - - VPackSlice sanitized = builder.slice(); - TRI_doc_mptr_t mptr; TRI_voc_rid_t actualRevision = 0; - TRI_doc_update_policy_t policy(expectedRevision == 0 ? - TRI_DOC_UPDATE_LAST_WRITE : - TRI_DOC_UPDATE_ERROR, - expectedRevision, &actualRevision); - VPackBuilder oldBuilder; - { VPackObjectBuilder guard(&oldBuilder); - oldBuilder.add(TRI_VOC_ATTRIBUTE_KEY, VPackValue(key)); - if (expectedRevision != 0) { - oldBuilder.add(TRI_VOC_ATTRIBUTE_REV, - VPackValue(std::to_string(expectedRevision))); - } + if (operation == TRI_VOC_DOCUMENT_OPERATION_REPLACE) { + res = document->replace(this, newVal, &mptr, options, + !isLocked(document, TRI_TRANSACTION_WRITE), actualRevision); + } else { + res = document->update(this, newVal, &mptr, options, + !isLocked(document, TRI_TRANSACTION_WRITE), actualRevision); } - VPackSlice oldVal = oldBuilder.slice(); - - res = document->replace(this, &oldVal, &sanitized, &mptr, &policy, - options, !isLocked(document, TRI_TRANSACTION_WRITE)); + std::string key = newVal.get(TRI_VOC_ATTRIBUTE_KEY).copyString(); if (res == TRI_ERROR_ARANGO_CONFLICT) { // still return - buildDocumentIdentity(resultBuilder, cid, key, mptr.revisionId(), ""); + buildDocumentIdentity(resultBuilder, cid, key, actualRevision, ""); return TRI_ERROR_ARANGO_CONFLICT; } else if (res != TRI_ERROR_NO_ERROR) { return res; @@ -1188,7 +1052,8 @@ OperationResult Transaction::replaceLocal(std::string const& collectionName, TRI_ASSERT(mptr.getDataPtr() != nullptr); if (!options.silent) { - buildDocumentIdentity(resultBuilder, cid, key, std::to_string(revisionId), std::to_string(actualRevision)); + buildDocumentIdentity(resultBuilder, cid, key, + std::to_string(mptr.revisionId()), std::to_string(actualRevision)); } return TRI_ERROR_NO_ERROR; }; diff --git a/arangod/Utils/Transaction.h b/arangod/Utils/Transaction.h index e477c72c66..de119dbbfe 100644 --- a/arangod/Utils/Transaction.h +++ b/arangod/Utils/Transaction.h @@ -699,17 +699,14 @@ class Transaction { VPackSlice const newValue, OperationOptions& options); - OperationResult updateLocal(std::string const& collectionName, - VPackSlice const newValue, - OperationOptions& options); - OperationResult replaceCoordinator(std::string const& collectionName, VPackSlice const newValue, OperationOptions& options); - OperationResult replaceLocal(std::string const& collectionName, - VPackSlice const newValue, - OperationOptions& options); + OperationResult modifyLocal(std::string const& collectionName, + VPackSlice const newValue, + OperationOptions& options, + TRI_voc_document_operation_e operation); OperationResult removeCoordinator(std::string const& collectionName, VPackSlice const& value, diff --git a/arangod/V8Server/v8-collection.cpp b/arangod/V8Server/v8-collection.cpp index 4e9bf0b555..7730fcf4d4 100644 --- a/arangod/V8Server/v8-collection.cpp +++ b/arangod/V8Server/v8-collection.cpp @@ -71,12 +71,6 @@ struct LocalCollectionGuard { TRI_vocbase_col_t* _collection; }; -//////////////////////////////////////////////////////////////////////////////// -/// @brief mode to distinguish replace and update, which share code -//////////////////////////////////////////////////////////////////////////////// - -enum ModifyMode { REPLACE, UPDATE }; - //////////////////////////////////////////////////////////////////////////////// /// @brief extract the forceSync flag from the arguments /// must specify the argument index starting from 1 @@ -1365,7 +1359,7 @@ static void parseReplaceAndUpdateOptions( v8::Isolate* isolate, v8::FunctionCallbackInfo const& args, OperationOptions& options, - ModifyMode mode) { + TRI_voc_document_operation_e operation) { TRI_GET_GLOBALS(); TRI_ASSERT(args.Length() > 2); @@ -1384,7 +1378,7 @@ static void parseReplaceAndUpdateOptions( if (optionsObject->Has(SilentKey)) { options.silent = TRI_ObjectToBoolean(optionsObject->Get(SilentKey)); } - if (mode == UPDATE) { + if (operation == TRI_VOC_DOCUMENT_OPERATION_UPDATE) { TRI_GET_GLOBAL_STRING(KeepNullKey); if (optionsObject->Has(KeepNullKey)) { options.keepNull = TRI_ObjectToBoolean(optionsObject->Get(KeepNullKey)); @@ -1402,7 +1396,7 @@ static void parseReplaceAndUpdateOptions( // update(, , , , options.ignoreRevs = TRI_ObjectToBoolean(args[2]); if (args.Length() > 3) { - if (mode == REPLACE) { + if (operation == TRI_VOC_DOCUMENT_OPERATION_REPLACE) { options.waitForSync = TRI_ObjectToBoolean(args[3]); } else { // UPDATE options.keepNull = TRI_ObjectToBoolean(args[3]); @@ -1418,7 +1412,7 @@ static void parseReplaceAndUpdateOptions( /// @brief ModifyVocbaseCol //////////////////////////////////////////////////////////////////////////////// -static void ModifyVocbaseCol(ModifyMode mode, +static void ModifyVocbaseCol(TRI_voc_document_operation_e operation, v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); @@ -1426,8 +1420,9 @@ static void ModifyVocbaseCol(ModifyMode mode, // check the arguments uint32_t const argLength = args.Length(); - if (argLength < 2 || argLength > (mode == REPLACE) ? 3 : 5) { - if (mode == REPLACE) { + if (argLength < 2 || + argLength > (operation == TRI_VOC_DOCUMENT_OPERATION_REPLACE) ? 3 : 5) { + if (operation == TRI_VOC_DOCUMENT_OPERATION_REPLACE) { TRI_V8_THROW_EXCEPTION_USAGE( "replace(, , {overwrite: booleanValue," " waitForSync: booleanValue})"); @@ -1456,7 +1451,7 @@ static void ModifyVocbaseCol(ModifyMode mode, OperationOptions options; options.ignoreRevs = false; - parseReplaceAndUpdateOptions(isolate, args, options, mode); + parseReplaceAndUpdateOptions(isolate, args, options, operation); // Find collection and vocbase std::string collectionName; @@ -1523,7 +1518,7 @@ static void ModifyVocbaseCol(ModifyMode mode, VPackSlice const update = updateBuilder.slice(); OperationResult opResult; - if (mode == REPLACE) { + if (operation == TRI_VOC_DOCUMENT_OPERATION_REPLACE) { opResult = trx.replace(collectionName, update, options); } else { opResult = trx.update(collectionName, update, options); @@ -1556,7 +1551,7 @@ static void ModifyVocbaseCol(ModifyMode mode, static void JS_ReplaceVocbaseCol( v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); - ModifyVocbaseCol(REPLACE, args); + ModifyVocbaseCol(TRI_VOC_DOCUMENT_OPERATION_REPLACE, args); TRI_V8_TRY_CATCH_END } @@ -1567,7 +1562,7 @@ static void JS_ReplaceVocbaseCol( static void JS_UpdateVocbaseCol( v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); - return ModifyVocbaseCol(UPDATE, args); + ModifyVocbaseCol(TRI_VOC_DOCUMENT_OPERATION_UPDATE, args); TRI_V8_TRY_CATCH_END } @@ -1575,7 +1570,7 @@ static void JS_UpdateVocbaseCol( /// @brief ModifyVocbase, database method, only single documents //////////////////////////////////////////////////////////////////////////////// -static void ModifyVocbase(ModifyMode mode, +static void ModifyVocbase(TRI_voc_document_operation_e operation, v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); @@ -1583,8 +1578,9 @@ static void ModifyVocbase(ModifyMode mode, // check the arguments uint32_t const argLength = args.Length(); - if (argLength < 2 || argLength > (mode == REPLACE) ? 3 : 5) { - if (mode == REPLACE) { + if (argLength < 2 || + argLength > (operation == TRI_VOC_DOCUMENT_OPERATION_REPLACE) ? 3 : 5) { + if (operation == TRI_VOC_DOCUMENT_OPERATION_REPLACE) { TRI_V8_THROW_EXCEPTION_USAGE( "_replace(, , {overwrite: booleanValue, waitForSync: " "booleanValue})"); @@ -1604,7 +1600,7 @@ static void ModifyVocbase(ModifyMode mode, OperationOptions options; options.ignoreRevs = false; if (args.Length() > 2) { - parseReplaceAndUpdateOptions(isolate, args, options, mode); + parseReplaceAndUpdateOptions(isolate, args, options, operation); } TRI_vocbase_col_t const* col = nullptr; @@ -1645,7 +1641,7 @@ static void ModifyVocbase(ModifyMode mode, VPackSlice const update = updateBuilder.slice(); OperationResult opResult; - if (mode == REPLACE) { + if (operation == TRI_VOC_DOCUMENT_OPERATION_REPLACE) { opResult = trx.replace(collectionName, update, options); } else { opResult = trx.update(collectionName, update, options); @@ -1677,7 +1673,7 @@ static void ModifyVocbase(ModifyMode mode, static void JS_ReplaceVocbase(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); - ModifyVocbase(REPLACE, args); + ModifyVocbase(TRI_VOC_DOCUMENT_OPERATION_REPLACE, args); TRI_V8_TRY_CATCH_END } @@ -1687,7 +1683,7 @@ static void JS_ReplaceVocbase(v8::FunctionCallbackInfo const& args) { static void JS_UpdateVocbase(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); - ModifyVocbase(UPDATE, args); + ModifyVocbase(TRI_VOC_DOCUMENT_OPERATION_UPDATE, args); TRI_V8_TRY_CATCH_END } diff --git a/arangod/VocBase/document-collection.cpp b/arangod/VocBase/document-collection.cpp index 7e861f323e..d5b6d147a5 100644 --- a/arangod/VocBase/document-collection.cpp +++ b/arangod/VocBase/document-collection.cpp @@ -3889,17 +3889,17 @@ int TRI_document_collection_t::insert(Transaction* trx, VPackSlice const* slice, /// @brief updates a document or edge in a collection //////////////////////////////////////////////////////////////////////////////// -int TRI_document_collection_t::update(Transaction* trx, VPackSlice const* slice, - VPackSlice const* newSlice, +int TRI_document_collection_t::update(Transaction* trx, + VPackSlice const newSlice, TRI_doc_mptr_t* mptr, - TRI_doc_update_policy_t const* policy, OperationOptions& options, - bool lock) { + bool lock, + TRI_voc_rid_t& prevRev) { // initialize the result TRI_ASSERT(mptr != nullptr); mptr->setDataPtr(nullptr); - TRI_voc_rid_t revisionId = Transaction::extractRevisionId(*newSlice); + TRI_voc_rid_t revisionId = TRI_NewTickServer(); TRI_voc_tick_t markerTick = 0; int res; @@ -3910,8 +3910,9 @@ int TRI_document_collection_t::update(Transaction* trx, VPackSlice const* slice, // get the header pointer of the previous revision TRI_doc_mptr_t* oldHeader; - res = lookupDocument(trx, slice, policy, oldHeader); - + VPackSlice key = newSlice.get(TRI_VOC_ATTRIBUTE_KEY); + TRI_ASSERT(!key.isNone()); + res = lookupDocument(trx, key, oldHeader); if (res != TRI_ERROR_NO_ERROR) { return res; } @@ -3926,8 +3927,21 @@ int TRI_document_collection_t::update(Transaction* trx, VPackSlice const* slice, THROW_ARANGO_EXCEPTION(TRI_ERROR_DEBUG); } + prevRev = oldHeader->revisionId(); + + // Check old revision: + if (!options.ignoreRevs) { + VPackSlice expectedRevSlice = newSlice.get(TRI_VOC_ATTRIBUTE_REV); + int res = checkRevision(trx, expectedRevSlice, prevRev); + if (res != TRI_ERROR_NO_ERROR) { + return res; + } + } + // merge old and new values - VPackBuilder builder = mergeObjects(trx, false, VPackSlice(oldHeader->vpack()), *newSlice, options.mergeObjects, !options.keepNull); + VPackBuilder builder = mergeObjectsForUpdate( + trx, VPackSlice(oldHeader->vpack()), newSlice, + std::to_string(revisionId), options.mergeObjects, options.keepNull); // create marker std::unique_ptr marker; @@ -3935,7 +3949,9 @@ int TRI_document_collection_t::update(Transaction* trx, VPackSlice const* slice, marker.reset(createVPackInsertMarker(trx, builder.slice())); } - auto actualMarker = (options.recoveryMarker == nullptr ? marker.get() : options.recoveryMarker); + auto actualMarker = (options.recoveryMarker == nullptr + ? marker.get() + : options.recoveryMarker); bool const freeMarker = (options.recoveryMarker == nullptr); arangodb::wal::DocumentOperation operation( @@ -3975,17 +3991,17 @@ int TRI_document_collection_t::update(Transaction* trx, VPackSlice const* slice, /// @brief replaces a document or edge in a collection //////////////////////////////////////////////////////////////////////////////// -int TRI_document_collection_t::replace(Transaction* trx, VPackSlice const* slice, - VPackSlice const* newSlice, +int TRI_document_collection_t::replace(Transaction* trx, + VPackSlice const newSlice, TRI_doc_mptr_t* mptr, - TRI_doc_update_policy_t const* policy, OperationOptions& options, - bool lock) { + bool lock, + TRI_voc_rid_t& prevRev) { // initialize the result TRI_ASSERT(mptr != nullptr); mptr->setDataPtr(nullptr); - TRI_voc_rid_t revisionId = Transaction::extractRevisionId(*newSlice); + TRI_voc_rid_t revisionId = TRI_NewTickServer(); TRI_voc_tick_t markerTick = 0; int res; @@ -3996,8 +4012,9 @@ int TRI_document_collection_t::replace(Transaction* trx, VPackSlice const* slice // get the header pointer of the previous revision TRI_doc_mptr_t* oldHeader; - res = lookupDocument(trx, slice, policy, oldHeader); - + VPackSlice key = newSlice.get(TRI_VOC_ATTRIBUTE_KEY); + TRI_ASSERT(!key.isNone()); + res = lookupDocument(trx, key, oldHeader); if (res != TRI_ERROR_NO_ERROR) { return res; } @@ -4012,8 +4029,21 @@ int TRI_document_collection_t::replace(Transaction* trx, VPackSlice const* slice THROW_ARANGO_EXCEPTION(TRI_ERROR_DEBUG); } + prevRev = oldHeader->revisionId(); + + // Check old revision: + if (!options.ignoreRevs) { + VPackSlice expectedRevSlice = newSlice.get(TRI_VOC_ATTRIBUTE_REV); + int res = checkRevision(trx, expectedRevSlice, prevRev); + if (res != TRI_ERROR_NO_ERROR) { + return res; + } + } + // merge old and new values - VPackBuilder builder = mergeObjects(trx, true, VPackSlice(oldHeader->vpack()), *newSlice, false, true); + VPackBuilder builder = newObjectForReplace( + trx, VPackSlice(oldHeader->vpack()), + newSlice, std::to_string(revisionId)); // create marker std::unique_ptr marker; @@ -4025,7 +4055,7 @@ int TRI_document_collection_t::replace(Transaction* trx, VPackSlice const* slice bool const freeMarker = (options.recoveryMarker == nullptr); arangodb::wal::DocumentOperation operation( - trx, actualMarker, freeMarker, this, TRI_VOC_DOCUMENT_OPERATION_UPDATE); + trx, actualMarker, freeMarker, this, TRI_VOC_DOCUMENT_OPERATION_REPLACE); marker.release(); @@ -4175,7 +4205,8 @@ int TRI_document_collection_t::rollbackOperation(arangodb::Transaction* trx, _numberDocuments--; return TRI_ERROR_NO_ERROR; - } else if (type == TRI_VOC_DOCUMENT_OPERATION_UPDATE) { + } else if (type == TRI_VOC_DOCUMENT_OPERATION_UPDATE || + type == TRI_VOC_DOCUMENT_OPERATION_REPLACE) { // copy the existing header's state TRI_doc_mptr_t copy = *header; @@ -4231,6 +4262,7 @@ arangodb::wal::Marker* TRI_document_collection_t::createVPackRemoveMarker( //////////////////////////////////////////////////////////////////////////////// /// @brief looks up a document by key, low level worker /// the caller must make sure the read lock on the collection is held +/// the slice contains _key and possibly _rev //////////////////////////////////////////////////////////////////////////////// int TRI_document_collection_t::lookupDocument( @@ -4262,6 +4294,54 @@ int TRI_document_collection_t::lookupDocument( return TRI_ERROR_NO_ERROR; } +//////////////////////////////////////////////////////////////////////////////// +/// @brief looks up a document by key, low level worker +/// the caller must make sure the read lock on the collection is held +/// the key must be a string slice, no revision check is performed +//////////////////////////////////////////////////////////////////////////////// + +int TRI_document_collection_t::lookupDocument( + arangodb::Transaction* trx, VPackSlice const key, + TRI_doc_mptr_t*& header) { + + if (!key.isString()) { + return TRI_ERROR_INTERNAL; + } + VPackBuilder searchValue; + searchValue.openArray(); + searchValue.openObject(); + searchValue.add(TRI_SLICE_KEY_EQUAL, key); + searchValue.close(); + searchValue.close(); + + header = primaryIndex()->lookup(trx, searchValue.slice()); + + if (header == nullptr) { + return TRI_ERROR_ARANGO_DOCUMENT_NOT_FOUND; + } + + return TRI_ERROR_NO_ERROR; +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief checks the revision of a document +//////////////////////////////////////////////////////////////////////////////// + +int TRI_document_collection_t::checkRevision(Transaction* trx, + VPackSlice const expected, + TRI_voc_rid_t found) { + TRI_voc_rid_t expectedRev = 0; + if (expected.isString()) { + expectedRev = arangodb::basics::VelocyPackHelper::stringUInt64(expected); + } else if (expected.isNumber()) { + expectedRev = expected.getNumber(); + } + if (expectedRev != 0 && found != expectedRev) { + return TRI_ERROR_ARANGO_CONFLICT; + } + return TRI_ERROR_NO_ERROR; +} + //////////////////////////////////////////////////////////////////////////////// /// @brief updates an existing document, low level worker /// the caller must make sure the write lock on the collection is held @@ -4491,18 +4571,18 @@ int TRI_document_collection_t::deleteSecondaryIndexes( } //////////////////////////////////////////////////////////////////////////////// -/// @brief merge two object slices +/// @brief new object for replace, oldValue must have _key and _id correctly +/// set. //////////////////////////////////////////////////////////////////////////////// -VPackBuilder TRI_document_collection_t::mergeObjects(arangodb::Transaction* trx, - bool isReplace, - VPackSlice const& oldValue, - VPackSlice const& newValue, - bool mergeObjects, bool keepNull) { - if (isReplace) { - // replace - VPackBuilder builder; - builder.openObject(); +VPackBuilder TRI_document_collection_t::newObjectForReplace( + Transaction* trx, + VPackSlice const oldValue, + VPackSlice const newValue, + std::string const& rev) { + // replace + VPackBuilder builder; + { VPackObjectBuilder guard(&builder); VPackObjectIterator it(newValue); while (it.valid()) { @@ -4510,33 +4590,101 @@ VPackBuilder TRI_document_collection_t::mergeObjects(arangodb::Transaction* trx, if (key[0] != '_' || (key != TRI_VOC_ATTRIBUTE_ID && key != TRI_VOC_ATTRIBUTE_KEY && - key != TRI_VOC_ATTRIBUTE_REV && - key != TRI_VOC_ATTRIBUTE_FROM && - key != TRI_VOC_ATTRIBUTE_TO)) { + key != TRI_VOC_ATTRIBUTE_REV)) { builder.add(key, it.value()); } it.next(); } + VPackSlice s = oldValue.get(TRI_VOC_ATTRIBUTE_ID); + TRI_ASSERT(!s.isNone()); + builder.add(TRI_VOC_ATTRIBUTE_ID, s); + s = oldValue.get(TRI_VOC_ATTRIBUTE_KEY); + TRI_ASSERT(!s.isNone()); + builder.add(TRI_VOC_ATTRIBUTE_KEY, s); + builder.add(TRI_VOC_ATTRIBUTE_REV, VPackValue(rev)); + } + return builder; +} - VPackObjectIterator it2(oldValue); - while (it2.valid()) { - std::string key(it2.key().copyString()); - if (key[0] == '_' && - (key == TRI_VOC_ATTRIBUTE_ID || - key == TRI_VOC_ATTRIBUTE_KEY || - key == TRI_VOC_ATTRIBUTE_REV || - key == TRI_VOC_ATTRIBUTE_FROM || - key == TRI_VOC_ATTRIBUTE_TO)) { - builder.add(key, it2.value()); +//////////////////////////////////////////////////////////////////////////////// +/// @brief merge two objects for update, oldValue must have correctly set +/// _key and _id attributes +//////////////////////////////////////////////////////////////////////////////// + +VPackBuilder TRI_document_collection_t::mergeObjectsForUpdate( + arangodb::Transaction* trx, + VPackSlice const oldValue, + VPackSlice const newValue, + std::string const& rev, + bool mergeObjects, bool keepNull) { + + VPackBuilder b; + { VPackObjectBuilder guard(&b); + + // Find the attributes in the newValue object: + std::unordered_map newValues; + { VPackObjectIterator it(newValue); + while (it.valid()) { + std::string key = it.key().copyString(); + if (key != TRI_VOC_ATTRIBUTE_KEY && + key != TRI_VOC_ATTRIBUTE_ID && + key != TRI_VOC_ATTRIBUTE_REV) { + newValues.emplace(it.key().copyString(), it.value()); + it.next(); + } } - it2.next(); } - builder.close(); - return builder; - } + { VPackObjectIterator it(oldValue); + while (it.valid()) { + auto key = it.key().copyString(); + if (key == TRI_VOC_ATTRIBUTE_REV) { + it.next(); + continue; + } + auto found = newValues.find(key); - // update - return VPackCollection::merge(oldValue, newValue, mergeObjects, keepNull); + if (found == newValues.end()) { + // use old value + b.add(key, it.value()); + } else if (mergeObjects && it.value().isObject() && + (*found).second.isObject()) { + // merge both values + auto& value = (*found).second; + if (keepNull || (!value.isNone() && !value.isNull())) { + VPackBuilder sub = VPackCollection::merge(it.value(), value, + true, !keepNull); + b.add(key, sub.slice()); + } + // clear the value in the map so its not added again + (*found).second = VPackSlice(); + } else { + // use new value + auto& value = (*found).second; + if (keepNull || (!value.isNone() && !value.isNull())) { + b.add(key, value); + } + // clear the value in the map so its not added again + (*found).second = VPackSlice(); + } + it.next(); + } + } + + // add remaining values that were only in new object + for (auto& it : newValues) { + auto& s = it.second; + if (s.isNone()) { + continue; + } + if (!keepNull && s.isNull()) { + continue; + } + b.add(std::move(it.first), s); + } + + // Finally, add the new revision: + b.add(TRI_VOC_ATTRIBUTE_REV, VPackValue(rev)); + } + return b; } - diff --git a/arangod/VocBase/document-collection.h b/arangod/VocBase/document-collection.h index 1bf81751dd..a93e661c82 100644 --- a/arangod/VocBase/document-collection.h +++ b/arangod/VocBase/document-collection.h @@ -308,12 +308,12 @@ struct TRI_document_collection_t : public TRI_collection_t { TRI_doc_mptr_t*, bool); int insert(arangodb::Transaction*, arangodb::velocypack::Slice const*, TRI_doc_mptr_t*, arangodb::OperationOptions&, bool); - int update(arangodb::Transaction*, arangodb::velocypack::Slice const*, - arangodb::velocypack::Slice const*, TRI_doc_mptr_t*, - TRI_doc_update_policy_t const*, arangodb::OperationOptions&, bool); - int replace(arangodb::Transaction*, arangodb::velocypack::Slice const*, - arangodb::velocypack::Slice const*, TRI_doc_mptr_t*, - TRI_doc_update_policy_t const*, arangodb::OperationOptions&, bool); + int update(arangodb::Transaction*, arangodb::velocypack::Slice const, + TRI_doc_mptr_t*, arangodb::OperationOptions&, bool, + TRI_voc_rid_t&); + int replace(arangodb::Transaction*, arangodb::velocypack::Slice const, + TRI_doc_mptr_t*, arangodb::OperationOptions&, bool, + TRI_voc_rid_t&); int remove(arangodb::Transaction*, arangodb::velocypack::Slice const*, TRI_doc_update_policy_t const*, arangodb::OperationOptions&, bool); @@ -329,6 +329,10 @@ struct TRI_document_collection_t : public TRI_collection_t { arangodb::Transaction*, arangodb::velocypack::Slice const*); int lookupDocument(arangodb::Transaction*, arangodb::velocypack::Slice const*, TRI_doc_update_policy_t const*, TRI_doc_mptr_t*&); + int lookupDocument(arangodb::Transaction*, arangodb::velocypack::Slice const, + TRI_doc_mptr_t*&); + int checkRevision(arangodb::Transaction*, arangodb::velocypack::Slice const, + TRI_voc_rid_t); int updateDocument(arangodb::Transaction*, TRI_voc_rid_t, TRI_doc_mptr_t*, arangodb::wal::DocumentOperation&, TRI_doc_mptr_t*, bool&); @@ -345,14 +349,25 @@ struct TRI_document_collection_t : public TRI_collection_t { bool); //////////////////////////////////////////////////////////////////////////////// -/// @brief merge two object slices +/// @brief new object for Replace //////////////////////////////////////////////////////////////////////////////// - arangodb::velocypack::Builder mergeObjects(arangodb::Transaction* trx, - bool isReplace, - arangodb::velocypack::Slice const& oldValue, - arangodb::velocypack::Slice const& newValue, - bool mergeObjects, bool keepNull); + arangodb::velocypack::Builder newObjectForReplace( + arangodb::Transaction* trx, + arangodb::velocypack::Slice const oldValue, + arangodb::velocypack::Slice const newValue, + std::string const& rev); + +//////////////////////////////////////////////////////////////////////////////// +/// @brief merge two objects for update +//////////////////////////////////////////////////////////////////////////////// + + arangodb::velocypack::Builder mergeObjectsForUpdate( + arangodb::Transaction* trx, + arangodb::velocypack::Slice const oldValue, + arangodb::velocypack::Slice const newValue, + std::string const& rev, + bool mergeObjects, bool keepNull); }; //////////////////////////////////////////////////////////////////////////////// diff --git a/arangod/VocBase/voc-types.h b/arangod/VocBase/voc-types.h index 4256c7c7e9..a2c0fe99ec 100644 --- a/arangod/VocBase/voc-types.h +++ b/arangod/VocBase/voc-types.h @@ -122,6 +122,7 @@ enum TRI_voc_document_operation_e : uint8_t { TRI_VOC_DOCUMENT_OPERATION_UNKNOWN = 0, TRI_VOC_DOCUMENT_OPERATION_INSERT, TRI_VOC_DOCUMENT_OPERATION_UPDATE, + TRI_VOC_DOCUMENT_OPERATION_REPLACE, TRI_VOC_DOCUMENT_OPERATION_REMOVE }; diff --git a/arangod/Wal/DocumentOperation.h b/arangod/Wal/DocumentOperation.h index e602d53d26..12a0a4d844 100644 --- a/arangod/Wal/DocumentOperation.h +++ b/arangod/Wal/DocumentOperation.h @@ -70,6 +70,7 @@ struct DocumentOperation { void init() { if (type == TRI_VOC_DOCUMENT_OPERATION_UPDATE || + type == TRI_VOC_DOCUMENT_OPERATION_REPLACE || type == TRI_VOC_DOCUMENT_OPERATION_REMOVE) { // copy the old header into a safe area TRI_ASSERT(header != nullptr); @@ -86,7 +87,8 @@ struct DocumentOperation { TRI_ASSERT(header != nullptr); TRI_ASSERT(status == StatusType::INDEXED); - if (type == TRI_VOC_DOCUMENT_OPERATION_UPDATE) { + if (type == TRI_VOC_DOCUMENT_OPERATION_UPDATE || + type == TRI_VOC_DOCUMENT_OPERATION_REPLACE) { // move header to the end of the list document->_masterPointers.moveBack(header, &oldHeader); // PROTECTED by trx } @@ -108,7 +110,8 @@ struct DocumentOperation { if (type == TRI_VOC_DOCUMENT_OPERATION_INSERT) { document->_masterPointers.release(header, true); // PROTECTED by trx - } else if (type == TRI_VOC_DOCUMENT_OPERATION_UPDATE) { + } else if (type == TRI_VOC_DOCUMENT_OPERATION_UPDATE || + type == TRI_VOC_DOCUMENT_OPERATION_REPLACE) { if (status != StatusType::CREATED && status != StatusType::INDEXED) { document->_masterPointers.move(header, &oldHeader); // PROTECTED by trx in trxCollection }