1
0
Fork 0
arangodb/arangod/V8Server/v8-vocindex.cpp

1211 lines
41 KiB
C++

////////////////////////////////////////////////////////////////////////////////
/// DISCLAIMER
///
/// Copyright 2014-2016 ArangoDB GmbH, Cologne, Germany
/// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is ArangoDB GmbH, Cologne, Germany
///
/// @author Dr. Frank Celler
////////////////////////////////////////////////////////////////////////////////
#include "v8-vocindex.h"
#include "Basics/conversions.h"
#include "Basics/ReadLocker.h"
#include "Basics/StringUtils.h"
#include "Basics/tri-strings.h"
#include "Basics/VelocyPackHelper.h"
#include "Cluster/ClusterInfo.h"
#include "Cluster/ClusterMethods.h"
#include "FulltextIndex/fulltext-index.h"
#include "Indexes/EdgeIndex.h"
#include "Indexes/Index.h"
#include "Indexes/PrimaryIndex.h"
#include "Utils/Events.h"
#include "Utils/SingleCollectionTransaction.h"
#include "Utils/V8TransactionContext.h"
#include "V8/v8-conv.h"
#include "V8/v8-globals.h"
#include "V8/v8-utils.h"
#include "V8/v8-vpack.h"
#include "V8Server/v8-collection.h"
#include "V8Server/v8-vocbase.h"
#include "V8Server/v8-vocbaseprivate.h"
#include "VocBase/modes.h"
#include "VocBase/LogicalCollection.h"
#include "Indexes/RocksDBIndex.h"
#include <velocypack/Builder.h>
#include <velocypack/Iterator.h>
#include <velocypack/velocypack-aliases.h>
using namespace arangodb;
using namespace arangodb::basics;
using namespace arangodb::rest;
////////////////////////////////////////////////////////////////////////////////
/// @brief extract the unique flag from the data
////////////////////////////////////////////////////////////////////////////////
static bool ExtractBoolFlag(v8::Isolate* isolate,
v8::Handle<v8::Object> const obj,
v8::Handle<v8::String> name, bool defaultValue) {
// extract unique flag
if (obj->Has(name)) {
return TRI_ObjectToBoolean(obj->Get(name));
}
return defaultValue;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief checks if argument is an index identifier
////////////////////////////////////////////////////////////////////////////////
static bool IsIndexHandle(v8::Handle<v8::Value> const arg,
std::string& collectionName, TRI_idx_iid_t& iid) {
TRI_ASSERT(collectionName.empty());
TRI_ASSERT(iid == 0);
if (arg->IsNumber()) {
// numeric index id
iid = (TRI_idx_iid_t)arg->ToNumber()->Value();
return true;
}
if (!arg->IsString()) {
return false;
}
v8::String::Utf8Value str(arg);
if (*str == nullptr) {
return false;
}
size_t split;
if (arangodb::Index::validateHandle(*str, &split)) {
collectionName = std::string(*str, split);
iid = StringUtils::uint64(*str + split + 1, str.length() - split - 1);
return true;
}
if (arangodb::Index::validateId(*str)) {
iid = StringUtils::uint64(*str, str.length());
return true;
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief returns the index representation
////////////////////////////////////////////////////////////////////////////////
static v8::Handle<v8::Value> IndexRep(v8::Isolate* isolate,
std::string const& collectionName,
VPackSlice const& idx) {
v8::EscapableHandleScope scope(isolate);
TRI_ASSERT(!idx.isNone());
v8::Handle<v8::Object> rep = TRI_VPackToV8(isolate, idx)->ToObject();
std::string iid = TRI_ObjectToString(rep->Get(TRI_V8_ASCII_STRING("id")));
std::string const id = collectionName + TRI_INDEX_HANDLE_SEPARATOR_STR + iid;
rep->Set(TRI_V8_ASCII_STRING("id"), TRI_V8_STD_STRING(id));
return scope.Escape<v8::Value>(rep);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief process the fields list and add them to the json
////////////////////////////////////////////////////////////////////////////////
static int ProcessIndexFields(v8::Isolate* isolate,
v8::Handle<v8::Object> const obj,
VPackBuilder& builder, int numFields,
bool create) {
v8::HandleScope scope(isolate);
std::set<std::string> fields;
v8::Handle<v8::String> fieldsString = TRI_V8_ASCII_STRING("fields");
if (obj->Has(fieldsString) && obj->Get(fieldsString)->IsArray()) {
// "fields" is a list of fields
v8::Handle<v8::Array> fieldList =
v8::Handle<v8::Array>::Cast(obj->Get(fieldsString));
uint32_t const n = fieldList->Length();
for (uint32_t i = 0; i < n; ++i) {
if (!fieldList->Get(i)->IsString()) {
return TRI_ERROR_BAD_PARAMETER;
}
std::string const f = TRI_ObjectToString(fieldList->Get(i));
if (f.empty() || (create && f == StaticStrings::IdString)) {
// accessing internal attributes is disallowed
return TRI_ERROR_BAD_PARAMETER;
}
if (fields.find(f) != fields.end()) {
// duplicate attribute name
return TRI_ERROR_BAD_PARAMETER;
}
fields.insert(f);
}
}
if (fields.empty() || (numFields > 0 && (int)fields.size() != numFields)) {
return TRI_ERROR_BAD_PARAMETER;
}
try {
builder.add(VPackValue("fields"));
int res = TRI_V8ToVPack(isolate, builder,
obj->Get(TRI_V8_ASCII_STRING("fields")), false);
if (res != TRI_ERROR_NO_ERROR) {
return res;
}
} catch (...) {
return TRI_ERROR_OUT_OF_MEMORY;
}
return TRI_ERROR_NO_ERROR;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief process the geojson flag and add it to the json
////////////////////////////////////////////////////////////////////////////////
static void ProcessIndexGeoJsonFlag(v8::Isolate* isolate,
v8::Handle<v8::Object> const obj,
VPackBuilder& builder) {
v8::HandleScope scope(isolate);
bool geoJson =
ExtractBoolFlag(isolate, obj, TRI_V8_ASCII_STRING("geoJson"), false);
builder.add("geoJson", VPackValue(geoJson));
}
////////////////////////////////////////////////////////////////////////////////
/// @brief process the sparse flag and add it to the json
////////////////////////////////////////////////////////////////////////////////
static void ProcessIndexSparseFlag(v8::Isolate* isolate,
v8::Handle<v8::Object> const obj,
VPackBuilder& builder, bool create) {
v8::HandleScope scope(isolate);
if (obj->Has(TRI_V8_ASCII_STRING("sparse"))) {
bool sparse =
ExtractBoolFlag(isolate, obj, TRI_V8_ASCII_STRING("sparse"), false);
builder.add("sparse", VPackValue(sparse));
} else if (create) {
// not set. now add a default value
builder.add("sparse", VPackValue(false));
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief process the unique flag and add it to the json
////////////////////////////////////////////////////////////////////////////////
static void ProcessIndexUniqueFlag(v8::Isolate* isolate,
v8::Handle<v8::Object> const obj,
VPackBuilder& builder) {
v8::HandleScope scope(isolate);
bool unique =
ExtractBoolFlag(isolate, obj, TRI_V8_ASCII_STRING("unique"), false);
builder.add("unique", VPackValue(unique));
}
////////////////////////////////////////////////////////////////////////////////
/// @brief enhances the json of a geo1 index
////////////////////////////////////////////////////////////////////////////////
static int EnhanceJsonIndexGeo1(v8::Isolate* isolate,
v8::Handle<v8::Object> const obj,
VPackBuilder& builder, bool create) {
int res = ProcessIndexFields(isolate, obj, builder, 1, create);
if (ServerState::instance()->isCoordinator()) {
builder.add("ignoreNull", VPackValue(true));
builder.add("constraint", VPackValue(false));
}
builder.add("sparse", VPackValue(true));
builder.add("unique", VPackValue(false));
ProcessIndexGeoJsonFlag(isolate, obj, builder);
return res;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief enhances the json of a geo2 index
////////////////////////////////////////////////////////////////////////////////
static int EnhanceJsonIndexGeo2(v8::Isolate* isolate,
v8::Handle<v8::Object> const obj,
VPackBuilder& builder, bool create) {
int res = ProcessIndexFields(isolate, obj, builder, 2, create);
if (ServerState::instance()->isCoordinator()) {
builder.add("ignoreNull", VPackValue(true));
builder.add("constraint", VPackValue(false));
}
builder.add("sparse", VPackValue(true));
builder.add("unique", VPackValue(false));
ProcessIndexGeoJsonFlag(isolate, obj, builder);
return res;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief enhances the json of a hash index
////////////////////////////////////////////////////////////////////////////////
static int EnhanceJsonIndexHash(v8::Isolate* isolate,
v8::Handle<v8::Object> const obj,
VPackBuilder& builder, bool create) {
int res = ProcessIndexFields(isolate, obj, builder, 0, create);
ProcessIndexSparseFlag(isolate, obj, builder, create);
ProcessIndexUniqueFlag(isolate, obj, builder);
return res;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief enhances the json of a skiplist index
////////////////////////////////////////////////////////////////////////////////
static int EnhanceJsonIndexSkiplist(v8::Isolate* isolate,
v8::Handle<v8::Object> const obj,
VPackBuilder& builder, bool create) {
int res = ProcessIndexFields(isolate, obj, builder, 0, create);
ProcessIndexSparseFlag(isolate, obj, builder, create);
ProcessIndexUniqueFlag(isolate, obj, builder);
return res;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief enhances the json of a RocksDB index
////////////////////////////////////////////////////////////////////////////////
static int EnhanceJsonIndexRocksDB(v8::Isolate* isolate,
v8::Handle<v8::Object> const obj,
VPackBuilder& builder, bool create) {
int res = ProcessIndexFields(isolate, obj, builder, 0, create);
ProcessIndexSparseFlag(isolate, obj, builder, create);
ProcessIndexUniqueFlag(isolate, obj, builder);
return res;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief enhances the json of a fulltext index
////////////////////////////////////////////////////////////////////////////////
static int EnhanceJsonIndexFulltext(v8::Isolate* isolate,
v8::Handle<v8::Object> const obj,
VPackBuilder& builder, bool create) {
int res = ProcessIndexFields(isolate, obj, builder, 1, create);
// handle "minLength" attribute
int minWordLength = TRI_FULLTEXT_MIN_WORD_LENGTH_DEFAULT;
if (obj->Has(TRI_V8_ASCII_STRING("minLength"))) {
if (obj->Get(TRI_V8_ASCII_STRING("minLength"))->IsNumber() ||
obj->Get(TRI_V8_ASCII_STRING("minLength"))->IsNumberObject()) {
minWordLength =
(int)TRI_ObjectToInt64(obj->Get(TRI_V8_ASCII_STRING("minLength")));
} else if (!obj->Get(TRI_V8_ASCII_STRING("minLength"))->IsNull() &&
!obj->Get(TRI_V8_ASCII_STRING("minLength"))->IsUndefined()) {
return TRI_ERROR_BAD_PARAMETER;
}
}
builder.add("minLength", VPackValue(minWordLength));
return res;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief enhances the json of an index
////////////////////////////////////////////////////////////////////////////////
static int EnhanceIndexJson(v8::FunctionCallbackInfo<v8::Value> const& args,
VPackBuilder& builder, bool create) {
v8::Isolate* isolate = args.GetIsolate();
v8::HandleScope scope(isolate);
v8::Handle<v8::Object> obj = args[0].As<v8::Object>();
// extract index type
arangodb::Index::IndexType type = arangodb::Index::TRI_IDX_TYPE_UNKNOWN;
if (obj->Has(TRI_V8_ASCII_STRING("type")) &&
obj->Get(TRI_V8_ASCII_STRING("type"))->IsString()) {
TRI_Utf8ValueNFC typeString(TRI_UNKNOWN_MEM_ZONE,
obj->Get(TRI_V8_ASCII_STRING("type")));
if (*typeString == nullptr) {
return TRI_ERROR_OUT_OF_MEMORY;
}
std::string t(*typeString);
// rewrite type "geo" into either "geo1" or "geo2", depending on the number
// of fields
if (t == "geo") {
t = "geo1";
if (obj->Has(TRI_V8_ASCII_STRING("fields")) &&
obj->Get(TRI_V8_ASCII_STRING("fields"))->IsArray()) {
v8::Handle<v8::Array> f = v8::Handle<v8::Array>::Cast(
obj->Get(TRI_V8_ASCII_STRING("fields")));
if (f->Length() == 2) {
t = "geo2";
}
}
}
type = arangodb::Index::type(t);
}
if (type == arangodb::Index::TRI_IDX_TYPE_UNKNOWN) {
return TRI_ERROR_BAD_PARAMETER;
}
if (create) {
if (type == arangodb::Index::TRI_IDX_TYPE_PRIMARY_INDEX ||
type == arangodb::Index::TRI_IDX_TYPE_EDGE_INDEX) {
// creating these indexes yourself is forbidden
return TRI_ERROR_FORBIDDEN;
}
}
TRI_ASSERT(builder.isEmpty());
int res = TRI_ERROR_INTERNAL;
try {
VPackObjectBuilder b(&builder);
if (obj->Has(TRI_V8_ASCII_STRING("id"))) {
uint64_t id = TRI_ObjectToUInt64(obj->Get(TRI_V8_ASCII_STRING("id")), true);
if (id > 0) {
builder.add("id", VPackValue(std::to_string(id)));
}
}
char const* idxType = arangodb::Index::typeName(type);
builder.add("type", VPackValue(idxType));
switch (type) {
case arangodb::Index::TRI_IDX_TYPE_UNKNOWN: {
res = TRI_ERROR_BAD_PARAMETER;
break;
}
case arangodb::Index::TRI_IDX_TYPE_PRIMARY_INDEX:
case arangodb::Index::TRI_IDX_TYPE_EDGE_INDEX: {
break;
}
case arangodb::Index::TRI_IDX_TYPE_GEO1_INDEX:
res = EnhanceJsonIndexGeo1(isolate, obj, builder, create);
break;
case arangodb::Index::TRI_IDX_TYPE_GEO2_INDEX:
res = EnhanceJsonIndexGeo2(isolate, obj, builder, create);
break;
case arangodb::Index::TRI_IDX_TYPE_HASH_INDEX:
res = EnhanceJsonIndexHash(isolate, obj, builder, create);
break;
case arangodb::Index::TRI_IDX_TYPE_SKIPLIST_INDEX:
res = EnhanceJsonIndexSkiplist(isolate, obj, builder, create);
break;
case arangodb::Index::TRI_IDX_TYPE_ROCKSDB_INDEX:
res = EnhanceJsonIndexRocksDB(isolate, obj, builder, create);
break;
case arangodb::Index::TRI_IDX_TYPE_FULLTEXT_INDEX:
res = EnhanceJsonIndexFulltext(isolate, obj, builder, create);
break;
}
} catch (...) {
// TODO Check for different type of Errors
return TRI_ERROR_OUT_OF_MEMORY;
}
return res;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief ensures an index, coordinator case
////////////////////////////////////////////////////////////////////////////////
int EnsureIndexCoordinator(std::string const& databaseName,
std::string const& cid,
VPackSlice const slice, bool create,
VPackBuilder& resultBuilder, std::string& errorMsg) {
TRI_ASSERT(!slice.isNone());
return ClusterInfo::instance()->ensureIndexCoordinator(
databaseName, cid, slice, create, &arangodb::Index::Compare,
resultBuilder, errorMsg, 360.0);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief ensures an index, locally
////////////////////////////////////////////////////////////////////////////////
static void EnsureIndexLocal(v8::FunctionCallbackInfo<v8::Value> const& args,
arangodb::LogicalCollection* collection,
VPackSlice const& slice, bool create) {
v8::Isolate* isolate = args.GetIsolate();
v8::HandleScope scope(isolate);
TRI_ASSERT(collection != nullptr);
READ_LOCKER(readLocker, collection->vocbase()->_inventoryLock);
SingleCollectionTransaction trx(
V8TransactionContext::Create(collection->vocbase(), true),
collection->cid(), create ? TRI_TRANSACTION_WRITE : TRI_TRANSACTION_READ);
int res = trx.begin();
if (res != TRI_ERROR_NO_ERROR) {
TRI_V8_THROW_EXCEPTION(res);
}
// disallow index creation in read-only mode
if (!collection->isSystem() && create &&
TRI_GetOperationModeServer() == TRI_VOCBASE_MODE_NO_CREATE) {
TRI_V8_THROW_EXCEPTION(TRI_ERROR_ARANGO_READ_ONLY);
}
bool created = false;
std::shared_ptr<arangodb::Index> idx;
if (create) {
// TODO Encapsulate in try{}catch(){} instead of errno()
idx = collection->createIndex(&trx, slice, created);
if (idx == nullptr) {
// something went wrong during creation
int res = TRI_errno();
TRI_V8_THROW_EXCEPTION(res);
}
} else {
idx = collection->lookupIndex(slice);
if (idx == nullptr) {
// Index not found
TRI_V8_RETURN_NULL();
}
}
TransactionBuilderLeaser builder(&trx);
builder->openObject();
try {
idx->toVelocyPack(*(builder.get()), false);
} catch (...) {
TRI_V8_THROW_EXCEPTION_MEMORY();
}
builder->close();
v8::Handle<v8::Value> ret =
IndexRep(isolate, collection->name(), builder->slice());
res = trx.commit();
if (res != TRI_ERROR_NO_ERROR) {
TRI_V8_THROW_EXCEPTION_MESSAGE(res, TRI_errno_string(res));
}
if (ret->IsObject()) {
ret->ToObject()->Set(TRI_V8_ASCII_STRING("isNewlyCreated"),
v8::Boolean::New(isolate, created));
}
TRI_V8_RETURN(ret);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief ensures an index
////////////////////////////////////////////////////////////////////////////////
static void EnsureIndex(v8::FunctionCallbackInfo<v8::Value> const& args,
bool create, char const* functionName) {
v8::Isolate* isolate = args.GetIsolate();
v8::HandleScope scope(isolate);
arangodb::LogicalCollection* collection =
TRI_UnwrapClass<arangodb::LogicalCollection>(args.Holder(), WRP_VOCBASE_COL_TYPE);
if (collection == nullptr) {
TRI_V8_THROW_EXCEPTION_INTERNAL("cannot extract collection");
}
if (args.Length() != 1 || !args[0]->IsObject()) {
std::string name(functionName);
name.append("(<description>)");
TRI_V8_THROW_EXCEPTION_USAGE(name.c_str());
}
VPackBuilder builder;
int res = EnhanceIndexJson(args, builder, create);
if (res != TRI_ERROR_NO_ERROR) {
TRI_V8_THROW_EXCEPTION(res);
}
VPackSlice slice = builder.slice();
if (res == TRI_ERROR_NO_ERROR && ServerState::instance()->isCoordinator()) {
TRI_ASSERT(slice.isObject());
std::string const dbname(collection->dbName());
std::string const collname(collection->name());
auto c = ClusterInfo::instance()->getCollection(dbname, collname);
// check if there is an attempt to create a unique index on non-shard keys
if (create) {
Index::validateFields(slice);
VPackSlice v = slice.get("unique");
/* the following combinations of shardKeys and indexKeys are allowed/not allowed:
shardKeys indexKeys
a a ok
a b not ok
a a b ok
a b a not ok
a b b not ok
a b a b ok
a b a b c ok
a b c a b not ok
a b c a b c ok
*/
if (v.isBoolean() && v.getBoolean()) {
// unique index, now check if fields and shard keys match
VPackSlice flds = slice.get("fields");
if (flds.isArray() && c->numberOfShards() > 1) {
std::vector<std::string> const& shardKeys = c->shardKeys();
std::unordered_set<std::string> indexKeys;
size_t n = static_cast<size_t>(flds.length());
for (size_t i = 0; i < n; ++i) {
VPackSlice f = flds.at(i);
if (!f.isString()) {
// index attributes must be strings
TRI_V8_THROW_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "index field names should be strings");
}
indexKeys.emplace(f.copyString());
}
// all shard-keys must be covered by the index
for (auto& it : shardKeys) {
if (indexKeys.find(it) == indexKeys.end()) {
TRI_V8_THROW_EXCEPTION_MESSAGE(TRI_ERROR_CLUSTER_UNSUPPORTED, "shard key '" + it + "' must be present in unique index");
}
}
}
}
}
}
TRI_ASSERT(!slice.isNone());
events::CreateIndex(collection->name(), slice);
// ensure an index, coordinator case
if (ServerState::instance()->isCoordinator()) {
VPackBuilder resultBuilder;
std::string errorMsg;
#ifdef USE_ENTERPRISE
int res = EnsureIndexCoordinatorEnterprise(collection, slice, create,
resultBuilder, errorMsg);
#else
std::string const databaseName(collection->dbName());
std::string const cid = collection->cid_as_string();
int res = EnsureIndexCoordinator(databaseName, cid, slice, create,
resultBuilder, errorMsg);
#endif
if (res != TRI_ERROR_NO_ERROR) {
TRI_V8_THROW_EXCEPTION_MESSAGE(res, errorMsg);
}
if (resultBuilder.slice().isNone()) {
if (!create) {
// did not find a suitable index
TRI_V8_RETURN_NULL();
}
TRI_V8_THROW_EXCEPTION_MEMORY();
}
v8::Handle<v8::Value> ret = IndexRep(isolate, collection->name(), resultBuilder.slice());
TRI_V8_RETURN(ret);
} else {
EnsureIndexLocal(args, collection, slice, create);
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create a collection on the coordinator
////////////////////////////////////////////////////////////////////////////////
std::unique_ptr<LogicalCollection> CreateCollectionCoordinator(
LogicalCollection* parameters) {
std::string distributeShardsLike = parameters->distributeShardsLike();
std::vector<std::string> avoidServers = parameters->avoidServers();
std::vector<std::string> dbServers;
ClusterInfo* ci = ClusterInfo::instance();
if (!distributeShardsLike.empty()) {
CollectionNameResolver resolver(parameters->vocbase());
TRI_voc_cid_t otherCid =
resolver.getCollectionIdCluster(distributeShardsLike);
if (otherCid != 0) {
std::string otherCidString
= arangodb::basics::StringUtils::itoa(otherCid);
try {
std::shared_ptr<LogicalCollection> collInfo =
ci->getCollection(parameters->dbName(), otherCidString);
auto shards = collInfo->shardIds();
auto shardList = ci->getShardList(otherCidString);
for (auto const& s : *shardList) {
auto it = shards->find(s);
if (it != shards->end()) {
for (auto const& s : it->second) {
dbServers.push_back(s);
}
}
}
} catch (...) {
}
parameters->distributeShardsLike(otherCidString);
}
} else if (!avoidServers.empty()) {
auto const replicationFactor = parameters->replicationFactor();
dbServers = ci->getCurrentDBServers();
long left = dbServers.size() - avoidServers.size() - replicationFactor;
if (left >= 0) {
std::sort(avoidServers.begin(), avoidServers.end());
dbServers.erase(
remove_if(dbServers.begin(), dbServers.end(), [&](std::string x) {
return binary_search(avoidServers.begin(), avoidServers.end(), x);}),
dbServers.end());
}
if ((long)dbServers.size() > replicationFactor) {
std::random_shuffle(dbServers.begin(), dbServers.end());
dbServers.erase(dbServers.begin()+replicationFactor, dbServers.end());
}
}
// If the list dbServers is still empty, it will be filled in
// distributeShards below.
// Now create the shards:
auto shards = std::make_shared<
std::unordered_map<std::string, std::vector<std::string>>>(
arangodb::distributeShards(parameters->numberOfShards(),
parameters->replicationFactor(), dbServers));
if (shards->empty() && !parameters->isSmart()) {
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL,
"no database servers found in cluster");
}
parameters->setShardMap(shards);
VPackBuilder velocy;
parameters->toVelocyPackForAgency(velocy);
std::string errorMsg;
int myerrno = ci->createCollectionCoordinator(
parameters->dbName(), parameters->cid_as_string(),
parameters->numberOfShards(), parameters->replicationFactor(), velocy.slice(), errorMsg, 240.0);
if (myerrno != TRI_ERROR_NO_ERROR) {
if (errorMsg.empty()) {
errorMsg = TRI_errno_string(myerrno);
}
THROW_ARANGO_EXCEPTION_MESSAGE(myerrno, errorMsg);
}
ci->loadPlan();
auto c = ci->getCollection(parameters->dbName(), parameters->cid_as_string());
// We never get a nullptr here because an exception is thrown if the
// collection does not exist. Also, the create collection should have
// failed before.
TRI_ASSERT(c != nullptr);
return c->clone();
}
////////////////////////////////////////////////////////////////////////////////
/// @brief was docuBlock collectionEnsureIndex
////////////////////////////////////////////////////////////////////////////////
static void JS_EnsureIndexVocbaseCol(
v8::FunctionCallbackInfo<v8::Value> const& args) {
TRI_V8_TRY_CATCH_BEGIN(isolate);
v8::HandleScope scope(isolate);
PREVENT_EMBEDDED_TRANSACTION();
EnsureIndex(args, true, "ensureIndex");
TRI_V8_TRY_CATCH_END
}
////////////////////////////////////////////////////////////////////////////////
/// @brief looks up an index
////////////////////////////////////////////////////////////////////////////////
static void JS_LookupIndexVocbaseCol(
v8::FunctionCallbackInfo<v8::Value> const& args) {
TRI_V8_TRY_CATCH_BEGIN(isolate);
v8::HandleScope scope(isolate);
EnsureIndex(args, false, "lookupIndex");
TRI_V8_TRY_CATCH_END
}
////////////////////////////////////////////////////////////////////////////////
/// @brief drops an index, coordinator case
////////////////////////////////////////////////////////////////////////////////
int DropIndexCoordinator(
std::string const& databaseName,
std::string const& cid,
TRI_idx_iid_t const iid) {
std::string errorMsg;
return ClusterInfo::instance()->dropIndexCoordinator(databaseName, cid,
iid, errorMsg, 0.0);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief was docuBlock col_dropIndex
////////////////////////////////////////////////////////////////////////////////
static void JS_DropIndexVocbaseCol(
v8::FunctionCallbackInfo<v8::Value> const& args) {
TRI_V8_TRY_CATCH_BEGIN(isolate);
v8::HandleScope scope(isolate);
PREVENT_EMBEDDED_TRANSACTION();
arangodb::LogicalCollection* collection =
TRI_UnwrapClass<arangodb::LogicalCollection>(args.Holder(), WRP_VOCBASE_COL_TYPE);
if (collection == nullptr) {
TRI_V8_THROW_EXCEPTION_INTERNAL("cannot extract collection");
}
if (args.Length() != 1) {
TRI_V8_THROW_EXCEPTION_USAGE("dropIndex(<index-handle>)");
}
if (ServerState::instance()->isCoordinator()) {
std::string collectionName;
TRI_idx_iid_t iid = 0;
v8::Handle<v8::Value> const val = args[0];
// extract the index identifier from a string
if (val->IsString() || val->IsStringObject() || val->IsNumber()) {
if (!IsIndexHandle(val, collectionName, iid)) {
TRI_V8_THROW_EXCEPTION(TRI_ERROR_ARANGO_INDEX_HANDLE_BAD);
}
}
// extract the index identifier from an object
else if (val->IsObject()) {
TRI_GET_GLOBALS();
v8::Handle<v8::Object> obj = val->ToObject();
TRI_GET_GLOBAL_STRING(IdKey);
v8::Handle<v8::Value> iidVal = obj->Get(IdKey);
if (!IsIndexHandle(iidVal, collectionName, iid)) {
TRI_V8_THROW_EXCEPTION(TRI_ERROR_ARANGO_INDEX_HANDLE_BAD);
}
}
if (!collectionName.empty()) {
CollectionNameResolver resolver(collection->vocbase());
if (!EqualCollection(&resolver, collectionName, collection)) {
TRI_V8_THROW_EXCEPTION(TRI_ERROR_ARANGO_CROSS_COLLECTION_REQUEST);
}
}
#ifdef USE_ENTERPRISE
int res = DropIndexCoordinatorEnterprise(collection, iid);
#else
std::string const databaseName(collection->dbName());
std::string const cid = collection->cid_as_string();
int res = DropIndexCoordinator(databaseName, cid, iid);
#endif
if (res == TRI_ERROR_NO_ERROR) {
TRI_V8_RETURN_TRUE();
}
TRI_V8_RETURN_FALSE();
}
READ_LOCKER(readLocker, collection->vocbase()->_inventoryLock);
SingleCollectionTransaction trx(
V8TransactionContext::Create(collection->vocbase(), true),
collection->cid(), TRI_TRANSACTION_WRITE);
int res = trx.begin();
if (res != TRI_ERROR_NO_ERROR) {
TRI_V8_THROW_EXCEPTION(res);
}
LogicalCollection* col = trx.documentCollection();
auto idx = TRI_LookupIndexByHandle(isolate, trx.resolver(), collection,
args[0], true);
if (idx == nullptr || idx->id() == 0) {
TRI_V8_RETURN_FALSE();
}
if (!idx->canBeDropped()) {
TRI_V8_THROW_EXCEPTION(TRI_ERROR_FORBIDDEN);
}
bool ok = col->dropIndex(idx->id(), true);
if (ok) {
TRI_V8_RETURN_TRUE();
}
TRI_V8_RETURN_FALSE();
TRI_V8_TRY_CATCH_END
}
////////////////////////////////////////////////////////////////////////////////
/// @brief returns information about the indexes, coordinator case
////////////////////////////////////////////////////////////////////////////////
static void GetIndexesCoordinator(
v8::FunctionCallbackInfo<v8::Value> const& args,
arangodb::LogicalCollection const* collection, bool withFigures) {
// warning This may be obsolete.
v8::Isolate* isolate = args.GetIsolate();
v8::HandleScope scope(isolate);
std::string const databaseName(collection->dbName());
std::string const cid = collection->cid_as_string();
std::string const collectionName(collection->name());
auto c = ClusterInfo::instance()->getCollection(databaseName, cid);
v8::Handle<v8::Array> ret = v8::Array::New(isolate);
VPackBuilder tmp;
c->getIndexesVPack(tmp, withFigures);
VPackSlice slice = tmp.slice();
if (slice.isArray()) {
uint32_t j = 0;
for (auto const& v : VPackArrayIterator(slice)) {
if (!v.isNone()) {
ret->Set(j++, IndexRep(isolate, collectionName, v));
}
}
}
TRI_V8_RETURN(ret);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief was docuBlock collectionGetIndexes
////////////////////////////////////////////////////////////////////////////////
static void JS_GetIndexesVocbaseCol(
v8::FunctionCallbackInfo<v8::Value> const& args) {
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) {
TRI_V8_THROW_EXCEPTION_INTERNAL("cannot extract collection");
}
bool withFigures = false;
if (args.Length() > 0) {
withFigures = TRI_ObjectToBoolean(args[0]);
}
if (ServerState::instance()->isCoordinator()) {
GetIndexesCoordinator(args, collection, withFigures);
return;
}
SingleCollectionTransaction trx(
V8TransactionContext::Create(collection->vocbase(), true),
collection->cid(), TRI_TRANSACTION_READ);
trx.addHint(TRI_TRANSACTION_HINT_NO_USAGE_LOCK, false);
int res = trx.begin();
if (res != TRI_ERROR_NO_ERROR) {
TRI_V8_THROW_EXCEPTION(res);
}
// READ-LOCK start
trx.lockRead();
std::string const collectionName(collection->name());
// get list of indexes
TransactionBuilderLeaser builder(&trx);
auto indexes = collection->getIndexes();
trx.finish(res);
// READ-LOCK end
size_t const n = indexes.size();
v8::Handle<v8::Array> result = v8::Array::New(isolate, static_cast<int>(n));
for (size_t i = 0; i < n; ++i) {
auto const& idx = indexes[i];
builder->clear();
builder->openObject();
idx->toVelocyPack(*(builder.get()), withFigures);
builder->close();
result->Set(static_cast<uint32_t>(i),
IndexRep(isolate, collectionName, builder->slice()));
}
TRI_V8_RETURN(result);
TRI_V8_TRY_CATCH_END
}
////////////////////////////////////////////////////////////////////////////////
/// @brief looks up an index identifier
////////////////////////////////////////////////////////////////////////////////
std::shared_ptr<arangodb::Index> TRI_LookupIndexByHandle(
v8::Isolate* isolate, arangodb::CollectionNameResolver const* resolver,
arangodb::LogicalCollection const* collection,
v8::Handle<v8::Value> const val, bool ignoreNotFound) {
// reset the collection identifier
std::string collectionName;
TRI_idx_iid_t iid = 0;
// assume we are already loaded
TRI_ASSERT(collection != nullptr);
// extract the index identifier from a string
if (val->IsString() || val->IsStringObject() || val->IsNumber()) {
if (!IsIndexHandle(val, collectionName, iid)) {
TRI_V8_SET_EXCEPTION(TRI_ERROR_ARANGO_INDEX_HANDLE_BAD);
return nullptr;
}
}
// extract the index identifier from an object
else if (val->IsObject()) {
TRI_GET_GLOBALS();
v8::Handle<v8::Object> obj = val->ToObject();
TRI_GET_GLOBAL_STRING(IdKey);
v8::Handle<v8::Value> iidVal = obj->Get(IdKey);
if (!IsIndexHandle(iidVal, collectionName, iid)) {
TRI_V8_SET_EXCEPTION(TRI_ERROR_ARANGO_INDEX_HANDLE_BAD);
return nullptr;
}
}
if (!collectionName.empty()) {
if (!EqualCollection(resolver, collectionName, collection)) {
// I wish this error provided me with more information!
// e.g. 'cannot access index outside the collection it was defined in'
TRI_V8_SET_EXCEPTION(TRI_ERROR_ARANGO_CROSS_COLLECTION_REQUEST);
return nullptr;
}
}
auto idx = collection->lookupIndex(iid);
if (idx == nullptr) {
if (!ignoreNotFound) {
TRI_V8_SET_EXCEPTION(TRI_ERROR_ARANGO_INDEX_NOT_FOUND);
return nullptr;
}
}
return idx;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create a collection
////////////////////////////////////////////////////////////////////////////////
static void CreateVocBase(v8::FunctionCallbackInfo<v8::Value> const& args,
TRI_col_type_e collectionType) {
v8::Isolate* isolate = args.GetIsolate();
v8::HandleScope scope(isolate);
TRI_vocbase_t* vocbase = GetContextVocBase(isolate);
if (vocbase == nullptr) {
TRI_V8_THROW_EXCEPTION(TRI_ERROR_ARANGO_DATABASE_NOT_FOUND);
}
// ...........................................................................
// We require exactly 1 or exactly 2 arguments -- anything else is an error
// ...........................................................................
if (args.Length() < 1 || args.Length() > 3) {
TRI_V8_THROW_EXCEPTION_USAGE("_create(<name>, <properties>, <type>)");
}
if (TRI_GetOperationModeServer() == TRI_VOCBASE_MODE_NO_CREATE) {
TRI_V8_THROW_EXCEPTION(TRI_ERROR_ARANGO_READ_ONLY);
}
// optional, third parameter can override collection type
if (args.Length() == 3 && args[2]->IsString()) {
std::string typeString = TRI_ObjectToString(args[2]);
if (typeString == "edge") {
collectionType = TRI_COL_TYPE_EDGE;
} else if (typeString == "document") {
collectionType = TRI_COL_TYPE_DOCUMENT;
}
}
PREVENT_EMBEDDED_TRANSACTION();
// extract the name
std::string const name = TRI_ObjectToString(args[0]);
VPackBuilder builder;
VPackSlice infoSlice;
if (2 <= args.Length()) {
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("type"),
v8::Number::New(isolate, static_cast<int>(collectionType)));
obj->Set(TRI_V8_ASCII_STRING("name"), TRI_V8_STD_STRING(name));
int res = TRI_V8ToVPack(isolate, builder, obj, 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();
}
infoSlice = builder.slice();
if (ServerState::instance()->isCoordinator()) {
#ifndef USE_ENTERPRISE
auto parameters = std::make_unique<LogicalCollection>(vocbase, infoSlice, false);
TRI_V8_RETURN(
WrapCollection(isolate, CreateCollectionCoordinator(parameters.get()).release()));
#else
TRI_V8_RETURN(
WrapCollection(isolate, CreateCollectionCoordinatorEnterprise(
collectionType, vocbase, infoSlice).release()));
#endif
}
try {
TRI_voc_cid_t cid = 0;
arangodb::LogicalCollection const* collection =
vocbase->createCollection(infoSlice, cid, true);
TRI_ASSERT(collection != nullptr);
v8::Handle<v8::Value> result = WrapCollection(isolate, collection);
if (result.IsEmpty()) {
TRI_V8_THROW_EXCEPTION_MEMORY();
}
TRI_V8_RETURN(result);
} 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");
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief was docuBlock collectionDatabaseCreate
////////////////////////////////////////////////////////////////////////////////
static void JS_CreateVocbase(v8::FunctionCallbackInfo<v8::Value> const& args) {
TRI_V8_TRY_CATCH_BEGIN(isolate);
CreateVocBase(args, TRI_COL_TYPE_DOCUMENT);
TRI_V8_TRY_CATCH_END
}
////////////////////////////////////////////////////////////////////////////////
/// @brief was docuBlock collectionCreateDocumentCollection
////////////////////////////////////////////////////////////////////////////////
static void JS_CreateDocumentCollectionVocbase(
v8::FunctionCallbackInfo<v8::Value> const& args) {
TRI_V8_TRY_CATCH_BEGIN(isolate);
CreateVocBase(args, TRI_COL_TYPE_DOCUMENT);
TRI_V8_TRY_CATCH_END
}
////////////////////////////////////////////////////////////////////////////////
/// @brief was docuBlock collectionCreateEdgeCollection
////////////////////////////////////////////////////////////////////////////////
static void JS_CreateEdgeCollectionVocbase(
v8::FunctionCallbackInfo<v8::Value> const& args) {
TRI_V8_TRY_CATCH_BEGIN(isolate);
CreateVocBase(args, TRI_COL_TYPE_EDGE);
TRI_V8_TRY_CATCH_END
}
void TRI_InitV8indexArangoDB(v8::Isolate* isolate,
v8::Handle<v8::ObjectTemplate> rt) {
TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING("_create"),
JS_CreateVocbase, true);
TRI_AddMethodVocbase(isolate, rt,
TRI_V8_ASCII_STRING("_createEdgeCollection"),
JS_CreateEdgeCollectionVocbase);
TRI_AddMethodVocbase(isolate, rt,
TRI_V8_ASCII_STRING("_createDocumentCollection"),
JS_CreateDocumentCollectionVocbase);
}
void TRI_InitV8indexCollection(v8::Isolate* isolate,
v8::Handle<v8::ObjectTemplate> rt) {
TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING("dropIndex"),
JS_DropIndexVocbaseCol);
TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING("ensureIndex"),
JS_EnsureIndexVocbaseCol);
TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING("lookupIndex"),
JS_LookupIndexVocbaseCol);
TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING("getIndexes"),
JS_GetIndexesVocbaseCol);
}