mirror of https://gitee.com/bigwinds/arangodb
Replacing /_api/collection with RestHandler (#3543)
This commit is contained in:
parent
14bc59317b
commit
64e9377c05
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -201,6 +201,7 @@ void MMFilesRestExportHandler::createCursor() {
|
|||
parseVelocyPackBody(parseSuccess);
|
||||
|
||||
if (!parseSuccess) {
|
||||
// error message generated in parseVelocyPackBody
|
||||
return;
|
||||
}
|
||||
VPackSlice body = parsedBody.get()->slice();
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -54,5 +54,5 @@ void RestAdminRoutingHandler::reloadRouting() {
|
|||
return;
|
||||
}
|
||||
|
||||
generateOk();
|
||||
resetResponse(rest::ResponseCode::NO_CONTENT);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -416,6 +416,7 @@ void RestCursorHandler::createCursor() {
|
|||
parseVelocyPackBody(parseSuccess);
|
||||
|
||||
if (!parseSuccess) {
|
||||
// error message generated in parseVelocyPackBody
|
||||
return;
|
||||
}
|
||||
VPackSlice body = parsedBody.get()->slice();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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] == '_'));
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -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
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -146,7 +146,7 @@ void RocksDBRestWalHandler::flush() {
|
|||
res = flushWalOnAllDBServers(waitForSync, waitForCollector);
|
||||
} else {
|
||||
if (waitForSync) {
|
||||
static_cast<RocksDBEngine*>(EngineSelectorFeature::ENGINE)->syncWal();
|
||||
EngineSelectorFeature::ENGINE->flushWal();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
});
|
|
@ -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`);
|
||||
|
|
|
@ -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
|
||||
// //////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue