mirror of https://gitee.com/bigwinds/arangodb
2063 lines
73 KiB
C++
2063 lines
73 KiB
C++
////////////////////////////////////////////////////////////////////////////////
|
|
/// DISCLAIMER
|
|
///
|
|
/// Copyright 2014-2016 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 Max Neunhoeffer
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include "Transaction.h"
|
|
#include "Basics/Exceptions.h"
|
|
#include "Basics/StringUtils.h"
|
|
#include "Basics/VelocyPackHelper.h"
|
|
#include "Cluster/ClusterMethods.h"
|
|
#include "Cluster/ServerState.h"
|
|
#include "Indexes/PrimaryIndex.h"
|
|
#include "Utils/CollectionNameResolver.h"
|
|
#include "Utils/OperationCursor.h"
|
|
#include "Utils/TransactionContext.h"
|
|
#include "VocBase/DatafileHelper.h"
|
|
#include "VocBase/Ditch.h"
|
|
#include "VocBase/document-collection.h"
|
|
#include "VocBase/KeyGenerator.h"
|
|
#include "VocBase/MasterPointers.h"
|
|
#include "VocBase/server.h"
|
|
|
|
#include <velocypack/Builder.h>
|
|
#include <velocypack/Collection.h>
|
|
#include <velocypack/Options.h>
|
|
#include <velocypack/Slice.h>
|
|
#include <velocypack/velocypack-aliases.h>
|
|
|
|
using namespace arangodb;
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief if this pointer is set to an actual set, then for each request
|
|
/// sent to a shardId using the ClusterComm library, an X-Arango-Nolock
|
|
/// header is generated.
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
thread_local std::unordered_set<std::string>* Transaction::_makeNolockHeaders =
|
|
nullptr;
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief Index Iterator Context
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
struct OpenIndexIteratorContext {
|
|
arangodb::Transaction* trx;
|
|
TRI_document_collection_t* collection;
|
|
};
|
|
|
|
Transaction::Transaction(std::shared_ptr<TransactionContext> transactionContext,
|
|
TRI_voc_tid_t externalId)
|
|
: _externalId(externalId),
|
|
_setupState(TRI_ERROR_NO_ERROR),
|
|
_nestingLevel(0),
|
|
_errorData(),
|
|
_hints(0),
|
|
_timeout(0.0),
|
|
_waitForSync(false),
|
|
_allowImplicitCollections(true),
|
|
_isReal(true),
|
|
_trx(nullptr),
|
|
_vocbase(transactionContext->vocbase()),
|
|
_transactionContext(transactionContext) {
|
|
TRI_ASSERT(_vocbase != nullptr);
|
|
TRI_ASSERT(_transactionContext != nullptr);
|
|
|
|
if (ServerState::instance()->isCoordinator()) {
|
|
_isReal = false;
|
|
}
|
|
|
|
this->setupTransaction();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief destroy the transaction
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
Transaction::~Transaction() {
|
|
if (_trx == nullptr) {
|
|
return;
|
|
}
|
|
|
|
if (isEmbeddedTransaction()) {
|
|
_trx->_nestingLevel--;
|
|
} else {
|
|
if (getStatus() == TRI_TRANSACTION_RUNNING) {
|
|
// auto abort a running transaction
|
|
this->abort();
|
|
}
|
|
|
|
// free the data associated with the transaction
|
|
freeTransaction();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief return the names of all collections used in the transaction
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
std::vector<std::string> Transaction::collectionNames() const {
|
|
std::vector<std::string> result;
|
|
|
|
for (size_t i = 0; i < _trx->_collections._length; ++i) {
|
|
auto trxCollection = static_cast<TRI_transaction_collection_t*>(
|
|
TRI_AtVectorPointer(&_trx->_collections, i));
|
|
|
|
if (trxCollection->_collection != nullptr) {
|
|
result.emplace_back(trxCollection->_collection->_name);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief return the collection name resolver
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
CollectionNameResolver const* Transaction::resolver() const {
|
|
CollectionNameResolver const* r = this->_transactionContext->getResolver();
|
|
TRI_ASSERT(r != nullptr);
|
|
return r;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief return the transaction collection for a document collection
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
TRI_transaction_collection_t* Transaction::trxCollection(TRI_voc_cid_t cid) const {
|
|
TRI_ASSERT(_trx != nullptr);
|
|
TRI_ASSERT(getStatus() == TRI_TRANSACTION_RUNNING);
|
|
|
|
return TRI_GetCollectionTransaction(_trx, cid, TRI_TRANSACTION_READ);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief order a ditch for a collection
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
DocumentDitch* Transaction::orderDitch(TRI_transaction_collection_t* trxCollection) {
|
|
TRI_ASSERT(_trx != nullptr);
|
|
TRI_ASSERT(trxCollection != nullptr);
|
|
TRI_ASSERT(getStatus() == TRI_TRANSACTION_RUNNING ||
|
|
getStatus() == TRI_TRANSACTION_CREATED);
|
|
TRI_ASSERT(trxCollection->_collection != nullptr);
|
|
|
|
TRI_document_collection_t* document =
|
|
trxCollection->_collection->_collection;
|
|
TRI_ASSERT(document != nullptr);
|
|
|
|
return _transactionContext->orderDitch(document);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief extract the _key attribute from a slice
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
std::string Transaction::extractKey(VPackSlice const* slice) {
|
|
TRI_ASSERT(slice != nullptr);
|
|
|
|
// extract _key
|
|
if (slice->isObject()) {
|
|
VPackSlice k = slice->get(TRI_VOC_ATTRIBUTE_KEY);
|
|
if (!k.isString()) {
|
|
return ""; // fail
|
|
}
|
|
return k.copyString();
|
|
}
|
|
if (slice->isString()) {
|
|
return slice->copyString();
|
|
}
|
|
return "";
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief extract the _rev attribute from a slice
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
TRI_voc_rid_t Transaction::extractRevisionId(VPackSlice const* slice) {
|
|
TRI_ASSERT(slice != nullptr);
|
|
TRI_ASSERT(slice->isObject());
|
|
|
|
VPackSlice r(slice->get(TRI_VOC_ATTRIBUTE_REV));
|
|
if (r.isString()) {
|
|
VPackValueLength length;
|
|
char const* p = r.getString(length);
|
|
return arangodb::basics::StringUtils::uint64(p, length);
|
|
}
|
|
if (r.isInteger()) {
|
|
return r.getNumber<TRI_voc_rid_t>();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
/// @brief build a VPack object with _id, _key and _rev, the result is
|
|
/// added to the builder in the argument as a single object.
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
void Transaction::buildDocumentIdentity(VPackBuilder& builder,
|
|
TRI_voc_cid_t cid,
|
|
std::string const& key,
|
|
std::string const& rid,
|
|
std::string const& oldRid) {
|
|
std::string collectionName = resolver()->getCollectionName(cid);
|
|
|
|
builder.openObject();
|
|
builder.add(TRI_VOC_ATTRIBUTE_ID, VPackValue(collectionName + "/" + key));
|
|
builder.add(TRI_VOC_ATTRIBUTE_KEY, VPackValue(key));
|
|
builder.add(TRI_VOC_ATTRIBUTE_REV, VPackValue(rid));
|
|
if (!oldRid.empty()) {
|
|
builder.add("_oldRev", VPackValue(oldRid));
|
|
}
|
|
builder.close();
|
|
}
|
|
|
|
void Transaction::buildDocumentIdentity(VPackBuilder& builder,
|
|
TRI_voc_cid_t cid,
|
|
std::string const& key,
|
|
TRI_voc_rid_t rid,
|
|
std::string const& oldRid) {
|
|
std::string ridSt = std::to_string(rid);
|
|
buildDocumentIdentity(builder, cid, key, ridSt, oldRid);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief begin the transaction
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int Transaction::begin() {
|
|
if (_trx == nullptr) {
|
|
return TRI_ERROR_TRANSACTION_INTERNAL;
|
|
}
|
|
|
|
if (_setupState != TRI_ERROR_NO_ERROR) {
|
|
return _setupState;
|
|
}
|
|
|
|
if (!_isReal) {
|
|
if (_nestingLevel == 0) {
|
|
_trx->_status = TRI_TRANSACTION_RUNNING;
|
|
}
|
|
return TRI_ERROR_NO_ERROR;
|
|
}
|
|
|
|
int res = TRI_BeginTransaction(_trx, _hints, _nestingLevel);
|
|
|
|
return res;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief commit / finish the transaction
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int Transaction::commit() {
|
|
if (_trx == nullptr || getStatus() != TRI_TRANSACTION_RUNNING) {
|
|
// transaction not created or not running
|
|
return TRI_ERROR_TRANSACTION_INTERNAL;
|
|
}
|
|
|
|
if (!_isReal) {
|
|
if (_nestingLevel == 0) {
|
|
_trx->_status = TRI_TRANSACTION_COMMITTED;
|
|
}
|
|
return TRI_ERROR_NO_ERROR;
|
|
}
|
|
|
|
int res = TRI_CommitTransaction(_trx, _nestingLevel);
|
|
|
|
return res;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief abort the transaction
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int Transaction::abort() {
|
|
if (_trx == nullptr || getStatus() != TRI_TRANSACTION_RUNNING) {
|
|
// transaction not created or not running
|
|
return TRI_ERROR_TRANSACTION_INTERNAL;
|
|
}
|
|
|
|
if (!_isReal) {
|
|
if (_nestingLevel == 0) {
|
|
_trx->_status = TRI_TRANSACTION_ABORTED;
|
|
}
|
|
|
|
return TRI_ERROR_NO_ERROR;
|
|
}
|
|
|
|
int res = TRI_AbortTransaction(_trx, _nestingLevel);
|
|
|
|
return res;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief finish a transaction (commit or abort), based on the previous state
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int Transaction::finish(int errorNum) {
|
|
if (errorNum == TRI_ERROR_NO_ERROR) {
|
|
// there was no previous error, so we'll commit
|
|
return this->commit();
|
|
}
|
|
|
|
// there was a previous error, so we'll abort
|
|
this->abort();
|
|
|
|
// return original error number
|
|
return errorNum;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief read any (random) document
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
OperationResult Transaction::any(std::string const& collectionName) {
|
|
return any(collectionName, 0, 1);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief read all master pointers, using skip and limit.
|
|
/// The resualt guarantees that all documents are contained exactly once
|
|
/// as long as the collection is not modified.
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
OperationResult Transaction::any(std::string const& collectionName,
|
|
uint64_t skip, uint64_t limit) {
|
|
if (ServerState::instance()->isCoordinator()) {
|
|
return anyCoordinator(collectionName, skip, limit);
|
|
}
|
|
return anyLocal(collectionName, skip, limit);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief fetches documents in a collection in random order, coordinator
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
OperationResult Transaction::anyCoordinator(std::string const&, uint64_t,
|
|
uint64_t) {
|
|
THROW_ARANGO_EXCEPTION(TRI_ERROR_NOT_IMPLEMENTED);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief fetches documents in a collection in random order, local
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
OperationResult Transaction::anyLocal(std::string const& collectionName,
|
|
uint64_t skip, uint64_t limit) {
|
|
TRI_voc_cid_t cid = resolver()->getCollectionIdLocal(collectionName);
|
|
|
|
if (cid == 0) {
|
|
return OperationResult(TRI_ERROR_ARANGO_COLLECTION_NOT_FOUND);
|
|
}
|
|
|
|
if (orderDitch(trxCollection(cid)) == nullptr) {
|
|
return OperationResult(TRI_ERROR_OUT_OF_MEMORY);
|
|
}
|
|
|
|
int res = lock(trxCollection(cid), TRI_TRANSACTION_READ);
|
|
|
|
if (res != TRI_ERROR_NO_ERROR) {
|
|
return OperationResult(res);
|
|
}
|
|
|
|
VPackBuilder resultBuilder;
|
|
resultBuilder.openArray();
|
|
|
|
OperationCursor cursor = indexScan(collectionName, Transaction::CursorType::ANY, "", {}, skip, limit, 1000, false);
|
|
|
|
while (cursor.hasMore()) {
|
|
int res = cursor.getMore();
|
|
|
|
if (res != TRI_ERROR_NO_ERROR) {
|
|
return OperationResult(res);
|
|
}
|
|
|
|
VPackSlice docs = cursor.slice();
|
|
VPackArrayIterator it(docs);
|
|
while (it.valid()) {
|
|
resultBuilder.add(it.value());
|
|
it.next();
|
|
}
|
|
}
|
|
|
|
resultBuilder.close();
|
|
|
|
res = unlock(trxCollection(cid), TRI_TRANSACTION_READ);
|
|
|
|
if (res != TRI_ERROR_NO_ERROR) {
|
|
return OperationCursor(res);
|
|
}
|
|
|
|
return OperationResult(resultBuilder.steal(),
|
|
transactionContext()->orderCustomTypeHandler(), "",
|
|
TRI_ERROR_NO_ERROR, false);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
/// @brief return the type of a collection
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool Transaction::isEdgeCollection(std::string const& collectionName) {
|
|
return getCollectionType(collectionName) == TRI_COL_TYPE_EDGE;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
/// @brief return the type of a collection
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool Transaction::isDocumentCollection(std::string const& collectionName) {
|
|
return getCollectionType(collectionName) == TRI_COL_TYPE_DOCUMENT;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
/// @brief return the type of a collection
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
TRI_col_type_t Transaction::getCollectionType(std::string const& collectionName) {
|
|
if (ServerState::instance()->isCoordinator()) {
|
|
return resolver()->getCollectionTypeCluster(collectionName);
|
|
}
|
|
return resolver()->getCollectionType(collectionName);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
/// @brief Iterate over all elements of the collection.
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
void Transaction::invokeOnAllElements(std::string const& collectionName,
|
|
std::function<bool(TRI_doc_mptr_t const*)> callback) {
|
|
TRI_ASSERT(getStatus() == TRI_TRANSACTION_RUNNING);
|
|
if (ServerState::instance()->isCoordinator()) {
|
|
THROW_ARANGO_EXCEPTION(TRI_ERROR_NOT_IMPLEMENTED);
|
|
}
|
|
|
|
TRI_voc_cid_t cid = resolver()->getCollectionIdLocal(collectionName);
|
|
|
|
if (cid == 0) {
|
|
THROW_ARANGO_EXCEPTION(TRI_ERROR_ARANGO_COLLECTION_NOT_FOUND);
|
|
}
|
|
TRI_transaction_collection_t* trxCol = trxCollection(cid);
|
|
|
|
TRI_document_collection_t* document = documentCollection(trxCol);
|
|
|
|
if (orderDitch(trxCol) == nullptr) {
|
|
THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY);
|
|
}
|
|
|
|
int res = lock(trxCol, TRI_TRANSACTION_READ);
|
|
|
|
if (res != TRI_ERROR_NO_ERROR) {
|
|
THROW_ARANGO_EXCEPTION(res);
|
|
}
|
|
|
|
auto primaryIndex = document->primaryIndex();
|
|
primaryIndex->invokeOnAllElements(callback);
|
|
|
|
res = unlock(trxCol, TRI_TRANSACTION_READ);
|
|
|
|
if (res != TRI_ERROR_NO_ERROR) {
|
|
THROW_ARANGO_EXCEPTION(res);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
/// @brief return one or multiple documents from a collection
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
OperationResult Transaction::document(std::string const& collectionName,
|
|
VPackSlice const& value,
|
|
OperationOptions& options) {
|
|
TRI_ASSERT(getStatus() == TRI_TRANSACTION_RUNNING);
|
|
|
|
if (!value.isObject() && !value.isArray()) {
|
|
// must provide a document object or an array of documents
|
|
THROW_ARANGO_EXCEPTION(TRI_ERROR_ARANGO_DOCUMENT_TYPE_INVALID);
|
|
}
|
|
|
|
if (value.isArray()) {
|
|
// multi-document variant is not yet implemented
|
|
THROW_ARANGO_EXCEPTION(TRI_ERROR_NOT_IMPLEMENTED);
|
|
}
|
|
|
|
if (ServerState::instance()->isCoordinator()) {
|
|
return documentCoordinator(collectionName, value, options);
|
|
}
|
|
|
|
return documentLocal(collectionName, value, options);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
/// @brief read one or multiple documents in a collection, coordinator
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
OperationResult Transaction::documentCoordinator(std::string const& collectionName,
|
|
VPackSlice const& value,
|
|
OperationOptions& options) {
|
|
auto headers = std::make_unique<std::map<std::string, std::string>>();
|
|
arangodb::rest::HttpResponse::HttpResponseCode responseCode;
|
|
std::map<std::string, std::string> resultHeaders;
|
|
std::string resultBody;
|
|
|
|
std::string key(Transaction::extractKey(&value));
|
|
if (key.empty()) {
|
|
return OperationResult(TRI_ERROR_ARANGO_DOCUMENT_KEY_BAD);
|
|
}
|
|
TRI_voc_rid_t expectedRevision = Transaction::extractRevisionId(&value);
|
|
|
|
int res = arangodb::getDocumentOnCoordinator(
|
|
_vocbase->_name, collectionName, key, expectedRevision, headers, true,
|
|
responseCode, resultHeaders, resultBody);
|
|
|
|
if (res == TRI_ERROR_NO_ERROR) {
|
|
if (responseCode == arangodb::rest::HttpResponse::OK ||
|
|
responseCode == arangodb::rest::HttpResponse::PRECONDITION_FAILED) {
|
|
VPackParser parser;
|
|
try {
|
|
parser.parse(resultBody);
|
|
auto bui = parser.steal();
|
|
auto buf = bui->steal();
|
|
return OperationResult(buf, nullptr, "",
|
|
responseCode == arangodb::rest::HttpResponse::OK ?
|
|
TRI_ERROR_NO_ERROR : TRI_ERROR_ARANGO_CONFLICT,
|
|
TRI_ERROR_NO_ERROR);
|
|
}
|
|
catch (VPackException& e) {
|
|
std::string message = "JSON from DBserver not parseable: "
|
|
+ resultBody + ":" + e.what();
|
|
return OperationResult(TRI_ERROR_INTERNAL, message);
|
|
}
|
|
} else if (responseCode == arangodb::rest::HttpResponse::NOT_FOUND) {
|
|
return OperationResult(TRI_ERROR_ARANGO_DOCUMENT_NOT_FOUND);
|
|
} else {
|
|
return OperationResult(TRI_ERROR_INTERNAL);
|
|
}
|
|
}
|
|
return OperationResult(res);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
/// @brief read one or multiple documents in a collection, local
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
OperationResult Transaction::documentLocal(std::string const& collectionName,
|
|
VPackSlice const& value,
|
|
OperationOptions& options) {
|
|
TRI_voc_cid_t cid = resolver()->getCollectionIdLocal(collectionName);
|
|
|
|
if (cid == 0) {
|
|
return OperationResult(TRI_ERROR_ARANGO_COLLECTION_NOT_FOUND);
|
|
}
|
|
|
|
std::string key(Transaction::extractKey(&value));
|
|
if (key.empty()) {
|
|
return OperationResult(TRI_ERROR_ARANGO_DOCUMENT_KEY_BAD);
|
|
}
|
|
|
|
TRI_voc_rid_t expectedRevision = Transaction::extractRevisionId(&value);
|
|
|
|
// TODO: clean this up
|
|
TRI_document_collection_t* document = documentCollection(trxCollection(cid));
|
|
|
|
if (orderDitch(trxCollection(cid)) == nullptr) {
|
|
return OperationResult(TRI_ERROR_OUT_OF_MEMORY);
|
|
}
|
|
|
|
TRI_doc_mptr_t mptr;
|
|
int res = document->read(this, key, &mptr, !isLocked(document, TRI_TRANSACTION_READ));
|
|
|
|
if (res != TRI_ERROR_NO_ERROR) {
|
|
return OperationResult(res);
|
|
}
|
|
|
|
TRI_ASSERT(mptr.getDataPtr() != nullptr);
|
|
if (expectedRevision != 0 && expectedRevision != mptr.revisionId()) {
|
|
// still return
|
|
VPackBuilder resultBuilder;
|
|
buildDocumentIdentity(resultBuilder, cid, key, mptr.revisionId(), "");
|
|
|
|
return OperationResult(resultBuilder.steal(), nullptr, "",
|
|
TRI_ERROR_ARANGO_CONFLICT,
|
|
options.waitForSync || document->_info.waitForSync());
|
|
}
|
|
|
|
VPackBuilder resultBuilder;
|
|
if (!options.silent) {
|
|
resultBuilder.add(VPackSlice(mptr.vpack()));
|
|
}
|
|
|
|
return OperationResult(resultBuilder.steal(),
|
|
transactionContext()->orderCustomTypeHandler(), "",
|
|
TRI_ERROR_NO_ERROR, false);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
/// @brief create one or multiple documents in a collection
|
|
/// the single-document variant of this operation will either succeed or,
|
|
/// if it fails, clean up after itself
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
OperationResult Transaction::insert(std::string const& collectionName,
|
|
VPackSlice const& value,
|
|
OperationOptions const& options) {
|
|
TRI_ASSERT(getStatus() == TRI_TRANSACTION_RUNNING);
|
|
|
|
if (!value.isObject() && !value.isArray()) {
|
|
// must provide a document object or an array of documents
|
|
THROW_ARANGO_EXCEPTION(TRI_ERROR_ARANGO_DOCUMENT_TYPE_INVALID);
|
|
}
|
|
|
|
// Validate Edges
|
|
if (isEdgeCollection(collectionName)) {
|
|
// Check _from
|
|
auto checkFrom = [&](VPackSlice const value) -> void {
|
|
size_t split;
|
|
VPackSlice from = value.get(TRI_VOC_ATTRIBUTE_FROM);
|
|
if (!from.isString()) {
|
|
THROW_ARANGO_EXCEPTION(TRI_ERROR_ARANGO_INVALID_EDGE_ATTRIBUTE);
|
|
}
|
|
std::string docId = from.copyString();
|
|
if (!TRI_ValidateDocumentIdKeyGenerator(docId.c_str(), &split)) {
|
|
THROW_ARANGO_EXCEPTION(TRI_ERROR_ARANGO_INVALID_EDGE_ATTRIBUTE);
|
|
}
|
|
std::string cName = docId.substr(0, split);
|
|
if (TRI_COL_TYPE_UNKNOWN == resolver()->getCollectionType(cName)) {
|
|
THROW_ARANGO_EXCEPTION(TRI_ERROR_ARANGO_COLLECTION_NOT_FOUND);
|
|
}
|
|
};
|
|
|
|
// Check _to
|
|
auto checkTo = [&](VPackSlice const value) -> void {
|
|
size_t split;
|
|
VPackSlice to = value.get(TRI_VOC_ATTRIBUTE_TO);
|
|
if (!to.isString()) {
|
|
THROW_ARANGO_EXCEPTION(TRI_ERROR_ARANGO_INVALID_EDGE_ATTRIBUTE);
|
|
}
|
|
std::string docId = to.copyString();
|
|
if (!TRI_ValidateDocumentIdKeyGenerator(docId.c_str(), &split)) {
|
|
THROW_ARANGO_EXCEPTION(TRI_ERROR_ARANGO_INVALID_EDGE_ATTRIBUTE);
|
|
}
|
|
std::string cName = docId.substr(0, split);
|
|
if (TRI_COL_TYPE_UNKNOWN == resolver()->getCollectionType(cName)) {
|
|
THROW_ARANGO_EXCEPTION(TRI_ERROR_ARANGO_COLLECTION_NOT_FOUND);
|
|
}
|
|
};
|
|
|
|
if (value.isArray()) {
|
|
for (auto s : VPackArrayIterator(value)) {
|
|
checkFrom(s);
|
|
checkTo(s);
|
|
}
|
|
} else {
|
|
checkFrom(value);
|
|
checkTo(value);
|
|
}
|
|
}
|
|
|
|
OperationOptions optionsCopy = options;
|
|
|
|
if (ServerState::instance()->isCoordinator()) {
|
|
return insertCoordinator(collectionName, value, optionsCopy);
|
|
}
|
|
|
|
return insertLocal(collectionName, value, optionsCopy);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
/// @brief create one or multiple documents in a collection, coordinator
|
|
/// the single-document variant of this operation will either succeed or,
|
|
/// if it fails, clean up after itself
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
OperationResult Transaction::insertCoordinator(std::string const& collectionName,
|
|
VPackSlice const& value,
|
|
OperationOptions& options) {
|
|
|
|
if (value.isArray()) {
|
|
// must provide a document object
|
|
THROW_ARANGO_EXCEPTION(TRI_ERROR_ARANGO_DOCUMENT_TYPE_INVALID);
|
|
}
|
|
|
|
std::map<std::string, std::string> headers;
|
|
arangodb::rest::HttpResponse::HttpResponseCode responseCode;
|
|
std::map<std::string, std::string> resultHeaders;
|
|
std::string resultBody;
|
|
|
|
int res = arangodb::createDocumentOnCoordinator(
|
|
_vocbase->_name, collectionName, options.waitForSync,
|
|
value, headers, responseCode, resultHeaders, resultBody);
|
|
|
|
if (res == TRI_ERROR_NO_ERROR) {
|
|
if (responseCode == arangodb::rest::HttpResponse::ACCEPTED ||
|
|
responseCode == arangodb::rest::HttpResponse::CREATED) {
|
|
VPackParser parser;
|
|
try {
|
|
parser.parse(resultBody);
|
|
auto bui = parser.steal();
|
|
auto buf = bui->steal();
|
|
return OperationResult(buf, nullptr, "", TRI_ERROR_NO_ERROR,
|
|
responseCode == arangodb::rest::HttpResponse::CREATED);
|
|
}
|
|
catch (VPackException& e) {
|
|
std::string message = "JSON from DBserver not parseable: "
|
|
+ resultBody + ":" + e.what();
|
|
return OperationResult(TRI_ERROR_INTERNAL, message);
|
|
}
|
|
} else if (responseCode == arangodb::rest::HttpResponse::PRECONDITION_FAILED) {
|
|
return OperationResult(TRI_ERROR_ARANGO_CONFLICT);
|
|
} else if (responseCode == arangodb::rest::HttpResponse::BAD) {
|
|
return OperationResult(TRI_ERROR_INTERNAL,
|
|
"JSON sent to DBserver was bad");
|
|
} else if (responseCode == arangodb::rest::HttpResponse::NOT_FOUND) {
|
|
return OperationResult(TRI_ERROR_ARANGO_COLLECTION_NOT_FOUND);
|
|
} else {
|
|
return OperationResult(TRI_ERROR_INTERNAL);
|
|
}
|
|
}
|
|
return OperationResult(res);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
/// @brief create 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::insertLocal(std::string const& collectionName,
|
|
VPackSlice const& value,
|
|
OperationOptions& options) {
|
|
|
|
TRI_voc_cid_t cid = resolver()->getCollectionIdLocal(collectionName);
|
|
|
|
if (cid == 0) {
|
|
return OperationResult(TRI_ERROR_ARANGO_COLLECTION_NOT_FOUND);
|
|
}
|
|
|
|
TRI_document_collection_t* document = documentCollection(trxCollection(cid));
|
|
|
|
VPackBuilder resultBuilder;
|
|
|
|
auto workForOneDocument = [&](VPackSlice const value) -> int {
|
|
// add missing attributes for document (_id, _rev, _key)
|
|
VPackBuilder merge;
|
|
merge.openObject();
|
|
|
|
// generate a new tick value
|
|
TRI_voc_tick_t const revisionId = TRI_NewTickServer();
|
|
std::string keyString;
|
|
auto key = value.get(TRI_VOC_ATTRIBUTE_KEY);
|
|
|
|
if (key.isNone()) {
|
|
// "_key" attribute not present in object
|
|
keyString = document->_keyGenerator->generate(revisionId);
|
|
merge.add(TRI_VOC_ATTRIBUTE_KEY, VPackValue(keyString));
|
|
} else if (!key.isString()) {
|
|
// "_key" present but wrong type
|
|
return TRI_ERROR_ARANGO_DOCUMENT_KEY_BAD;
|
|
} else {
|
|
keyString = key.copyString();
|
|
int res = document->_keyGenerator->validate(keyString, false);
|
|
|
|
if (res != TRI_ERROR_NO_ERROR) {
|
|
// invalid key value
|
|
return res;
|
|
}
|
|
}
|
|
|
|
// add _rev attribute
|
|
merge.add(TRI_VOC_ATTRIBUTE_REV, VPackValue(std::to_string(revisionId)));
|
|
|
|
// add _id attribute
|
|
uint8_t* p = merge.add(TRI_VOC_ATTRIBUTE_ID, VPackValuePair(9ULL, VPackValueType::Custom));
|
|
*p++ = 0xf3; // custom type for _id
|
|
DatafileHelper::StoreNumber<uint64_t>(p, cid, sizeof(uint64_t));
|
|
|
|
merge.close();
|
|
|
|
VPackBuilder toInsert = VPackCollection::merge(value, merge.slice(), false, false);
|
|
VPackSlice insertSlice = toInsert.slice();
|
|
|
|
TRI_doc_mptr_t mptr;
|
|
int res = document->insert(this, &insertSlice, &mptr, options,
|
|
!isLocked(document, TRI_TRANSACTION_WRITE));
|
|
|
|
if (res != TRI_ERROR_NO_ERROR) {
|
|
return res;
|
|
}
|
|
|
|
if (options.silent) {
|
|
// no need to construct the result object
|
|
return TRI_ERROR_NO_ERROR;
|
|
}
|
|
|
|
TRI_ASSERT(mptr.getDataPtr() != nullptr);
|
|
|
|
buildDocumentIdentity(resultBuilder, cid, keyString, mptr.revisionId(), "");
|
|
return TRI_ERROR_NO_ERROR;
|
|
};
|
|
|
|
int res;
|
|
if (value.isArray()) {
|
|
VPackArrayBuilder b(&resultBuilder);
|
|
for (auto const s : VPackArrayIterator(value)) {
|
|
res = workForOneDocument(s);
|
|
if (res != TRI_ERROR_NO_ERROR) {
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
res = workForOneDocument(value);
|
|
}
|
|
|
|
return OperationResult(resultBuilder.steal(), nullptr, "", res,
|
|
options.waitForSync);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
/// @brief update/patch one or multiple documents in a collection
|
|
/// the single-document variant of this operation will either succeed or,
|
|
/// if it fails, clean up after itself
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
OperationResult Transaction::update(std::string const& collectionName,
|
|
VPackSlice const& oldValue,
|
|
VPackSlice const& newValue,
|
|
OperationOptions const& options) {
|
|
TRI_ASSERT(getStatus() == TRI_TRANSACTION_RUNNING);
|
|
|
|
if (!oldValue.isObject() && !oldValue.isArray()) {
|
|
// must provide a document object or an array of documents
|
|
THROW_ARANGO_EXCEPTION(TRI_ERROR_ARANGO_DOCUMENT_TYPE_INVALID);
|
|
}
|
|
|
|
if (!newValue.isObject() && !newValue.isArray()) {
|
|
// must provide a document object or an array of documents
|
|
THROW_ARANGO_EXCEPTION(TRI_ERROR_ARANGO_DOCUMENT_TYPE_INVALID);
|
|
}
|
|
|
|
if (oldValue.isArray() || newValue.isArray()) {
|
|
// multi-document variant is not yet implemented
|
|
THROW_ARANGO_EXCEPTION(TRI_ERROR_NOT_IMPLEMENTED);
|
|
}
|
|
|
|
OperationOptions optionsCopy = options;
|
|
|
|
if (ServerState::instance()->isCoordinator()) {
|
|
return updateCoordinator(collectionName, oldValue, newValue, optionsCopy);
|
|
}
|
|
|
|
return updateLocal(collectionName, oldValue, newValue, optionsCopy);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
/// @brief update one or multiple documents in a collection, coordinator
|
|
/// the single-document variant of this operation will either succeed or,
|
|
/// if it fails, clean up after itself
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
OperationResult Transaction::updateCoordinator(std::string const& collectionName,
|
|
VPackSlice const& oldValue,
|
|
VPackSlice const& newValue,
|
|
OperationOptions& options) {
|
|
auto headers = std::make_unique<std::map<std::string, std::string>>();
|
|
arangodb::rest::HttpResponse::HttpResponseCode responseCode;
|
|
std::map<std::string, std::string> resultHeaders;
|
|
std::string resultBody;
|
|
|
|
std::string key(Transaction::extractKey(&oldValue));
|
|
if (key.empty()) {
|
|
return OperationResult(TRI_ERROR_ARANGO_DOCUMENT_KEY_BAD);
|
|
}
|
|
TRI_voc_rid_t expectedRevision = Transaction::extractRevisionId(&oldValue);
|
|
|
|
int res = arangodb::modifyDocumentOnCoordinator(
|
|
_vocbase->_name, collectionName, key, expectedRevision,
|
|
TRI_DOC_UPDATE_ERROR, options.waitForSync, true /* isPatch */,
|
|
options.keepNull, options.mergeObjects, newValue,
|
|
headers, responseCode, resultHeaders, resultBody);
|
|
|
|
if (res == TRI_ERROR_NO_ERROR) {
|
|
if (responseCode == arangodb::rest::HttpResponse::ACCEPTED ||
|
|
responseCode == arangodb::rest::HttpResponse::CREATED ||
|
|
responseCode == arangodb::rest::HttpResponse::PRECONDITION_FAILED) {
|
|
VPackParser parser;
|
|
try {
|
|
parser.parse(resultBody);
|
|
auto bui = parser.steal();
|
|
auto buf = bui->steal();
|
|
return OperationResult(buf, nullptr, "",
|
|
responseCode == arangodb::rest::HttpResponse::PRECONDITION_FAILED ?
|
|
TRI_ERROR_ARANGO_CONFLICT : TRI_ERROR_NO_ERROR,
|
|
responseCode == arangodb::rest::HttpResponse::CREATED);
|
|
}
|
|
catch (VPackException& e) {
|
|
std::string message = "JSON from DBserver not parseable: "
|
|
+ resultBody + ":" + e.what();
|
|
return OperationResult(TRI_ERROR_INTERNAL, message);
|
|
}
|
|
} else if (responseCode == arangodb::rest::HttpResponse::BAD) {
|
|
return OperationResult(TRI_ERROR_INTERNAL,
|
|
"JSON sent to DBserver was bad");
|
|
} else if (responseCode == arangodb::rest::HttpResponse::NOT_FOUND) {
|
|
return OperationResult(TRI_ERROR_ARANGO_COLLECTION_NOT_FOUND);
|
|
} else {
|
|
return OperationResult(TRI_ERROR_INTERNAL);
|
|
}
|
|
}
|
|
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& oldValue,
|
|
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(&oldValue));
|
|
TRI_voc_rid_t const expectedRevision = Transaction::extractRevisionId(&oldValue);
|
|
|
|
// 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);
|
|
}
|
|
|
|
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,
|
|
/// if it fails, clean up after itself
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
OperationResult Transaction::replace(std::string const& collectionName,
|
|
VPackSlice const& oldValue,
|
|
VPackSlice const& newValue,
|
|
OperationOptions const& options) {
|
|
TRI_ASSERT(getStatus() == TRI_TRANSACTION_RUNNING);
|
|
|
|
if (!oldValue.isObject() && !oldValue.isArray()) {
|
|
// must provide a document object or an array of documents
|
|
THROW_ARANGO_EXCEPTION(TRI_ERROR_ARANGO_DOCUMENT_TYPE_INVALID);
|
|
}
|
|
|
|
if (!newValue.isObject() && !newValue.isArray()) {
|
|
// must provide a document object or an array of documents
|
|
THROW_ARANGO_EXCEPTION(TRI_ERROR_ARANGO_DOCUMENT_TYPE_INVALID);
|
|
}
|
|
|
|
if (oldValue.isArray() || newValue.isArray()) {
|
|
// multi-document variant is not yet implemented
|
|
THROW_ARANGO_EXCEPTION(TRI_ERROR_NOT_IMPLEMENTED);
|
|
}
|
|
|
|
OperationOptions optionsCopy = options;
|
|
|
|
if (ServerState::instance()->isCoordinator()) {
|
|
return replaceCoordinator(collectionName, oldValue, newValue, optionsCopy);
|
|
}
|
|
|
|
return replaceLocal(collectionName, oldValue, newValue, optionsCopy);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
/// @brief replace one or multiple documents in a collection, coordinator
|
|
/// the single-document variant of this operation will either succeed or,
|
|
/// if it fails, clean up after itself
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
OperationResult Transaction::replaceCoordinator(std::string const& collectionName,
|
|
VPackSlice const& oldValue,
|
|
VPackSlice const& newValue,
|
|
OperationOptions& options) {
|
|
auto headers = std::make_unique<std::map<std::string, std::string>>();
|
|
arangodb::rest::HttpResponse::HttpResponseCode responseCode;
|
|
std::map<std::string, std::string> resultHeaders;
|
|
std::string resultBody;
|
|
|
|
std::string key(Transaction::extractKey(&oldValue));
|
|
if (key.empty()) {
|
|
return OperationResult(TRI_ERROR_ARANGO_DOCUMENT_KEY_BAD);
|
|
}
|
|
TRI_voc_rid_t expectedRevision = Transaction::extractRevisionId(&oldValue);
|
|
|
|
int res = arangodb::modifyDocumentOnCoordinator(
|
|
_vocbase->_name, collectionName, key, expectedRevision,
|
|
TRI_DOC_UPDATE_ERROR, options.waitForSync, false /* isPatch */,
|
|
false /* keepNull */, false /* mergeObjects */, newValue,
|
|
headers, responseCode, resultHeaders, resultBody);
|
|
|
|
if (res == TRI_ERROR_NO_ERROR) {
|
|
if (responseCode == arangodb::rest::HttpResponse::ACCEPTED ||
|
|
responseCode == arangodb::rest::HttpResponse::CREATED ||
|
|
responseCode == arangodb::rest::HttpResponse::PRECONDITION_FAILED) {
|
|
VPackParser parser;
|
|
try {
|
|
parser.parse(resultBody);
|
|
auto bui = parser.steal();
|
|
auto buf = bui->steal();
|
|
return OperationResult(buf, nullptr, "",
|
|
responseCode == arangodb::rest::HttpResponse::PRECONDITION_FAILED ?
|
|
TRI_ERROR_ARANGO_CONFLICT : TRI_ERROR_NO_ERROR,
|
|
responseCode == arangodb::rest::HttpResponse::CREATED);
|
|
}
|
|
catch (VPackException& e) {
|
|
std::string message = "JSON from DBserver not parseable: "
|
|
+ resultBody + ":" + e.what();
|
|
return OperationResult(TRI_ERROR_INTERNAL, message);
|
|
}
|
|
} else if (responseCode == arangodb::rest::HttpResponse::BAD) {
|
|
return OperationResult(TRI_ERROR_INTERNAL,
|
|
"JSON sent to DBserver was bad");
|
|
} else if (responseCode == arangodb::rest::HttpResponse::NOT_FOUND) {
|
|
return OperationResult(TRI_ERROR_ARANGO_COLLECTION_NOT_FOUND);
|
|
} else {
|
|
return OperationResult(TRI_ERROR_INTERNAL);
|
|
}
|
|
}
|
|
return OperationResult(res);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
/// @brief replace 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::replaceLocal(std::string const& collectionName,
|
|
VPackSlice const& oldValue,
|
|
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 key and expected revision
|
|
std::string const key(Transaction::extractKey(&oldValue));
|
|
TRI_voc_rid_t const expectedRevision = Transaction::extractRevisionId(&oldValue);
|
|
|
|
// 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 the (new) _rev attributes
|
|
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);
|
|
}
|
|
|
|
res = document->replace(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 remove one or multiple documents in a collection
|
|
/// the single-document variant of this operation will either succeed or,
|
|
/// if it fails, clean up after itself
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
OperationResult Transaction::remove(std::string const& collectionName,
|
|
VPackSlice const& value,
|
|
OperationOptions const& options) {
|
|
TRI_ASSERT(getStatus() == TRI_TRANSACTION_RUNNING);
|
|
|
|
if (!value.isObject() && !value.isArray() && !value.isString()) {
|
|
// must provide a document object or an array of documents
|
|
THROW_ARANGO_EXCEPTION(TRI_ERROR_ARANGO_DOCUMENT_TYPE_INVALID);
|
|
}
|
|
|
|
if (value.isArray()) {
|
|
// multi-document variant is not yet implemented
|
|
THROW_ARANGO_EXCEPTION(TRI_ERROR_NOT_IMPLEMENTED);
|
|
}
|
|
|
|
OperationOptions optionsCopy = options;
|
|
|
|
if (ServerState::instance()->isCoordinator()) {
|
|
return removeCoordinator(collectionName, value, optionsCopy);
|
|
}
|
|
|
|
return removeLocal(collectionName, value, optionsCopy);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
/// @brief remove one or multiple documents in a collection, coordinator
|
|
/// the single-document variant of this operation will either succeed or,
|
|
/// if it fails, clean up after itself
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
OperationResult Transaction::removeCoordinator(std::string const& collectionName,
|
|
VPackSlice const& value,
|
|
OperationOptions& options) {
|
|
auto headers = std::make_unique<std::map<std::string, std::string>>();
|
|
arangodb::rest::HttpResponse::HttpResponseCode responseCode;
|
|
std::map<std::string, std::string> resultHeaders;
|
|
std::string resultBody;
|
|
|
|
std::string key(Transaction::extractKey(&value));
|
|
if (key.empty()) {
|
|
return OperationResult(TRI_ERROR_ARANGO_DOCUMENT_KEY_BAD);
|
|
}
|
|
TRI_voc_rid_t expectedRevision = Transaction::extractRevisionId(&value);
|
|
|
|
int res = arangodb::deleteDocumentOnCoordinator(
|
|
_vocbase->_name, collectionName, key, expectedRevision,
|
|
TRI_DOC_UPDATE_ERROR, options.waitForSync,
|
|
headers, responseCode, resultHeaders, resultBody);
|
|
|
|
if (res == TRI_ERROR_NO_ERROR) {
|
|
if (responseCode == arangodb::rest::HttpResponse::OK ||
|
|
responseCode == arangodb::rest::HttpResponse::ACCEPTED ||
|
|
responseCode == arangodb::rest::HttpResponse::PRECONDITION_FAILED) {
|
|
VPackParser parser;
|
|
try {
|
|
parser.parse(resultBody);
|
|
auto bui = parser.steal();
|
|
auto buf = bui->steal();
|
|
return OperationResult(buf, nullptr, "", TRI_ERROR_NO_ERROR, true);
|
|
}
|
|
catch (VPackException& e) {
|
|
std::string message = "JSON from DBserver not parseable: "
|
|
+ resultBody + ":" + e.what();
|
|
return OperationResult(TRI_ERROR_INTERNAL, message);
|
|
}
|
|
} else if (responseCode == arangodb::rest::HttpResponse::BAD) {
|
|
return OperationResult(TRI_ERROR_INTERNAL,
|
|
"JSON sent to DBserver was bad");
|
|
} else if (responseCode == arangodb::rest::HttpResponse::NOT_FOUND) {
|
|
return OperationResult(TRI_ERROR_ARANGO_COLLECTION_NOT_FOUND);
|
|
} else {
|
|
return OperationResult(TRI_ERROR_INTERNAL);
|
|
}
|
|
}
|
|
return OperationResult(res);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
/// @brief remove 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::removeLocal(std::string const& collectionName,
|
|
VPackSlice const& value,
|
|
OperationOptions& options) {
|
|
TRI_voc_cid_t cid = resolver()->getCollectionIdLocal(collectionName);
|
|
|
|
if (cid == 0) {
|
|
return OperationResult(TRI_ERROR_ARANGO_COLLECTION_NOT_FOUND);
|
|
}
|
|
|
|
// TODO: clean this up
|
|
TRI_document_collection_t* document = documentCollection(trxCollection(cid));
|
|
|
|
std::string key;
|
|
TRI_voc_rid_t const expectedRevision = Transaction::extractRevisionId(&value);
|
|
|
|
VPackBuilder builder;
|
|
builder.openObject();
|
|
|
|
// extract _key
|
|
if (value.isObject()) {
|
|
VPackSlice k = value.get(TRI_VOC_ATTRIBUTE_KEY);
|
|
if (!k.isString()) {
|
|
return OperationResult(TRI_ERROR_ARANGO_DOCUMENT_KEY_BAD);
|
|
}
|
|
builder.add(TRI_VOC_ATTRIBUTE_KEY, k);
|
|
key = k.copyString();
|
|
} else if (value.isString()) {
|
|
builder.add(TRI_VOC_ATTRIBUTE_KEY, value);
|
|
key = value.copyString();
|
|
}
|
|
|
|
// add _rev
|
|
builder.add(TRI_VOC_ATTRIBUTE_REV, VPackValue(std::to_string(expectedRevision)));
|
|
builder.close();
|
|
|
|
VPackSlice removeSlice = builder.slice();
|
|
|
|
TRI_voc_rid_t actualRevision = 0;
|
|
TRI_doc_update_policy_t updatePolicy(expectedRevision == 0 ? TRI_DOC_UPDATE_LAST_WRITE : TRI_DOC_UPDATE_ERROR, expectedRevision, &actualRevision);
|
|
int res = document->remove(this, &removeSlice, &updatePolicy, options, !isLocked(document, TRI_TRANSACTION_WRITE));
|
|
|
|
if (res != TRI_ERROR_NO_ERROR) {
|
|
return OperationResult(res);
|
|
}
|
|
|
|
if (options.silent) {
|
|
return OperationResult(TRI_ERROR_NO_ERROR);
|
|
}
|
|
|
|
VPackBuilder resultBuilder;
|
|
buildDocumentIdentity(resultBuilder, cid, key, std::to_string(actualRevision), "");
|
|
|
|
return OperationResult(resultBuilder.steal(), nullptr, "", TRI_ERROR_NO_ERROR,
|
|
options.waitForSync);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief fetches all document keys in a collection
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
OperationResult Transaction::allKeys(std::string const& collectionName,
|
|
std::string const& type,
|
|
OperationOptions const& options) {
|
|
TRI_ASSERT(getStatus() == TRI_TRANSACTION_RUNNING);
|
|
|
|
std::string prefix;
|
|
|
|
if (type == "key") {
|
|
prefix = "";
|
|
} else if (type == "id") {
|
|
prefix = collectionName + "/";
|
|
} else {
|
|
// default return type: paths to documents
|
|
if (isEdgeCollection(collectionName)) {
|
|
prefix = std::string("/_db/") + _vocbase->_name + "/_api/edge/" + collectionName + "/";
|
|
} else {
|
|
prefix = std::string("/_db/") + _vocbase->_name + "/_api/document/" + collectionName + "/";
|
|
}
|
|
}
|
|
|
|
OperationOptions optionsCopy = options;
|
|
|
|
if (ServerState::instance()->isCoordinator()) {
|
|
return allKeysCoordinator(collectionName, type, prefix, optionsCopy);
|
|
}
|
|
|
|
return allKeysLocal(collectionName, type, prefix, optionsCopy);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief fetches all document keys in a collection, coordinator
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
OperationResult Transaction::allKeysCoordinator(std::string const& collectionName,
|
|
std::string const& type,
|
|
std::string const& prefix,
|
|
OperationOptions& options) {
|
|
THROW_ARANGO_EXCEPTION(TRI_ERROR_NOT_IMPLEMENTED);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief fetches all document keys in a collection, local
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
OperationResult Transaction::allKeysLocal(std::string const& collectionName,
|
|
std::string const& type,
|
|
std::string const& prefix,
|
|
OperationOptions& options) {
|
|
TRI_voc_cid_t cid = resolver()->getCollectionIdLocal(collectionName);
|
|
|
|
if (cid == 0) {
|
|
return OperationResult(TRI_ERROR_ARANGO_COLLECTION_NOT_FOUND);
|
|
}
|
|
|
|
if (orderDitch(trxCollection(cid)) == nullptr) {
|
|
return OperationResult(TRI_ERROR_OUT_OF_MEMORY);
|
|
}
|
|
|
|
int res = lock(trxCollection(cid), TRI_TRANSACTION_READ);
|
|
|
|
if (res != TRI_ERROR_NO_ERROR) {
|
|
return OperationResult(res);
|
|
}
|
|
|
|
VPackBuilder resultBuilder;
|
|
resultBuilder.add(VPackValue(VPackValueType::Object));
|
|
resultBuilder.add("documents", VPackValue(VPackValueType::Array));
|
|
|
|
OperationCursor cursor = indexScan(collectionName, Transaction::CursorType::ALL, "", {}, 0, UINT64_MAX, 1000, false);
|
|
|
|
while (cursor.hasMore()) {
|
|
int res = cursor.getMore();
|
|
|
|
if (res != TRI_ERROR_NO_ERROR) {
|
|
return OperationResult(res);
|
|
}
|
|
|
|
std::string value;
|
|
VPackSlice docs = cursor.slice();
|
|
VPackArrayIterator it(docs);
|
|
while (it.valid()) {
|
|
value.assign(prefix);
|
|
value.append(it.value().get(TRI_VOC_ATTRIBUTE_KEY).copyString());
|
|
resultBuilder.add(VPackValue(value));
|
|
it.next();
|
|
}
|
|
}
|
|
|
|
resultBuilder.close(); // array
|
|
resultBuilder.close(); // object
|
|
|
|
res = unlock(trxCollection(cid), TRI_TRANSACTION_READ);
|
|
|
|
if (res != TRI_ERROR_NO_ERROR) {
|
|
return OperationCursor(res);
|
|
}
|
|
|
|
return OperationResult(resultBuilder.steal(),
|
|
transactionContext()->orderCustomTypeHandler(), "",
|
|
TRI_ERROR_NO_ERROR, false);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief fetches all documents in a collection
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
OperationResult Transaction::all(std::string const& collectionName,
|
|
uint64_t skip, uint64_t limit,
|
|
OperationOptions const& options) {
|
|
TRI_ASSERT(getStatus() == TRI_TRANSACTION_RUNNING);
|
|
|
|
OperationOptions optionsCopy = options;
|
|
|
|
if (ServerState::instance()->isCoordinator()) {
|
|
return allCoordinator(collectionName, skip, limit, optionsCopy);
|
|
}
|
|
|
|
return allLocal(collectionName, skip, limit, optionsCopy);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief fetches all documents in a collection, coordinator
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
OperationResult Transaction::allCoordinator(std::string const& collectionName,
|
|
uint64_t skip, uint64_t limit,
|
|
OperationOptions& options) {
|
|
THROW_ARANGO_EXCEPTION(TRI_ERROR_NOT_IMPLEMENTED);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief fetches all documents in a collection, local
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
OperationResult Transaction::allLocal(std::string const& collectionName,
|
|
uint64_t skip, uint64_t limit,
|
|
OperationOptions& options) {
|
|
TRI_voc_cid_t cid = resolver()->getCollectionIdLocal(collectionName);
|
|
|
|
if (cid == 0) {
|
|
return OperationResult(TRI_ERROR_ARANGO_COLLECTION_NOT_FOUND);
|
|
}
|
|
|
|
if (orderDitch(trxCollection(cid)) == nullptr) {
|
|
return OperationResult(TRI_ERROR_OUT_OF_MEMORY);
|
|
}
|
|
|
|
int res = lock(trxCollection(cid), TRI_TRANSACTION_READ);
|
|
|
|
if (res != TRI_ERROR_NO_ERROR) {
|
|
return OperationResult(res);
|
|
}
|
|
|
|
VPackBuilder resultBuilder;
|
|
resultBuilder.openArray();
|
|
|
|
OperationCursor cursor = indexScan(collectionName, Transaction::CursorType::ALL, "", {}, skip, limit, 1000, false);
|
|
|
|
while (cursor.hasMore()) {
|
|
int res = cursor.getMore();
|
|
|
|
if (res != TRI_ERROR_NO_ERROR) {
|
|
return OperationResult(res);
|
|
}
|
|
|
|
VPackSlice docs = cursor.slice();
|
|
VPackArrayIterator it(docs);
|
|
while (it.valid()) {
|
|
resultBuilder.add(it.value());
|
|
it.next();
|
|
}
|
|
}
|
|
|
|
resultBuilder.close();
|
|
|
|
res = unlock(trxCollection(cid), TRI_TRANSACTION_READ);
|
|
|
|
if (res != TRI_ERROR_NO_ERROR) {
|
|
return OperationCursor(res);
|
|
}
|
|
|
|
return OperationResult(resultBuilder.steal(),
|
|
transactionContext()->orderCustomTypeHandler(), "",
|
|
TRI_ERROR_NO_ERROR, false);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief remove all documents in a collection
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
OperationResult Transaction::truncate(std::string const& collectionName,
|
|
OperationOptions const& options) {
|
|
TRI_ASSERT(getStatus() == TRI_TRANSACTION_RUNNING);
|
|
|
|
OperationOptions optionsCopy = options;
|
|
|
|
if (ServerState::instance()->isCoordinator()) {
|
|
return truncateCoordinator(collectionName, optionsCopy);
|
|
}
|
|
|
|
return truncateLocal(collectionName, optionsCopy);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief remove all documents in a collection, coordinator
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
OperationResult Transaction::truncateCoordinator(std::string const& collectionName,
|
|
OperationOptions& options) {
|
|
return OperationResult(
|
|
arangodb::truncateCollectionOnCoordinator(_vocbase->_name,
|
|
collectionName));
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief remove all documents in a collection, local
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
OperationResult Transaction::truncateLocal(std::string const& collectionName,
|
|
OperationOptions& options) {
|
|
TRI_voc_cid_t cid = resolver()->getCollectionIdLocal(collectionName);
|
|
|
|
if (cid == 0) {
|
|
return OperationResult(TRI_ERROR_ARANGO_COLLECTION_NOT_FOUND);
|
|
}
|
|
|
|
if (orderDitch(trxCollection(cid)) == nullptr) {
|
|
return OperationResult(TRI_ERROR_OUT_OF_MEMORY);
|
|
}
|
|
|
|
int res = lock(trxCollection(cid), TRI_TRANSACTION_WRITE);
|
|
|
|
if (res != TRI_ERROR_NO_ERROR) {
|
|
return OperationResult(res);
|
|
}
|
|
|
|
TRI_document_collection_t* document = documentCollection(trxCollection(cid));
|
|
|
|
TRI_voc_rid_t actualRevision = 0;
|
|
TRI_doc_update_policy_t updatePolicy(TRI_DOC_UPDATE_LAST_WRITE, 0, &actualRevision);
|
|
|
|
VPackBuilder keyBuilder;
|
|
auto primaryIndex = document->primaryIndex();
|
|
|
|
std::function<bool(TRI_doc_mptr_t*)> callback = [this, &document, &keyBuilder, &updatePolicy, &options](TRI_doc_mptr_t const* mptr) {
|
|
VPackSlice slice(mptr->vpack());
|
|
VPackSlice keySlice = slice.get(TRI_VOC_ATTRIBUTE_KEY);
|
|
|
|
keyBuilder.clear();
|
|
keyBuilder.openObject();
|
|
keyBuilder.add(TRI_VOC_ATTRIBUTE_KEY, keySlice);
|
|
keyBuilder.close();
|
|
|
|
VPackSlice builderSlice = keyBuilder.slice();
|
|
|
|
int res = document->remove(this, &builderSlice, &updatePolicy, options, false);
|
|
|
|
if (res != TRI_ERROR_NO_ERROR) {
|
|
THROW_ARANGO_EXCEPTION(res);
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
try {
|
|
primaryIndex->invokeOnAllElementsForRemoval(callback);
|
|
}
|
|
catch (basics::Exception const& ex) {
|
|
unlock(trxCollection(cid), TRI_TRANSACTION_WRITE);
|
|
return OperationResult(ex.code());
|
|
}
|
|
|
|
res = unlock(trxCollection(cid), TRI_TRANSACTION_WRITE);
|
|
|
|
if (res != TRI_ERROR_NO_ERROR) {
|
|
return OperationCursor(res);
|
|
}
|
|
|
|
return OperationResult(TRI_ERROR_NO_ERROR);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief count the number of documents in a collection
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
OperationResult Transaction::count(std::string const& collectionName) {
|
|
TRI_ASSERT(getStatus() == TRI_TRANSACTION_RUNNING);
|
|
|
|
if (ServerState::instance()->isCoordinator()) {
|
|
return countCoordinator(collectionName);
|
|
}
|
|
|
|
return countLocal(collectionName);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
/// @brief count the number of documents in a collection
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
OperationResult Transaction::countCoordinator(std::string const& collectionName) {
|
|
uint64_t count = 0;
|
|
int res = arangodb::countOnCoordinator(_vocbase->_name, collectionName, count);
|
|
|
|
if (res != TRI_ERROR_NO_ERROR) {
|
|
return OperationResult(res);
|
|
}
|
|
|
|
VPackBuilder resultBuilder;
|
|
resultBuilder.add(VPackValue(count));
|
|
|
|
return OperationResult(resultBuilder.steal(), nullptr, "", TRI_ERROR_NO_ERROR, false);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
/// @brief count the number of documents in a collection
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
OperationResult Transaction::countLocal(std::string const& collectionName) {
|
|
TRI_voc_cid_t cid = resolver()->getCollectionIdLocal(collectionName);
|
|
|
|
if (cid == 0) {
|
|
return OperationResult(TRI_ERROR_ARANGO_COLLECTION_NOT_FOUND);
|
|
}
|
|
|
|
int res = lock(trxCollection(cid), TRI_TRANSACTION_READ);
|
|
|
|
if (res != TRI_ERROR_NO_ERROR) {
|
|
return OperationResult(res);
|
|
}
|
|
|
|
TRI_document_collection_t* document = documentCollection(trxCollection(cid));
|
|
|
|
VPackBuilder resultBuilder;
|
|
resultBuilder.add(VPackValue(document->size()));
|
|
|
|
res = unlock(trxCollection(cid), TRI_TRANSACTION_READ);
|
|
|
|
if (res != TRI_ERROR_NO_ERROR) {
|
|
return OperationResult(res);
|
|
}
|
|
|
|
return OperationResult(resultBuilder.steal(), nullptr, "", TRI_ERROR_NO_ERROR, false);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
/// @brief factory for OperationCursor objects
|
|
/// note: the caller must have read-locked the underlying collection when
|
|
/// calling this method
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
OperationCursor Transaction::indexScan(
|
|
std::string const& collectionName, CursorType cursorType,
|
|
std::string const& indexId, VPackSlice const search,
|
|
uint64_t skip, uint64_t limit, uint64_t batchSize, bool reverse) {
|
|
|
|
// TODO Who checks if indexId is valid and is used for this collection?
|
|
// For now we assume indexId is the iid part of the index.
|
|
|
|
if (ServerState::instance()->isCoordinator()) {
|
|
// The index scan is only available on DBServers and Single Server.
|
|
THROW_ARANGO_EXCEPTION(TRI_ERROR_CLUSTER_ONLY_ON_DBSERVER);
|
|
}
|
|
|
|
TRI_voc_cid_t cid = resolver()->getCollectionIdLocal(collectionName);
|
|
|
|
if (cid == 0) {
|
|
return OperationCursor(TRI_ERROR_ARANGO_COLLECTION_NOT_FOUND);
|
|
}
|
|
|
|
if (limit == 0) {
|
|
// nothing to do
|
|
return OperationCursor(TRI_ERROR_NO_ERROR);
|
|
}
|
|
|
|
TRI_document_collection_t* document = documentCollection(trxCollection(cid));
|
|
|
|
std::unique_ptr<IndexIterator> iterator;
|
|
|
|
switch (cursorType) {
|
|
case CursorType::ANY: {
|
|
// We do not need search values
|
|
TRI_ASSERT(search.isNone());
|
|
// We do not need an index either
|
|
TRI_ASSERT(indexId.empty());
|
|
|
|
arangodb::PrimaryIndex* idx = document->primaryIndex();
|
|
|
|
if (idx == nullptr) {
|
|
THROW_ARANGO_EXCEPTION_MESSAGE(
|
|
TRI_ERROR_ARANGO_INDEX_NOT_FOUND,
|
|
"Could not find primary index in collection '" + collectionName + "'.");
|
|
}
|
|
|
|
iterator.reset(idx->anyIterator(this));
|
|
break;
|
|
}
|
|
case CursorType::ALL: {
|
|
// We do not need search values
|
|
TRI_ASSERT(search.isNone());
|
|
// We do not need an index either
|
|
TRI_ASSERT(indexId.empty());
|
|
|
|
arangodb::PrimaryIndex* idx = document->primaryIndex();
|
|
|
|
if (idx == nullptr) {
|
|
THROW_ARANGO_EXCEPTION_MESSAGE(
|
|
TRI_ERROR_ARANGO_INDEX_NOT_FOUND,
|
|
"Could not find primary index in collection '" + collectionName + "'.");
|
|
}
|
|
|
|
iterator.reset(idx->allIterator(this, reverse));
|
|
break;
|
|
}
|
|
case CursorType::INDEX: {
|
|
if (indexId.empty()) {
|
|
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_BAD_PARAMETER,
|
|
"The index id cannot be empty.");
|
|
}
|
|
arangodb::Index* idx = nullptr;
|
|
|
|
if (!arangodb::Index::validateId(indexId.c_str())) {
|
|
THROW_ARANGO_EXCEPTION(TRI_ERROR_ARANGO_INDEX_HANDLE_BAD);
|
|
}
|
|
TRI_idx_iid_t iid = arangodb::basics::StringUtils::uint64(indexId);
|
|
idx = document->lookupIndex(iid);
|
|
|
|
if (idx == nullptr) {
|
|
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_ARANGO_INDEX_NOT_FOUND,
|
|
"Could not find index '" + indexId +
|
|
"' in collection '" +
|
|
collectionName + "'.");
|
|
}
|
|
|
|
// We have successfully found an index with the requested id.
|
|
|
|
// Normalize the search values
|
|
VPackBuilder expander;
|
|
idx->expandInSearchValues(search, expander);
|
|
|
|
// Now collect the Iterator
|
|
IndexIteratorContext ctxt(_vocbase, resolver());
|
|
iterator.reset(idx->iteratorForSlice(this, &ctxt, expander.slice(), reverse));
|
|
}
|
|
}
|
|
if (iterator == nullptr) {
|
|
// We could not create an ITERATOR and it did not throw an error itself
|
|
return OperationCursor(TRI_ERROR_OUT_OF_MEMORY);
|
|
}
|
|
|
|
uint64_t unused = 0;
|
|
iterator->skip(skip, unused);
|
|
|
|
return OperationCursor(transactionContext()->orderCustomTypeHandler(),
|
|
iterator.release(), limit, batchSize);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief return the collection
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
TRI_document_collection_t* Transaction::documentCollection(
|
|
TRI_transaction_collection_t const* trxCollection) const {
|
|
TRI_ASSERT(_trx != nullptr);
|
|
TRI_ASSERT(getStatus() == TRI_TRANSACTION_RUNNING);
|
|
TRI_ASSERT(trxCollection->_collection != nullptr);
|
|
TRI_ASSERT(trxCollection->_collection->_collection != nullptr);
|
|
|
|
return trxCollection->_collection->_collection;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief add a collection by id, with the name supplied
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int Transaction::addCollection(TRI_voc_cid_t cid, char const* name,
|
|
TRI_transaction_type_e type) {
|
|
int res = this->addCollection(cid, type);
|
|
|
|
if (res != TRI_ERROR_NO_ERROR) {
|
|
_errorData = std::string(name);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief add a collection by id, with the name supplied
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int Transaction::addCollection(TRI_voc_cid_t cid, std::string const& name,
|
|
TRI_transaction_type_e type) {
|
|
return addCollection(cid, name.c_str(), type);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief add a collection by id
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int Transaction::addCollection(TRI_voc_cid_t cid, TRI_transaction_type_e type) {
|
|
if (_trx == nullptr) {
|
|
return registerError(TRI_ERROR_INTERNAL);
|
|
}
|
|
|
|
if (cid == 0) {
|
|
// invalid cid
|
|
return registerError(TRI_ERROR_ARANGO_COLLECTION_NOT_FOUND);
|
|
}
|
|
|
|
if (_setupState != TRI_ERROR_NO_ERROR) {
|
|
return _setupState;
|
|
}
|
|
|
|
TRI_transaction_status_e const status = getStatus();
|
|
|
|
if (status == TRI_TRANSACTION_COMMITTED ||
|
|
status == TRI_TRANSACTION_ABORTED) {
|
|
// transaction already finished?
|
|
return registerError(TRI_ERROR_TRANSACTION_INTERNAL);
|
|
}
|
|
|
|
if (this->isEmbeddedTransaction()) {
|
|
return this->addCollectionEmbedded(cid, type);
|
|
}
|
|
|
|
return this->addCollectionToplevel(cid, type);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief add a collection by name
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int Transaction::addCollection(std::string const& name, TRI_transaction_type_e type) {
|
|
if (_setupState != TRI_ERROR_NO_ERROR) {
|
|
return _setupState;
|
|
}
|
|
|
|
return addCollection(this->resolver()->getCollectionId(name),
|
|
name.c_str(), type);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test if a collection is already locked
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool Transaction::isLocked(TRI_transaction_collection_t const* trxCollection,
|
|
TRI_transaction_type_e type) {
|
|
if (_trx == nullptr || getStatus() != TRI_TRANSACTION_RUNNING) {
|
|
return false;
|
|
}
|
|
|
|
return TRI_IsLockedCollectionTransaction(trxCollection, type,
|
|
_nestingLevel);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test if a collection is already locked
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool Transaction::isLocked(TRI_document_collection_t* document,
|
|
TRI_transaction_type_e type) {
|
|
if (_trx == nullptr || getStatus() != TRI_TRANSACTION_RUNNING) {
|
|
return false;
|
|
}
|
|
|
|
TRI_transaction_collection_t* trxCollection =
|
|
TRI_GetCollectionTransaction(_trx, document->_info.id(), type);
|
|
return isLocked(trxCollection, type);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief read- or write-lock a collection
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int Transaction::lock(TRI_transaction_collection_t* trxCollection,
|
|
TRI_transaction_type_e type) {
|
|
if (_trx == nullptr || getStatus() != TRI_TRANSACTION_RUNNING) {
|
|
return TRI_ERROR_TRANSACTION_INTERNAL;
|
|
}
|
|
|
|
return TRI_LockCollectionTransaction(trxCollection, type, _nestingLevel);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief read- or write-unlock a collection
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int Transaction::unlock(TRI_transaction_collection_t* trxCollection,
|
|
TRI_transaction_type_e type) {
|
|
if (_trx == nullptr || getStatus() != TRI_TRANSACTION_RUNNING) {
|
|
return TRI_ERROR_TRANSACTION_INTERNAL;
|
|
}
|
|
|
|
return TRI_UnlockCollectionTransaction(trxCollection, type, _nestingLevel);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief add a collection to an embedded transaction
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int Transaction::addCollectionEmbedded(TRI_voc_cid_t cid, TRI_transaction_type_e type) {
|
|
TRI_ASSERT(_trx != nullptr);
|
|
|
|
int res = TRI_AddCollectionTransaction(_trx, cid, type, _nestingLevel,
|
|
false, _allowImplicitCollections);
|
|
|
|
if (res != TRI_ERROR_NO_ERROR) {
|
|
return registerError(res);
|
|
}
|
|
|
|
return TRI_ERROR_NO_ERROR;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief add a collection to a top-level transaction
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int Transaction::addCollectionToplevel(TRI_voc_cid_t cid, TRI_transaction_type_e type) {
|
|
TRI_ASSERT(_trx != nullptr);
|
|
|
|
int res;
|
|
|
|
if (getStatus() != TRI_TRANSACTION_CREATED) {
|
|
// transaction already started?
|
|
res = TRI_ERROR_TRANSACTION_INTERNAL;
|
|
} else {
|
|
res = TRI_AddCollectionTransaction(_trx, cid, type, _nestingLevel, false,
|
|
_allowImplicitCollections);
|
|
}
|
|
|
|
if (res != TRI_ERROR_NO_ERROR) {
|
|
registerError(res);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief initialize the transaction
|
|
/// this will first check if the transaction is embedded in a parent
|
|
/// transaction. if not, it will create a transaction of its own
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int Transaction::setupTransaction() {
|
|
// check in the context if we are running embedded
|
|
_trx = this->_transactionContext->getParentTransaction();
|
|
|
|
if (_trx != nullptr) {
|
|
// yes, we are embedded
|
|
_setupState = setupEmbedded();
|
|
} else {
|
|
// non-embedded
|
|
_setupState = setupToplevel();
|
|
}
|
|
|
|
// this may well be TRI_ERROR_NO_ERROR...
|
|
return _setupState;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief set up an embedded transaction
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int Transaction::setupEmbedded() {
|
|
TRI_ASSERT(_nestingLevel == 0);
|
|
|
|
_nestingLevel = ++_trx->_nestingLevel;
|
|
|
|
if (!this->_transactionContext->isEmbeddable()) {
|
|
// we are embedded but this is disallowed...
|
|
return TRI_ERROR_TRANSACTION_NESTED;
|
|
}
|
|
|
|
return TRI_ERROR_NO_ERROR;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief set up a top-level transaction
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int Transaction::setupToplevel() {
|
|
TRI_ASSERT(_nestingLevel == 0);
|
|
|
|
// we are not embedded. now start our own transaction
|
|
_trx = TRI_CreateTransaction(_vocbase, _externalId, _timeout, _waitForSync);
|
|
|
|
if (_trx == nullptr) {
|
|
return TRI_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
// register the transaction in the context
|
|
return this->_transactionContext->registerTransaction(_trx);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief free transaction
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void Transaction::freeTransaction() {
|
|
TRI_ASSERT(!isEmbeddedTransaction());
|
|
|
|
if (_trx != nullptr) {
|
|
auto id = _trx->_id;
|
|
bool hasFailedOperations = TRI_FreeTransaction(_trx);
|
|
_trx = nullptr;
|
|
|
|
// store result
|
|
this->_transactionContext->storeTransactionResult(id, hasFailedOperations);
|
|
this->_transactionContext->unregisterTransaction();
|
|
}
|
|
}
|
|
|