1
0
Fork 0

Replacing /_api/collection with RestHandler (#3543)

This commit is contained in:
Simon Grätzer 2017-11-02 14:57:17 +01:00 committed by Frank Celler
parent 14bc59317b
commit 64e9377c05
52 changed files with 1335 additions and 1347 deletions

View File

@ -141,7 +141,7 @@ describe ArangoDB do
doc.parsed_response['error'].should eq(true)
doc.parsed_response['code'].should eq(400)
doc.parsed_response['errorNum'].should eq(600)
doc.parsed_response['errorMessage'].should eq("SyntaxError: Unexpected token n in JSON at position 2")
doc.parsed_response['errorMessage'].should eq("VPackError error: Expecting '\"' or '}'")
end
it "creating a collection with a null body" do

View File

@ -141,7 +141,7 @@ describe ArangoDB do
doc.parsed_response['error'].should eq(true)
doc.parsed_response['code'].should eq(400)
doc.parsed_response['errorNum'].should eq(600)
doc.parsed_response['errorMessage'].should eq("SyntaxError: Unexpected token n in JSON at position 2")
doc.parsed_response['errorMessage'].should eq("VPackError error: Expecting '\"' or '}'")
end
it "creating a collection with a null body" do

View File

@ -289,6 +289,7 @@ SET(ARANGOD_SOURCES
RestHandler/RestAuthHandler.cpp
RestHandler/RestBaseHandler.cpp
RestHandler/RestBatchHandler.cpp
RestHandler/RestCollectionHandler.cpp
RestHandler/RestCursorHandler.cpp
RestHandler/RestDatabaseHandler.cpp
RestHandler/RestDebugHandler.cpp

View File

@ -276,7 +276,8 @@ void ClusterFeature::prepare() {
// register the prefix with the communicator
AgencyCommManager::initialize(_agencyPrefix);
TRI_ASSERT(AgencyCommManager::MANAGER != nullptr);
for (size_t i = 0; i < _agencyEndpoints.size(); ++i) {
std::string const unified = Endpoint::unifiedForm(_agencyEndpoints[i]);

View File

@ -521,7 +521,7 @@ void HeartbeatThread::runSingleServer() {
// if we stay a slave, the redirect will be turned on again
RestHandlerFactory::setServerMode(RestHandlerFactory::Mode::TRYAGAIN);
result = CasWithResult(_agency, leaderPath, myIdBuilder.slice(),
/* ttl */ std::min(30.0, interval * 4),
/* ttl */ std::max(30.0, interval * 4),
/* timeout */ 30.0);
if (result.successful()) { // sucessfull leadership takeover

View File

@ -48,6 +48,7 @@
#include "RestHandler/RestAqlFunctionsHandler.h"
#include "RestHandler/RestAuthHandler.h"
#include "RestHandler/RestBatchHandler.h"
#include "RestHandler/RestCollectionHandler.h"
#include "RestHandler/RestCursorHandler.h"
#include "RestHandler/RestDatabaseHandler.h"
#include "RestHandler/RestDebugHandler.h"
@ -352,6 +353,11 @@ void GeneralServerFeature::defineHandlers() {
_handlerFactory->addPrefixHandler(
RestVocbaseBaseHandler::BATCH_PATH,
RestHandlerCreator<RestBatchHandler>::createNoData);
_handlerFactory->addPrefixHandler(
RestVocbaseBaseHandler::COLLECTION_PATH,
RestHandlerCreator<RestCollectionHandler>::createNoData);
_handlerFactory->addPrefixHandler(
RestVocbaseBaseHandler::CURSOR_PATH,

View File

@ -148,7 +148,7 @@ double BaseOptions::LookupInfo::estimateCost(size_t& nrItems) const {
// If we do not have an index yet we cannot do anything.
// Should NOT be the case
TRI_ASSERT(!idxHandles.empty());
auto idx = idxHandles[0].getIndex();
std::shared_ptr<Index> idx = idxHandles[0].getIndex();
if (idx->hasSelectivityEstimate()) {
double expected = 1 / idx->selectivityEstimate();
nrItems += static_cast<size_t>(expected);

View File

@ -180,7 +180,7 @@ class MMFilesCollection final : public PhysicalCollection {
std::vector<MMFilesDatafile*>&& compactors);
/// @brief rotate the active journal - will do nothing if there is no journal
int rotateActiveJournal();
int rotateActiveJournal() override;
/// @brief sync the active journal - will do nothing if there is no journal
/// or if the journal is volatile

View File

@ -716,6 +716,12 @@ void MMFilesEngine::waitForSyncTimeout(double maxWait) {
MMFilesLogfileManager::instance()->waitForSync(maxWait);
}
Result MMFilesEngine::flushWal(bool waitForSync, bool waitForCollector,
bool writeShutdownFile) {
return MMFilesLogfileManager::instance()->flush(
waitForSync, waitForCollector, writeShutdownFile);
}
TRI_vocbase_t* MMFilesEngine::openDatabase(
arangodb::velocypack::Slice const& args, bool isUpgrade, int& status) {
VPackSlice idSlice = args.get("id");

View File

@ -170,6 +170,9 @@ class MMFilesEngine final : public StorageEngine {
void waitForSyncTick(TRI_voc_tick_t tick) override;
void waitForSyncTimeout(double maxWait) override;
Result flushWal(bool waitForSync, bool waitForCollector,
bool writeShutdownFile) override;
virtual TRI_vocbase_t* openDatabase(
arangodb::velocypack::Slice const& parameters, bool isUpgrade,

View File

@ -201,6 +201,7 @@ void MMFilesRestExportHandler::createCursor() {
parseVelocyPackBody(parseSuccess);
if (!parseSuccess) {
// error message generated in parseVelocyPackBody
return;
}
VPackSlice body = parsedBody.get()->slice();

View File

@ -378,10 +378,9 @@ struct MMFilesWalAccessContext : WalAccessContext {
}
WalAccessResult tail(uint64_t tickStart, uint64_t tickEnd, size_t chunkSize) {
MMFilesLogfileManagerState const state =
MMFilesLogfileManager::instance()->state();
MMFilesLogfileManager::instance()->state();
// ask the logfile manager which datafiles qualify
bool fromTickIncluded = false;
std::vector<arangodb::MMFilesWalLogfile*> logfiles =

View File

@ -295,6 +295,7 @@ void RestAdminLogHandler::setLogLevel() {
bool parseSuccess = true;
std::shared_ptr<VPackBuilder> parsedBody = parseVelocyPackBody(parseSuccess);
if (!parseSuccess) {
// error message generated in parseVelocyPackBody
return;
}

View File

@ -54,5 +54,5 @@ void RestAdminRoutingHandler::reloadRouting() {
return;
}
generateOk();
resetResponse(rest::ResponseCode::NO_CONTENT);
}

View File

@ -24,6 +24,7 @@
#include "RestBaseHandler.h"
#include <velocypack/Builder.h>
#include <velocypack/Collection.h>
#include <velocypack/Dumper.h>
#include <velocypack/Options.h>
#include <velocypack/velocypack-aliases.h>
@ -99,17 +100,18 @@ void RestBaseHandler::generateResult(
writeResult(std::forward<Payload>(payload), *(context->getVPackOptionsForDump()));
}
void RestBaseHandler::generateSuccess(rest::ResponseCode code, VPackSlice const& payload) {
void RestBaseHandler::generateOk(rest::ResponseCode code,
VPackSlice const& payload) {
resetResponse(code);
VPackBuffer<uint8_t> buffer;
VPackBuilder builder(buffer);
try {
builder.add(VPackValue(VPackValueType::Object));
builder.add("error", VPackValue(false));
builder.add("code", VPackValue(static_cast<int>(code)));
builder.add("result", payload);
builder.close();
VPackBuffer<uint8_t> buffer;
VPackBuilder tmp(buffer);
tmp.add(VPackValue(VPackValueType::Object));
tmp.add("error", VPackValue(false));
tmp.add("code", VPackValue(static_cast<int>(code)));
tmp.add("result", payload);
tmp.close();
VPackOptions options(VPackOptions::Defaults);
options.escapeUnicode = true;
@ -119,6 +121,27 @@ void RestBaseHandler::generateSuccess(rest::ResponseCode code, VPackSlice const&
}
}
void RestBaseHandler::generateOk(rest::ResponseCode code,
VPackBuilder const& payload) {
resetResponse(code);
try {
VPackBuilder tmp;
tmp.add(VPackValue(VPackValueType::Object));
tmp.add("error", VPackValue(false));
tmp.add("code", VPackValue(static_cast<int>(code)));
tmp.close();
tmp = VPackCollection::merge(tmp.slice(), payload.slice(), false);
VPackOptions options(VPackOptions::Defaults);
options.escapeUnicode = true;
writeResult(tmp.slice(), options);
} catch (...) {
// Building the error response failed
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief generates an error
////////////////////////////////////////////////////////////////////////////////

View File

@ -61,7 +61,9 @@ class RestBaseHandler : public rest::RestHandler {
/// convenience function akin to generateError,
/// renders payload in 'result' field
void generateSuccess(rest::ResponseCode, velocypack::Slice const&);
void generateOk(rest::ResponseCode, velocypack::Slice const&);
void generateOk(rest::ResponseCode, velocypack::Builder const&);
// generates an error
void generateError(rest::ResponseCode, int);

View File

@ -0,0 +1,491 @@
////////////////////////////////////////////////////////////////////////////////
/// DISCLAIMER
///
/// Copyright 2014-2017 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 Simon Grätzer
////////////////////////////////////////////////////////////////////////////////
#include "RestCollectionHandler.h"
#include "ApplicationFeatures/ApplicationServer.h"
#include "Cluster/ClusterFeature.h"
#include "Cluster/ClusterInfo.h"
#include "Cluster/ServerState.h"
#include "Rest/HttpRequest.h"
#include "StorageEngine/EngineSelectorFeature.h"
#include "StorageEngine/PhysicalCollection.h"
#include "StorageEngine/StorageEngine.h"
#include "Transaction/StandaloneContext.h"
#include "Utils/OperationOptions.h"
#include "Utils/SingleCollectionTransaction.h"
#include "VocBase/LogicalCollection.h"
#include "VocBase/Methods/Collections.h"
#include <velocypack/Builder.h>
#include <velocypack/Collection.h>
#include <velocypack/velocypack-aliases.h>
using namespace arangodb;
using namespace arangodb::basics;
using namespace arangodb::rest;
RestCollectionHandler::RestCollectionHandler(GeneralRequest* request,
GeneralResponse* response)
: RestVocbaseBaseHandler(request, response) {}
RestStatus RestCollectionHandler::execute() {
switch (_request->requestType()) {
case rest::RequestType::GET:
handleCommandGet();
break;
case rest::RequestType::POST:
handleCommandPost();
break;
case rest::RequestType::PUT:
handleCommandPut();
break;
case rest::RequestType::DELETE_REQ:
handleCommandDelete();
break;
default:
generateError(rest::ResponseCode::METHOD_NOT_ALLOWED,
TRI_ERROR_HTTP_METHOD_NOT_ALLOWED);
}
return RestStatus::DONE;
}
void RestCollectionHandler::handleCommandGet() {
std::vector<std::string> suffixes = _request->decodedSuffixes();
VPackBuilder builder;
// /_api/collection
if (suffixes.empty()) {
bool excludeSystem = _request->parsedValue("excludeSystem", false);
builder.openArray();
methods::Collections::enumerate(_vocbase, [&](LogicalCollection* coll) {
ExecContext const* exec = ExecContext::CURRENT;
bool canUse = exec == nullptr ||
exec->canUseCollection(coll->name(), AuthLevel::RO);
if (canUse && (!excludeSystem || !coll->isSystem())) {
collectionRepresentation(builder, coll,
/*showProperties*/ false,
/*showFigures*/ false, /*showCount*/ false,
/*aggregateCount*/ false);
}
});
builder.close();
generateOk(rest::ResponseCode::OK, builder.slice());
return;
}
std::string const name = suffixes[0];
// /_api/collection/<name>
if (suffixes.size() == 1) {
collectionRepresentation(builder, name, /*showProperties*/ false,
/*showFigures*/ false, /*showCount*/ false,
/*aggregateCount*/ false);
generateOk(rest::ResponseCode::OK, builder);
return;
}
if (suffixes.size() > 2) {
generateError(rest::ResponseCode::BAD, TRI_ERROR_HTTP_BAD_PARAMETER,
"expect GET /_api/collection/<collection-name>/<method>");
return;
}
std::string const sub = suffixes[1];
bool skipGenerate = false;
Result found = methods::Collections::lookup(
_vocbase, name, [&](LogicalCollection* coll) {
if (sub == "checksum") {
// /_api/collection/<identifier>/checksum
if (!coll->isLocal()) {
THROW_ARANGO_EXCEPTION(TRI_ERROR_NOT_IMPLEMENTED);
}
bool withRevisions = _request->parsedValue("withRevisions", false);
bool withData = _request->parsedValue("withData", false);
ChecksumResult result = coll->checksum(withRevisions, withData);
if (result.ok()) {
VPackObjectBuilder obj(&builder, true);
obj->add("checksum", result.slice().get("checksum"));
obj->add("revision", result.slice().get("revision"));
collectionRepresentation(builder, name, /*showProperties*/ false,
/*showFigures*/ false, /*showCount*/ false,
/*aggregateCount*/ false);
} else {
skipGenerate = true;
this->generateError(result);
}
} else if (sub == "figures") {
// /_api/collection/<identifier>/figures
collectionRepresentation(builder, name, /*showProperties*/ true,
/*showFigures*/ true, /*showCount*/ true,
/*aggregateCount*/ true);
} else if (sub == "count") {
// /_api/collection/<identifier>/count
bool details = _request->parsedValue("details", false);
collectionRepresentation(builder, name, /*showProperties*/ true,
/*showFigures*/ false, /*showCount*/ true,
/*aggregateCount*/ !details);
} else if (sub == "properties") {
// /_api/collection/<identifier>/properties
collectionRepresentation(builder, name, /*showProperties*/ true,
/*showFigures*/ false, /*showCount*/ false,
/*aggregateCount*/ false);
} else if (sub == "revision") {
// /_api/collection/<identifier>/revision
TRI_voc_rid_t revisionId;
Result res =
methods::Collections::revisionId(_vocbase, coll, revisionId);
if (res.fail()) {
THROW_ARANGO_EXCEPTION(res);
}
VPackObjectBuilder obj(&builder, true);
obj->add("revision", VPackValue(StringUtils::itoa(revisionId)));
collectionRepresentation(builder, name, /*showProperties*/ true,
/*showFigures*/ false, /*showCount*/ false,
/*aggregateCount*/ false);
} else if (sub == "shards") {
// /_api/collection/<identifier>/shards
if (!ServerState::instance()->isRunningInCluster()) {
skipGenerate = true; // must be internal error for tests
this->generateError(Result(TRI_ERROR_INTERNAL));
return;
}
VPackObjectBuilder obj(&builder, true); // need to open object
collectionRepresentation(builder, name, /*showProperties*/ true,
/*showFigures*/ false, /*showCount*/ false,
/*aggregateCount*/ false);
auto shards =
ClusterInfo::instance()->getShardList(coll->planId_as_string());
VPackArrayBuilder arr(&builder, "shards", true);
for (ShardID const& shard : *shards) {
arr->add(VPackValue(shard));
}
} else {
skipGenerate = true;
this->generateError(
rest::ResponseCode::NOT_FOUND, TRI_ERROR_HTTP_NOT_FOUND,
"expecting one of the resources 'checksum', 'count', "
"'figures', 'properties', 'revision', 'shards'");
}
});
if (skipGenerate) {
return;
}
if (found.ok()) {
generateOk(rest::ResponseCode::OK, builder);
_response->setHeader("location", _request->requestPath());
} else {
generateError(found);
}
}
// create a collection
void RestCollectionHandler::handleCommandPost() {
bool parseSuccess = true;
std::shared_ptr<VPackBuilder> parsedBody = parseVelocyPackBody(parseSuccess);
if (!parseSuccess) {
// error message generated in parseVelocyPackBody
return;
}
VPackSlice const body = parsedBody->slice();
VPackSlice nameSlice;
if (!body.isObject() || !(nameSlice = body.get("name")).isString() ||
nameSlice.getStringLength() == 0) {
generateError(rest::ResponseCode::BAD, TRI_ERROR_ARANGO_ILLEGAL_NAME);
return;
}
auto cluster =
application_features::ApplicationServer::getFeature<ClusterFeature>(
"Cluster");
bool waitsForSync = cluster->createWaitsForSyncReplication();
waitsForSync = VelocyPackHelper::getBooleanValue(body, "body", waitsForSync);
TRI_col_type_e type = TRI_col_type_e::TRI_COL_TYPE_DOCUMENT;
VPackSlice typeSlice = body.get("type");
if ((typeSlice.isString() && (typeSlice.compareString("edge") == 0 ||
typeSlice.compareString("3") == 0)) ||
(typeSlice.isNumber() &&
typeSlice.getUInt() == TRI_col_type_e::TRI_COL_TYPE_EDGE)) {
type = TRI_col_type_e::TRI_COL_TYPE_EDGE;
}
// for some "security" a white-list of allowed parameters
VPackBuilder filtered = VPackCollection::keep(
parsedBody->slice(),
std::unordered_set<std::string>{
"doCompact", "isSystem", "id", "isVolatile", "journalSize",
"indexBuckets", "keyOptions", "waitForSync", "cacheEnabled",
"shardKeys", "numberOfShards", "distributeShardsLike", "avoidServers",
"isSmart", "smartGraphAttribute", "replicationFactor", "servers"});
VPackSlice const parameters = filtered.slice();
// now we can create the collection
std::string const& name = nameSlice.copyString();
VPackBuilder builder;
Result res = methods::Collections::create(
_vocbase, name, type, parameters, waitsForSync,
[&](LogicalCollection* coll) {
collectionRepresentation(builder, coll->name(), /*showProperties*/ true,
/*showFigures*/ false, /*showCount*/ false,
/*aggregateCount*/ false);
if (!coll->isLocal()) { // FIXME: this is crappy design
delete coll;
}
});
if (res.ok()) {
generateOk(rest::ResponseCode::OK, builder);
} else {
generateError(res);
}
}
void RestCollectionHandler::handleCommandPut() {
std::vector<std::string> suffixes = _request->decodedSuffixes();
if (suffixes.size() != 2) {
generateError(rest::ResponseCode::BAD, TRI_ERROR_HTTP_BAD_PARAMETER,
"expected PUT /_api/collection/<collection-name>/<action>");
return;
}
bool parseSuccess = true;
std::shared_ptr<VPackBuilder> parsedBody = parseVelocyPackBody(parseSuccess);
if (!parseSuccess) {
// error message generated in parseVelocyPackBody
return;
}
VPackSlice body = parsedBody->slice();
if (!body.isObject()) {
body = VPackSlice::emptyObjectSlice();
}
std::string const name = suffixes[0];
std::string const sub = suffixes[1];
Result res;
VPackBuilder builder;
Result found = methods::Collections::lookup(
_vocbase, name, [&](LogicalCollection* coll) {
if (sub == "load") {
res = methods::Collections::load(_vocbase, coll);
if (res.ok()) {
bool cc = VelocyPackHelper::getBooleanValue(body, "count", true);
collectionRepresentation(builder, name, /*showProperties*/ false,
/*showFigures*/ false, /*showCount*/ cc,
/*aggregateCount*/ true);
}
} else if (sub == "unload") {
bool flush = _request->parsedValue("flush", false);
if (flush &&
coll->status() ==
TRI_vocbase_col_status_e::TRI_VOC_COL_STATUS_LOADED) {
EngineSelectorFeature::ENGINE->flushWal(false, false, false);
}
res = methods::Collections::unload(_vocbase, coll);
if (res.ok()) {
collectionRepresentation(builder, name, /*showProperties*/ false,
/*showFigures*/ false, /*showCount*/ false,
/*aggregateCount*/ false);
}
} else if (sub == "truncate") {
OperationOptions opts;
opts.waitForSync = _request->parsedValue("waitForSync", false);
opts.isSynchronousReplicationFrom =
_request->value("isSynchronousReplication");
auto ctx = transaction::StandaloneContext::Create(_vocbase);
SingleCollectionTransaction trx(ctx, coll->cid(),
AccessMode::Type::EXCLUSIVE);
res = trx.begin();
if (res.ok()) {
OperationResult result = trx.truncate(coll->name(), opts);
res = trx.finish(result.code);
}
if (res.ok()) {
if (!coll->isLocal()) { // ClusterInfo::loadPlan eventually updates status
coll->setStatus(TRI_vocbase_col_status_e::TRI_VOC_COL_STATUS_LOADED);
}
collectionRepresentation(builder, coll, /*showProperties*/ false,
/*showFigures*/ false, /*showCount*/ false,
/*aggregateCount*/ false);
}
} else if (sub == "properties") {
std::vector<std::string> keep = {"doCompact", "journalSize",
"waitForSync", "indexBuckets",
"replicationFactor", "cacheEnabled"};
VPackBuilder props = VPackCollection::keep(body, keep);
res = methods::Collections::updateProperties(coll, props.slice());
if (res.ok()) {
collectionRepresentation(builder, name, /*showProperties*/ true,
/*showFigures*/ false, /*showCount*/ false,
/*aggregateCount*/ false);
}
} else if (sub == "rename") {
VPackSlice const newNameSlice = body.get("name");
if (!newNameSlice.isString()) {
res = Result(TRI_ERROR_ARANGO_ILLEGAL_NAME, "name is empty");
return;
}
std::string const newName = newNameSlice.copyString();
res = methods::Collections::rename(coll, newName, false);
if (res.ok()) {
collectionRepresentation(builder, newName, /*showProperties*/ false,
/*showFigures*/ false, /*showCount*/ false,
/*aggregateCount*/ false);
}
} else if (sub == "rotate") {
auto ctx = transaction::StandaloneContext::Create(_vocbase);
SingleCollectionTransaction trx(ctx, coll->cid(),
AccessMode::Type::READ);
res = trx.begin();
if (res.ok()) {
res.reset(coll->getPhysical()->rotateActiveJournal());
}
} else if (sub == "loadIndexesIntoMemory") {
res = methods::Collections::warmup(_vocbase, coll);
VPackObjectBuilder obj(&builder, true);
obj->add("result", VPackValue(res.ok()));
} else {
res.reset(TRI_ERROR_HTTP_NOT_FOUND,
"expecting one of the actions 'load', 'unload', 'truncate',"
" 'properties', 'rename', 'loadIndexesIntoMemory'");
}
});
if (found.fail()) {
generateError(found);
} else if (res.ok()) {
generateOk(rest::ResponseCode::OK, builder);
_response->setHeader("location", _request->requestPath());
} else {
generateError(res);
}
}
void RestCollectionHandler::handleCommandDelete() {
std::vector<std::string> suffixes = _request->decodedSuffixes();
if (suffixes.size() != 1) {
generateError(rest::ResponseCode::BAD, TRI_ERROR_HTTP_BAD_PARAMETER,
"expected DELETE /_api/collection/<collection-name>");
return;
}
std::string const name = suffixes[0];
bool allowDropSystem = _request->parsedValue("isSystem", false);
VPackBuilder builder;
Result res;
Result found = methods::Collections::lookup(
_vocbase, name, [&](LogicalCollection* coll) {
std::string cid = coll->cid_as_string();
VPackObjectBuilder obj(&builder, true);
obj->add("id", VPackValue(cid));
res = methods::Collections::drop(_vocbase, coll, allowDropSystem, -1.0);
});
if (found.fail()) {
generateError(found);
} else if (res.fail()) {
generateError(res);
} else {
generateOk(rest::ResponseCode::OK, builder);
}
}
/// generate collection info. We lookup the collection again, because in the
/// cluster someinfo is lazily added in loadPlan, which means load, unload,
/// truncate
/// and create will not immediately show the expected results on a collection
/// object.
void RestCollectionHandler::collectionRepresentation(
VPackBuilder& builder, std::string const& name, bool showProperties,
bool showFigures, bool showCount, bool aggregateCount) {
Result r = methods::Collections::lookup(
_vocbase, name, [&](LogicalCollection* coll) {
collectionRepresentation(builder, coll, showProperties, showFigures,
showCount, aggregateCount);
});
if (r.fail()) {
THROW_ARANGO_EXCEPTION(r);
}
}
void RestCollectionHandler::collectionRepresentation(
VPackBuilder& builder, LogicalCollection* coll, bool showProperties,
bool showFigures, bool showCount, bool aggregateCount) {
bool wasOpen = builder.isOpenObject();
if (!wasOpen) {
builder.openObject();
}
// `methods::Collections::properties` will filter these out
builder.add("id", VPackValue(coll->cid_as_string()));
builder.add("name", VPackValue(coll->name()));
builder.add("status", VPackValue(coll->status()));
builder.add("type", VPackValue(coll->type()));
if (!showProperties) {
builder.add("isSystem", VPackValue(coll->isSystem()));
builder.add("globallyUniqueId", VPackValue(coll->globallyUniqueId()));
} else {
Result res = methods::Collections::properties(coll, builder);
if (res.fail()) {
THROW_ARANGO_EXCEPTION(res);
}
}
if (showFigures) {
auto figures = coll->figures();
builder.add("figures", figures->slice());
}
if (showCount) {
auto ctx = transaction::StandaloneContext::Create(_vocbase);
SingleCollectionTransaction trx(ctx, coll->cid(), AccessMode::Type::READ);
Result res = trx.begin();
if (res.fail()) {
THROW_ARANGO_EXCEPTION(res);
}
OperationResult opRes = trx.count(coll->name(), aggregateCount);
trx.finish(opRes.code);
if (!opRes.successful()) {
THROW_ARANGO_EXCEPTION_MESSAGE(opRes.code, opRes.errorMessage);
}
builder.add("count", opRes.slice());
}
if (!wasOpen) {
builder.close();
}
}

View File

@ -0,0 +1,55 @@
////////////////////////////////////////////////////////////////////////////////
/// DISCLAIMER
///
/// Copyright 2014-2017 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 Simon Grätzer
////////////////////////////////////////////////////////////////////////////////
#ifndef ARANGOD_REST_HANDLER_REST_COLLECTION_HANDLER_H
#define ARANGOD_REST_HANDLER_REST_COLLECTION_HANDLER_H 1
#include "RestHandler/RestVocbaseBaseHandler.h"
namespace arangodb {
class LogicalCollection;
class RestCollectionHandler : public arangodb::RestVocbaseBaseHandler {
public:
RestCollectionHandler(GeneralRequest*, GeneralResponse*);
public:
char const* name() const override final { return "RestCollectionHandler"; }
bool isDirect() const override { return false; }
RestStatus execute() override;
private:
void handleCommandGet();
void handleCommandPost();
void handleCommandPut();
void handleCommandDelete();
void collectionRepresentation(VPackBuilder& builder, std::string const& name,
bool showProperties, bool showFigures,
bool showCount, bool aggregateCount);
void collectionRepresentation(VPackBuilder& builder, LogicalCollection* coll,
bool showProperties, bool showFigures,
bool showCount, bool aggregateCount);
};
}
#endif

View File

@ -416,6 +416,7 @@ void RestCursorHandler::createCursor() {
parseVelocyPackBody(parseSuccess);
if (!parseSuccess) {
// error message generated in parseVelocyPackBody
return;
}
VPackSlice body = parsedBody.get()->slice();

View File

@ -69,8 +69,8 @@ RestStatus RestDatabaseHandler::getDatabases() {
return RestStatus::DONE;
}
VPackBuilder result;
VPackBuilder builder;
if (suffixes.empty() || suffixes[0] == "user") {
std::vector<std::string> names;
if (suffixes.empty()) {
@ -79,23 +79,23 @@ RestStatus RestDatabaseHandler::getDatabases() {
names = methods::Databases::list(_request->user());
}
result.openArray();
builder.openArray();
for (std::string const& name : names) {
result.add(VPackValue(name));
builder.add(VPackValue(name));
}
result.close();
builder.close();
} else if (suffixes[0] == "current") {
Result res = methods::Databases::info(_vocbase, result);
Result res = methods::Databases::info(_vocbase, builder);
if (!res.ok()) {
generateError(rest::ResponseCode::BAD, res.errorNumber());
return RestStatus::DONE;
}
}
if (result.isEmpty()) {
if (builder.isEmpty()) {
generateError(rest::ResponseCode::BAD, TRI_ERROR_BAD_PARAMETER);
} else {
generateSuccess(rest::ResponseCode::OK, result.slice());
generateOk(rest::ResponseCode::OK, builder.slice());
}
return RestStatus::DONE;
}
@ -124,7 +124,7 @@ RestStatus RestDatabaseHandler::createDatabase() {
Result res = methods::Databases::create(dbName, users, options);
if (res.ok()) {
generateSuccess(rest::ResponseCode::CREATED, VPackSlice::trueSlice());
generateOk(rest::ResponseCode::CREATED, VPackSlice::trueSlice());
} else {
if (res.errorNumber() == TRI_ERROR_FORBIDDEN ||
res.errorNumber() == TRI_ERROR_ARANGO_DUPLICATE_NAME) {
@ -154,7 +154,7 @@ RestStatus RestDatabaseHandler::deleteDatabase() {
std::string const& dbName = suffixes[0];
Result res = methods::Databases::drop(_vocbase, dbName);
if (res.ok()) {
generateSuccess(rest::ResponseCode::OK, VPackSlice::trueSlice());
generateOk(rest::ResponseCode::OK, VPackSlice::trueSlice());
} else {
generateError(res);
}

View File

@ -48,8 +48,7 @@ RestStatus RestPregelHandler::execute() {
if (!parseSuccess || !body.isObject()) {
LOG_TOPIC(ERR, Logger::PREGEL) << "Bad request body\n";
generateError(rest::ResponseCode::BAD,
TRI_ERROR_NOT_IMPLEMENTED, "illegal request for /_api/pregel");
// error message generated in parseVelocyPackBody
return RestStatus::DONE;
}
if (_request->requestType() != rest::RequestType::POST) {

View File

@ -35,6 +35,7 @@
#include "Cluster/ClusterHelpers.h"
#include "Cluster/ClusterMethods.h"
#include "Cluster/FollowerInfo.h"
#include "GeneralServer/AuthenticationFeature.h"
#include "Indexes/Index.h"
#include "Replication/DatabaseInitialSyncer.h"
#include "Replication/DatabaseReplicationApplier.h"
@ -1027,7 +1028,6 @@ int RestReplicationHandler::processRestoreCollectionCoordinator(
if (name.empty()) {
errorMsg = "collection name is missing";
return TRI_ERROR_HTTP_BAD_PARAMETER;
}
@ -1150,6 +1150,15 @@ int RestReplicationHandler::processRestoreCollectionCoordinator(
collectionType, _vocbase, merged, ignoreDistributeShardsLikeErrors,
createWaitsForSyncReplication);
TRI_ASSERT(col != nullptr);
ExecContext const* exe = ExecContext::CURRENT;
if (exe != nullptr && !exe->isSuperuser()) {
AuthenticationFeature *auth = AuthenticationFeature::INSTANCE;
auth->authInfo()->updateUser(ExecContext::CURRENT->user(),
[&](AuthUserEntry& entry) {
entry.grantCollection(dbName, col->name(), AuthLevel::RW);
});
}
} catch (basics::Exception const& e) {
// Error, report it.
errorMsg = e.message();
@ -2444,7 +2453,7 @@ int RestReplicationHandler::createCollection(VPackSlice slice,
if (dst != nullptr) {
*dst = nullptr;
}
if (!slice.isObject()) {
return TRI_ERROR_HTTP_BAD_PARAMETER;
}
@ -2504,7 +2513,14 @@ int RestReplicationHandler::createCollection(VPackSlice slice,
return TRI_ERROR_INTERNAL;
}
TRI_ASSERT(col != nullptr);
ExecContext const* exe = ExecContext::CURRENT;
if (exe != nullptr && !exe->isSuperuser() &&
ServerState::instance()->isSingleServer()) {
AuthenticationFeature *auth = AuthenticationFeature::INSTANCE;
auth->authInfo()->updateUser(exe->user(), [&](AuthUserEntry& entry) {
entry.grantCollection(_vocbase->name(), col->name(), AuthLevel::RW);
});
}
/* Temporary ASSERTS to prove correctness of new constructor */
TRI_ASSERT(col->isSystem() == (name[0] == '_'));

View File

@ -75,6 +75,7 @@ void RestSimpleQueryHandler::allDocuments() {
parseVelocyPackBody(parseSuccess);
if (!parseSuccess) {
// error message generated in parseVelocyPackBody
return;
}
@ -166,6 +167,7 @@ void RestSimpleQueryHandler::allDocumentKeys() {
parseVelocyPackBody(parseSuccess);
if (!parseSuccess) {
// error message generated in parseVelocyPackBody
return;
}

View File

@ -87,9 +87,9 @@ RestStatus RestTransactionHandler::execute() {
if (res.ok()){
VPackSlice slice = result.slice();
if (slice.isNone()) {
generateSuccess(rest::ResponseCode::OK, VPackSlice::nullSlice());
generateOk(rest::ResponseCode::OK, VPackSlice::nullSlice());
} else {
generateSuccess(rest::ResponseCode::OK, slice);
generateOk(rest::ResponseCode::OK, slice);
}
} else {
generateError(res);

View File

@ -99,7 +99,7 @@ RestStatus RestUsersHandler::getRequest(AuthInfo* authInfo) {
if (suffixes.empty()) {
if (isAdminUser()) {
VPackBuilder users = authInfo->allUsers();
generateSuccess(ResponseCode::OK, users.slice());
generateOk(ResponseCode::OK, users.slice());
} else {
generateError(ResponseCode::FORBIDDEN, TRI_ERROR_HTTP_FORBIDDEN);
}
@ -133,7 +133,7 @@ RestStatus RestUsersHandler::getRequest(AuthInfo* authInfo) {
AuthLevel lvl = authInfo->canUseDatabase(user, suffixes[2]);
VPackBuilder data;
data.add(VPackValue(convertFromAuthLevel(lvl)));
generateSuccess(ResponseCode::OK, data.slice());
generateOk(ResponseCode::OK, data.slice());
} else if (suffixes.size() == 4) {
//_api/user/<user>/database/<dbname>/<collection>
@ -141,7 +141,7 @@ RestStatus RestUsersHandler::getRequest(AuthInfo* authInfo) {
authInfo->canUseCollection(user, suffixes[2], suffixes[3]);
VPackBuilder data;
data.add(VPackValue(convertFromAuthLevel(lvl)));
generateSuccess(ResponseCode::OK, data.slice());
generateOk(ResponseCode::OK, data.slice());
} else {
generateError(ResponseCode::BAD, TRI_ERROR_BAD_PARAMETER);
}
@ -153,7 +153,7 @@ RestStatus RestUsersHandler::getRequest(AuthInfo* authInfo) {
if (suffixes.size() == 3) {
resp = data.slice().get(suffixes[2]);
}
generateSuccess(ResponseCode::OK,
generateOk(ResponseCode::OK,
resp.isNone() ? VPackSlice::nullSlice() : resp);
} else {
generateError(ResponseCode::BAD, TRI_ERROR_BAD_PARAMETER);
@ -183,7 +183,7 @@ void RestUsersHandler::generateDatabaseResult(AuthInfo* authInfo,
VPackObjectBuilder b(&data, vocbase->name(), true);
data.add("permission", VPackValue(str));
VPackObjectBuilder b2(&data, "collections", true);
methods::Collections::enumerateCollections(
methods::Collections::enumerate(
vocbase, [&](LogicalCollection* c) {
if (entry.hasSpecificCollection(vocbase->name(), c->name())) {
lvl = entry.collectionAuthLevel(vocbase->name(), c->name());
@ -206,7 +206,7 @@ void RestUsersHandler::generateDatabaseResult(AuthInfo* authInfo,
});
data.close();
if (res.ok()) {
generateSuccess(ResponseCode::OK, data.slice());
generateOk(ResponseCode::OK, data.slice());
} else {
generateError(res);
}
@ -284,7 +284,7 @@ RestStatus RestUsersHandler::postRequest(AuthInfo* authInfo) {
}
AuthResult result = authInfo->checkPassword(user, password);
if (result._authorized) {
generateSuccess(rest::ResponseCode::OK, VPackSlice::trueSlice());
generateOk(rest::ResponseCode::OK, VPackSlice::trueSlice());
} else {
generateError(rest::ResponseCode::NOT_FOUND, TRI_ERROR_USER_NOT_FOUND);
}

View File

@ -213,7 +213,7 @@ void RestViewHandler::deleteView() {
int res = _vocbase->dropView(name);
if (res == TRI_ERROR_NO_ERROR) {
generateOk();
resetResponse(rest::ResponseCode::NO_CONTENT);
} else if (res == TRI_ERROR_ARANGO_VIEW_NOT_FOUND) {
generateError(rest::ResponseCode::NOT_FOUND,
TRI_ERROR_ARANGO_VIEW_NOT_FOUND);

View File

@ -66,6 +66,12 @@ std::string const RestVocbaseBaseHandler::AGENCY_PRIV_PATH =
std::string const RestVocbaseBaseHandler::BATCH_PATH = "/_api/batch";
////////////////////////////////////////////////////////////////////////////////
/// @brief collection path
////////////////////////////////////////////////////////////////////////////////
std::string const RestVocbaseBaseHandler::COLLECTION_PATH = "/_api/collection";
////////////////////////////////////////////////////////////////////////////////
/// @brief cursor path
////////////////////////////////////////////////////////////////////////////////

View File

@ -63,6 +63,12 @@ class RestVocbaseBaseHandler : public RestBaseHandler {
//////////////////////////////////////////////////////////////////////////////
static std::string const BATCH_PATH;
//////////////////////////////////////////////////////////////////////////////
/// @brief collection path
//////////////////////////////////////////////////////////////////////////////
static std::string const COLLECTION_PATH;
//////////////////////////////////////////////////////////////////////////////
/// @brief cursor path
@ -177,18 +183,6 @@ class RestVocbaseBaseHandler : public RestBaseHandler {
void generate20x(arangodb::OperationResult const&, std::string const&,
TRI_col_type_e, arangodb::velocypack::Options const*);
//////////////////////////////////////////////////////////////////////////////
/// @brief generates ok message without content
//////////////////////////////////////////////////////////////////////////////
void generateOk() { resetResponse(rest::ResponseCode::NO_CONTENT); }
//////////////////////////////////////////////////////////////////////////////
/// @brief generates ok message with no body but with certain status code
//////////////////////////////////////////////////////////////////////////////
void generateOk(rest::ResponseCode code) { resetResponse(code); }
//////////////////////////////////////////////////////////////////////////////
/// @brief generates message for a saved document
//////////////////////////////////////////////////////////////////////////////

View File

@ -142,17 +142,17 @@ void RocksDBCollection::setPath(std::string const&) {
// we do not have any path
}
arangodb::Result RocksDBCollection::updateProperties(VPackSlice const& slice,
bool doSync) {
_cacheEnabled = basics::VelocyPackHelper::readBooleanValue(
slice, "cacheEnabled", _cacheEnabled);
Result RocksDBCollection::updateProperties(VPackSlice const& slice, bool doSync) {
bool isSys = _logicalCollection != nullptr && _logicalCollection->isSystem();
_cacheEnabled = !isSys && basics::VelocyPackHelper::readBooleanValue(slice,
"cacheEnabled", _cacheEnabled);
primaryIndex()->setCacheEnabled(_cacheEnabled);
if (_cacheEnabled) {
createCache();
primaryIndex()->createCache();
} else if (useCache()) {
destroyCache();
primaryIndex()->destroyCache();
primaryIndex()->destroyCache();
}
// nothing else to do

View File

@ -1250,14 +1250,12 @@ std::pair<TRI_voc_tick_t, TRI_voc_cid_t> RocksDBEngine::mapObjectToCollection(
return it->second;
}
arangodb::Result RocksDBEngine::syncWal(bool waitForSync,
bool waitForCollector,
bool /*writeShutdownFile*/) {
Result RocksDBEngine::flushWal(bool waitForSync, bool waitForCollector,
bool /*writeShutdownFile*/) {
rocksdb::Status status;
#ifndef _WIN32
// SyncWAL always reports "not implemented" on Windows
status = _db->GetBaseDB()->SyncWAL();
if (!status.ok()) {
return rocksutils::convertStatus(status);
}
@ -1273,7 +1271,7 @@ arangodb::Result RocksDBEngine::syncWal(bool waitForSync,
}
}
}
return arangodb::Result();
return TRI_ERROR_NO_ERROR;
}
std::vector<std::string> RocksDBEngine::currentWalFiles() {
@ -1702,7 +1700,7 @@ Result RocksDBEngine::handleSyncKeys(arangodb::DatabaseInitialSyncer& syncer,
Result RocksDBEngine::createLoggerState(TRI_vocbase_t* vocbase,
VPackBuilder& builder) {
syncWal();
flushWal(false, false, false);
builder.openObject(); // Base
rocksdb::SequenceNumber lastTick = _db->GetLatestSequenceNumber();

View File

@ -160,6 +160,8 @@ class RocksDBEngine final : public StorageEngine {
// intentionally empty, not useful for this type of engine
void waitForSyncTick(TRI_voc_tick_t) override {}
void waitForSyncTimeout(double) override {}
Result flushWal(bool waitForSync, bool waitForCollector,
bool writeShutdownFile) override;
virtual TRI_vocbase_t* openDatabase(velocypack::Slice const& parameters,
bool isUpgrade, int&) override;
@ -286,10 +288,6 @@ class RocksDBEngine final : public StorageEngine {
TRI_ASSERT(_replicationManager);
return _replicationManager.get();
}
arangodb::Result syncWal(bool waitForSync = false,
bool waitForCollector = false,
bool writeShutdownFile = false);
private:
/// single rocksdb database used in this storage engine

View File

@ -146,7 +146,7 @@ void RocksDBRestWalHandler::flush() {
res = flushWalOnAllDBServers(waitForSync, waitForCollector);
} else {
if (waitForSync) {
static_cast<RocksDBEngine*>(EngineSelectorFeature::ENGINE)->syncWal();
EngineSelectorFeature::ENGINE->flushWal();
}
}

View File

@ -78,12 +78,8 @@ static void JS_FlushWal(v8::FunctionCallbackInfo<v8::Value> const& args) {
}
}
arangodb::Result ret =
static_cast<RocksDBEngine*>(EngineSelectorFeature::ENGINE)->syncWal(
waitForSync, waitForCollector, writeShutdownFile);
if (!ret.ok()) {
THROW_ARANGO_EXCEPTION_MESSAGE(ret.errorNumber(), ret.errorMessage());
}
EngineSelectorFeature::ENGINE->flushWal(waitForSync, waitForCollector,
writeShutdownFile);
TRI_V8_RETURN_TRUE();
TRI_V8_TRY_CATCH_END
}

View File

@ -64,7 +64,7 @@ Result RocksDBWalAccess::tickRange(
/// }}
///
TRI_voc_tick_t RocksDBWalAccess::lastTick() const {
rocksutils::globalRocksEngine()->syncWal();
rocksutils::globalRocksEngine()->flushWal(false, false, false);
return rocksutils::globalRocksDB()->GetLatestSequenceNumber();
}

View File

@ -79,6 +79,9 @@ class PhysicalCollection {
virtual int close() = 0;
virtual void load() = 0;
virtual void unload() = 0;
/// @brief rotate the active journal - will do nothing if there is no journal
virtual int rotateActiveJournal() { return TRI_ERROR_NO_ERROR; }
// @brief Return the number of documents in this collection
virtual uint64_t numberDocuments(transaction::Methods* trx) const = 0;
@ -90,7 +93,7 @@ class PhysicalCollection {
virtual void open(bool ignoreErrors) = 0;
void drop();
////////////////////////////////////
// -- SECTION Indexes --
///////////////////////////////////
@ -194,7 +197,7 @@ class PhysicalCollection {
/// it at that moment.
virtual void deferDropCollection(
std::function<bool(LogicalCollection*)> callback) = 0;
protected:
/// @brief Inject figures that are specific to StorageEngine
virtual void figuresSpecific(

View File

@ -153,6 +153,9 @@ class StorageEngine : public application_features::ApplicationFeature {
virtual void waitForSyncTick(TRI_voc_tick_t tick) = 0;
virtual void waitForSyncTimeout(double maxWait) = 0;
virtual Result flushWal(bool waitForSync = false, bool waitForCollector = false,
bool writeShutdownFile = false) = 0;
//// operations on databasea

View File

@ -51,6 +51,15 @@ struct WalAccessResult : public Result {
_fromTickIncluded(other._fromTickIncluded),
_lastIncludedTick(other._lastIncludedTick),
_latestTick(other._latestTick) {}
WalAccessResult& operator=(WalAccessResult const& other) {
_errorNumber = other._errorNumber;
_errorMessage = other._errorMessage;
_fromTickIncluded = other._fromTickIncluded;
_lastIncludedTick = other._lastIncludedTick;
_latestTick = other._latestTick;
return *this;
}
bool fromTickIncluded() const { return _fromTickIncluded; }
TRI_voc_tick_t lastIncludedTick() const { return _lastIncludedTick; }

View File

@ -69,6 +69,7 @@
#include "VocBase/KeyGenerator.h"
#include "VocBase/LogicalCollection.h"
#include "VocBase/modes.h"
#include "VocBase/Methods/Collections.h"
#include <velocypack/Builder.h>
#include <velocypack/HexDump.h>
@ -917,54 +918,6 @@ static void JS_BinaryDocumentVocbaseCol(
TRI_V8_TRY_CATCH_END
}
#ifndef USE_ENTERPRISE
////////////////////////////////////////////////////////////////////////////////
/// @brief unloads a collection, case of a coordinator in a cluster
////////////////////////////////////////////////////////////////////////////////
static int ULVocbaseColCoordinator(std::string const& databaseName,
std::string const& collectionCID,
TRI_vocbase_col_status_e status) {
return ClusterInfo::instance()->setCollectionStatusCoordinator(
databaseName, collectionCID, status);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief drops a collection, case of a coordinator in a cluster
////////////////////////////////////////////////////////////////////////////////
static void DropVocbaseColCoordinator(
v8::FunctionCallbackInfo<v8::Value> const& args,
arangodb::LogicalCollection* collection,
bool allowDropSystem) {
// cppcheck-suppress *
v8::Isolate* isolate = args.GetIsolate();
if (collection->isSystem() && !allowDropSystem) {
TRI_V8_THROW_EXCEPTION(TRI_ERROR_FORBIDDEN);
}
std::string const databaseName(collection->dbName());
std::string const cid = collection->cid_as_string();
ClusterInfo* ci = ClusterInfo::instance();
std::string errorMsg;
int res = ci->dropCollectionCoordinator(databaseName, cid, errorMsg, 120.0);
if (res != TRI_ERROR_NO_ERROR) {
TRI_V8_THROW_EXCEPTION_MESSAGE(res, errorMsg);
}
collection->setStatus(TRI_VOC_COL_STATUS_DELETED);
TRI_V8_RETURN_UNDEFINED();
}
#endif
////////////////////////////////////////////////////////////////////////////////
/// @brief was docuBlock collectionDrop
////////////////////////////////////////////////////////////////////////////////
@ -972,6 +925,11 @@ static void DropVocbaseColCoordinator(
static void JS_DropVocbaseCol(v8::FunctionCallbackInfo<v8::Value> const& args) {
TRI_V8_TRY_CATCH_BEGIN(isolate);
v8::HandleScope scope(isolate);
TRI_vocbase_t* vocbase = GetContextVocBase(isolate);
if (vocbase == nullptr || vocbase->isDangling()) {
TRI_V8_THROW_EXCEPTION(TRI_ERROR_ARANGO_DATABASE_NOT_FOUND);
}
arangodb::LogicalCollection* collection =
TRI_UnwrapClass<arangodb::LogicalCollection>(args.Holder(), WRP_VOCBASE_COL_TYPE);
@ -979,20 +937,8 @@ static void JS_DropVocbaseCol(v8::FunctionCallbackInfo<v8::Value> const& args) {
TRI_V8_THROW_EXCEPTION_INTERNAL("cannot extract collection");
}
ExecContext const* exec = ExecContext::CURRENT;
if (exec != nullptr) {
if (exec->databaseAuthLevel() != AuthLevel::RW ||
!exec->canUseCollection(collection->name(), AuthLevel::RW)) {
TRI_V8_THROW_EXCEPTION_MESSAGE(TRI_ERROR_FORBIDDEN,
"Insufficient rights to drop collection");
}
}
PREVENT_EMBEDDED_TRANSACTION();
std::string const dbname = collection->dbName();
std::string const collName = collection->name();
bool allowDropSystem = false;
double timeout = -1.0; // forever, unless specified otherwise
if (args.Length() > 0) {
@ -1012,26 +958,11 @@ static void JS_DropVocbaseCol(v8::FunctionCallbackInfo<v8::Value> const& args) {
allowDropSystem = TRI_ObjectToBoolean(args[0]);
}
}
// If we are a coordinator in a cluster, we have to behave differently:
if (ServerState::instance()->isCoordinator()) {
#ifdef USE_ENTERPRISE
DropVocbaseColCoordinatorEnterprise(args, collection, allowDropSystem);
#else
DropVocbaseColCoordinator(args, collection, allowDropSystem);
#endif
} else {
int res = collection->vocbase()->dropCollection(collection, allowDropSystem, timeout);
if (res != TRI_ERROR_NO_ERROR) {
TRI_V8_THROW_EXCEPTION_MESSAGE(res, "cannot drop collection");
}
}
if (ServerState::instance()->isSingleServerOrCoordinator()) {
AuthenticationFeature* auth = AuthenticationFeature::INSTANCE;
auth->authInfo()->enumerateUsers([&](AuthUserEntry& entry) {
entry.removeCollection(dbname, collName);
});
Result res = methods::Collections::drop(vocbase, collection,
allowDropSystem, timeout);
if (res.fail()) {
TRI_V8_THROW_EXCEPTION(res);
}
TRI_V8_RETURN_UNDEFINED();
@ -1287,7 +1218,6 @@ static void JS_GetFollowers(v8::FunctionCallbackInfo<v8::Value> const& args) {
v8::HandleScope scope(isolate);
TRI_vocbase_t* vocbase = GetContextVocBase(isolate);
if (vocbase == nullptr || vocbase->isDropped()) {
TRI_V8_THROW_EXCEPTION(TRI_ERROR_ARANGO_DATABASE_NOT_FOUND);
}
@ -1331,8 +1261,13 @@ static void JS_GetFollowers(v8::FunctionCallbackInfo<v8::Value> const& args) {
static void JS_LoadVocbaseCol(v8::FunctionCallbackInfo<v8::Value> const& args) {
TRI_V8_TRY_CATCH_BEGIN(isolate);
v8::HandleScope scope(isolate);
TRI_vocbase_t* vocbase = GetContextVocBase(isolate);
if (vocbase == nullptr || vocbase->isDropped()) {
TRI_V8_THROW_EXCEPTION(TRI_ERROR_ARANGO_DATABASE_NOT_FOUND);
}
arangodb::LogicalCollection const* collection =
arangodb::LogicalCollection* collection =
TRI_UnwrapClass<arangodb::LogicalCollection>(args.Holder(),
WRP_VOCBASE_COL_TYPE);
@ -1340,42 +1275,13 @@ static void JS_LoadVocbaseCol(v8::FunctionCallbackInfo<v8::Value> const& args) {
TRI_V8_THROW_EXCEPTION_INTERNAL("cannot extract collection");
}
if (ServerState::instance()->isCoordinator()) {
int res =
#ifdef USE_ENTERPRISE
ULVocbaseColCoordinatorEnterprise(
collection->dbName(), collection->cid_as_string(),
TRI_VOC_COL_STATUS_LOADED);
#else
ULVocbaseColCoordinator(
collection->dbName(), collection->cid_as_string(),
TRI_VOC_COL_STATUS_LOADED);
#endif
if (res != TRI_ERROR_NO_ERROR) {
TRI_V8_THROW_EXCEPTION(res);
}
TRI_V8_RETURN_UNDEFINED();
}
SingleCollectionTransaction trx(
transaction::V8Context::Create(collection->vocbase(), true),
collection->cid(), AccessMode::Type::READ);
Result res = trx.begin();
if (!res.ok()) {
Result res = methods::Collections::load(vocbase, collection);
if (res.fail()) {
TRI_V8_THROW_EXCEPTION(res);
}
res = trx.finish(res);
if (!res.ok()) {
TRI_V8_THROW_EXCEPTION(res);
}
TRI_V8_RETURN_UNDEFINED();
TRI_V8_TRY_CATCH_END
}
////////////////////////////////////////////////////////////////////////////////
@ -1461,130 +1367,46 @@ static void JS_PropertiesVocbaseCol(
TRI_V8_TRY_CATCH_BEGIN(isolate);
v8::HandleScope scope(isolate);
arangodb::LogicalCollection* collection =
TRI_UnwrapClass<arangodb::LogicalCollection>(args.Holder(), WRP_VOCBASE_COL_TYPE);
if (collection == nullptr) {
LogicalCollection* consoleColl =
TRI_UnwrapClass<LogicalCollection>(args.Holder(), WRP_VOCBASE_COL_TYPE);
if (consoleColl == nullptr) {
TRI_V8_THROW_EXCEPTION_INTERNAL("cannot extract collection");
}
TRI_vocbase_t* vocbase = consoleColl->vocbase();
bool const isModification = (args.Length() != 0);
ExecContext const* exec = ExecContext::CURRENT;
if (exec != nullptr) {
bool canModify = exec->canUseCollection(collection->name(), AuthLevel::RW);
bool canRead = exec->canUseCollection(collection->name(), AuthLevel::RO);
if ((isModification && (exec->databaseAuthLevel() != AuthLevel::RW || !canModify)) ||
exec->databaseAuthLevel() == AuthLevel::NONE || !canRead) {
TRI_V8_THROW_EXCEPTION(TRI_ERROR_FORBIDDEN);
}
}
if (ServerState::instance()->isCoordinator()) {
std::string const databaseName(collection->dbName());
std::shared_ptr<LogicalCollection> info =
ClusterInfo::instance()->getCollection(
databaseName, collection->cid_as_string());
if (0 < args.Length()) {
v8::Handle<v8::Value> par = args[0];
if (par->IsObject()) {
VPackBuilder builder;
{
int res = TRI_V8ToVPack(isolate, builder, args[0], false);
if (res != TRI_ERROR_NO_ERROR) {
TRI_V8_THROW_EXCEPTION(res);
}
}
VPackSlice const slice = builder.slice();
arangodb::Result res = info->updateProperties(slice, false);
if (!res.ok()) {
TRI_V8_THROW_EXCEPTION_MESSAGE(res.errorNumber(), res.errorMessage());
}
}
}
auto c = ClusterInfo::instance()->getCollection(
databaseName, StringUtils::itoa(collection->cid()));
std::unordered_set<std::string> const ignoreKeys{
"allowUserKeys", "cid", "count", "deleted", "id",
"indexes", "name", "path", "planId", "shards",
"status", "type", "version"};
VPackBuilder vpackProperties = c->toVelocyPackIgnore(ignoreKeys, true, false);
// return the current parameter set
v8::Handle<v8::Object> result =
TRI_VPackToV8(isolate, vpackProperties.slice())->ToObject();
TRI_V8_RETURN(result);
}
SingleCollectionTransaction trx(
transaction::V8Context::Create(collection->vocbase(), true),
collection->cid(),
isModification ? AccessMode::Type::EXCLUSIVE : AccessMode::Type::READ);
if (!isModification) {
trx.addHint(transaction::Hints::Hint::NO_USAGE_LOCK);
}
Result res = trx.begin();
if (!res.ok()) {
TRI_V8_THROW_EXCEPTION(res);
}
// check if we want to change some parameters
if (isModification) {
v8::Handle<v8::Value> par = args[0];
if (par->IsObject()) {
VPackBuilder builder;
int res = TRI_V8ToVPack(isolate, builder, args[0], false);
if (res != TRI_ERROR_NO_ERROR) {
{
int res = TRI_V8ToVPack(isolate, builder, args[0], false);
if (res != TRI_ERROR_NO_ERROR) {
TRI_V8_THROW_EXCEPTION(res);
}
}
Result res = methods::Collections::updateProperties(consoleColl, builder.slice());
if (res.fail() && ServerState::instance()->isCoordinator()) {
TRI_V8_THROW_EXCEPTION(res);
}
VPackSlice const slice = builder.slice();
// try to write new parameter to file
bool doSync = application_features::ApplicationServer::getFeature<DatabaseFeature>("Database")->forceSyncProperties();
arangodb::Result updateRes = collection->updateProperties(slice, doSync);
if (!updateRes.ok()) {
TRI_V8_THROW_EXCEPTION_MESSAGE(updateRes.errorNumber(), updateRes.errorMessage());
}
auto physical = collection->getPhysical();
TRI_ASSERT(physical != nullptr);
arangodb::Result res2 = physical->persistProperties();
// TODO Review
// TODO API compatibility, for now we ignore if persisting fails...
}
}
std::unordered_set<std::string> const ignoreKeys{
"allowUserKeys", "cid", "count", "deleted", "id", "indexes", "name",
"path", "planId", "shards", "status", "type", "version",
/* These are only relevant for cluster */
"distributeShardsLike", "isSmart", "numberOfShards", "replicationFactor",
"shardKeys"};
VPackBuilder vpackProperties = collection->toVelocyPackIgnore(ignoreKeys, true, false);
// in the cluster the collection object might contain outdated
// properties, which will break tests. We need an extra lookup
VPackBuilder builder;
methods::Collections::lookup(vocbase, consoleColl->name(),
[&](LogicalCollection* coll) {
VPackObjectBuilder object(&builder, true);
Result res = methods::Collections::properties(coll, builder);
if (res.fail()) {
TRI_V8_THROW_EXCEPTION(res);
}
});
// return the current parameter set
v8::Handle<v8::Object> result =
TRI_VPackToV8(isolate, vpackProperties.slice())->ToObject();
trx.finish(res);
TRI_V8_RETURN(result);
TRI_V8_RETURN(TRI_VPackToV8(isolate, builder.slice())->ToObject());
TRI_V8_TRY_CATCH_END
}
@ -1596,30 +1418,6 @@ static void JS_RemoveVocbaseCol(
TRI_V8_TRY_CATCH_END
}
////////////////////////////////////////////////////////////////////////////////
/// @brief helper function to rename collections in _graphs as well
////////////////////////////////////////////////////////////////////////////////
static int RenameGraphCollections(v8::Isolate* isolate,
std::string const& oldName,
std::string const& newName) {
v8::HandleScope scope(isolate);
StringBuffer buffer(true);
buffer.appendText("require('@arangodb/general-graph')._renameCollection(");
buffer.appendJsonEncoded(oldName.c_str(), oldName.size());
buffer.appendChar(',');
buffer.appendJsonEncoded(newName.c_str(), newName.size());
buffer.appendText(");");
TRI_ExecuteJavaScriptString(
isolate, isolate->GetCurrentContext(),
TRI_V8_ASCII_PAIR_STRING(isolate, buffer.c_str(), buffer.length()),
TRI_V8_ASCII_STRING(isolate, "collection rename"), false);
return TRI_ERROR_NO_ERROR;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief was docuBlock collectionRename
////////////////////////////////////////////////////////////////////////////////
@ -1633,11 +1431,6 @@ static void JS_RenameVocbaseCol(
TRI_V8_THROW_EXCEPTION_USAGE("rename(<name>)");
}
if (ServerState::instance()->isCoordinator()) {
// renaming a collection in a cluster is unsupported
TRI_V8_THROW_EXCEPTION(TRI_ERROR_CLUSTER_UNSUPPORTED);
}
std::string const name = TRI_ObjectToString(args[0]);
// second parameter "override" is to override renaming restrictions, e.g.
@ -1647,45 +1440,19 @@ static void JS_RenameVocbaseCol(
if (args.Length() > 1) {
doOverride = TRI_ObjectToBoolean(args[1]);
}
if (name.empty()) {
TRI_V8_THROW_EXCEPTION_PARAMETER("<name> must be non-empty");
}
PREVENT_EMBEDDED_TRANSACTION();
arangodb::LogicalCollection* collection =
TRI_UnwrapClass<arangodb::LogicalCollection>(args.Holder(), WRP_VOCBASE_COL_TYPE);
TRI_UnwrapClass<arangodb::LogicalCollection>(args.Holder(), WRP_VOCBASE_COL_TYPE);
if (collection == nullptr) {
TRI_V8_THROW_EXCEPTION_INTERNAL("cannot extract collection");
}
ExecContext const* exec = ExecContext::CURRENT;
if (exec != nullptr) {
if (!exec->canUseDatabase(AuthLevel::RW) ||
!exec->canUseCollection(collection->name(), AuthLevel::RW)) {
TRI_V8_THROW_EXCEPTION(TRI_ERROR_FORBIDDEN);
}
Result res = methods::Collections::rename(collection, name, doOverride);
if (res.fail()) {
TRI_V8_THROW_EXCEPTION(res);
}
PREVENT_EMBEDDED_TRANSACTION();
if (ServerState::instance()->isCoordinator()) {
// renaming a collection in a cluster is unsupported
TRI_V8_THROW_EXCEPTION(TRI_ERROR_CLUSTER_UNSUPPORTED);
}
std::string const oldName(collection->name());
int res =
collection->vocbase()->renameCollection(collection, name, doOverride);
if (res != TRI_ERROR_NO_ERROR) {
TRI_V8_THROW_EXCEPTION_MESSAGE(res, "cannot rename collection");
}
// rename collection inside _graphs as well
RenameGraphCollections(isolate, oldName, name);
TRI_V8_RETURN_UNDEFINED();
TRI_V8_TRY_CATCH_END
}
@ -2333,43 +2100,6 @@ static void JS_PregelAQLResult(v8::FunctionCallbackInfo<v8::Value> const& args)
TRI_V8_TRY_CATCH_END
}
////////////////////////////////////////////////////////////////////////////////
/// @brief fetch the revision for a local collection
////////////////////////////////////////////////////////////////////////////////
static int GetRevision(arangodb::LogicalCollection* collection, TRI_voc_rid_t& rid) {
TRI_ASSERT(collection != nullptr);
SingleCollectionTransaction trx(
transaction::V8Context::Create(collection->vocbase(), true),
collection->cid(), AccessMode::Type::READ);
Result res = trx.begin();
if (!res.ok()) {
return res.errorNumber();
}
rid = collection->revision(&trx);
trx.finish(res);
return TRI_ERROR_NO_ERROR;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief fetch the revision for a sharded collection
////////////////////////////////////////////////////////////////////////////////
static int GetRevisionCoordinator(arangodb::LogicalCollection* collection,
TRI_voc_rid_t& rid) {
TRI_ASSERT(collection != nullptr);
std::string const databaseName(collection->dbName());
std::string const cid = collection->cid_as_string();
return revisionOnCoordinator(databaseName, cid, rid);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief was docuBlock collectionRevision
////////////////////////////////////////////////////////////////////////////////
@ -2379,23 +2109,17 @@ static void JS_RevisionVocbaseCol(
TRI_V8_TRY_CATCH_BEGIN(isolate);
v8::HandleScope scope(isolate);
arangodb::LogicalCollection* collection =
TRI_UnwrapClass<arangodb::LogicalCollection>(args.Holder(), WRP_VOCBASE_COL_TYPE);
LogicalCollection* collection =
TRI_UnwrapClass<LogicalCollection>(args.Holder(), WRP_VOCBASE_COL_TYPE);
if (collection == nullptr) {
TRI_V8_THROW_EXCEPTION_INTERNAL("cannot extract collection");
}
TRI_voc_rid_t revisionId;
int res;
if (ServerState::instance()->isCoordinator()) {
res = GetRevisionCoordinator(collection, revisionId);
} else {
res = GetRevision(collection, revisionId);
}
if (res != TRI_ERROR_NO_ERROR) {
Result res = methods::Collections::revisionId(collection->vocbase(),
collection, revisionId);
if (res.fail()) {
TRI_V8_THROW_EXCEPTION(res);
}
@ -2778,14 +2502,14 @@ static void JS_TruncateVocbaseCol(
}
// Manually check this here, because truncate messes up the return code
ExecContext const* exec = ExecContext::CURRENT;
/*ExecContext const* exec = ExecContext::CURRENT;
if (exec != nullptr) {
CollectionNameResolver resolver(collection->vocbase());
std::string const cName = resolver.getCollectionNameCluster(collection->cid());
if (!exec->canUseCollection(collection->vocbase()->name(), cName, AuthLevel::RW)) {
TRI_V8_THROW_EXCEPTION(TRI_ERROR_FORBIDDEN);
}
}
}*/
SingleCollectionTransaction trx(
transaction::V8Context::Create(collection->vocbase(), true),
@ -2797,7 +2521,6 @@ static void JS_TruncateVocbaseCol(
}
OperationResult result = trx.truncate(collection->name(), opOptions);
res = trx.finish(result.code);
if (result.failed()) {
@ -2839,7 +2562,7 @@ static void JS_TypeVocbaseCol(v8::FunctionCallbackInfo<v8::Value> const& args) {
TRI_V8_RETURN(v8::Number::New(isolate, (int)collection->type()));
}
}
// fallthru intentional
// fallthrough intentional
TRI_col_type_e type = collection->type();
@ -2864,26 +2587,8 @@ static void JS_UnloadVocbaseCol(
TRI_V8_THROW_EXCEPTION_INTERNAL("cannot extract collection");
}
int res;
if (ServerState::instance()->isCoordinator()) {
res =
#ifdef USE_ENTERPRISE
ULVocbaseColCoordinatorEnterprise(
collection->dbName(), collection->cid_as_string(),
TRI_VOC_COL_STATUS_UNLOADED);
#else
ULVocbaseColCoordinator(
collection->dbName(), collection->cid_as_string(),
TRI_VOC_COL_STATUS_UNLOADED);
#endif
} else {
res = collection->vocbase()->unloadCollection(collection, false);
}
if (res != TRI_ERROR_NO_ERROR) {
Result res = methods::Collections::unload(collection->vocbase(), collection);
if (res.fail()) {
TRI_V8_THROW_EXCEPTION(res);
}
@ -3280,45 +2985,11 @@ static void JS_WarmupVocbaseCol(
if (collection == nullptr) {
TRI_V8_THROW_EXCEPTION_INTERNAL("cannot extract collection");
}
if (ServerState::instance()->isCoordinator()) {
std::string const databaseName(collection->dbName());
std::string const cid = collection->cid_as_string();
int res = warmupOnCoordinator(databaseName, cid);
if (res != TRI_ERROR_NO_ERROR) {
TRI_V8_THROW_EXCEPTION(res);
}
TRI_V8_RETURN_UNDEFINED();
}
SingleCollectionTransaction trx(
transaction::V8Context::Create(collection->vocbase(), true),
collection->cid(),
AccessMode::Type::READ);
Result trxRes = trx.begin();
if (!trxRes.ok()) {
TRI_V8_THROW_EXCEPTION(trxRes);
}
auto idxs = collection->getIndexes();
auto queue = std::make_shared<basics::LocalTaskQueue>();
for (auto& idx : idxs) {
idx->warmup(&trx, queue);
}
queue->dispatchAndWait();
if (queue->status() == TRI_ERROR_NO_ERROR) {
trxRes = trx.commit();
} else {
TRI_V8_THROW_EXCEPTION(queue->status());
}
if (!trxRes.ok()) {
TRI_V8_THROW_EXCEPTION(trxRes);
TRI_vocbase_t* vocbase = collection->vocbase();
Result res = methods::Collections::warmup(vocbase, collection);
if (res.fail()) {
TRI_V8_THROW_EXCEPTION(res);
}
TRI_V8_RETURN_UNDEFINED();

View File

@ -69,16 +69,4 @@ void TRI_InitV8Collections(v8::Handle<v8::Context> context,
TRI_v8_global_t* v8g, v8::Isolate* isolate,
v8::Handle<v8::ObjectTemplate> ArangoDBNS);
#ifdef USE_ENTERPRISE
void DropVocbaseColCoordinatorEnterprise(
v8::FunctionCallbackInfo<v8::Value> const& args,
arangodb::LogicalCollection* collection,
bool allowDropSystem);
int ULVocbaseColCoordinatorEnterprise(std::string const& databaseName,
std::string const& collectionCID,
TRI_vocbase_col_status_e status);
#endif
#endif

View File

@ -50,6 +50,7 @@
#include "V8Server/v8-externals.h"
#include "V8Server/v8-vocbase.h"
#include "V8Server/v8-vocbaseprivate.h"
#include "VocBase/Methods/Collections.h"
#include "VocBase/Methods/Indexes.h"
#include "VocBase/LogicalCollection.h"
#include "VocBase/modes.h"
@ -203,19 +204,14 @@ static void CreateVocBase(v8::FunctionCallbackInfo<v8::Value> const& args,
v8::HandleScope scope(isolate);
TRI_vocbase_t* vocbase = GetContextVocBase(isolate);
if (vocbase == nullptr) {
if (vocbase == nullptr || vocbase->isDangling()) {
TRI_V8_THROW_EXCEPTION(TRI_ERROR_ARANGO_DATABASE_NOT_FOUND);
}
if (args.Length() < 1 || args.Length() > 4) {
} else if (args.Length() < 1 || args.Length() > 4) {
TRI_V8_THROW_EXCEPTION_USAGE("_create(<name>, <properties>, <type>, <options>)");
}
if (TRI_GetOperationModeServer() == TRI_VOCBASE_MODE_NO_CREATE) {
} else if (TRI_GetOperationModeServer() == TRI_VOCBASE_MODE_NO_CREATE) {
TRI_V8_THROW_EXCEPTION(TRI_ERROR_ARANGO_READ_ONLY);
}
AuthenticationFeature* auth = FeatureCacheFeature::instance()->authenticationFeature();
if (ExecContext::CURRENT != nullptr &&
!ExecContext::CURRENT->canUseDatabase(vocbase->name(), AuthLevel::RW)) {
TRI_V8_THROW_EXCEPTION(TRI_ERROR_FORBIDDEN);
@ -235,87 +231,42 @@ static void CreateVocBase(v8::FunctionCallbackInfo<v8::Value> const& args,
// extract the name
std::string const name = TRI_ObjectToString(args[0]);
VPackBuilder builder;
VPackSlice infoSlice;
if (2 <= args.Length()) {
VPackBuilder properties;
VPackSlice propSlice = VPackSlice::emptyObjectSlice();
if (args.Length() >= 2) {
if (!args[1]->IsObject()) {
TRI_V8_THROW_TYPE_ERROR("<properties> must be an object");
}
v8::Handle<v8::Object> obj = args[1]->ToObject();
// Add the type and name into the object. Easier in v8 than in VPack
obj->Set(TRI_V8_ASCII_STRING(isolate, "type"),
v8::Number::New(isolate, static_cast<int>(collectionType)));
obj->Set(TRI_V8_ASCII_STRING(isolate, "name"), TRI_V8_STD_STRING(isolate, name));
int res = TRI_V8ToVPack(isolate, builder, obj, false);
int res = TRI_V8ToVPack(isolate, properties, args[1]->ToObject(), false);
if (res != TRI_ERROR_NO_ERROR) {
TRI_V8_THROW_EXCEPTION(res);
}
} else {
// create an empty properties object
builder.openObject();
builder.add("type", VPackValue(static_cast<int>(collectionType)));
builder.add("name", VPackValue(name));
builder.close();
propSlice = properties.slice();
}
infoSlice = builder.slice();
// waitForSync can be 3. or 4. parameter
auto cluster = application_features::ApplicationServer::getFeature<ClusterFeature>("Cluster");
bool createWaitsForSyncReplication = cluster->createWaitsForSyncReplication();
if (args.Length() >= 3 && args[args.Length()-1]->IsObject()) {
v8::Handle<v8::Object> obj = args[args.Length()-1]->ToObject();
auto v8WaitForSyncReplication = obj->Get(TRI_V8_ASCII_STRING(isolate, "waitForSyncReplication"));
if (!v8WaitForSyncReplication->IsUndefined()) {
createWaitsForSyncReplication = TRI_ObjectToBoolean(v8WaitForSyncReplication);
}
}
v8::Handle<v8::Value> result;
if (ServerState::instance()->isCoordinator()) {
bool createWaitsForSyncReplication =
application_features::ApplicationServer::getFeature<ClusterFeature>("Cluster")->createWaitsForSyncReplication();
if (args.Length() >= 3 && args[args.Length()-1]->IsObject()) {
v8::Handle<v8::Object> obj = args[args.Length()-1]->ToObject();
auto v8WaitForSyncReplication = obj->Get(TRI_V8_ASCII_STRING(isolate, "waitForSyncReplication"));
if (!v8WaitForSyncReplication->IsUndefined()) {
createWaitsForSyncReplication = TRI_ObjectToBoolean(v8WaitForSyncReplication);
}
}
std::unique_ptr<LogicalCollection> col =
ClusterMethods::createCollectionOnCoordinator(
collectionType, vocbase, infoSlice, false,
createWaitsForSyncReplication);
result = WrapCollection(isolate, col.release());
} else {
try {
arangodb::LogicalCollection const* collection =
vocbase->createCollection(infoSlice);
TRI_ASSERT(collection != nullptr);
result = WrapCollection(isolate, collection);
} catch (basics::Exception const& ex) {
TRI_V8_THROW_EXCEPTION_MESSAGE(ex.code(), ex.what());
} catch (std::exception const& ex) {
TRI_V8_THROW_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, ex.what());
} catch (...) {
TRI_V8_THROW_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL,
"cannot create collection");
}
Result res = methods::Collections::create(vocbase, name, collectionType,
propSlice,
createWaitsForSyncReplication,
[&](LogicalCollection* collection) {
result = WrapCollection(isolate, collection);
});
if (res.fail()) {
TRI_V8_THROW_EXCEPTION(res);
}
if (result.IsEmpty()) {
TRI_V8_THROW_EXCEPTION_MEMORY();
}
// do not grant rights on system collections
// in case of success we grant the creating user RW access
if (name[0] != '_' && ExecContext::CURRENT != nullptr &&
(ServerState::instance()->isCoordinator() ||
!ServerState::instance()->isRunningInCluster())) {
// this should not fail, we can not get here without database RW access
auth->authInfo()->updateUser(ExecContext::CURRENT->user(),
[&](AuthUserEntry& entry) {
entry.grantCollection(vocbase->name(), name, AuthLevel::RW);
});
}
TRI_V8_RETURN(result);
}

View File

@ -598,6 +598,7 @@ std::string LogicalCollection::statusString() const {
// SECTION: Properties
TRI_voc_rid_t LogicalCollection::revision(transaction::Methods* trx) const {
// TODO CoordinatorCase
TRI_ASSERT(!ServerState::instance()->isCoordinator());
return _physical->revision(trx);
}
@ -1060,7 +1061,7 @@ arangodb::Result LogicalCollection::updateProperties(VPackSlice const& slice,
}
/// @brief return the figures for a collection
std::shared_ptr<arangodb::velocypack::Builder> LogicalCollection::figures() {
std::shared_ptr<arangodb::velocypack::Builder> LogicalCollection::figures() const {
if (ServerState::instance()->isCoordinator()) {
auto builder = std::make_shared<VPackBuilder>();
builder->openObject();

View File

@ -263,7 +263,7 @@ class LogicalCollection {
virtual arangodb::Result updateProperties(velocypack::Slice const&, bool);
/// @brief return the figures for a collection
virtual std::shared_ptr<velocypack::Builder> figures();
virtual std::shared_ptr<velocypack::Builder> figures() const;
/// @brief opens an existing collection
void open(bool ignoreErrors);

View File

@ -23,25 +23,24 @@
#include "Collections.h"
#include "Basics/Common.h"
#include "Basics/LocalTaskQueue.h"
#include "Basics/ReadLocker.h"
#include "Basics/StringUtils.h"
#include "Basics/StringUtils.h"
#include "Basics/VelocyPackHelper.h"
#include "Basics/conversions.h"
#include "Basics/tri-strings.h"
#include "Cluster/ClusterComm.h"
#include "Cluster/ClusterInfo.h"
#include "Cluster/ClusterFeature.h"
#include "Cluster/ClusterInfo.h"
#include "Cluster/ClusterMethods.h"
#include "Cluster/ServerState.h"
#include "GeneralServer/AuthenticationFeature.h"
#include "Indexes/Index.h"
#include "Indexes/IndexFactory.h"
#include "Rest/HttpRequest.h"
#include "RestServer/DatabaseFeature.h"
#include "StorageEngine/EngineSelectorFeature.h"
#include "StorageEngine/StorageEngine.h"
#include "Transaction/Helpers.h"
#include "V8Server/v8-collection.h"
#include "StorageEngine/PhysicalCollection.h"
#include "Transaction/StandaloneContext.h"
#include "Utils/ExecContext.h"
#include "Utils/SingleCollectionTransaction.h"
#include "V8/v8-conv.h"
#include "V8/v8-utils.h"
#include "V8Server/V8Context.h"
#include "V8Server/V8DealerFeature.h"
#include "VocBase/AuthInfo.h"
#include "VocBase/LogicalCollection.h"
#include "VocBase/modes.h"
@ -51,13 +50,12 @@
#include <velocypack/Collection.h>
#include <velocypack/Iterator.h>
#include <velocypack/velocypack-aliases.h>
#include <regex>
using namespace arangodb;
using namespace arangodb::basics;
using namespace arangodb::methods;
void methods::Collections::enumerateCollections(
void Collections::enumerate(
TRI_vocbase_t* vocbase,
std::function<void(LogicalCollection*)> const& func) {
if (ServerState::instance()->isCoordinator()) {
@ -80,27 +78,431 @@ void methods::Collections::enumerateCollections(
}
}
bool methods::Collections::lookupCollection(
TRI_vocbase_t* vocbase, std::string const& collection,
std::function<void(LogicalCollection*)> const& func) {
if (!collection.empty()) {
if (ServerState::instance()->isCoordinator()) {
try {
std::shared_ptr<LogicalCollection> coll =
ClusterInfo::instance()->getCollection(vocbase->name(), collection);
if (coll) {
func(coll.get());
return true;
Result methods::Collections::lookup(TRI_vocbase_t* vocbase,
std::string const& name,
FuncCallback func) {
if (name.empty()) {
return TRI_ERROR_ARANGO_COLLECTION_NOT_FOUND;
}
ExecContext const* exec = ExecContext::CURRENT;
if (ServerState::instance()->isCoordinator()) {
try {
auto coll = ClusterInfo::instance()->getCollection(vocbase->name(), name);
if (coll) {
// check authentication after ensuring the collection exists
if (exec != nullptr &&
!exec->canUseCollection(vocbase->name(), coll->name(), AuthLevel::RO)) {
return Result(TRI_ERROR_FORBIDDEN, "No access to collection '" + name + "'");
}
} catch (...) {
func(coll.get());
return TRI_ERROR_NO_ERROR;
}
} catch (basics::Exception const& ex) {
return Result(ex.code(), ex.what());
} catch (std::exception const& ex) {
return Result(TRI_ERROR_INTERNAL, ex.what());
} catch (...) {
return TRI_ERROR_INTERNAL;
}
return TRI_ERROR_ARANGO_COLLECTION_NOT_FOUND;
}
LogicalCollection* coll = vocbase->lookupCollection(name);
if (coll != nullptr) {
// check authentication after ensuring the collection exists
if (exec != nullptr &&
!exec->canUseCollection(vocbase->name(), coll->name(), AuthLevel::RO)) {
return Result(TRI_ERROR_FORBIDDEN, "No access to collection '" + name + "'");
}
func(coll);
return TRI_ERROR_NO_ERROR;
}
return TRI_ERROR_ARANGO_COLLECTION_NOT_FOUND;
}
Result Collections::create(TRI_vocbase_t* vocbase, std::string const& name,
TRI_col_type_e collectionType,
velocypack::Slice const& properties,
bool createWaitsForSyncReplication,
FuncCallback func) {
if (name.empty()) {
return TRI_ERROR_ARANGO_ILLEGAL_NAME;
} else if (collectionType != TRI_col_type_e::TRI_COL_TYPE_DOCUMENT &&
collectionType != TRI_col_type_e::TRI_COL_TYPE_EDGE) {
return TRI_ERROR_ARANGO_COLLECTION_TYPE_INVALID;
}
ExecContext const* exec = ExecContext::CURRENT;
if (exec != nullptr &&
!exec->canUseDatabase(vocbase->name(), AuthLevel::RW)) {
return Result(TRI_ERROR_FORBIDDEN,
"cannot create collection in " + vocbase->name());
}
TRI_ASSERT(vocbase && !vocbase->isDangling());
TRI_ASSERT(properties.isObject());
/*VPackBuilder defaultProps;
defaultProps.openObject();
defaultProps.add("shardKeys", VPackSlice::emptyObjectSlice());
defaultProps.add("numberOfShards", VPackValue(0));
defaultProps.add("distributeShardsLike", VPackValue(""));
defaultProps.add("avoidServers", VPackSlice::emptyArraySlice());
defaultProps.add("shardKeysisSmart", VPackValue(""));
defaultProps.add("smartGraphAttribute", VPackValue(""));
defaultProps.add("replicationFactor", VPackValue(0));
defaultProps.add("servers", VPackValue(""));
defaultProps.close();*/
VPackBuilder builder;
builder.openObject();
builder.add("type", VPackValue(static_cast<int>(collectionType)));
builder.add("name", VPackValue(name));
builder.close();
VPackBuilder info =
VPackCollection::merge(properties, builder.slice(), false);
VPackSlice const infoSlice = info.slice();
try {
ExecContext const* exe = ExecContext::CURRENT;
AuthenticationFeature* auth = AuthenticationFeature::INSTANCE;
if (ServerState::instance()->isCoordinator()) {
std::unique_ptr<LogicalCollection> col =
ClusterMethods::createCollectionOnCoordinator(
collectionType, vocbase, infoSlice, false,
createWaitsForSyncReplication);
if (!col) {
return Result(TRI_ERROR_INTERNAL, "createCollectionOnCoordinator");
}
// do not grant rights on system collections
// in case of success we grant the creating user RW access
if (name[0] != '_' && exe != nullptr && !exe->isSuperuser()) {
// this should not fail, we can not get here without database RW access
auth->authInfo()->updateUser(
ExecContext::CURRENT->user(), [&](AuthUserEntry& entry) {
entry.grantCollection(vocbase->name(), name, AuthLevel::RW);
});
}
// reload otherwise collection might not be in yet
func(col.release());
} else {
LogicalCollection* coll = vocbase->lookupCollection(collection);
if (coll != nullptr) {
func(coll);
return true;
arangodb::LogicalCollection* col = vocbase->createCollection(infoSlice);
TRI_ASSERT(col != nullptr);
// do not grant rights on system collections
// in case of success we grant the creating user RW access
if (name[0] != '_' && exe != nullptr && !exe->isSuperuser() &&
ServerState::instance()->isSingleServerOrCoordinator()) {
// this should not fail, we can not get here without database RW access
auth->authInfo()->updateUser(
ExecContext::CURRENT->user(), [&](AuthUserEntry& entry) {
entry.grantCollection(vocbase->name(), name, AuthLevel::RW);
});
}
func(col);
}
} catch (basics::Exception const& ex) {
return Result(ex.code(), ex.what());
} catch (std::exception const& ex) {
return Result(TRI_ERROR_INTERNAL, ex.what());
} catch (...) {
return Result(TRI_ERROR_INTERNAL, "cannot create collection");
}
return TRI_ERROR_NO_ERROR;
}
Result Collections::load(TRI_vocbase_t* vocbase, LogicalCollection* coll) {
TRI_ASSERT(coll != nullptr);
if (ServerState::instance()->isCoordinator()) {
#ifdef USE_ENTERPRISE
return ULColCoordinatorEnterprise(coll->dbName(), coll->cid_as_string(),
TRI_VOC_COL_STATUS_LOADED);
#else
auto ci = ClusterInfo::instance();
return ci->setCollectionStatusCoordinator(
coll->dbName(), coll->cid_as_string(), TRI_VOC_COL_STATUS_LOADED);
#endif
}
auto ctx = transaction::StandaloneContext::Create(vocbase);
SingleCollectionTransaction trx(ctx, coll->cid(), AccessMode::Type::READ);
Result res = trx.begin();
if (res.fail()) {
return res;
}
return trx.finish(res);
}
Result Collections::unload(TRI_vocbase_t* vocbase, LogicalCollection* coll) {
if (ServerState::instance()->isCoordinator()) {
#ifdef USE_ENTERPRISE
return ULColCoordinatorEnterprise(vocbase->name(), coll->cid_as_string(),
TRI_VOC_COL_STATUS_UNLOADED);
#else
auto ci = ClusterInfo::instance();
return ci->setCollectionStatusCoordinator(
vocbase->name(), coll->cid_as_string(), TRI_VOC_COL_STATUS_UNLOADED);
#endif
}
return vocbase->unloadCollection(coll, false);
}
Result Collections::properties(LogicalCollection* coll, VPackBuilder& builder) {
TRI_ASSERT(coll != nullptr);
ExecContext const* exec = ExecContext::CURRENT;
if (exec != nullptr) {
bool canRead = exec->canUseCollection(coll->name(), AuthLevel::RO);
if (exec->databaseAuthLevel() == AuthLevel::NONE || !canRead) {
return Result(TRI_ERROR_FORBIDDEN, "cannot access " + coll->name());
}
}
return false;
std::unordered_set<std::string> ignoreKeys{
"allowUserKeys", "cid", "count", "deleted", "id", "indexes", "name",
"path", "planId", "shards", "status", "type", "version"};
std::unique_ptr<SingleCollectionTransaction> trx;
if (!ServerState::instance()->isCoordinator()) {
auto ctx = transaction::StandaloneContext::Create(coll->vocbase());
trx.reset(new SingleCollectionTransaction(ctx, coll->cid(),
AccessMode::Type::READ));
trx->addHint(transaction::Hints::Hint::NO_USAGE_LOCK);
Result res = trx->begin();
// These are only relevant for cluster
ignoreKeys.insert({"distributeShardsLike", "isSmart", "numberOfShards",
"replicationFactor", "shardKeys"});
if (res.fail()) {
return res;
}
}
VPackBuilder props = coll->toVelocyPackIgnore(ignoreKeys, true, false);
TRI_ASSERT(builder.isOpenObject());
builder.add(VPackObjectIterator(props.slice()));
return TRI_ERROR_NO_ERROR;
}
Result Collections::updateProperties(LogicalCollection* coll,
VPackSlice const& props) {
ExecContext const* exec = ExecContext::CURRENT;
if (exec != nullptr) {
bool canModify = exec->canUseCollection(coll->name(), AuthLevel::RW);
if ((exec->databaseAuthLevel() != AuthLevel::RW || !canModify)) {
return TRI_ERROR_FORBIDDEN;
}
}
if (ServerState::instance()->isCoordinator()) {
ClusterInfo* ci = ClusterInfo::instance();
auto info = ci->getCollection(coll->dbName(), coll->cid_as_string());
return info->updateProperties(props, false);
} else {
auto ctx = transaction::StandaloneContext::Create(coll->vocbase());
SingleCollectionTransaction trx(ctx, coll->cid(),
AccessMode::Type::EXCLUSIVE);
Result res = trx.begin();
if (!res.ok()) {
return res;
}
// try to write new parameter to file
bool doSync = DatabaseFeature::DATABASE->forceSyncProperties();
arangodb::Result updateRes = coll->updateProperties(props, doSync);
if (!updateRes.ok()) {
return updateRes;
}
auto physical = coll->getPhysical();
TRI_ASSERT(physical != nullptr);
return physical->persistProperties();
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief helper function to rename collections in _graphs as well
////////////////////////////////////////////////////////////////////////////////
static int RenameGraphCollections(TRI_vocbase_t* vocbase,
std::string const& oldName,
std::string const& newName) {
StringBuffer buffer(true);
buffer.appendText("require('@arangodb/general-graph')._renameCollection(");
buffer.appendJsonEncoded(oldName.c_str(), oldName.size());
buffer.appendChar(',');
buffer.appendJsonEncoded(newName.c_str(), newName.size());
buffer.appendText(");");
V8Context* context = V8DealerFeature::DEALER->enterContext(vocbase, false);
if (context == nullptr) {
LOG_TOPIC(WARN, Logger::FIXME) << "RenameGraphCollections: no V8 context";
return TRI_ERROR_OUT_OF_MEMORY;
}
TRI_DEFER(V8DealerFeature::DEALER->exitContext(context));
auto isolate = context->_isolate;
v8::HandleScope scope(isolate);
TRI_ExecuteJavaScriptString(
isolate, isolate->GetCurrentContext(),
TRI_V8_ASCII_PAIR_STRING(isolate, buffer.c_str(), buffer.length()),
TRI_V8_ASCII_STRING(isolate, "collection rename"), false);
return TRI_ERROR_NO_ERROR;
}
Result Collections::rename(LogicalCollection* coll, std::string const& newName,
bool doOverride) {
if (ServerState::instance()->isCoordinator()) {
// renaming a collection in a cluster is unsupported
return TRI_ERROR_CLUSTER_UNSUPPORTED;
}
if (newName.empty()) {
return Result(TRI_ERROR_BAD_PARAMETER, "<name> must be non-empty");
}
ExecContext const* exec = ExecContext::CURRENT;
if (exec != nullptr) {
if (!exec->canUseDatabase(AuthLevel::RW) ||
!exec->canUseCollection(coll->name(), AuthLevel::RW)) {
return TRI_ERROR_FORBIDDEN;
}
}
std::string const oldName(coll->name());
int res = coll->vocbase()->renameCollection(coll, newName, doOverride);
if (res != TRI_ERROR_NO_ERROR) {
return Result(res, "cannot rename collection");
}
// rename collection inside _graphs as well
return RenameGraphCollections(coll->vocbase(), oldName, newName);
}
#ifndef USE_ENTERPRISE
////////////////////////////////////////////////////////////////////////////////
/// @brief drops a collection, case of a coordinator in a cluster
////////////////////////////////////////////////////////////////////////////////
static Result DropVocbaseColCoordinator(arangodb::LogicalCollection* collection,
bool allowDropSystem) {
if (collection->isSystem() && !allowDropSystem) {
return TRI_ERROR_FORBIDDEN;
}
std::string const databaseName(collection->dbName());
std::string const cid = collection->cid_as_string();
ClusterInfo* ci = ClusterInfo::instance();
std::string errorMsg;
int res = ci->dropCollectionCoordinator(databaseName, cid, errorMsg, 120.0);
if (res != TRI_ERROR_NO_ERROR) {
return Result(res, errorMsg);
}
collection->setStatus(TRI_VOC_COL_STATUS_DELETED);
return TRI_ERROR_NO_ERROR;
}
#endif
Result Collections::drop(TRI_vocbase_t* vocbase, LogicalCollection* coll,
bool allowDropSystem, double timeout) {
ExecContext const* exec = ExecContext::CURRENT;
if (exec != nullptr &&
(!exec->canUseDatabase(vocbase->name(), AuthLevel::RW) ||
!exec->canUseCollection(coll->name(), AuthLevel::RW))) {
return Result(TRI_ERROR_FORBIDDEN,
"Insufficient rights to drop "
"collection " +
coll->name());
}
std::string const dbname = coll->dbName();
std::string const collName = coll->name();
Result res;
// If we are a coordinator in a cluster, we have to behave differently:
if (ServerState::instance()->isCoordinator()) {
#ifdef USE_ENTERPRISE
res = DropColCoordinatorEnterprise(coll, allowDropSystem);
#else
res = DropVocbaseColCoordinator(coll, allowDropSystem);
#endif
} else {
int r = coll->vocbase()->dropCollection(coll, allowDropSystem, timeout);
if (r != TRI_ERROR_NO_ERROR) {
res.reset(r, "cannot drop collection");
}
}
if (res.ok() && ServerState::instance()->isSingleServerOrCoordinator()) {
AuthenticationFeature* auth = AuthenticationFeature::INSTANCE;
auth->authInfo()->enumerateUsers([&](AuthUserEntry& entry) {
entry.removeCollection(dbname, collName);
});
}
return res;
}
Result Collections::warmup(TRI_vocbase_t* vocbase, LogicalCollection* coll) {
if (ServerState::instance()->isCoordinator()) {
std::string const cid = coll->cid_as_string();
return warmupOnCoordinator(vocbase->name(), cid);
}
auto ctx = transaction::StandaloneContext::Create(vocbase);
SingleCollectionTransaction trx(ctx, coll->cid(), AccessMode::Type::READ);
Result res = trx.begin();
if (res.fail()) {
return res;
}
auto idxs = coll->getIndexes();
auto queue = std::make_shared<basics::LocalTaskQueue>();
for (auto& idx : idxs) {
idx->warmup(&trx, queue);
}
queue->dispatchAndWait();
if (queue->status() == TRI_ERROR_NO_ERROR) {
res = trx.commit();
} else {
return queue->status();
}
return res;
}
Result Collections::revisionId(TRI_vocbase_t* vocbase,
LogicalCollection* coll,
TRI_voc_rid_t& rid) {
TRI_ASSERT(coll != nullptr);
std::string const databaseName(coll->dbName());
std::string const cid = coll->cid_as_string();
if (ServerState::instance()->isCoordinator()) {
return revisionOnCoordinator(databaseName, cid, rid);
} else {
auto ctx = transaction::StandaloneContext::Create(vocbase);
SingleCollectionTransaction trx(ctx, coll->cid(),
AccessMode::Type::READ);
Result res = trx.begin();
if (res.fail()) {
THROW_ARANGO_EXCEPTION(res);
}
rid = coll->revision(&trx);
return TRI_ERROR_NO_ERROR;
}
}

View File

@ -23,28 +23,61 @@
#ifndef ARANGOD_VOC_BASE_API_COLLECTIONS_H
#define ARANGOD_VOC_BASE_API_COLLECTIONS_H 1
#include <velocypack/Builder.h>
#include <velocypack/Slice.h>
#include "Basics/Result.h"
#include "VocBase/voc-types.h"
#include "VocBase/vocbase.h"
#include <velocypack/Builder.h>
#include <velocypack/Slice.h>
#include <functional>
struct TRI_vocbase_t;
namespace arangodb {
class LogicalCollection;
namespace methods {
/// Common code for collection REST handler and v8-collections
struct Collections {
static void enumerateCollections(
TRI_vocbase_t* vocbase, std::function<void(LogicalCollection*)> const&);
static bool lookupCollection(TRI_vocbase_t* vocbase,
std::string const& collection,
std::function<void(LogicalCollection*)> const&);
};
}
}
typedef std::function<void(LogicalCollection*)> const& FuncCallback;
static void enumerate(TRI_vocbase_t* vocbase, FuncCallback);
/// @brief lookup a collection in vocbase or clusterinfo.
static Result lookup(TRI_vocbase_t* vocbase, std::string const& collection,
FuncCallback);
/// Create collection, ownership of collection in callback is
/// transferred to callee
static Result create(TRI_vocbase_t* vocbase, std::string const& name,
TRI_col_type_e collectionType,
velocypack::Slice const& properties,
bool createWaitsForSyncReplication, FuncCallback);
static Result load(TRI_vocbase_t* vocbase, LogicalCollection* coll);
static Result unload(TRI_vocbase_t* vocbase, LogicalCollection* coll);
static Result properties(LogicalCollection* coll, velocypack::Builder&);
static Result updateProperties(LogicalCollection* coll,
velocypack::Slice const&);
static Result rename(LogicalCollection* coll, std::string const& newName,
bool doOverride);
static Result drop(TRI_vocbase_t* vocbase, LogicalCollection* coll,
bool allowDropSystem, double timeout);
static Result warmup(TRI_vocbase_t* vocbase,
LogicalCollection* coll);
static Result revisionId(TRI_vocbase_t* vocbase, LogicalCollection* coll,
TRI_voc_rid_t& rid);
};
#ifdef USE_ENTERPRISE
Result ULColCoordinatorEnterprise(std::string const& databaseName,
std::string const& collectionCID,
TRI_vocbase_col_status_e status);
Result DropColCoordinatorEnterprise(LogicalCollection* collection,
bool allowDropSystem);
#endif
}
}
#endif

View File

@ -835,7 +835,7 @@ void TRI_vocbase_t::inventory(
if (!nameFilter(collection)) {
continue;
}
StorageEngine* engine = EngineSelectorFeature::ENGINE;
engine->getCollectionInfo(collection->vocbase(), collection->cid(),
result, true, maxTick);

View File

@ -1,731 +0,0 @@
/* jshint strict: false */
/*global ArangoClusterInfo */
// //////////////////////////////////////////////////////////////////////////////
// / @brief querying and managing collections
// /
// / @file
// /
// / DISCLAIMER
// /
// / Copyright 2014 ArangoDB 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 Achim Brandt
// / @author Copyright 2014, ArangoDB GmbH, Cologne, Germany
// / @author Copyright 2012, triAGENS GmbH, Cologne, Germany
// //////////////////////////////////////////////////////////////////////////////
var arangodb = require('@arangodb');
var actions = require('@arangodb/actions');
var cluster = require('@arangodb/cluster');
var errors = require('internal').errors;
// //////////////////////////////////////////////////////////////////////////////
// / @brief return a prefixed URL
// //////////////////////////////////////////////////////////////////////////////
function databasePrefix (req, url) {
// location response (e.g. /_db/dbname/_api/collection/xyz)
return '/_db/' + encodeURIComponent(arangodb.db._name()) + url;
}
// //////////////////////////////////////////////////////////////////////////////
// / @brief collection representation
// //////////////////////////////////////////////////////////////////////////////
function collectionRepresentation(collection, showProperties, showCount, showFigures) {
var result = {};
result.id = collection._id;
result.name = collection.name();
result.isSystem = (result.name.charAt(0) === '_');
if (showProperties) {
var properties = collection.properties();
result.doCompact = properties.doCompact;
result.isVolatile = properties.isVolatile;
result.journalSize = properties.journalSize;
result.keyOptions = properties.keyOptions;
result.waitForSync = properties.waitForSync;
result.indexBuckets = properties.indexBuckets;
if (properties.cacheEnabled) {
result.cacheEnabled = properties.cacheEnabled;
}
if (cluster.isCoordinator()) {
result.avoidServers = properties.avoidServers;
result.numberOfShards = properties.numberOfShards;
result.replicationFactor = properties.replicationFactor;
result.avoidServers = properties.avoidServers;
result.distributeShardsLike = properties.distributeShardsLike;
result.shardKeys = properties.shardKeys;
}
}
if (showCount) {
// show either the count value as a number or the detailed shard counts
result.count = collection.count(showCount === 'details');
}
if (showFigures) {
var figures = collection.figures();
if (figures) {
result.figures = figures;
}
}
result.globallyUniqueId = collection.globallyUniqueId();
result.status = collection.status();
result.type = collection.type();
return result;
}
// //////////////////////////////////////////////////////////////////////////////
// / @brief helper to parse arguments for creating collections
// //////////////////////////////////////////////////////////////////////////////
function parseBodyForCreateCollection (req, res) {
var body = actions.getJsonBody(req, res);
if (body === undefined) {
return { bodyIsEmpty: true };
}
var r = {};
if (!body.hasOwnProperty('name')) {
r.name = '';
} else {
r.name = body.name;
}
r.parameters = { waitForSync: require('internal').options()['database.wait-for-sync']};
r.type = arangodb.ArangoCollection.TYPE_DOCUMENT;
if (body.hasOwnProperty('doCompact')) {
r.parameters.doCompact = body.doCompact;
}
if (body.hasOwnProperty('isSystem')) {
r.parameters.isSystem = (body.isSystem && r.name[0] === '_');
}
if (body.hasOwnProperty('id')) {
r.parameters.id = body.id;
}
if (body.hasOwnProperty('isVolatile')) {
r.parameters.isVolatile = body.isVolatile;
}
if (body.hasOwnProperty('journalSize')) {
r.parameters.journalSize = body.journalSize;
}
if (body.hasOwnProperty('indexBuckets')) {
r.parameters.indexBuckets = body.indexBuckets;
}
if (body.hasOwnProperty('keyOptions')) {
r.parameters.keyOptions = body.keyOptions;
}
if (body.hasOwnProperty('type')) {
r.type = body.type;
}
if (body.hasOwnProperty('waitForSync')) {
r.parameters.waitForSync = body.waitForSync;
}
if (body.hasOwnProperty('cacheEnabled')) {
r.parameters.cacheEnabled = body.cacheEnabled;
}
if (cluster.isCoordinator()) {
if (body.hasOwnProperty('shardKeys')) {
r.parameters.shardKeys = body.shardKeys || { };
}
if (body.hasOwnProperty('numberOfShards')) {
r.parameters.numberOfShards = body.numberOfShards || 0;
}
if (body.hasOwnProperty('distributeShardsLike')) {
r.parameters.distributeShardsLike = body.distributeShardsLike || '';
}
if (body.hasOwnProperty('avoidServers')) {
r.parameters.avoidServers = body.avoidServers || [];
}
if (body.hasOwnProperty('isSmart')) {
r.parameters.isSmart = body.isSmart || '';
}
if (body.hasOwnProperty('smartGraphAttribute')) {
r.parameters.smartGraphAttribute = body.smartGraphAttribute || '';
}
if (body.hasOwnProperty('replicationFactor')) {
r.parameters.replicationFactor = body.replicationFactor || '';
}
if (body.hasOwnProperty('servers')) {
r.parameters.servers = body.servers || '';
}
}
return r;
}
// //////////////////////////////////////////////////////////////////////////////
// / @brief was docuBlock JSF_post_api_collection
// //////////////////////////////////////////////////////////////////////////////
function post_api_collection (req, res) {
var r = parseBodyForCreateCollection(req, res);
if (r.bodyIsEmpty) {
return; // error in JSON, is already reported
}
if (r.name === '') {
actions.resultBad(req, res, arangodb.ERROR_ARANGO_ILLEGAL_NAME,
'name must be non-empty');
return;
}
try {
var options = {};
if (req.parameters.hasOwnProperty('waitForSyncReplication')) {
var value = req.parameters.waitForSyncReplication.toLowerCase();
if (value === 'true' || value === 'yes' || value === 'on' || value === 'y' || value === '1') {
options.waitForSyncReplication = true;
} else {
options.waitForSyncReplication = false;
}
}
var collection;
if (typeof (r.type) === 'string') {
if (r.type.toLowerCase() === 'edge' || r.type === '3') {
r.type = arangodb.ArangoCollection.TYPE_EDGE;
}
}
if (r.type === arangodb.ArangoCollection.TYPE_EDGE) {
collection = arangodb.db._createEdgeCollection(r.name, r.parameters, options);
} else {
collection = arangodb.db._createDocumentCollection(r.name, r.parameters, options);
}
var result = {};
result.id = collection._id;
result.name = collection.name();
result.waitForSync = r.parameters.waitForSync || false;
result.isVolatile = r.parameters.isVolatile || false;
result.isSystem = r.parameters.isSystem || false;
result.status = collection.status();
result.type = collection.type();
result.keyOptions = collection.keyOptions;
if (r.parameters.cacheEnabled !== undefined) {
result.cacheEnabled = r.parameters.cacheEnabled;
}
if (cluster.isCoordinator()) {
result.shardKeys = collection.shardKeys;
result.numberOfShards = collection.numberOfShards;
result.distributeShardsLike = collection.distributeShardsLike || '';
}
var headers = {
location: databasePrefix(req, '/_api/collection/' + result.name)
};
actions.resultOk(req, res, actions.HTTP_OK, result, headers);
} catch (err) {
actions.resultException(req, res, err, undefined, false);
}
}
// //////////////////////////////////////////////////////////////////////////////
// / @brief was docuBlock JSF_get_api_collections
// //////////////////////////////////////////////////////////////////////////////
function get_api_collections (req, res) {
var excludeSystem;
var collections = arangodb.db._collections();
excludeSystem = false;
if (req.parameters.hasOwnProperty('excludeSystem')) {
var value = req.parameters.excludeSystem.toLowerCase();
if (value === 'true' || value === 'yes' || value === 'on' || value === 'y' || value === '1') {
excludeSystem = true;
}
}
var list = [];
for (var i = 0; i < collections.length; ++i) {
var collection = collections[i];
var rep = collectionRepresentation(collection);
// include system collections or exclude them?
if (!excludeSystem || rep.name.substr(0, 1) !== '_') {
list.push(rep);
}
}
actions.resultOk(req, res, actions.HTTP_OK, { result: list });
}
// //////////////////////////////////////////////////////////////////////////////
// / @brief was docuBlock JSA_get_api_collection_name
// //////////////////////////////////////////////////////////////////////////////
// //////////////////////////////////////////////////////////////////////////////
// / @brief was docuBlock JSA_get_api_collection_properties
// //////////////////////////////////////////////////////////////////////////////
// //////////////////////////////////////////////////////////////////////////////
// / @brief was docuBlock JSA_get_api_collection_count
// //////////////////////////////////////////////////////////////////////////////
// //////////////////////////////////////////////////////////////////////////////
// / @brief was docuBlock JSA_get_api_collection_figures
// //////////////////////////////////////////////////////////////////////////////
// //////////////////////////////////////////////////////////////////////////////
// / @brief was docuBlock JSA_get_api_collection_revision
// //////////////////////////////////////////////////////////////////////////////
// //////////////////////////////////////////////////////////////////////////////
// / @brief was docuBlock JSA_get_api_collection_checksum
// //////////////////////////////////////////////////////////////////////////////
function get_api_collection (req, res) {
var name;
var result;
var sub;
// .............................................................................
// /_api/collection
// .............................................................................
if (req.suffix.length === 0 && req.parameters.id === undefined) {
get_api_collections(req, res);
return;
}
// .............................................................................
// /_api/collection/<name>
// .............................................................................
name = req.suffix[0];
var collection = arangodb.db._collection(name);
if (collection === null) {
actions.collectionNotFound(req, res, name);
return;
}
var headers;
// .............................................................................
// /_api/collection/<name>
// .............................................................................
if (req.suffix.length === 1) {
result = collectionRepresentation(collection, false, false, false);
headers = {
location: databasePrefix(req, '/_api/collection/' + collection.name())
};
actions.resultOk(req, res, actions.HTTP_OK, result, headers);
return;
}
if (req.suffix.length === 2) {
sub = req.suffix[1];
// .............................................................................
// /_api/collection/<identifier>/checksum
// .............................................................................
if (sub === 'checksum') {
var withRevisions = false;
var withData = false;
var value;
if (req.parameters.hasOwnProperty('withRevisions')) {
value = req.parameters.withRevisions.toLowerCase();
if (value === 'true' || value === 'yes' || value === 'on' || value === 'y' || value === '1') {
withRevisions = true;
}
}
if (req.parameters.hasOwnProperty('withData')) {
value = req.parameters.withData.toLowerCase();
if (value === 'true' || value === 'yes' || value === 'on' || value === 'y' || value === '1') {
withData = true;
}
}
result = collectionRepresentation(collection, false, false, false);
var checksum = collection.checksum(withRevisions, withData);
result.checksum = checksum.checksum;
result.revision = checksum.revision;
actions.resultOk(req, res, actions.HTTP_OK, result);
}
// .............................................................................
// /_api/collection/<identifier>/figures
// .............................................................................
else if (sub === 'figures') {
result = collectionRepresentation(collection, true, true, true);
headers = {
location: databasePrefix(req, '/_api/collection/' + collection.name() + '/figures')
};
actions.resultOk(req, res, actions.HTTP_OK, result, headers);
}
// .............................................................................
// /_api/collection/<identifier>/count
// .............................................................................
else if (sub === 'count') {
// show either the count value as a number or the detailed shard counts
if (req.parameters.details === 'true') {
result = collectionRepresentation(collection, true, 'details', false);
} else {
result = collectionRepresentation(collection, true, true, false);
}
headers = {
location: databasePrefix(req, '/_api/collection/' + collection.name() + '/count')
};
actions.resultOk(req, res, actions.HTTP_OK, result, headers);
}
// .............................................................................
// /_api/collection/<identifier>/properties
// .............................................................................
else if (sub === 'properties') {
result = collectionRepresentation(collection, true, false, false);
headers = {
location: databasePrefix(req, '/_api/collection/' + collection.name() + '/properties')
};
actions.resultOk(req, res, actions.HTTP_OK, result, headers);
}
// .............................................................................
// /_api/collection/<identifier>/revision
// .............................................................................
else if (sub === 'revision') {
result = collectionRepresentation(collection, false, false, false);
result.revision = collection.revision();
actions.resultOk(req, res, actions.HTTP_OK, result);
}
else if (sub === 'shards') {
result = collectionRepresentation(collection, false, false, false);
result.shards = Object.keys(ArangoClusterInfo.getCollectionInfo(arangodb.db._name(), collection.name()).shardShorts);
actions.resultOk(req, res, actions.HTTP_OK, result);
} else {
actions.resultNotFound(req, res, arangodb.ERROR_HTTP_NOT_FOUND,
"expecting one of the resources 'checksum', 'count',"
+ " 'figures', 'properties', 'revision', 'shards'");
}
} else {
actions.resultBad(req, res, arangodb.ERROR_HTTP_BAD_PARAMETER,
'expect GET /_api/collection/<collection-name>/<method>');
}
}
// //////////////////////////////////////////////////////////////////////////////
// / @brief was docuBlock JSF_put_api_collection_load
// //////////////////////////////////////////////////////////////////////////////
function put_api_collection_load (req, res, collection) {
try {
collection.load();
var showCount = true;
var body = actions.getJsonBody(req, res);
if (body && body.hasOwnProperty('count')) {
showCount = body.count;
}
var result = collectionRepresentation(collection, false, showCount, false);
actions.resultOk(req, res, actions.HTTP_OK, result);
} catch (err) {
actions.resultException(req, res, err, undefined, false);
}
}
// //////////////////////////////////////////////////////////////////////////////
// / @brief was docuBlock JSF_put_api_collection_loadIndexesIntoMemory
// //////////////////////////////////////////////////////////////////////////////
function put_api_collection_load_indexes_in_memory (req, res, collection) {
try {
// Load all index values into Memory
collection.loadIndexesIntoMemory();
actions.resultOk(req, res, actions.HTTP_OK, { result: true });
} catch (err) {
actions.resultException(req, res, err, undefined, false);
}
}
// //////////////////////////////////////////////////////////////////////////////
// / @brief was docuBlock JSF_put_api_collection_unload
// //////////////////////////////////////////////////////////////////////////////
function put_api_collection_unload (req, res, collection) {
try {
if (req.parameters.hasOwnProperty('flush')) {
var value = req.parameters.flush.toLowerCase();
if (value === 'true' || value === 'yes' || value === 'on' || value === 'y' || value === '1') {
if (collection.status() === 3 /* loaded */ &&
collection.figures().uncollectedLogfileEntries > 0) {
// flush WAL so uncollected logfile entries can get collected
require('internal').wal.flush();
}
}
}
// then unload
collection.unload();
var result = collectionRepresentation(collection);
actions.resultOk(req, res, actions.HTTP_OK, result);
} catch (err) {
actions.resultException(req, res, err, undefined, false);
}
}
// //////////////////////////////////////////////////////////////////////////////
// / @brief was docuBlock JSF_put_api_collection_truncate
// //////////////////////////////////////////////////////////////////////////////
function put_api_collection_truncate (req, res, collection) {
let waitForSync = false;
if (req.parameters.hasOwnProperty('waitForSync')) {
let value = req.parameters.waitForSync.toLowerCase();
if (value === 'true' || value === 'yes' ||
value === 'on' || value === 'y' || value === '1') {
waitForSync = true;
}
}
let isSynchronousReplicationFrom = "";
if (req.parameters.hasOwnProperty('isSynchronousReplication')) {
isSynchronousReplicationFrom = req.parameters.isSynchronousReplication;
}
try {
collection.truncate(waitForSync, isSynchronousReplicationFrom);
var result = collectionRepresentation(collection);
actions.resultOk(req, res, actions.HTTP_OK, result);
} catch (err) {
actions.resultException(req, res, err, undefined, false);
}
}
// //////////////////////////////////////////////////////////////////////////////
// / @brief was docuBlock JSF_put_api_collection_properties
// //////////////////////////////////////////////////////////////////////////////
function put_api_collection_properties (req, res, collection) {
var body = actions.getJsonBody(req, res);
if (body === undefined) {
return;
}
try {
collection.properties(body);
var result = collectionRepresentation(collection, true);
actions.resultOk(req, res, actions.HTTP_OK, result);
} catch (err) {
actions.resultException(req, res, err, undefined, false);
}
}
// //////////////////////////////////////////////////////////////////////////////
// / @brief was docuBlock JSF_put_api_collection_rename
// //////////////////////////////////////////////////////////////////////////////
function put_api_collection_rename (req, res, collection) {
var body = actions.getJsonBody(req, res);
if (body === undefined) {
return;
}
if (!body.hasOwnProperty('name')) {
actions.resultBad(req, res, arangodb.ERROR_ARANGO_ILLEGAL_NAME,
'name must be non-empty');
return;
}
var name = body.name;
try {
collection.rename(name);
var result = collectionRepresentation(collection);
actions.resultOk(req, res, actions.HTTP_OK, result);
} catch (err) {
actions.resultException(req, res, err, undefined, false);
}
}
// //////////////////////////////////////////////////////////////////////////////
// / @brief was docuBlock JSF_put_api_collection_rotate
// //////////////////////////////////////////////////////////////////////////////
function put_api_collection_rotate (req, res, collection) {
try {
collection.rotate();
actions.resultOk(req, res, actions.HTTP_OK, { result: true });
} catch (err) {
actions.resultException(req, res, err, undefined, false);
}
}
// //////////////////////////////////////////////////////////////////////////////
// / @brief changes a collection
// //////////////////////////////////////////////////////////////////////////////
function put_api_collection (req, res) {
if (req.suffix.length !== 2) {
actions.resultBad(req, res, arangodb.ERROR_HTTP_BAD_PARAMETER,
'expected PUT /_api/collection/<collection-name>/<action>');
return;
}
var name = req.suffix[0];
var collection = arangodb.db._collection(name);
if (collection === null) {
actions.collectionNotFound(req, res, name);
return;
}
var sub = req.suffix[1];
if (sub === 'load') {
put_api_collection_load(req, res, collection);
} else if (sub === 'unload') {
put_api_collection_unload(req, res, collection);
collection = null;
// run garbage collection once in all threads
require('internal').executeGlobalContextFunction('collectGarbage');
} else if (sub === 'truncate') {
put_api_collection_truncate(req, res, collection);
} else if (sub === 'properties') {
put_api_collection_properties(req, res, collection);
} else if (sub === 'rename') {
put_api_collection_rename(req, res, collection);
} else if (sub === 'rotate') {
put_api_collection_rotate(req, res, collection);
} else if (sub === 'loadIndexesIntoMemory') {
put_api_collection_load_indexes_in_memory(req, res, collection);
} else {
actions.resultNotFound(req, res, arangodb.ERROR_HTTP_NOT_FOUND,
"expecting one of the actions 'load', 'unload',"
+ " 'truncate', 'properties', 'rename', 'loadIndexesIntoMemory'");
}
}
// //////////////////////////////////////////////////////////////////////////////
// / @brief was docuBlock JSF_delete_api_collection
// //////////////////////////////////////////////////////////////////////////////
function delete_api_collection (req, res) {
if (req.suffix.length !== 1) {
actions.resultBad(req, res, arangodb.ERROR_HTTP_BAD_PARAMETER,
'expected DELETE /_api/collection/<collection-name>');
} else {
var name = req.suffix[0];
var collection = arangodb.db._collection(name);
if (collection === null) {
actions.collectionNotFound(req, res, name);
} else {
try {
var result = {
id: collection._id
};
var options = {};
if (req.parameters.hasOwnProperty('isSystem')) {
// are we allowed to drop system collections?
var value = req.parameters.isSystem.toLowerCase();
if (value === 'true' || value === 'yes' || value === 'on' || value === 'y' || value === '1') {
options.isSystem = true;
}
}
collection.drop(options);
actions.resultOk(req, res, actions.HTTP_OK, result);
} catch (err) {
actions.resultException(req, res, err, undefined, false);
}
}
}
}
// //////////////////////////////////////////////////////////////////////////////
// / @brief handles a collection request
// //////////////////////////////////////////////////////////////////////////////
actions.defineHttp({
url: '_api/collection',
callback: function (req, res) {
try {
if (req.requestType === actions.GET) {
get_api_collection(req, res);
} else if (req.requestType === actions.DELETE) {
delete_api_collection(req, res);
} else if (req.requestType === actions.POST) {
post_api_collection(req, res);
} else if (req.requestType === actions.PUT) {
put_api_collection(req, res);
} else {
actions.resultUnsupported(req, res);
}
} catch (err) {
actions.resultException(req, res, err, undefined, false);
}
}
});

View File

@ -141,7 +141,8 @@ describe('User Rights Management', () => {
col.truncate();
success = true;
} catch (e) {
expect(e.errorNum).to.equal(errors.ERROR_FORBIDDEN.code, `${name} getting an unexpected error code`);
const err = colLevel['ro'].has(name) ? errors.ERROR_ARANGO_READ_ONLY : errors.ERROR_FORBIDDEN;
expect(e.errorNum).to.equal(err.code, `${name} getting an unexpected error code`);
}
expect(success).to.equal(false, `${name} succeeded with truncate without getting an error (insufficent rights)`);
expect(rootCount()).to.equal(6, `${name} could not truncate the collection with sufficient rights`);

View File

@ -105,6 +105,7 @@ var findOrCreateCollectionByName = function (name, type, noCreate, options) {
err2.errorMessage = name + ' cannot be used as relation. It is not an edge collection';
throw err2;
}
checkROPermission(name);
return res;
};
@ -590,6 +591,24 @@ var checkRWPermission = function (c) {
}
};
var checkROPermission = function(c) {
if (!users.isAuthActive()) {
return;
}
let user = users.currentUser();
if (user) {
let p = users.permission(user, db._name(), c);
var err = new ArangoError();
if (p === 'none') {
//print(`Denied ${user} access to ${db._name()}/${c}`);
err.errorNum = arangodb.errors.ERROR_FORBIDDEN.code;
err.errorMessage = arangodb.errors.ERROR_FORBIDDEN.message;
throw err;
}
}
};
// //////////////////////////////////////////////////////////////////////////////
// / @brief internal function for editing edge definitions
// //////////////////////////////////////////////////////////////////////////////

View File

@ -228,3 +228,38 @@ void GeneralRequest::addSuffix(std::string&& part) {
// part will not be URL-decoded here!
_suffixes.emplace_back(std::move(part));
}
// needs to be here because of a gcc bug with templates and namespaces
// https://stackoverflow.com/a/25594741/1473569
namespace arangodb {
template <>
bool GeneralRequest::parsedValue(std::string const& key, bool valueNotFound) {
bool found = false;
std::string const& val = value(key, found);
if (found) {
return StringUtils::boolean(val);
}
return valueNotFound;
}
template <>
uint64_t GeneralRequest::parsedValue(std::string const& key, uint64_t valueNotFound) {
bool found = false;
std::string const& val = value(key, found);
if (found) {
return StringUtils::uint64(val);
}
return valueNotFound;
}
template <>
double GeneralRequest::parsedValue(std::string const& key, double valueNotFound) {
bool found = false;
std::string const& val = value(key, found);
if (found) {
return StringUtils::doubleDecimal(val);
}
return valueNotFound;
}
}

View File

@ -176,6 +176,9 @@ class GeneralRequest {
virtual std::string const& value(std::string const& key) const = 0;
virtual std::string const& value(std::string const& key,
bool& found) const = 0;
template <typename T>
T parsedValue(std::string const& key, T valueNotFound);
virtual std::unordered_map<std::string, std::string> values() const = 0;
virtual std::unordered_map<std::string, std::vector<std::string>>
arrayValues() const = 0;

View File

@ -321,6 +321,7 @@ rest::ResponseCode GeneralResponse::responseCode(int code) {
case TRI_ERROR_ARANGO_VALIDATION_FAILED:
case TRI_ERROR_ARANGO_ATTRIBUTE_PARSER_FAILED:
case TRI_ERROR_ARANGO_CROSS_COLLECTION_REQUEST:
case TRI_ERROR_ARANGO_ILLEGAL_NAME:
case TRI_ERROR_ARANGO_INDEX_HANDLE_BAD:
case TRI_ERROR_ARANGO_DOCUMENT_TOO_LARGE:
case TRI_ERROR_QUERY_PARSE: