//////////////////////////////////////////////////////////////////////////////// /// @brief V8-vocbase bridge /// /// @file /// /// DISCLAIMER /// /// Copyright 2014 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 /// @author Copyright 2014, ArangoDB GmbH, Cologne, Germany /// @author Copyright 2011-2013, triAGENS GmbH, Cologne, Germany //////////////////////////////////////////////////////////////////////////////// #include "v8-vocindex.h" #include "Basics/conversions.h" #include "FulltextIndex/fulltext-index.h" #include "Indexes/CapConstraint.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/SkiplistIndex2.h" #include "Utils/transactions.h" #include "Utils/V8TransactionContext.h" #include "V8/v8-conv.h" #include "V8/v8-globals.h" #include "V8/v8-utils.h" #include "V8Server/v8-collection.h" #include "V8Server/v8-vocbase.h" #include "V8Server/v8-vocbaseprivate.h" using namespace std; using namespace triagens::basics; using namespace triagens::arango; using namespace triagens::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, 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 (triagens::arango::Index::validateHandle(*str, &split)) { collectionName = string(*str, split); iid = TRI_UInt64String2(*str + split + 1, str.length() - split - 1); return true; } if (triagens::arango::Index::validateId(*str)) { iid = TRI_UInt64String2(*str, str.length()); return true; } return false; } //////////////////////////////////////////////////////////////////////////////// /// @brief returns the index representation //////////////////////////////////////////////////////////////////////////////// static v8::Handle IndexRep (v8::Isolate* isolate, string const& collectionName, TRI_json_t const* idx) { v8::EscapableHandleScope scope(isolate); TRI_ASSERT(idx != nullptr); v8::Handle rep = TRI_ObjectJson(isolate, idx)->ToObject(); string iid = TRI_ObjectToString(rep->Get(TRI_V8_ASCII_STRING("id"))); 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, TRI_json_t* json, int numFields, bool create) { v8::HandleScope scope(isolate); 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; } string const f = TRI_ObjectToString(fieldList->Get(i)); if (f.empty() || (create && f[0] == '_')) { // 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; } TRI_json_t* fieldJson = TRI_ObjectToJson(isolate, obj->Get(TRI_V8_ASCII_STRING("fields"))); if (fieldJson == nullptr) { return TRI_ERROR_OUT_OF_MEMORY; } TRI_Insert3ObjectJson(TRI_UNKNOWN_MEM_ZONE, json, "fields", fieldJson); return TRI_ERROR_NO_ERROR; } //////////////////////////////////////////////////////////////////////////////// /// @brief process the geojson flag and add it to the json //////////////////////////////////////////////////////////////////////////////// static int ProcessIndexGeoJsonFlag (v8::Isolate* isolate, v8::Handle const obj, TRI_json_t* json) { v8::HandleScope scope(isolate); bool geoJson = ExtractBoolFlag(isolate, obj, TRI_V8_ASCII_STRING("geoJson"), false); TRI_Insert3ObjectJson(TRI_UNKNOWN_MEM_ZONE, json, "geoJson", TRI_CreateBooleanJson(TRI_UNKNOWN_MEM_ZONE, geoJson)); return TRI_ERROR_NO_ERROR; } //////////////////////////////////////////////////////////////////////////////// /// @brief process the sparse flag and add it to the json //////////////////////////////////////////////////////////////////////////////// static int ProcessIndexSparseFlag (v8::Isolate* isolate, v8::Handle const obj, TRI_json_t* json, 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); TRI_Insert3ObjectJson(TRI_UNKNOWN_MEM_ZONE, json, "sparse", TRI_CreateBooleanJson(TRI_UNKNOWN_MEM_ZONE, sparse)); } else if (create) { // not set. now add a default value TRI_Insert3ObjectJson(TRI_UNKNOWN_MEM_ZONE, json, "sparse", TRI_CreateBooleanJson(TRI_UNKNOWN_MEM_ZONE, false)); } return TRI_ERROR_NO_ERROR; } //////////////////////////////////////////////////////////////////////////////// /// @brief process the unique flag and add it to the json //////////////////////////////////////////////////////////////////////////////// static int ProcessIndexUniqueFlag (v8::Isolate* isolate, v8::Handle const obj, TRI_json_t* json) { v8::HandleScope scope(isolate); bool unique = ExtractBoolFlag(isolate, obj, TRI_V8_ASCII_STRING("unique"), false); TRI_Insert3ObjectJson(TRI_UNKNOWN_MEM_ZONE, json, "unique", TRI_CreateBooleanJson(TRI_UNKNOWN_MEM_ZONE, unique)); return TRI_ERROR_NO_ERROR; } //////////////////////////////////////////////////////////////////////////////// /// @brief enhances the json of a geo1 index //////////////////////////////////////////////////////////////////////////////// static int EnhanceJsonIndexGeo1 (v8::Isolate* isolate, v8::Handle const obj, TRI_json_t* json, bool create) { int res = ProcessIndexFields(isolate, obj, json, 1, create); if (ServerState::instance()->isCoordinator()) { TRI_Insert3ObjectJson(TRI_UNKNOWN_MEM_ZONE, json, "ignoreNull", TRI_CreateBooleanJson(TRI_UNKNOWN_MEM_ZONE, true)); TRI_Insert3ObjectJson(TRI_UNKNOWN_MEM_ZONE, json, "constraint", TRI_CreateBooleanJson(TRI_UNKNOWN_MEM_ZONE, false)); } TRI_Insert3ObjectJson(TRI_UNKNOWN_MEM_ZONE, json, "sparse", TRI_CreateBooleanJson(TRI_UNKNOWN_MEM_ZONE, true)); TRI_Insert3ObjectJson(TRI_UNKNOWN_MEM_ZONE, json, "unique", TRI_CreateBooleanJson(TRI_UNKNOWN_MEM_ZONE, false)); ProcessIndexGeoJsonFlag(isolate, obj, json); return res; } //////////////////////////////////////////////////////////////////////////////// /// @brief enhances the json of a geo2 index //////////////////////////////////////////////////////////////////////////////// static int EnhanceJsonIndexGeo2 (v8::Isolate* isolate, v8::Handle const obj, TRI_json_t* json, bool create) { int res = ProcessIndexFields(isolate, obj, json, 2, create); if (ServerState::instance()->isCoordinator()) { TRI_Insert3ObjectJson(TRI_UNKNOWN_MEM_ZONE, json, "ignoreNull", TRI_CreateBooleanJson(TRI_UNKNOWN_MEM_ZONE, true)); TRI_Insert3ObjectJson(TRI_UNKNOWN_MEM_ZONE, json, "constraint", TRI_CreateBooleanJson(TRI_UNKNOWN_MEM_ZONE, false)); } TRI_Insert3ObjectJson(TRI_UNKNOWN_MEM_ZONE, json, "sparse", TRI_CreateBooleanJson(TRI_UNKNOWN_MEM_ZONE, true)); TRI_Insert3ObjectJson(TRI_UNKNOWN_MEM_ZONE, json, "unique", TRI_CreateBooleanJson(TRI_UNKNOWN_MEM_ZONE, false)); ProcessIndexGeoJsonFlag(isolate, obj, json); return res; } //////////////////////////////////////////////////////////////////////////////// /// @brief enhances the json of a hash index //////////////////////////////////////////////////////////////////////////////// static int EnhanceJsonIndexHash (v8::Isolate* isolate, v8::Handle const obj, TRI_json_t* json, bool create) { int res = ProcessIndexFields(isolate, obj, json, 0, create); ProcessIndexSparseFlag(isolate, obj, json, create); ProcessIndexUniqueFlag(isolate, obj, json); return res; } //////////////////////////////////////////////////////////////////////////////// /// @brief enhances the json of a skiplist index //////////////////////////////////////////////////////////////////////////////// static int EnhanceJsonIndexSkiplist (v8::Isolate* isolate, v8::Handle const obj, TRI_json_t* json, bool create) { int res = ProcessIndexFields(isolate, obj, json, 0, create); ProcessIndexSparseFlag(isolate, obj, json, create); ProcessIndexUniqueFlag(isolate, obj, json); return res; } //////////////////////////////////////////////////////////////////////////////// /// @brief enhances the json of a fulltext index //////////////////////////////////////////////////////////////////////////////// static int EnhanceJsonIndexFulltext (v8::Isolate* isolate, v8::Handle const obj, TRI_json_t* json, bool create) { int res = ProcessIndexFields(isolate, obj, json, 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; } } TRI_Insert3ObjectJson(TRI_UNKNOWN_MEM_ZONE, json, "minLength", TRI_CreateNumberJson(TRI_UNKNOWN_MEM_ZONE, minWordLength)); return res; } //////////////////////////////////////////////////////////////////////////////// /// @brief enhances the json of a cap constraint //////////////////////////////////////////////////////////////////////////////// static int EnhanceJsonIndexCap (v8::Isolate* isolate, v8::Handle const obj, TRI_json_t* json) { // handle "size" attribute size_t count = 0; if (obj->Has(TRI_V8_ASCII_STRING("size")) && obj->Get(TRI_V8_ASCII_STRING("size"))->IsNumber()) { int64_t value = TRI_ObjectToInt64(obj->Get(TRI_V8_ASCII_STRING("size"))); if (value < 0 || value > INT64_MAX) { return TRI_ERROR_BAD_PARAMETER; } count = (size_t) value; } // handle "byteSize" attribute int64_t byteSize = 0; if (obj->Has(TRI_V8_ASCII_STRING("byteSize")) && obj->Get(TRI_V8_ASCII_STRING("byteSize"))->IsNumber()) { byteSize = TRI_ObjectToInt64(obj->Get(TRI_V8_ASCII_STRING("byteSize"))); } if (count == 0 && byteSize <= 0) { return TRI_ERROR_BAD_PARAMETER; } if (byteSize < 0 || (byteSize > 0 && byteSize < triagens::arango::CapConstraint::MinSize)) { return TRI_ERROR_BAD_PARAMETER; } TRI_Insert3ObjectJson(TRI_UNKNOWN_MEM_ZONE, json, "size", TRI_CreateNumberJson(TRI_UNKNOWN_MEM_ZONE, (double) count)); TRI_Insert3ObjectJson(TRI_UNKNOWN_MEM_ZONE, json, "byteSize", TRI_CreateNumberJson(TRI_UNKNOWN_MEM_ZONE, (double) byteSize)); return TRI_ERROR_NO_ERROR; } //////////////////////////////////////////////////////////////////////////////// /// @brief enhances the json of an index //////////////////////////////////////////////////////////////////////////////// static int EnhanceIndexJson (const v8::FunctionCallbackInfo& args, TRI_json_t*& json, bool create) { v8::Isolate* isolate = args.GetIsolate(); v8::HandleScope scope(isolate); v8::Handle obj = args[0].As(); // extract index type triagens::arango::Index::IndexType type = triagens::arango::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; } 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 = triagens::arango::Index::type(t.c_str()); } if (type == triagens::arango::Index::TRI_IDX_TYPE_UNKNOWN) { return TRI_ERROR_BAD_PARAMETER; } if (create) { if (type == triagens::arango::Index::TRI_IDX_TYPE_PRIMARY_INDEX || type == triagens::arango::Index::TRI_IDX_TYPE_EDGE_INDEX) { // creating these indexes yourself is forbidden return TRI_ERROR_FORBIDDEN; } } json = TRI_CreateObjectJson(TRI_UNKNOWN_MEM_ZONE); if (json == nullptr) { return TRI_ERROR_OUT_OF_MEMORY; } 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); TRI_Insert3ObjectJson(TRI_UNKNOWN_MEM_ZONE, json, "id", TRI_CreateStringCopyJson(TRI_UNKNOWN_MEM_ZONE, idString, strlen(idString))); TRI_FreeString(TRI_CORE_MEM_ZONE, idString); } } char const* idxType = triagens::arango::Index::typeName(type); TRI_Insert3ObjectJson(TRI_UNKNOWN_MEM_ZONE, json, "type", TRI_CreateStringCopyJson(TRI_UNKNOWN_MEM_ZONE, idxType, strlen(idxType))); int res = TRI_ERROR_INTERNAL; switch (type) { case triagens::arango::Index::TRI_IDX_TYPE_UNKNOWN: case triagens::arango::Index::TRI_IDX_TYPE_PRIORITY_QUEUE_INDEX: { res = TRI_ERROR_BAD_PARAMETER; break; } case triagens::arango::Index::TRI_IDX_TYPE_PRIMARY_INDEX: case triagens::arango::Index::TRI_IDX_TYPE_EDGE_INDEX: case triagens::arango::Index::TRI_IDX_TYPE_BITARRAY_INDEX: { break; } case triagens::arango::Index::TRI_IDX_TYPE_GEO1_INDEX: res = EnhanceJsonIndexGeo1(isolate, obj, json, create); break; case triagens::arango::Index::TRI_IDX_TYPE_GEO2_INDEX: res = EnhanceJsonIndexGeo2(isolate, obj, json, create); break; case triagens::arango::Index::TRI_IDX_TYPE_HASH_INDEX: res = EnhanceJsonIndexHash(isolate, obj, json, create); break; case triagens::arango::Index::TRI_IDX_TYPE_SKIPLIST_INDEX: res = EnhanceJsonIndexSkiplist(isolate, obj, json, create); break; case triagens::arango::Index::TRI_IDX_TYPE_FULLTEXT_INDEX: res = EnhanceJsonIndexFulltext(isolate, obj, json, create); break; case triagens::arango::Index::TRI_IDX_TYPE_CAP_CONSTRAINT: res = EnhanceJsonIndexCap(isolate, obj, json); break; } return res; } //////////////////////////////////////////////////////////////////////////////// /// @brief ensures an index, coordinator case //////////////////////////////////////////////////////////////////////////////// static void EnsureIndexCoordinator (const v8::FunctionCallbackInfo& args, TRI_vocbase_col_t const* collection, TRI_json_t const* json, bool create) { v8::Isolate* isolate = args.GetIsolate(); v8::HandleScope scope(isolate); TRI_ASSERT(collection != nullptr); TRI_ASSERT(json != nullptr); string const databaseName(collection->_dbName); string const cid = StringUtils::itoa(collection->_cid); // TODO: protect against races on _name string const collectionName(collection->_name); TRI_json_t* resultJson = nullptr; string errorMsg; int res = ClusterInfo::instance()->ensureIndexCoordinator(databaseName, cid, json, create, &triagens::arango::Index::Compare, resultJson, errorMsg, 360.0); if (res != TRI_ERROR_NO_ERROR) { TRI_V8_THROW_EXCEPTION_MESSAGE(res, errorMsg); } if (resultJson == nullptr) { if (! create) { // did not find a suitable index TRI_V8_RETURN_NULL(); } TRI_V8_THROW_EXCEPTION_MEMORY(); } v8::Handle ret = IndexRep(isolate, collectionName, resultJson); TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, resultJson); TRI_V8_RETURN(ret); } //////////////////////////////////////////////////////////////////////////////// /// @brief ensures an index, locally //////////////////////////////////////////////////////////////////////////////// static void EnsureIndexLocal (const v8::FunctionCallbackInfo& args, TRI_vocbase_col_t const* collection, TRI_json_t const* json, bool create) { v8::Isolate* isolate = args.GetIsolate(); v8::HandleScope scope(isolate); TRI_ASSERT(collection != nullptr); TRI_ASSERT(json != nullptr); // extract type TRI_json_t* value = TRI_LookupObjectJson(json, "type"); if (! TRI_IsStringJson(value)) { TRI_V8_THROW_EXCEPTION_MEMORY(); } triagens::arango::Index::IndexType type = triagens::arango::Index::type(value->_value._string.data); // extract unique flag bool unique = false; value = TRI_LookupObjectJson(json, "unique"); if (TRI_IsBooleanJson(value)) { unique = value->_value._boolean; } // extract sparse flag bool sparse = false; int sparsity = -1; // not set value = TRI_LookupObjectJson(json, "sparse"); if (TRI_IsBooleanJson(value)) { sparse = value->_value._boolean; sparsity = sparse ? 1 : 0; } // extract id TRI_idx_iid_t iid = 0; value = TRI_LookupObjectJson(json, "id"); if (TRI_IsStringJson(value)) { iid = TRI_UInt64String2(value->_value._string.data, value->_value._string.length - 1); } std::vector attributes; // extract fields value = TRI_LookupObjectJson(json, "fields"); if (TRI_IsArrayJson(value)) { // note: "fields" is not mandatory for all index types // copy all field names (attributes) size_t const n = TRI_LengthArrayJson(value); for (size_t i = 0; i < n; ++i) { auto v = static_cast(TRI_AtVector(&value->_value._objects, i)); if (TRI_IsStringJson(v)) { attributes.emplace_back(std::string(v->_value._string.data, v->_value._string.length - 1)); } } } SingleCollectionReadOnlyTransaction trx(new V8TransactionContext(true), collection->_vocbase, collection->_cid); 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; triagens::arango::Index* idx = nullptr; switch (type) { case triagens::arango::Index::TRI_IDX_TYPE_UNKNOWN: case triagens::arango::Index::TRI_IDX_TYPE_PRIMARY_INDEX: case triagens::arango::Index::TRI_IDX_TYPE_EDGE_INDEX: case triagens::arango::Index::TRI_IDX_TYPE_PRIORITY_QUEUE_INDEX: case triagens::arango::Index::TRI_IDX_TYPE_BITARRAY_INDEX: { // these indexes cannot be created directly TRI_V8_THROW_EXCEPTION(TRI_ERROR_INTERNAL); } case triagens::arango::Index::TRI_IDX_TYPE_GEO1_INDEX: { if (attributes.size() != 1) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_INTERNAL); } bool geoJson = false; value = TRI_LookupObjectJson(json, "geoJson"); if (TRI_IsBooleanJson(value)) { geoJson = value->_value._boolean; } if (create) { idx = static_cast(TRI_EnsureGeoIndex1DocumentCollection(document, iid, attributes[0], geoJson, &created)); } else { idx = static_cast(TRI_LookupGeoIndex1DocumentCollection(document, attributes[0], geoJson)); } break; } case triagens::arango::Index::TRI_IDX_TYPE_GEO2_INDEX: { if (attributes.size() != 2) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_INTERNAL); } if (create) { idx = static_cast(TRI_EnsureGeoIndex2DocumentCollection(document, iid, attributes[0], attributes[1], &created)); } else { idx = static_cast(TRI_LookupGeoIndex2DocumentCollection(document, attributes[0], attributes[1])); } break; } case triagens::arango::Index::TRI_IDX_TYPE_HASH_INDEX: { if (attributes.empty()) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_INTERNAL); } if (create) { idx = static_cast(TRI_EnsureHashIndexDocumentCollection(document, iid, attributes, sparse, unique, &created)); } else { idx = static_cast(TRI_LookupHashIndexDocumentCollection(document, attributes, sparsity, unique)); } break; } case triagens::arango::Index::TRI_IDX_TYPE_SKIPLIST_INDEX: { if (attributes.empty()) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_INTERNAL); } if (create) { idx = static_cast(TRI_EnsureSkiplistIndexDocumentCollection(document, iid, attributes, sparse, unique, &created)); } else { idx = static_cast(TRI_LookupSkiplistIndexDocumentCollection(document, attributes, sparsity, unique)); } break; } case triagens::arango::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; TRI_json_t const* value = TRI_LookupObjectJson(json, "minLength"); if (TRI_IsNumberJson(value)) { minWordLength = (int) value->_value._number; } else if (value != nullptr) { // minLength defined but no number TRI_V8_THROW_EXCEPTION_PARAMETER(" must be a number"); } if (create) { idx = static_cast(TRI_EnsureFulltextIndexDocumentCollection(document, iid, attributes[0], minWordLength, &created)); } else { idx = static_cast(TRI_LookupFulltextIndexDocumentCollection(document, attributes[0], minWordLength)); } break; } case triagens::arango::Index::TRI_IDX_TYPE_CAP_CONSTRAINT: { size_t size = 0; TRI_json_t const* value = TRI_LookupObjectJson(json, "size"); if (TRI_IsNumberJson(value)) { size = (size_t) value->_value._number; } int64_t byteSize = 0; value = TRI_LookupObjectJson(json, "byteSize"); if (TRI_IsNumberJson(value)) { byteSize = (int64_t) value->_value._number; } if (create) { idx = static_cast(TRI_EnsureCapConstraintDocumentCollection(document, iid, size, byteSize, &created)); } else { idx = static_cast(TRI_LookupCapConstraintDocumentCollection(document)); } 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 auto indexJson = idx->toJson(TRI_UNKNOWN_MEM_ZONE, false); if (indexJson.json() == nullptr) { TRI_V8_THROW_EXCEPTION_MEMORY(); } v8::Handle ret = IndexRep(isolate, collectionName, indexJson.json()); 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 (const v8::FunctionCallbackInfo& 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()) { string name(functionName); name.append("()"); TRI_V8_THROW_EXCEPTION_USAGE(name.c_str()); } TRI_json_t* json = nullptr; int res = EnhanceIndexJson(args, json, create); if (res == TRI_ERROR_NO_ERROR && ServerState::instance()->isCoordinator()) { std::string const dbname(collection->_dbName); // TODO: someone might rename the collection while we're reading its name... std::string const collname(collection->_name); shared_ptr const& 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) { TRI_json_t const* v = TRI_LookupObjectJson(json, "unique"); if (TRI_IsBooleanJson(v) && v->_value._boolean) { // unique index, now check if fields and shard keys match TRI_json_t const* flds = TRI_LookupObjectJson(json, "fields"); if (TRI_IsArrayJson(flds) && c->numberOfShards() > 1) { vector const& shardKeys = c->shardKeys(); size_t const n = TRI_LengthArrayJson(flds); if (shardKeys.size() != n) { res = TRI_ERROR_CLUSTER_UNSUPPORTED; } else { for (size_t i = 0; i < n; ++i) { TRI_json_t const* f = TRI_LookupArrayJson(flds, i); if (! TRI_IsStringJson(f)) { res = TRI_ERROR_INTERNAL; continue; } else { if (! TRI_EqualString(f->_value._string.data, shardKeys[i].c_str())) { res = TRI_ERROR_CLUSTER_UNSUPPORTED; } } } } } } } } if (res != TRI_ERROR_NO_ERROR) { if (json != nullptr) { TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, json); } TRI_V8_THROW_EXCEPTION(res); } TRI_ASSERT(json != nullptr); // ensure an index, coordinator case if (ServerState::instance()->isCoordinator()) { EnsureIndexCoordinator(args, collection, json, create); } else { EnsureIndexLocal(args, collection, json, create); } TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, json); } //////////////////////////////////////////////////////////////////////////////// /// @brief create a collection on the coordinator //////////////////////////////////////////////////////////////////////////////// static void CreateCollectionCoordinator (const v8::FunctionCallbackInfo& args, TRI_col_type_e collectionType, std::string const& databaseName, TRI_col_info_t& parameters, TRI_vocbase_t* vocbase) { v8::Isolate* isolate = args.GetIsolate(); v8::HandleScope scope(isolate); 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; vector shardKeys; // default shard key shardKeys.push_back("_key"); string distributeShardsLike; 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"))) { 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()) { 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"))); } } if (numberOfShards == 0 || numberOfShards > 1000) { TRI_V8_THROW_EXCEPTION_PARAMETER("invalid number of shards"); } 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 plus one for each shard to create uint64_t const id = ci->uniqid(1 + numberOfShards); // collection id is the first unique id we got string const cid = StringUtils::itoa(id); vector dbServers; if (distributeShardsLike.empty()) { // fetch list of available servers in cluster, and shuffle them randomly dbServers = ci->getCurrentDBServers(); if (dbServers.empty()) { TRI_V8_THROW_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "no database servers found in cluster"); } random_shuffle(dbServers.begin(), dbServers.end()); } else { CollectionNameResolver resolver(vocbase); TRI_voc_cid_t otherCid = resolver.getCollectionIdCluster(distributeShardsLike); shared_ptr collInfo = ci->getCollection(databaseName, triagens::basics::StringUtils::itoa(otherCid)); auto shards = collInfo->shardIds(); for (auto it = shards.begin(); it != shards.end(); ++it) { dbServers.push_back(it->second); } // FIXME: need to sort shards numerically and not alphabetically } // now create the shards std::map shards; for (uint64_t i = 0; i < numberOfShards; ++i) { // determine responsible server string serverId = dbServers[i % dbServers.size()]; // determine shard id string shardId = "s" + StringUtils::itoa(id + 1 + i); shards.insert(std::make_pair(shardId, serverId)); } // now create the JSON for the collection TRI_json_t* json = TRI_CreateObjectJson(TRI_UNKNOWN_MEM_ZONE); if (json == nullptr) { TRI_V8_THROW_EXCEPTION_MEMORY(); } TRI_Insert3ObjectJson(TRI_UNKNOWN_MEM_ZONE, json, "id", TRI_CreateStringCopyJson(TRI_UNKNOWN_MEM_ZONE, cid.c_str(), cid.size())); TRI_Insert3ObjectJson(TRI_UNKNOWN_MEM_ZONE, json, "name", TRI_CreateStringCopyJson(TRI_UNKNOWN_MEM_ZONE, name.c_str(), name.size())); TRI_Insert3ObjectJson(TRI_UNKNOWN_MEM_ZONE, json, "type", TRI_CreateNumberJson(TRI_UNKNOWN_MEM_ZONE, (int) collectionType)); TRI_Insert3ObjectJson(TRI_UNKNOWN_MEM_ZONE, json, "status", TRI_CreateNumberJson(TRI_UNKNOWN_MEM_ZONE, (int) TRI_VOC_COL_STATUS_LOADED)); TRI_Insert3ObjectJson(TRI_UNKNOWN_MEM_ZONE, json, "deleted", TRI_CreateBooleanJson(TRI_UNKNOWN_MEM_ZONE, parameters._deleted)); TRI_Insert3ObjectJson(TRI_UNKNOWN_MEM_ZONE, json, "doCompact", TRI_CreateBooleanJson(TRI_UNKNOWN_MEM_ZONE, parameters._doCompact)); TRI_Insert3ObjectJson(TRI_UNKNOWN_MEM_ZONE, json, "isSystem", TRI_CreateBooleanJson(TRI_UNKNOWN_MEM_ZONE, parameters._isSystem)); TRI_Insert3ObjectJson(TRI_UNKNOWN_MEM_ZONE, json, "isVolatile", TRI_CreateBooleanJson(TRI_UNKNOWN_MEM_ZONE, parameters._isVolatile)); TRI_Insert3ObjectJson(TRI_UNKNOWN_MEM_ZONE, json, "waitForSync", TRI_CreateBooleanJson(TRI_UNKNOWN_MEM_ZONE, parameters._waitForSync)); TRI_Insert3ObjectJson(TRI_UNKNOWN_MEM_ZONE, json, "journalSize", TRI_CreateNumberJson(TRI_UNKNOWN_MEM_ZONE, parameters._maximalSize)); TRI_Insert3ObjectJson(TRI_UNKNOWN_MEM_ZONE, json, "indexBuckets", TRI_CreateNumberJson(TRI_UNKNOWN_MEM_ZONE, parameters._indexBuckets)); TRI_json_t* keyOptions = TRI_CreateObjectJson(TRI_UNKNOWN_MEM_ZONE); if (keyOptions != nullptr) { TRI_Insert3ObjectJson(TRI_UNKNOWN_MEM_ZONE, keyOptions, "type", TRI_CreateStringCopyJson(TRI_UNKNOWN_MEM_ZONE, "traditional", strlen("traditional"))); TRI_Insert3ObjectJson(TRI_UNKNOWN_MEM_ZONE, keyOptions, "allowUserKeys", TRI_CreateBooleanJson(TRI_UNKNOWN_MEM_ZONE, allowUserKeys)); TRI_Insert3ObjectJson(TRI_UNKNOWN_MEM_ZONE, json, "keyOptions", keyOptions); } TRI_Insert3ObjectJson(TRI_UNKNOWN_MEM_ZONE, json, "shardKeys", JsonHelper::stringArray(TRI_UNKNOWN_MEM_ZONE, shardKeys)); TRI_Insert3ObjectJson(TRI_UNKNOWN_MEM_ZONE, json, "shards", JsonHelper::stringObject(TRI_UNKNOWN_MEM_ZONE, shards)); TRI_json_t* indexes = TRI_CreateArrayJson(TRI_UNKNOWN_MEM_ZONE); if (indexes == nullptr) { TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, json); TRI_V8_THROW_EXCEPTION_MEMORY(); } // create a dummy primary index std::unique_ptr primaryIndex(new triagens::arango::PrimaryIndex(nullptr)); auto idxJson = primaryIndex->toJson(TRI_UNKNOWN_MEM_ZONE, false); TRI_PushBack3ArrayJson(TRI_UNKNOWN_MEM_ZONE, indexes, TRI_CopyJson(TRI_UNKNOWN_MEM_ZONE, idxJson.json())); if (collectionType == TRI_COL_TYPE_EDGE) { // create a dummy edge index std::unique_ptr edgeIndex(new triagens::arango::EdgeIndex(id, nullptr)); auto idxJson = edgeIndex->toJson(TRI_UNKNOWN_MEM_ZONE, false); TRI_PushBack3ArrayJson(TRI_UNKNOWN_MEM_ZONE, indexes, TRI_CopyJson(TRI_UNKNOWN_MEM_ZONE, idxJson.json())); } TRI_Insert3ObjectJson(TRI_UNKNOWN_MEM_ZONE, json, "indexes", indexes); string errorMsg; int myerrno = ci->createCollectionCoordinator(databaseName, cid, numberOfShards, json, errorMsg, 240.0); TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, json); if (myerrno != TRI_ERROR_NO_ERROR) { TRI_V8_THROW_EXCEPTION_MESSAGE(myerrno, errorMsg); } ci->loadPlannedCollections(); shared_ptr const& c = ci->getCollection(databaseName, cid); TRI_vocbase_col_t* newcoll = CoordinatorCollection(vocbase, *c); TRI_V8_RETURN(WrapCollection(isolate, newcoll)); } //////////////////////////////////////////////////////////////////////////////// /// @brief ensures that an index exists /// @startDocuBlock collectionEnsureIndex /// `collection.ensureIndex(index-description)` /// /// Ensures that an index according to the *index-description* exists. A /// new index will be created if none exists with the given description. /// /// The *index-description* must contain at least a *type* attribute. /// Other attributes may be necessary, depending on the index type. /// /// **type** can be one of the following values: /// - *hash*: hash index /// - *skiplist*: skiplist index /// - *fulltext*: fulltext index /// - *geo1*: geo index, with one attribute /// - *geo2*: geo index, with two attributes /// - *cap*: cap constraint /// /// **sparse** can be *true* or *false*. /// /// For *hash*, and *skiplist* the sparsity can be controlled, *fulltext* and *geo* /// are [sparse](WhichIndex.html) by definition. /// /// **unique** can be *true* or *false* and is supported by *hash* or *skiplist* /// /// Calling this method returns an index object. Whether or not the index /// object existed before the call is indicated in the return attribute /// *isNewlyCreated*. /// /// @EXAMPLES /// /// @EXAMPLE_ARANGOSH_OUTPUT{collectionEnsureIndex} /// ~db._create("test"); /// db.test.ensureIndex({ type: "hash", fields: [ "a" ], sparse: true }); /// db.test.ensureIndex({ type: "hash", fields: [ "a", "b" ], unique: true }); /// ~db._drop("test"); /// @END_EXAMPLE_ARANGOSH_OUTPUT /// /// @endDocuBlock //////////////////////////////////////////////////////////////////////////////// static void JS_EnsureIndexVocbaseCol (const v8::FunctionCallbackInfo& 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 (const v8::FunctionCallbackInfo& 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 (const v8::FunctionCallbackInfo& args, TRI_vocbase_col_t const* collection, v8::Handle const val) { v8::Isolate* isolate = args.GetIsolate(); v8::HandleScope scope(isolate); 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); } } string const databaseName(collection->_dbName); string const cid = StringUtils::itoa(collection->_cid); 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 drops an index /// @startDocuBlock col_dropIndex /// `collection.dropIndex(index)` /// /// Drops the index. If the index does not exist, then *false* is /// returned. If the index existed and was dropped, then *true* is /// returned. Note that you cannot drop some special indexes (e.g. the primary /// index of a collection or the edge index of an edge collection). /// /// `collection.dropIndex(index-handle)` /// /// Same as above. Instead of an index an index handle can be given. /// /// @EXAMPLE_ARANGOSH_OUTPUT{col_dropIndex} /// ~db._create("example"); /// db.example.ensureSkiplist("a", "b"); /// var indexInfo = db.example.getIndexes(); /// indexInfo; /// db.example.dropIndex(indexInfo[0]) /// db.example.dropIndex(indexInfo[1].id) /// indexInfo = db.example.getIndexes(); /// ~db._drop("example"); /// @END_EXAMPLE_ARANGOSH_OUTPUT /// /// @endDocuBlock //////////////////////////////////////////////////////////////////////////////// static void JS_DropIndexVocbaseCol (const v8::FunctionCallbackInfo& 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; } SingleCollectionReadOnlyTransaction trx(new V8TransactionContext(true), collection->_vocbase, collection->_cid); 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) { return; } if (idx->id() == 0) { TRI_V8_RETURN_FALSE(); } if (idx->type() == triagens::arango::Index::TRI_IDX_TYPE_PRIMARY_INDEX || idx->type() == triagens::arango::Index::TRI_IDX_TYPE_EDGE_INDEX) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_FORBIDDEN); } // ............................................................................. // inside a write transaction, write-lock is acquired by TRI_DropIndex... // ............................................................................. bool ok = TRI_DropIndexDocumentCollection(document, idx->id(), true); // ............................................................................. // outside a write transaction // ............................................................................. 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 (const v8::FunctionCallbackInfo& args, TRI_vocbase_col_t const* collection) { v8::Isolate* isolate = args.GetIsolate(); v8::HandleScope scope(isolate); string const databaseName(collection->_dbName); string const cid = StringUtils::itoa(collection->_cid); 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); TRI_json_t const* json = (*c).getIndexes(); if (TRI_IsArrayJson(json)) { uint32_t j = 0; size_t const n = TRI_LengthArrayJson(json); for (size_t i = 0; i < n; ++i) { TRI_json_t const* v = TRI_LookupArrayJson(json, i); if (v != nullptr) { ret->Set(j++, IndexRep(isolate, collectionName, v)); } } } TRI_V8_RETURN(ret); } //////////////////////////////////////////////////////////////////////////////// /// @brief returns information about the indexes /// @startDocuBlock collectionGetIndexes /// `getIndexes()` /// /// Returns an array of all indexes defined for the collection. /// /// Note that `_key` implicitely has an index assigned to it. /// /// @EXAMPLE_ARANGOSH_OUTPUT{collectionGetIndexes} /// ~db._create("test"); /// ~db.test.ensureUniqueSkiplist("skiplistAttribute"); /// ~db.test.ensureUniqueSkiplist("skiplistUniqueAttribute"); /// ~db.test.ensureHashIndex("hashListAttribute", "hashListSecondAttribute.subAttribute"); /// db.test.getIndexes(); /// ~db._drop("test"); /// @END_EXAMPLE_ARANGOSH_OUTPUT /// @endDocuBlock //////////////////////////////////////////////////////////////////////////////// static void JS_GetIndexesVocbaseCol (const v8::FunctionCallbackInfo& 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]); } SingleCollectionReadOnlyTransaction trx(new V8TransactionContext(true), collection->_vocbase, collection->_cid); 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.json())); } TRI_V8_RETURN(result); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief looks up an index identifier //////////////////////////////////////////////////////////////////////////////// triagens::arango::Index* TRI_LookupIndexByHandle (v8::Isolate* isolate, triagens::arango::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 (const v8::FunctionCallbackInfo& 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(); // set default journal size TRI_voc_size_t effectiveSize = vocbase->_settings.defaultMaximalSize; // extract the name string const name = TRI_ObjectToString(args[0]); // extract the parameters TRI_col_info_t parameters; TRI_voc_cid_t cid = 0; if (2 <= args.Length()) { if (! args[1]->IsObject()) { TRI_V8_THROW_TYPE_ERROR(" must be an object"); } v8::Handle p = args[1]->ToObject(); TRI_GET_GLOBALS(); TRI_GET_GLOBAL_STRING(JournalSizeKey); if (p->Has(JournalSizeKey)) { double s = TRI_ObjectToDouble(p->Get(JournalSizeKey)); if (s < TRI_JOURNAL_MINIMAL_SIZE) { TRI_V8_THROW_EXCEPTION_PARAMETER(".journalSize is too small"); } // overwrite journal size with user-specified value effectiveSize = (TRI_voc_size_t) s; } // get optional values TRI_json_t* keyOptions = nullptr; TRI_GET_GLOBAL_STRING(KeyOptionsKey); if (p->Has(KeyOptionsKey)) { keyOptions = TRI_ObjectToJson(isolate, p->Get(KeyOptionsKey)); } // TRI_InitCollectionInfo will copy keyOptions TRI_InitCollectionInfo(vocbase, ¶meters, name.c_str(), collectionType, effectiveSize, keyOptions); if (keyOptions != nullptr) { TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, keyOptions); } if (p->Has(TRI_V8_ASCII_STRING("planId"))) { parameters._planId = TRI_ObjectToUInt64(p->Get(TRI_V8_ASCII_STRING("planId")), true); } TRI_GET_GLOBAL_STRING(WaitForSyncKey); if (p->Has(WaitForSyncKey)) { parameters._waitForSync = TRI_ObjectToBoolean(p->Get(WaitForSyncKey)); } TRI_GET_GLOBAL_STRING(DoCompactKey); if (p->Has(DoCompactKey)) { parameters._doCompact = TRI_ObjectToBoolean(p->Get(DoCompactKey)); } else { // default value for compaction parameters._doCompact = true; } TRI_GET_GLOBAL_STRING(IsSystemKey); if (p->Has(IsSystemKey)) { parameters._isSystem = TRI_ObjectToBoolean(p->Get(IsSystemKey)); } TRI_GET_GLOBAL_STRING(IsVolatileKey); if (p->Has(IsVolatileKey)) { #ifdef TRI_HAVE_ANONYMOUS_MMAP parameters._isVolatile = TRI_ObjectToBoolean(p->Get(IsVolatileKey)); #else TRI_FreeCollectionInfoOptions(¶meters); TRI_V8_THROW_EXCEPTION_PARAMETER("volatile collections are not supported on this platform"); #endif } if (parameters._isVolatile && parameters._waitForSync) { // the combination of waitForSync and isVolatile makes no sense TRI_FreeCollectionInfoOptions(¶meters); TRI_V8_THROW_EXCEPTION_PARAMETER("volatile collections do not support the waitForSync option"); } TRI_GET_GLOBAL_STRING(IdKey); if (p->Has(IdKey)) { // specify collection id - used for testing only cid = TRI_ObjectToUInt64(p->Get(IdKey), true); } if (p->Has(TRI_V8_ASCII_STRING("indexBuckets"))) { parameters._indexBuckets = static_cast(TRI_ObjectToUInt64(p->Get(TRI_V8_ASCII_STRING("indexBuckets")), true)); if (parameters._indexBuckets < 1 || parameters._indexBuckets > 1024) { TRI_FreeCollectionInfoOptions(¶meters); TRI_V8_THROW_EXCEPTION_PARAMETER("indexBuckets must be a two-power between 1 and 1024"); } } } else { TRI_InitCollectionInfo(vocbase, ¶meters, name.c_str(), collectionType, effectiveSize, nullptr); } if (ServerState::instance()->isCoordinator()) { CreateCollectionCoordinator(args, collectionType, vocbase->_name, parameters, vocbase); TRI_FreeCollectionInfoOptions(¶meters); return; } TRI_vocbase_col_t const* collection = TRI_CreateCollectionVocBase(vocbase, ¶meters, cid, true); TRI_FreeCollectionInfoOptions(¶meters); 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 creates a new document or edge collection /// @startDocuBlock collectionDatabaseCreate /// `db._create(collection-name)` /// /// Creates a new document collection named *collection-name*. /// If the collection name already exists or if the name format is invalid, an /// error is thrown. For more information on valid collection names please refer /// to the [naming conventions](../NamingConventions/README.md). /// /// `db._create(collection-name, properties)` /// /// *properties* must be an object with the following attributes: /// /// * *waitForSync* (optional, default *false*): If *true* creating /// a document will only return after the data was synced to disk. /// /// * *journalSize* (optional, default is a /// configuration parameter: The maximal /// size of a journal or datafile. Note that this also limits the maximal /// size of a single object. Must be at least 1MB. /// /// * *isSystem* (optional, default is *false*): If *true*, create a /// system collection. In this case *collection-name* should start with /// an underscore. End users should normally create non-system collections /// only. API implementors may be required to create system collections in /// very special occasions, but normally a regular collection will do. /// /// * *isVolatile* (optional, default is *false*): If *true then the /// collection data is kept in-memory only and not made persistent. Unloading /// the collection will cause the collection data to be discarded. Stopping /// or re-starting the server will also cause full loss of data in the /// collection. Setting this option will make the resulting collection be /// slightly faster than regular collections because ArangoDB does not /// enforce any synchronization to disk and does not calculate any CRC /// checksums for datafiles (as there are no datafiles). /// /// * *keyOptions* (optional): additional options for key generation. If /// specified, then *keyOptions* should be a JSON array containing the /// following attributes (**note**: some of them are optional): /// * *type*: specifies the type of the key generator. The currently /// available generators are *traditional* and *autoincrement*. /// * *allowUserKeys*: if set to *true*, then it is allowed to supply /// own key values in the *_key* attribute of a document. If set to /// *false*, then the key generator will solely be responsible for /// generating keys and supplying own key values in the *_key* attribute /// of documents is considered an error. /// * *increment*: increment value for *autoincrement* key generator. /// Not used for other key generator types. /// * *offset*: initial offset value for *autoincrement* key generator. /// Not used for other key generator types. /// /// * *numberOfShards* (optional, default is *1*): in a cluster, this value /// determines the number of shards to create for the collection. In a single /// server setup, this option is meaningless. /// /// * *shardKeys* (optional, default is *[ "_key" ]*): in a cluster, this /// attribute determines which document attributes are used to determine the /// target shard for documents. Documents are sent to shards based on the /// values they have in their shard key attributes. The values of all shard /// key attributes in a document are hashed, and the hash value is used to /// determine the target shard. Note that values of shard key attributes cannot /// be changed once set. /// This option is meaningless in a single server setup. /// /// When choosing the shard keys, one must be aware of the following /// rules and limitations: In a sharded collection with more than /// one shard it is not possible to set up a unique constraint on /// an attribute that is not the one and only shard key given in /// *shardKeys*. This is because enforcing a unique constraint /// would otherwise make a global index necessary or need extensive /// communication for every single write operation. Furthermore, if /// *_key* is not the one and only shard key, then it is not possible /// to set the *_key* attribute when inserting a document, provided /// the collection has more than one shard. Again, this is because /// the database has to enforce the unique constraint on the *_key* /// attribute and this can only be done efficiently if this is the /// only shard key by delegating to the individual shards. /// /// `db._create(collection-name, properties, type)` /// /// Specifies the optional *type* of the collection, it can either be *document* /// or *edge*. On default it is document. Instead of giving a type you can also use /// *db._createEdgeCollection* or *db._createDocumentCollection*. /// /// @EXAMPLES /// /// With defaults: /// /// @EXAMPLE_ARANGOSH_OUTPUT{collectionDatabaseCreate} /// c = db._create("users"); /// c.properties(); /// ~ db._drop("users"); /// @END_EXAMPLE_ARANGOSH_OUTPUT /// /// With properties: /// /// @EXAMPLE_ARANGOSH_OUTPUT{collectionDatabaseCreateProperties} /// c = db._create("users", { waitForSync : true, journalSize : 1024 * 1204 }); /// c.properties(); /// ~ db._drop("users"); /// @END_EXAMPLE_ARANGOSH_OUTPUT /// /// With a key generator: /// /// @EXAMPLE_ARANGOSH_OUTPUT{collectionDatabaseCreateKey} /// db._create("users", { keyOptions: { type: "autoincrement", offset: 10, increment: 5 } }); /// db.users.save({ name: "user 1" }); /// db.users.save({ name: "user 2" }); /// db.users.save({ name: "user 3" }); /// ~ db._drop("users"); /// @END_EXAMPLE_ARANGOSH_OUTPUT /// /// With a special key option: /// /// @EXAMPLE_ARANGOSH_OUTPUT{collectionDatabaseCreateSpecialKey} /// db._create("users", { keyOptions: { allowUserKeys: false } }); /// db.users.save({ name: "user 1" }); /// db.users.save({ name: "user 2", _key: "myuser" }); // xpError(ERROR_ARANGO_DOCUMENT_KEY_UNEXPECTED) /// db.users.save({ name: "user 3" }); /// ~ db._drop("users"); /// @END_EXAMPLE_ARANGOSH_OUTPUT /// @endDocuBlock //////////////////////////////////////////////////////////////////////////////// static void JS_CreateVocbase (const v8::FunctionCallbackInfo& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); CreateVocBase(args, TRI_COL_TYPE_DOCUMENT); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief creates a new document collection /// @startDocuBlock collectionCreateDocumentCollection /// `db._createDocumentCollection(collection-name)` /// /// Creates a new document collection named *collection-name*. If the /// document name already exists and error is thrown. /// @endDocuBlock //////////////////////////////////////////////////////////////////////////////// static void JS_CreateDocumentCollectionVocbase (const v8::FunctionCallbackInfo& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); CreateVocBase(args, TRI_COL_TYPE_DOCUMENT); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief creates a new edge collection /// @startDocuBlock collectionCreateEdgeCollection /// `db._createEdgeCollection(collection-name)` /// /// Creates a new edge collection named *collection-name*. If the /// collection name already exists an error is thrown. The default value /// for *waitForSync* is *false*. /// /// `db._createEdgeCollection(collection-name, properties)` /// /// *properties* must be an object with the following attributes: /// /// * *waitForSync* (optional, default *false*): If *true* creating /// a document will only return after the data was synced to disk. /// * *journalSize* (optional, default is /// "configuration parameter"): The maximal size of /// a journal or datafile. Note that this also limits the maximal /// size of a single object and must be at least 1MB. /// /// @endDocuBlock //////////////////////////////////////////////////////////////////////////////// static void JS_CreateEdgeCollectionVocbase (const v8::FunctionCallbackInfo& 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); }