//////////////////////////////////////////////////////////////////////////////// /// 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/StringUtils.h" #include "Basics/tri-strings.h" #include "Basics/VelocyPackHelper.h" #include "Cluster/ClusterMethods.h" #include "FulltextIndex/fulltext-index.h" #include "Indexes/EdgeIndex.h" #include "Indexes/FulltextIndex.h" #include "Indexes/GeoIndex2.h" #include "Indexes/HashIndex.h" #include "Indexes/Index.h" #include "Indexes/PrimaryIndex.h" #include "Indexes/SkiplistIndex.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" #ifdef ARANGODB_ENABLE_ROCKSDB #include "Indexes/RocksDBIndex.h" #endif #include #include #include 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 const obj, v8::Handle 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 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 IndexRep(v8::Isolate* isolate, std::string const& collectionName, VPackSlice const& idx) { v8::EscapableHandleScope scope(isolate); TRI_ASSERT(!idx.isNone()); v8::Handle 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(rep); } //////////////////////////////////////////////////////////////////////////////// /// @brief process the fields list and add them to the json //////////////////////////////////////////////////////////////////////////////// static int ProcessIndexFields(v8::Isolate* isolate, v8::Handle const obj, VPackBuilder& builder, int numFields, bool create) { v8::HandleScope scope(isolate); std::set fields; v8::Handle fieldsString = TRI_V8_ASCII_STRING("fields"); if (obj->Has(fieldsString) && obj->Get(fieldsString)->IsArray()) { // "fields" is a list of fields v8::Handle fieldList = v8::Handle::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 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 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 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 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 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 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 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 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 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 const& args, VPackBuilder& builder, bool create) { v8::Isolate* isolate = args.GetIsolate(); v8::HandleScope scope(isolate); v8::Handle obj = args[0].As(); // 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 f = v8::Handle::Cast( obj->Get(TRI_V8_ASCII_STRING("fields"))); if (f->Length() == 2) { t = "geo2"; } } } type = arangodb::Index::type(t.c_str()); } 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) { char* idString = TRI_StringUInt64(id); builder.add("id", VPackValue(idString)); TRI_FreeString(TRI_CORE_MEM_ZONE, idString); } } 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 //////////////////////////////////////////////////////////////////////////////// static void EnsureIndexCoordinator( v8::FunctionCallbackInfo const& args, TRI_vocbase_col_t const* collection, VPackSlice const slice, bool create) { v8::Isolate* isolate = args.GetIsolate(); v8::HandleScope scope(isolate); TRI_ASSERT(collection != nullptr); TRI_ASSERT(!slice.isNone()); std::string const databaseName(collection->_dbName); std::string const cid = StringUtils::itoa(collection->_cid); // TODO: protect against races on _name std::string const collectionName(collection->_name); VPackBuilder resultBuilder; std::string errorMsg; int res = ClusterInfo::instance()->ensureIndexCoordinator( databaseName, cid, slice, create, &arangodb::Index::Compare, resultBuilder, errorMsg, 360.0); 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 ret = IndexRep(isolate, collectionName, resultBuilder.slice()); TRI_V8_RETURN(ret); } //////////////////////////////////////////////////////////////////////////////// /// @brief ensures an index, locally //////////////////////////////////////////////////////////////////////////////// static void EnsureIndexLocal(v8::FunctionCallbackInfo const& args, TRI_vocbase_col_t const* collection, VPackSlice const& slice, bool create) { v8::Isolate* isolate = args.GetIsolate(); v8::HandleScope scope(isolate); TRI_ASSERT(collection != nullptr); if (!slice.isObject()) { TRI_V8_THROW_EXCEPTION_MEMORY(); } // extract type VPackSlice value = slice.get("type"); if (!value.isString()) { TRI_V8_THROW_EXCEPTION_MEMORY(); } std::string tmp = value.copyString(); arangodb::Index::IndexType const type = arangodb::Index::type(tmp.c_str()); // extract unique flag bool unique = arangodb::basics::VelocyPackHelper::getBooleanValue( slice, "unique", false); // extract sparse flag bool sparse = false; int sparsity = -1; // not set value = slice.get("sparse"); if (value.isBoolean()) { sparse = value.getBoolean(); sparsity = sparse ? 1 : 0; } // extract id TRI_idx_iid_t iid = 0; value = slice.get("id"); if (value.isString()) { std::string tmp = value.copyString(); iid = StringUtils::uint64(tmp); } std::vector attributes; // extract fields value = slice.get("fields"); if (value.isArray()) { // note: "fields" is not mandatory for all index types // copy all field names (attributes) for (auto const& v : VPackArrayIterator(value)) { if (v.isString()) { std::string val = v.copyString(); if (val.find("[*]") != std::string::npos) { if (type != arangodb::Index::TRI_IDX_TYPE_HASH_INDEX && type != arangodb::Index::TRI_IDX_TYPE_SKIPLIST_INDEX && type != arangodb::Index::TRI_IDX_TYPE_ROCKSDB_INDEX) { // expansion used in index type that does not support it TRI_V8_THROW_EXCEPTION_MESSAGE( TRI_ERROR_BAD_PARAMETER, "cannot use [*] expansion for this type of index"); } else { // expansion used in index type that supports it // count number of [*] occurrences size_t found = 0; size_t offset = 0; while ((offset = val.find("[*]", offset)) != std::string::npos) { ++found; offset += strlen("[*]"); } // only one occurrence is allowed if (found > 1) { TRI_V8_THROW_EXCEPTION_MESSAGE(TRI_ERROR_BAD_PARAMETER, "cannot use multiple [*] " "expansions for a single index " "field"); } } } attributes.emplace_back(val); } } } 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); } TRI_document_collection_t* document = trx.documentCollection(); std::string const& collectionName = std::string(collection->_name); // disallow index creation in read-only mode if (!TRI_IsSystemNameCollection(collectionName.c_str()) && create && TRI_GetOperationModeServer() == TRI_VOCBASE_MODE_NO_CREATE) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_ARANGO_READ_ONLY); } bool created = false; arangodb::Index* idx = nullptr; switch (type) { case arangodb::Index::TRI_IDX_TYPE_UNKNOWN: case arangodb::Index::TRI_IDX_TYPE_PRIMARY_INDEX: case arangodb::Index::TRI_IDX_TYPE_EDGE_INDEX: { // these indexes cannot be created directly TRI_V8_THROW_EXCEPTION(TRI_ERROR_INTERNAL); } case arangodb::Index::TRI_IDX_TYPE_GEO1_INDEX: { if (attributes.size() != 1) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_INTERNAL); } bool geoJson = arangodb::basics::VelocyPackHelper::getBooleanValue( slice, "geoJson", false); if (create) { idx = static_cast( TRI_EnsureGeoIndex1DocumentCollection( &trx, document, iid, attributes[0], geoJson, created)); } else { std::vector location = arangodb::basics::StringUtils::split(attributes[0], "."); idx = static_cast( TRI_LookupGeoIndex1DocumentCollection(document, location, geoJson)); } break; } case arangodb::Index::TRI_IDX_TYPE_GEO2_INDEX: { if (attributes.size() != 2) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_INTERNAL); } if (create) { idx = static_cast( TRI_EnsureGeoIndex2DocumentCollection( &trx, document, iid, attributes[0], attributes[1], created)); } else { std::vector lat = arangodb::basics::StringUtils::split(attributes[0], "."); std::vector lon = arangodb::basics::StringUtils::split(attributes[0], "."); idx = static_cast( TRI_LookupGeoIndex2DocumentCollection(document, lon, lat)); } break; } case arangodb::Index::TRI_IDX_TYPE_HASH_INDEX: { if (attributes.empty()) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_INTERNAL); } if (create) { idx = static_cast( TRI_EnsureHashIndexDocumentCollection( &trx, document, iid, attributes, sparse, unique, created)); } else { idx = static_cast( TRI_LookupHashIndexDocumentCollection(document, attributes, sparsity, unique)); } break; } case arangodb::Index::TRI_IDX_TYPE_SKIPLIST_INDEX: { if (attributes.empty()) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_INTERNAL); } if (create) { idx = static_cast( TRI_EnsureSkiplistIndexDocumentCollection( &trx, document, iid, attributes, sparse, unique, created)); } else { idx = static_cast( TRI_LookupSkiplistIndexDocumentCollection(document, attributes, sparsity, unique)); } break; } case arangodb::Index::TRI_IDX_TYPE_ROCKSDB_INDEX: { if (attributes.empty()) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_INTERNAL); } #ifdef ARANGODB_ENABLE_ROCKSDB if (create) { idx = static_cast( TRI_EnsureRocksDBIndexDocumentCollection( &trx, document, iid, attributes, sparse, unique, created)); } else { idx = static_cast( TRI_LookupRocksDBIndexDocumentCollection(document, attributes, sparsity, unique)); } break; #else TRI_V8_THROW_EXCEPTION_MESSAGE(TRI_ERROR_NOT_IMPLEMENTED, "index type not supported in this build"); #endif } case arangodb::Index::TRI_IDX_TYPE_FULLTEXT_INDEX: { if (attributes.size() != 1) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_INTERNAL); } int minWordLength = TRI_FULLTEXT_MIN_WORD_LENGTH_DEFAULT; VPackSlice const value = slice.get("minLength"); if (value.isNumber()) { minWordLength = value.getNumericValue(); } else if (!value.isNone()) { // minLength defined but no number TRI_V8_THROW_EXCEPTION_PARAMETER(" must be a number"); } if (create) { idx = static_cast( TRI_EnsureFulltextIndexDocumentCollection( &trx, document, iid, attributes[0], minWordLength, created)); } else { idx = static_cast( TRI_LookupFulltextIndexDocumentCollection(document, attributes[0], minWordLength)); } break; } } if (idx == nullptr && create) { // something went wrong during creation int res = TRI_errno(); TRI_V8_THROW_EXCEPTION(res); } if (idx == nullptr && !create) { // no index found TRI_V8_RETURN_NULL(); } // found some index to return std::shared_ptr indexVPack; try { indexVPack = idx->toVelocyPack(false); } catch (...) { TRI_V8_THROW_EXCEPTION_MEMORY(); } if (indexVPack == nullptr) { TRI_V8_THROW_EXCEPTION_MEMORY(); } v8::Handle ret = IndexRep(isolate, collectionName, indexVPack->slice()); if (ret->IsObject()) { ret->ToObject()->Set(TRI_V8_ASCII_STRING("isNewlyCreated"), v8::Boolean::New(isolate, create && created)); } TRI_V8_RETURN(ret); } //////////////////////////////////////////////////////////////////////////////// /// @brief ensures an index //////////////////////////////////////////////////////////////////////////////// static void EnsureIndex(v8::FunctionCallbackInfo const& args, bool create, char const* functionName) { v8::Isolate* isolate = args.GetIsolate(); v8::HandleScope scope(isolate); TRI_vocbase_col_t* collection = TRI_UnwrapClass(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("()"); TRI_V8_THROW_EXCEPTION_USAGE(name.c_str()); } VPackBuilder builder; int res = EnhanceIndexJson(args, builder, create); VPackSlice slice = builder.slice(); if (res == TRI_ERROR_NO_ERROR && ServerState::instance()->isCoordinator()) { TRI_ASSERT(slice.isObject()); std::string const dbname(collection->_dbName); // TODO: someone might rename the collection while we're reading its name... std::string const collname(collection->_name); std::shared_ptr c = ClusterInfo::instance()->getCollection(dbname, collname); if (c->empty()) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_ARANGO_COLLECTION_NOT_FOUND); } // check if there is an attempt to create a unique index on non-shard keys if (create) { VPackSlice v = slice.get("unique"); 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 const& shardKeys = c->shardKeys(); size_t n = static_cast(flds.length()); if (shardKeys.size() != n) { res = TRI_ERROR_CLUSTER_UNSUPPORTED; } else { for (size_t i = 0; i < n; ++i) { VPackSlice f = flds.at(i); if (!f.isString()) { res = TRI_ERROR_INTERNAL; continue; } else { std::string tmp = f.copyString(); if (!TRI_EqualString(tmp.c_str(), shardKeys[i].c_str())) { res = TRI_ERROR_CLUSTER_UNSUPPORTED; } } } } } } } } if (res != TRI_ERROR_NO_ERROR) { TRI_V8_THROW_EXCEPTION(res); } TRI_ASSERT(!slice.isNone()); // ensure an index, coordinator case if (ServerState::instance()->isCoordinator()) { EnsureIndexCoordinator(args, collection, slice, create); } else { EnsureIndexLocal(args, collection, slice, create); } } //////////////////////////////////////////////////////////////////////////////// /// @brief create a collection on the coordinator //////////////////////////////////////////////////////////////////////////////// static void CreateCollectionCoordinator( v8::FunctionCallbackInfo const& args, TRI_col_type_e collectionType, std::string const& databaseName, VocbaseCollectionInfo& parameters, TRI_vocbase_t* vocbase) { v8::Isolate* isolate = args.GetIsolate(); v8::HandleScope scope(isolate); std::string const name = TRI_ObjectToString(args[0]); if (!TRI_IsAllowedNameCollection(parameters.isSystem(), name.c_str())) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_ARANGO_ILLEGAL_NAME); } bool allowUserKeys = true; uint64_t numberOfShards = 1; std::vector shardKeys; uint64_t replicationFactor = 1; // default shard key shardKeys.push_back("_key"); std::string distributeShardsLike; std::string cid = ""; // Could come from properties if (2 <= args.Length()) { if (!args[1]->IsObject()) { TRI_V8_THROW_TYPE_ERROR(" must be an object"); } v8::Handle p = args[1]->ToObject(); if (p->Has(TRI_V8_ASCII_STRING("keyOptions")) && p->Get(TRI_V8_ASCII_STRING("keyOptions"))->IsObject()) { v8::Handle o = v8::Handle::Cast( p->Get(TRI_V8_ASCII_STRING("keyOptions"))); if (o->Has(TRI_V8_ASCII_STRING("type"))) { std::string const type = TRI_ObjectToString(o->Get(TRI_V8_ASCII_STRING("type"))); if (type != "" && type != "traditional") { // invalid key generator TRI_V8_THROW_EXCEPTION_MESSAGE(TRI_ERROR_CLUSTER_UNSUPPORTED, "non-traditional key generators are " "not supported for sharded " "collections"); } } if (o->Has(TRI_V8_ASCII_STRING("allowUserKeys"))) { allowUserKeys = TRI_ObjectToBoolean(o->Get(TRI_V8_ASCII_STRING("allowUserKeys"))); } } if (p->Has(TRI_V8_ASCII_STRING("numberOfShards"))) { numberOfShards = TRI_ObjectToUInt64( p->Get(TRI_V8_ASCII_STRING("numberOfShards")), false); } if (p->Has(TRI_V8_ASCII_STRING("shardKeys"))) { shardKeys.clear(); if (p->Get(TRI_V8_ASCII_STRING("shardKeys"))->IsArray()) { v8::Handle k = v8::Handle::Cast( p->Get(TRI_V8_ASCII_STRING("shardKeys"))); for (uint32_t i = 0; i < k->Length(); ++i) { v8::Handle v = k->Get(i); if (v->IsString()) { std::string const key = TRI_ObjectToString(v); // system attributes are not allowed (except _key) if (!key.empty() && (key[0] != '_' || key == "_key")) { shardKeys.push_back(key); } } } } } if (p->Has(TRI_V8_ASCII_STRING("distributeShardsLike")) && p->Get(TRI_V8_ASCII_STRING("distributeShardsLike"))->IsString()) { distributeShardsLike = TRI_ObjectToString( p->Get(TRI_V8_ASCII_STRING("distributeShardsLike"))); } auto idKey = TRI_V8_ASCII_STRING("id"); if (p->Has(idKey) && p->Get(idKey)->IsString()) { cid = TRI_ObjectToString(p->Get(idKey)); } if (p->Has(TRI_V8_ASCII_STRING("replicationFactor"))) { replicationFactor = TRI_ObjectToUInt64(p->Get(TRI_V8_ASCII_STRING("replicationFactor")), false); } } if (numberOfShards == 0 || numberOfShards > 1000) { TRI_V8_THROW_EXCEPTION_PARAMETER("invalid number of shards"); } if (replicationFactor == 0 || replicationFactor > 10) { TRI_V8_THROW_EXCEPTION_PARAMETER("invalid replicationFactor"); } if (shardKeys.empty() || shardKeys.size() > 8) { TRI_V8_THROW_EXCEPTION_PARAMETER("invalid number of shard keys"); } ClusterInfo* ci = ClusterInfo::instance(); // fetch a unique id for the new collection, this is also used for the // edge index, if that is needed, therefore we create the id anyway: uint64_t const id = ci->uniqid(1); if (cid.empty()) { // collection id is the first unique id we got cid = StringUtils::itoa(id); // if id was given, the first unique id is wasted, this does not matter } std::vector dbServers; if (!distributeShardsLike.empty()) { CollectionNameResolver resolver(vocbase); TRI_voc_cid_t otherCid = resolver.getCollectionIdCluster(distributeShardsLike); if (otherCid != 0) { std::string otherCidString = arangodb::basics::StringUtils::itoa(otherCid); std::shared_ptr collInfo = ci->getCollection(databaseName, otherCidString); if (!collInfo->empty()) { 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); } } } } } } // If the list dbServers is still empty, it will be filled in // distributeShards below. // Now create the shards: std::map> shards = arangodb::distributeShards(numberOfShards, replicationFactor, dbServers); if (shards.empty()) { TRI_V8_THROW_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "no database servers found in cluster"); } // now create the VelocyPack for the collection arangodb::velocypack::Builder velocy; using arangodb::velocypack::Value; using arangodb::velocypack::ValueType; using arangodb::velocypack::ObjectBuilder; using arangodb::velocypack::ArrayBuilder; { ObjectBuilder ob(&velocy); velocy("id", Value(cid)) ("name", Value(name)) ("type", Value((int) collectionType)) ("status", Value((int) TRI_VOC_COL_STATUS_LOADED)) ("deleted", Value(parameters.deleted())) ("doCompact", Value(parameters.doCompact())) ("isSystem", Value(parameters.isSystem())) ("isVolatile", Value(parameters.isVolatile())) ("waitForSync", Value(parameters.waitForSync())) ("journalSize", Value(parameters.maximalSize())) ("indexBuckets", Value(parameters.indexBuckets())) ("replicationFactor", Value(replicationFactor)) ("keyOptions", Value(ValueType::Object)) ("type", Value("traditional")) ("allowUserKeys", Value(allowUserKeys)) .close(); { ArrayBuilder ab(&velocy, "shardKeys"); for (auto const& sk : shardKeys) { velocy(Value(sk)); } } { ObjectBuilder ob(&velocy, "shards"); for (auto const& p : shards) { ArrayBuilder ab(&velocy, p.first); for (auto const& s : p.second) { velocy(Value(s)); } } } { ArrayBuilder ab(&velocy, "indexes"); // create a dummy primary index TRI_document_collection_t* doc = nullptr; std::unique_ptr primaryIndex( new arangodb::PrimaryIndex(doc)); velocy.openObject(); primaryIndex->toVelocyPack(velocy, false); velocy.close(); if (collectionType == TRI_COL_TYPE_EDGE) { // create a dummy edge index auto edgeIndex = std::make_unique(id, nullptr); velocy.openObject(); edgeIndex->toVelocyPack(velocy, false); velocy.close(); } } } std::string errorMsg; int myerrno = ci->createCollectionCoordinator( databaseName, cid, numberOfShards, velocy.slice(), errorMsg, 240.0); if (myerrno != TRI_ERROR_NO_ERROR) { TRI_V8_THROW_EXCEPTION_MESSAGE(myerrno, errorMsg); } ci->loadPlan(); std::shared_ptr c = ci->getCollection(databaseName, cid); TRI_vocbase_col_t* newcoll = CoordinatorCollection(vocbase, *c); TRI_V8_RETURN(WrapCollection(isolate, newcoll)); } //////////////////////////////////////////////////////////////////////////////// /// @brief was docuBlock collectionEnsureIndex //////////////////////////////////////////////////////////////////////////////// static void JS_EnsureIndexVocbaseCol( v8::FunctionCallbackInfo 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 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 //////////////////////////////////////////////////////////////////////////////// static void DropIndexCoordinator( v8::FunctionCallbackInfo const& args, TRI_vocbase_col_t const* collection, v8::Handle const val) { v8::Isolate* isolate = args.GetIsolate(); v8::HandleScope scope(isolate); std::string collectionName; TRI_idx_iid_t iid = 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 obj = val->ToObject(); TRI_GET_GLOBAL_STRING(IdKey); v8::Handle 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); } } std::string const databaseName(collection->_dbName); std::string const cid = StringUtils::itoa(collection->_cid); std::string errorMsg; int res = ClusterInfo::instance()->dropIndexCoordinator(databaseName, cid, iid, errorMsg, 0.0); if (res == TRI_ERROR_NO_ERROR) { TRI_V8_RETURN_TRUE(); } TRI_V8_RETURN_FALSE(); } //////////////////////////////////////////////////////////////////////////////// /// @brief was docuBlock col_dropIndex //////////////////////////////////////////////////////////////////////////////// static void JS_DropIndexVocbaseCol( v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); PREVENT_EMBEDDED_TRANSACTION(); TRI_vocbase_col_t* collection = TRI_UnwrapClass(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()"); } if (ServerState::instance()->isCoordinator()) { DropIndexCoordinator(args, collection, args[0]); return; } 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); } TRI_document_collection_t* document = 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 = TRI_DropIndexDocumentCollection(document, 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 const& args, TRI_vocbase_col_t const* collection) { v8::Isolate* isolate = args.GetIsolate(); v8::HandleScope scope(isolate); std::string const databaseName(collection->_dbName); std::string const cid = StringUtils::itoa(collection->_cid); std::string const collectionName(collection->_name); std::shared_ptr c = ClusterInfo::instance()->getCollection(databaseName, cid); if ((*c).empty()) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_ARANGO_COLLECTION_NOT_FOUND); } v8::Handle ret = v8::Array::New(isolate); VPackSlice slice = c->getIndexes(); 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 const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); TRI_vocbase_col_t* collection = TRI_UnwrapClass(args.Holder(), WRP_VOCBASE_COL_TYPE); if (collection == nullptr) { TRI_V8_THROW_EXCEPTION_INTERNAL("cannot extract collection"); } if (ServerState::instance()->isCoordinator()) { GetIndexesCoordinator(args, collection); return; } bool withFigures = false; if (args.Length() > 0) { withFigures = TRI_ObjectToBoolean(args[0]); } SingleCollectionTransaction trx(V8TransactionContext::Create(collection->_vocbase, true), collection->_cid, TRI_TRANSACTION_READ); int res = trx.begin(); if (res != TRI_ERROR_NO_ERROR) { TRI_V8_THROW_EXCEPTION(res); } // READ-LOCK start trx.lockRead(); TRI_document_collection_t* document = trx.documentCollection(); std::string const& collectionName = std::string(collection->_name); // get list of indexes auto indexes(TRI_IndexesDocumentCollection(document, withFigures)); trx.finish(res); // READ-LOCK end size_t const n = indexes.size(); v8::Handle result = v8::Array::New(isolate, static_cast(n)); for (size_t i = 0; i < n; ++i) { auto const& idx = indexes[i]; result->Set(static_cast(i), IndexRep(isolate, collectionName, idx->slice())); } TRI_V8_RETURN(result); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief looks up an index identifier //////////////////////////////////////////////////////////////////////////////// arangodb::Index* TRI_LookupIndexByHandle( v8::Isolate* isolate, arangodb::CollectionNameResolver const* resolver, TRI_vocbase_col_t const* collection, v8::Handle 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); TRI_ASSERT(collection->_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 obj = val->ToObject(); TRI_GET_GLOBAL_STRING(IdKey); v8::Handle 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->_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 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(, , )"); } 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(" must be an object"); } int res = TRI_V8ToVPack(isolate, builder, args[1], false); if (res != TRI_ERROR_NO_ERROR) { TRI_V8_THROW_EXCEPTION(res); } infoSlice = builder.slice(); } VocbaseCollectionInfo parameters(vocbase, name.c_str(), collectionType, infoSlice, false); if (ServerState::instance()->isCoordinator()) { CreateCollectionCoordinator(args, collectionType, vocbase->_name, parameters, vocbase); return; } TRI_vocbase_col_t const* collection = TRI_CreateCollectionVocBase(vocbase, parameters, parameters.id(), true); if (collection == nullptr) { TRI_V8_THROW_EXCEPTION_MESSAGE(TRI_errno(), "cannot create collection"); } v8::Handle result = WrapCollection(isolate, collection); if (result.IsEmpty()) { TRI_V8_THROW_EXCEPTION_MEMORY(); } TRI_V8_RETURN(result); } //////////////////////////////////////////////////////////////////////////////// /// @brief was docuBlock collectionDatabaseCreate //////////////////////////////////////////////////////////////////////////////// static void JS_CreateVocbase(v8::FunctionCallbackInfo 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 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 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 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 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); }