//////////////////////////////////////////////////////////////////////////////// /// 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 Jan Steemann //////////////////////////////////////////////////////////////////////////////// #include "v8-user-structures.h" #include "Basics/Exceptions.h" #include "Basics/ReadLocker.h" #include "Basics/ReadWriteLock.h" #include "Basics/Utf8Helper.h" #include "Basics/WriteLocker.h" #include "Basics/hashes.h" #include "Basics/json.h" #include "Basics/tri-strings.h" #include "V8/v8-conv.h" #include "V8/v8-utils.h" #include "VocBase/vocbase.h" using namespace arangodb; int TRI_CompareValuesJson(TRI_json_t const* lhs, TRI_json_t const* rhs, bool useUTF8 = true); static TRI_json_t* MergeRecursive(TRI_json_t const* lhs, TRI_json_t const* rhs, bool nullMeansRemove, bool mergeObjects) { TRI_ASSERT(lhs != nullptr); std::unique_ptr result(TRI_CopyJson(lhs)); if (result == nullptr) { return nullptr; } auto r = result.get(); // shortcut variable size_t const n = TRI_LengthVector(&rhs->_value._objects); for (size_t i = 0; i < n; i += 2) { // enumerate all the replacement values auto key = static_cast(TRI_AtVector(&rhs->_value._objects, i)); auto value = static_cast(TRI_AtVector(&rhs->_value._objects, i + 1)); if (value->_type == TRI_JSON_NULL && nullMeansRemove) { // replacement value is a null and we don't want to store nulls => delete // attribute from the result TRI_DeleteObjectJson(r, key->_value._string.data); } else { // replacement value is not a null or we want to store nulls TRI_json_t const* lhsValue = TRI_LookupObjectJson(lhs, key->_value._string.data); if (lhsValue == nullptr) { // existing array does not have the attribute => append new attribute if (value->_type == TRI_JSON_OBJECT && nullMeansRemove) { TRI_json_t empty; TRI_InitObjectJson(&empty); TRI_json_t* merged = MergeRecursive(&empty, value, nullMeansRemove, mergeObjects); if (merged == nullptr) { return nullptr; } TRI_Insert3ObjectJson(r, key->_value._string.data, merged); } else { TRI_json_t* copy = TRI_CopyJson(value); if (copy == nullptr) { return nullptr; } TRI_Insert3ObjectJson(r, key->_value._string.data, copy); } } else { // existing array already has the attribute => replace attribute if (lhsValue->_type == TRI_JSON_OBJECT && value->_type == TRI_JSON_OBJECT && mergeObjects) { TRI_json_t* merged = MergeRecursive(lhsValue, value, nullMeansRemove, mergeObjects); if (merged == nullptr) { return nullptr; } TRI_ReplaceObjectJson(r, key->_value._string.data, merged); TRI_FreeJson(merged); } else { TRI_ReplaceObjectJson(r, key->_value._string.data, value); } } } } return result.release(); } //////////////////////////////////////////////////////////////////////////////// /// @brief get type weight of a json value usable for comparison and sorting //////////////////////////////////////////////////////////////////////////////// static int TypeWeight(TRI_json_t const* value) { if (value != nullptr) { switch (value->_type) { case TRI_JSON_BOOLEAN: return 1; case TRI_JSON_NUMBER: return 2; case TRI_JSON_STRING: case TRI_JSON_STRING_REFERENCE: // a string reference has the same weight as a regular string return 3; case TRI_JSON_ARRAY: return 4; case TRI_JSON_OBJECT: return 5; case TRI_JSON_NULL: case TRI_JSON_UNUSED: break; } } return 0; } //////////////////////////////////////////////////////////////////////////////// /// @brief uniquify a sorted json list into a new array //////////////////////////////////////////////////////////////////////////////// static TRI_json_t* UniquifyArrayJson(TRI_json_t const* array) { TRI_ASSERT(array != nullptr); TRI_ASSERT(array->_type == TRI_JSON_ARRAY); // create result array std::unique_ptr result(TRI_CreateArrayJson()); if (result == nullptr) { return nullptr; } size_t const n = TRI_LengthVector(&array->_value._objects); TRI_json_t const* last = nullptr; for (size_t i = 0; i < n; ++i) { auto p = static_cast(TRI_AtVector(&array->_value._objects, i)); // don't push value if it is the same as the last value if (last == nullptr || TRI_CompareValuesJson(p, last, false) != 0) { int res = TRI_PushBackArrayJson(result.get(), p); if (res != TRI_ERROR_NO_ERROR) { return nullptr; } // remember last element last = p; } } return result.release(); } //////////////////////////////////////////////////////////////////////////////// /// @brief callback function used for json value sorting //////////////////////////////////////////////////////////////////////////////// static int CompareJson(void const* lhs, void const* rhs) { return TRI_CompareValuesJson(static_cast(lhs), static_cast(rhs), true); } //////////////////////////////////////////////////////////////////////////////// /// @brief sorts a json array in place //////////////////////////////////////////////////////////////////////////////// static TRI_json_t* SortArrayJson(TRI_json_t* array) { TRI_ASSERT(array != nullptr); TRI_ASSERT(array->_type == TRI_JSON_ARRAY); size_t const n = TRI_LengthVector(&array->_value._objects); if (n > 1) { // only sort if more than one value in array qsort(TRI_BeginVector(&array->_value._objects), n, sizeof(TRI_json_t), &CompareJson); } return array; } //////////////////////////////////////////////////////////////////////////////// /// @brief merge two arrays of array keys, sort them and return a combined array //////////////////////////////////////////////////////////////////////////////// static TRI_json_t* GetMergedKeyArray(TRI_json_t const* lhs, TRI_json_t const* rhs) { TRI_ASSERT(lhs->_type == TRI_JSON_OBJECT); TRI_ASSERT(rhs->_type == TRI_JSON_OBJECT); size_t n = TRI_LengthVector(&lhs->_value._objects) + TRI_LengthVector(&rhs->_value._objects); std::unique_ptr keys(TRI_CreateArrayJson(n)); if (keys == nullptr) { return nullptr; } if (TRI_CapacityVector(&(keys.get()->_value._objects)) < n) { return nullptr; } n = TRI_LengthVector(&lhs->_value._objects); for (size_t i = 0; i < n; i += 2) { auto key = static_cast(TRI_AtVector(&lhs->_value._objects, i)); TRI_ASSERT(TRI_IsStringJson(key)); int res = TRI_PushBackArrayJson(keys.get(), key); if (res != TRI_ERROR_NO_ERROR) { return nullptr; } } n = TRI_LengthVector(&rhs->_value._objects); for (size_t i = 0; i < n; i += 2) { auto key = static_cast(TRI_AtVector(&rhs->_value._objects, i)); TRI_ASSERT(TRI_IsStringJson(key)); int res = TRI_PushBackArrayJson(keys.get(), key); if (res != TRI_ERROR_NO_ERROR) { return nullptr; } } // sort the key array in place SortArrayJson(keys.get()); // array is now sorted return UniquifyArrayJson(keys.get()); } //////////////////////////////////////////////////////////////////////////////// /// @brief compare two json values //////////////////////////////////////////////////////////////////////////////// int TRI_CompareValuesJson(TRI_json_t const* lhs, TRI_json_t const* rhs, bool useUTF8) { // note: both lhs and rhs may be NULL! { int lWeight = TypeWeight(lhs); int rWeight = TypeWeight(rhs); if (lWeight < rWeight) { return -1; } if (lWeight > rWeight) { return 1; } TRI_ASSERT(lWeight == rWeight); } // lhs and rhs have equal weights if (lhs == nullptr || rhs == nullptr) { // either lhs or rhs is a nullptr. we cannot be sure here that both are // nullptrs. // there can also exist the situation that lhs is a nullptr and rhs is a // JSON null value // (or vice versa). Anyway, the compare value is the same for both, return 0; } switch (lhs->_type) { case TRI_JSON_UNUSED: case TRI_JSON_NULL: { return 0; // null == null; } case TRI_JSON_BOOLEAN: { if (lhs->_value._boolean == rhs->_value._boolean) { return 0; } if (!lhs->_value._boolean && rhs->_value._boolean) { return -1; } return 1; } case TRI_JSON_NUMBER: { if (lhs->_value._number == rhs->_value._number) { return 0; } if (lhs->_value._number < rhs->_value._number) { return -1; } return 1; } case TRI_JSON_STRING: case TRI_JSON_STRING_REFERENCE: { // same for STRING and STRING_REFERENCE TRI_ASSERT(lhs->_value._string.data != nullptr); TRI_ASSERT(rhs->_value._string.data != nullptr); int res; size_t const nl = lhs->_value._string.length - 1; size_t const nr = rhs->_value._string.length - 1; if (useUTF8) { res = TRI_compare_utf8(lhs->_value._string.data, nl, rhs->_value._string.data, nr); } else { // beware of strings containing NUL bytes size_t len = nl < nr ? nl : nr; res = memcmp(lhs->_value._string.data, rhs->_value._string.data, len); } if (res < 0) { return -1; } else if (res > 0) { return 1; } // res == 0 if (nl == nr) { return 0; } // res == 0, but different string lengths return nl < nr ? -1 : 1; } case TRI_JSON_ARRAY: { size_t const nl = TRI_LengthVector(&lhs->_value._objects); size_t const nr = TRI_LengthVector(&rhs->_value._objects); size_t n; if (nl > nr) { n = nl; } else { n = nr; } for (size_t i = 0; i < n; ++i) { auto lhsValue = (i >= nl) ? nullptr : static_cast( TRI_AtVector(&lhs->_value._objects, i)); auto rhsValue = (i >= nr) ? nullptr : static_cast( TRI_AtVector(&rhs->_value._objects, i)); int result = TRI_CompareValuesJson(lhsValue, rhsValue, useUTF8); if (result != 0) { return result; } } return 0; } case TRI_JSON_OBJECT: { TRI_ASSERT(lhs->_type == TRI_JSON_OBJECT); TRI_ASSERT(rhs->_type == TRI_JSON_OBJECT); std::unique_ptr keys(GetMergedKeyArray(lhs, rhs)); if (keys == nullptr) { THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY); } auto json = keys.get(); size_t const n = TRI_LengthVector(&json->_value._objects); for (size_t i = 0; i < n; ++i) { auto keyElement = static_cast(TRI_AtVector(&json->_value._objects, i)); TRI_ASSERT(TRI_IsStringJson(keyElement)); TRI_json_t const* lhsValue = TRI_LookupObjectJson(lhs, keyElement->_value._string.data); // may be NULL TRI_json_t const* rhsValue = TRI_LookupObjectJson(rhs, keyElement->_value._string.data); // may be NULL int result = TRI_CompareValuesJson(lhsValue, rhsValue, useUTF8); if (result != 0) { return result; } } // fall-through to returning 0 } } return 0; } //////////////////////////////////////////////////////////////////////////////// /// @brief merge two JSON documents into one //////////////////////////////////////////////////////////////////////////////// static TRI_json_t* TRI_MergeJson(TRI_json_t const* lhs, TRI_json_t const* rhs, bool nullMeansRemove, bool mergeObjects) { TRI_ASSERT(lhs->_type == TRI_JSON_OBJECT); TRI_ASSERT(rhs->_type == TRI_JSON_OBJECT); return MergeRecursive(lhs, rhs, nullMeansRemove, mergeObjects); } //////////////////////////////////////////////////////////////////////////////// /// @brief converts a TRI_json_t into a V8 object //////////////////////////////////////////////////////////////////////////////// v8::Handle TRI_ObjectJson(v8::Isolate* isolate, TRI_json_t const*); //////////////////////////////////////////////////////////////////////////////// /// @brief returns the keys of a TRI_json_t* object into a V8 array //////////////////////////////////////////////////////////////////////////////// v8::Handle TRI_KeysJson(v8::Isolate* isolate, TRI_json_t const*); //////////////////////////////////////////////////////////////////////////////// /// @brief returns the values of a TRI_json_t* object into a V8 array //////////////////////////////////////////////////////////////////////////////// v8::Handle TRI_ValuesJson(v8::Isolate* isolate, TRI_json_t const*); /// @brief converts a TRI_json_t NULL into a V8 object static inline v8::Handle ObjectJsonNull(v8::Isolate* isolate, TRI_json_t const* json) { return v8::Null(isolate); } /// @brief converts a TRI_json_t BOOLEAN into a V8 object static inline v8::Handle ObjectJsonBoolean(v8::Isolate* isolate, TRI_json_t const* json) { return v8::Boolean::New(isolate, json->_value._boolean); } /// @brief converts a TRI_json_t NUMBER into a V8 object static inline v8::Handle ObjectJsonNumber(v8::Isolate* isolate, TRI_json_t const* json) { return v8::Number::New(isolate, json->_value._number); } /// @brief converts a TRI_json_t STRING into a V8 object static inline v8::Handle ObjectJsonString(v8::Isolate* isolate, TRI_json_t const* json) { return TRI_V8_PAIR_STRING(isolate, json->_value._string.data, json->_value._string.length - 1); } /// @brief converts a TRI_json_t OBJECT into a V8 object static v8::Handle ObjectJsonObject(v8::Isolate* isolate, TRI_json_t const* json) { v8::Handle object = v8::Object::New(isolate); if (object.IsEmpty()) { return v8::Undefined(isolate); } size_t const n = TRI_LengthVector(&json->_value._objects); for (size_t i = 0; i < n; i += 2) { TRI_json_t const* key = static_cast(TRI_AddressVector(&json->_value._objects, i)); if (!TRI_IsStringJson(key)) { continue; } TRI_json_t const* element = static_cast(TRI_AddressVector(&json->_value._objects, i + 1)); auto val = TRI_ObjectJson(isolate, element); if (!val.IsEmpty()) { auto k = TRI_V8_PAIR_STRING(isolate, key->_value._string.data, key->_value._string.length - 1); if (!k.IsEmpty()) { object->ForceSet(TRI_V8_PAIR_STRING(isolate, key->_value._string.data, key->_value._string.length - 1), val); } } } return object; } /// @brief converts a TRI_json_t ARRAY into a V8 object static v8::Handle ObjectJsonArray(v8::Isolate* isolate, TRI_json_t const* json) { uint32_t const n = static_cast(TRI_LengthArrayJson(json)); v8::Handle object = v8::Array::New(isolate, static_cast(n)); if (object.IsEmpty()) { return v8::Undefined(isolate); } uint32_t j = 0; for (uint32_t i = 0; i < n; ++i) { TRI_json_t const* element = static_cast(TRI_AddressVector(&json->_value._objects, i)); v8::Handle val = TRI_ObjectJson(isolate, element); if (!val.IsEmpty()) { object->Set(j++, val); } } return object; } /// @brief extracts keys or values from a TRI_json_t* object static v8::Handle ExtractObject(v8::Isolate* isolate, TRI_json_t const* json, size_t offset) { v8::EscapableHandleScope scope(isolate); if (json == nullptr || json->_type != TRI_JSON_OBJECT) { return scope.Escape(v8::Undefined(isolate)); } size_t const n = TRI_LengthVector(&json->_value._objects); v8::Handle result = v8::Array::New(isolate, static_cast(n / 2)); uint32_t count = 0; for (size_t i = offset; i < n; i += 2) { TRI_json_t const* value = static_cast(TRI_AtVector(&json->_value._objects, i)); if (value != nullptr) { result->Set(count++, TRI_ObjectJson(isolate, value)); } } return scope.Escape(result); } /// @brief returns the keys of a TRI_json_t* object into a V8 array v8::Handle TRI_KeysJson(v8::Isolate* isolate, TRI_json_t const* json) { return ExtractObject(isolate, json, 0); } /// @brief returns the values of a TRI_json_t* object into a V8 array v8::Handle TRI_ValuesJson(v8::Isolate* isolate, TRI_json_t const* json) { return ExtractObject(isolate, json, 1); } /// @brief converts a TRI_json_t into a V8 object v8::Handle TRI_ObjectJson(v8::Isolate* isolate, TRI_json_t const* json) { if (json == nullptr) { return v8::Undefined(isolate); } switch (json->_type) { case TRI_JSON_NULL: return ObjectJsonNull(isolate, json); case TRI_JSON_BOOLEAN: return ObjectJsonBoolean(isolate, json); case TRI_JSON_NUMBER: return ObjectJsonNumber(isolate, json); case TRI_JSON_STRING: case TRI_JSON_STRING_REFERENCE: return ObjectJsonString(isolate, json); case TRI_JSON_OBJECT: return ObjectJsonObject(isolate, json); case TRI_JSON_ARRAY: return ObjectJsonArray(isolate, json); case TRI_JSON_UNUSED: { } } return v8::Undefined(isolate); } /// @brief convert a V8 value to a TRI_json_t value static int ObjectToJson(v8::Isolate* isolate, TRI_json_t* result, v8::Handle const parameter, std::set& seenHashes, std::vector>& seenObjects) { v8::HandleScope scope(isolate); if (parameter->IsNull()) { TRI_InitNullJson(result); return TRI_ERROR_NO_ERROR; } if (parameter->IsBoolean()) { v8::Handle booleanParameter = parameter->ToBoolean(); TRI_InitBooleanJson(result, booleanParameter->Value()); return TRI_ERROR_NO_ERROR; } if (parameter->IsNumber()) { v8::Handle numberParameter = parameter->ToNumber(); TRI_InitNumberJson(result, numberParameter->Value()); return TRI_ERROR_NO_ERROR; } if (parameter->IsString()) { v8::Handle stringParameter = parameter->ToString(); TRI_Utf8ValueNFC str(stringParameter); if (*str == nullptr) { TRI_InitNullJson(result); return TRI_ERROR_OUT_OF_MEMORY; } // this passes ownership for the utf8 string to the JSON object TRI_InitStringJson(result, str.steal(), str.length()); return TRI_ERROR_NO_ERROR; } if (parameter->IsArray()) { v8::Handle array = v8::Handle::Cast(parameter); uint32_t const n = array->Length(); // allocate the result array in one go TRI_InitArrayJson(result, static_cast(n)); int res = TRI_ReserveVector(&result->_value._objects, static_cast(n)); if (res != TRI_ERROR_NO_ERROR) { // result array could not be allocated TRI_InitNullJson(result); return TRI_ERROR_OUT_OF_MEMORY; } for (uint32_t i = 0; i < n; ++i) { // get address of next element auto next = static_cast(TRI_NextVector(&result->_value._objects)); // the reserve call above made sure we could not have run out of memory TRI_ASSERT(next != nullptr); res = ObjectToJson(isolate, next, array->Get(i), seenHashes, seenObjects); if (res != TRI_ERROR_NO_ERROR) { // to mimic behavior of previous ArangoDB versions, we need to silently // ignore this error // now return the element to the vector TRI_ReturnVector(&result->_value._objects); // a better solution would be: // initialize the element at position, otherwise later cleanups may // peek into uninitialized memory // TRI_InitNullJson(next); // return res; } } return TRI_ERROR_NO_ERROR; } if (parameter->IsObject()) { if (parameter->IsBooleanObject()) { TRI_InitBooleanJson(result, v8::Handle::Cast(parameter)->BooleanValue()); return TRI_ERROR_NO_ERROR; } if (parameter->IsNumberObject()) { TRI_InitNumberJson(result, v8::Handle::Cast(parameter)->NumberValue()); return TRI_ERROR_NO_ERROR; } if (parameter->IsStringObject()) { v8::Handle stringParameter(parameter->ToString()); TRI_Utf8ValueNFC str(stringParameter); if (*str == nullptr) { TRI_InitNullJson(result); return TRI_ERROR_OUT_OF_MEMORY; } // this passes ownership for the utf8 string to the JSON object TRI_InitStringJson(result, str.steal(), str.length()); return TRI_ERROR_NO_ERROR; } if (parameter->IsRegExp() || parameter->IsFunction() || parameter->IsExternal()) { TRI_InitNullJson(result); return TRI_ERROR_BAD_PARAMETER; } v8::Handle o = parameter->ToObject(); // first check if the object has a "toJSON" function v8::Handle toJsonString = TRI_V8_PAIR_STRING(isolate, "toJSON", 6); if (o->Has(toJsonString)) { // call it if yes v8::Handle func = o->Get(toJsonString); if (func->IsFunction()) { v8::Handle toJson = v8::Handle::Cast(func); v8::Handle args; v8::Handle converted = toJson->Call(o, 0, &args); if (!converted.IsEmpty()) { // return whatever toJSON returned TRI_Utf8ValueNFC str(converted->ToString()); if (*str == nullptr) { TRI_InitNullJson(result); return TRI_ERROR_OUT_OF_MEMORY; } // this passes ownership for the utf8 string to the JSON object TRI_InitStringJson(result, str.steal(), str.length()); return TRI_ERROR_NO_ERROR; } } // intentionally falls through } int hash = o->GetIdentityHash(); if (seenHashes.find(hash) != seenHashes.end()) { // LOG_TOPIC(TRACE, arangodb::Logger::FIXME) << "found hash " << hash; for (auto it : seenObjects) { if (parameter->StrictEquals(it)) { // object is recursive TRI_InitNullJson(result); return TRI_ERROR_BAD_PARAMETER; } } } else { seenHashes.emplace(hash); } seenObjects.emplace_back(o); v8::Handle names = o->GetOwnPropertyNames(); uint32_t const n = names->Length(); // allocate the result object buffer in one go TRI_InitObjectJson(result, static_cast(n)); int res = TRI_ReserveVector(&result->_value._objects, static_cast(n * 2)); // key + value if (res != TRI_ERROR_NO_ERROR) { // result object buffer could not be allocated TRI_InitNullJson(result); return TRI_ERROR_OUT_OF_MEMORY; } for (uint32_t i = 0; i < n; ++i) { // process attribute name v8::Handle key = names->Get(i); TRI_Utf8ValueNFC str(key); if (*str == nullptr) { return TRI_ERROR_OUT_OF_MEMORY; } TRI_json_t* next = static_cast(TRI_NextVector(&result->_value._objects)); // the reserve call above made sure we could not have run out of memory TRI_ASSERT(next != nullptr); // this passes ownership for the utf8 string to the JSON object char* attributeName = str.steal(); TRI_InitStringJson(next, attributeName, str.length()); // process attribute value next = static_cast(TRI_NextVector(&result->_value._objects)); // the reserve call above made sure we could not have run out of memory TRI_ASSERT(next != nullptr); res = ObjectToJson(isolate, next, o->Get(key), seenHashes, seenObjects); if (res != TRI_ERROR_NO_ERROR) { // to mimic behavior of previous ArangoDB versions, we need to silently // ignore this error // now free the attributeName string and return the elements to the // vector TRI_FreeString(attributeName); TRI_ReturnVector(&result->_value._objects); TRI_ReturnVector(&result->_value._objects); // a better solution would be: // initialize the element at position, otherwise later cleanups may // peek into uninitialized memory // TRI_InitNullJson(next); // return res; } } seenObjects.pop_back(); return TRI_ERROR_NO_ERROR; } TRI_InitNullJson(result); return TRI_ERROR_BAD_PARAMETER; } /// @brief convert a V8 value to a json_t value TRI_json_t* TRI_ObjectToJson(v8::Isolate* isolate, v8::Handle const parameter) { TRI_json_t* json = TRI_CreateNullJson(); if (json == nullptr) { return nullptr; } std::set seenHashes; std::vector> seenObjects; int res = ObjectToJson(isolate, json, parameter, seenHashes, seenObjects); if (res != TRI_ERROR_NO_ERROR) { // some processing error occurred TRI_FreeJson(json); return nullptr; } return json; } //////////////////////////////////////////////////////////////////////////////// /// @brief convert a V8 value to a json_t value //////////////////////////////////////////////////////////////////////////////// TRI_json_t* TRI_ObjectToJson(v8::Isolate*, v8::Handle const); struct KeySpaceElement { KeySpaceElement() = delete; KeySpaceElement(char const* k, size_t length, TRI_json_t* json) : key(nullptr), json(json) { key = TRI_DuplicateString(k, length); if (key == nullptr) { THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY); } } ~KeySpaceElement() { if (key != nullptr) { TRI_FreeString(key); } if (json != nullptr) { TRI_FreeJson(json); } } void setValue(TRI_json_t* value) { if (json != nullptr) { TRI_FreeJson(json); json = nullptr; } json = value; } char* key; TRI_json_t* json; }; class KeySpace { public: explicit KeySpace(uint32_t initialSize) : _lock() {} ~KeySpace() { for (auto& it : _hash) { auto element = it.second; if (element != nullptr) { delete element; } } } uint32_t keyspaceCount() { READ_LOCKER(readLocker, _lock); return static_cast(_hash.size()); } uint32_t keyspaceCount(std::string const& prefix) { uint32_t count = 0; READ_LOCKER(readLocker, _lock); for (auto const& it : _hash) { auto data = it.second; if (data != nullptr) { if (TRI_IsPrefixString(data->key, prefix.c_str())) { ++count; } } } return count; } v8::Handle keyspaceRemove(v8::Isolate* isolate) { v8::EscapableHandleScope scope(isolate); WRITE_LOCKER(writeLocker, _lock); uint32_t deleted = 0; for (auto& it : _hash) { auto element = it.second; if (element != nullptr) { delete element; ++deleted; } } _hash.clear(); return scope.Escape(v8::Number::New(isolate, static_cast(deleted))); } v8::Handle keyspaceRemove(v8::Isolate* isolate, std::string const& prefix) { v8::EscapableHandleScope scope(isolate); WRITE_LOCKER(writeLocker, _lock); uint32_t deleted = 0; for (auto it = _hash.begin(); it != _hash.end(); /* no hoisting */) { auto element = (*it).second; if (element != nullptr) { if (TRI_IsPrefixString(element->key, prefix.c_str())) { it = _hash.erase(it); delete element; ++deleted; continue; } } ++it; } return scope.Escape(v8::Number::New(isolate, static_cast(deleted))); } v8::Handle keyspaceKeys(v8::Isolate* isolate) { v8::EscapableHandleScope scope(isolate); v8::Handle result; { READ_LOCKER(readLocker, _lock); uint32_t count = 0; result = v8::Array::New(isolate, static_cast(_hash.size())); for (auto const& it : _hash) { auto element = it.second; if (element != nullptr) { result->Set(count++, TRI_V8_PAIR_STRING(isolate, element->key, strlen(element->key))); } } } return scope.Escape(result); } v8::Handle keyspaceKeys(v8::Isolate* isolate, std::string const& prefix) { v8::EscapableHandleScope scope(isolate); v8::Handle result; { READ_LOCKER(readLocker, _lock); uint32_t count = 0; result = v8::Array::New(isolate); for (auto const& it : _hash) { auto element = it.second; if (element != nullptr) { if (TRI_IsPrefixString(element->key, prefix.c_str())) { result->Set(count++, TRI_V8_PAIR_STRING(isolate, element->key, strlen(element->key))); } } } } return scope.Escape(result); } v8::Handle keyspaceGet(v8::Isolate* isolate) { v8::EscapableHandleScope scope(isolate); v8::Handle result = v8::Object::New(isolate); { READ_LOCKER(readLocker, _lock); for (auto const& it : _hash) { auto element = it.second; if (element != nullptr) { result->Set(TRI_V8_PAIR_STRING(isolate, element->key, strlen(element->key)), TRI_ObjectJson(isolate, element->json)); } } } return scope.Escape(result); } v8::Handle keyspaceGet(v8::Isolate* isolate, std::string const& prefix) { v8::EscapableHandleScope scope(isolate); v8::Handle result = v8::Object::New(isolate); { READ_LOCKER(readLocker, _lock); for (auto const& it : _hash) { auto element = it.second; if (element != nullptr) { if (TRI_IsPrefixString(element->key, prefix.c_str())) { result->Set(TRI_V8_PAIR_STRING(isolate, element->key, strlen(element->key)), TRI_ObjectJson(isolate, element->json)); } } } } return scope.Escape(result); } bool keyCount(std::string const& key, uint32_t& result) { READ_LOCKER(readLocker, _lock); auto it = _hash.find(key); if (it != _hash.end()) { TRI_json_t const* value = (*it).second->json; if (TRI_IsArrayJson(value)) { result = static_cast(TRI_LengthVector(&value->_value._objects)); return true; } if (TRI_IsObjectJson(value)) { result = static_cast(TRI_LengthVector(&value->_value._objects) / 2); return true; } } result = 0; return false; } v8::Handle keyGet(v8::Isolate* isolate, std::string const& key) { v8::Handle result; { READ_LOCKER(readLocker, _lock); auto it = _hash.find(key); if (it == _hash.end()) { result = v8::Undefined(isolate); } else { result = TRI_ObjectJson(isolate, (*it).second->json); } } return result; } bool keySet(v8::Isolate* isolate, std::string const& key, v8::Handle const& value, bool replace) { auto element = new KeySpaceElement(key.c_str(), key.size(), TRI_ObjectToJson(isolate, value)); { WRITE_LOCKER(writeLocker, _lock); if (replace) { // delete previous entry auto it = _hash.find(key); if (it != _hash.end()) { delete (*it).second; _hash.erase(it); } } auto it = _hash.emplace(key, element); if (it.second) { return true; } } // insertion failed delete element; return false; } bool keySet(std::string const& key, double val) { TRI_json_t* json = TRI_CreateNumberJson(val); if (json == nullptr) { // OOM return false; } auto element = std::make_unique(key.c_str(), key.size(), json); { WRITE_LOCKER(writeLocker, _lock); auto it = _hash.find(key); if (it != _hash.end()) { it->second->json->_value._number = val; return true; } else { auto it2 = _hash.emplace(key, element.get()); if (it2.second) { element.release(); // _hash now has ownership return true; } } } // insertion failed return false; } int keyCas(v8::Isolate* isolate, std::string const& key, v8::Handle const& value, v8::Handle const& compare, bool& match) { auto element = new KeySpaceElement(key.c_str(), key.size(), TRI_ObjectToJson(isolate, value)); WRITE_LOCKER(writeLocker, _lock); auto it = _hash.emplace(key, element); if (it.second) { // no object saved yet match = true; return TRI_ERROR_NO_ERROR; } auto it2 = _hash.find(key); if (it2 == _hash.end()) { return TRI_ERROR_INTERNAL; } KeySpaceElement* found = (*it2).second; if (compare->IsUndefined()) { // other object saved, but we compare it with nothing => no match delete element; match = false; return TRI_ERROR_NO_ERROR; } TRI_json_t* other = TRI_ObjectToJson(isolate, compare); if (other == nullptr) { delete element; match = false; return TRI_ERROR_OUT_OF_MEMORY; } int res = TRI_CompareValuesJson(found->json, other); TRI_FreeJson(other); if (res != 0) { delete element; match = false; } else { auto it = _hash.find(key); if (it != _hash.end()) { auto found = (*it).second; _hash.erase(it); delete found; } _hash.emplace(key, element); match = true; } return TRI_ERROR_NO_ERROR; } bool keyRemove(std::string const& key) { KeySpaceElement* found = nullptr; { WRITE_LOCKER(writeLocker, _lock); auto it = _hash.find(key); if (it != _hash.end()) { found = (*it).second; _hash.erase(it); } } if (found != nullptr) { delete found; return true; } return false; } bool keyExists(std::string const& key) { READ_LOCKER(readLocker, _lock); return _hash.find(key) != _hash.end(); } int keyIncr(std::string const& key, double value, double& result) { WRITE_LOCKER(writeLocker, _lock); KeySpaceElement* found = nullptr; auto it = _hash.find(key); if (it != _hash.end()) { found = (*it).second; } if (found == nullptr) { auto element = new KeySpaceElement(key.c_str(), key.size(), TRI_CreateNumberJson(value)); _hash.emplace(key, element); result = value; } else { TRI_json_t* current = found->json; if (!TRI_IsNumberJson(current)) { return TRI_ERROR_ILLEGAL_NUMBER; } result = current->_value._number += value; } return TRI_ERROR_NO_ERROR; } int keyPush(v8::Isolate* isolate, std::string const& key, v8::Handle const& value) { WRITE_LOCKER(writeLocker, _lock); KeySpaceElement* found = nullptr; auto it = _hash.find(key); if (it != _hash.end()) { found = (*it).second; } if (found == nullptr) { TRI_json_t* list = TRI_CreateArrayJson(1); if (list == nullptr) { return TRI_ERROR_OUT_OF_MEMORY; } if (TRI_PushBack3ArrayJson(list, TRI_ObjectToJson(isolate, value)) != TRI_ERROR_NO_ERROR) { TRI_FreeJson(list); return TRI_ERROR_OUT_OF_MEMORY; } auto element = new KeySpaceElement(key.c_str(), key.size(), list); _hash.emplace(key, element); } else { TRI_json_t* current = found->json; if (!TRI_IsArrayJson(current)) { return TRI_ERROR_INTERNAL; } if (TRI_PushBack3ArrayJson(current, TRI_ObjectToJson(isolate, value)) != TRI_ERROR_NO_ERROR) { return TRI_ERROR_OUT_OF_MEMORY; } } return TRI_ERROR_NO_ERROR; } void keyPop(v8::FunctionCallbackInfo const& args, std::string const& key) { v8::Isolate* isolate = args.GetIsolate(); v8::HandleScope scope(isolate); WRITE_LOCKER(writeLocker, _lock); KeySpaceElement* found = nullptr; auto it = _hash.find(key); if (it != _hash.end()) { found = (*it).second; } if (found == nullptr) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_INTERNAL); } TRI_json_t* current = found->json; if (!TRI_IsArrayJson(current)) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_INTERNAL); } size_t const n = TRI_LengthVector(¤t->_value._objects); if (n == 0) { TRI_V8_RETURN_UNDEFINED(); } TRI_json_t* item = static_cast(TRI_AtVector(¤t->_value._objects, n - 1)); // hack: decrease the vector size TRI_SetLengthVector(¤t->_value._objects, TRI_LengthVector(¤t->_value._objects) - 1); v8::Handle result = TRI_ObjectJson(isolate, item); TRI_DestroyJson(item); TRI_V8_RETURN(result); } void keyTransfer(v8::FunctionCallbackInfo const& args, std::string const& keyFrom, std::string const& keyTo) { v8::Isolate* isolate = args.GetIsolate(); v8::HandleScope scope(isolate); WRITE_LOCKER(writeLocker, _lock); KeySpaceElement* source = nullptr; auto it = _hash.find(keyFrom); if (it != _hash.end()) { source = (*it).second; } if (source == nullptr) { TRI_V8_RETURN_UNDEFINED(); } TRI_json_t* current = source->json; if (!TRI_IsArrayJson(current)) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_INTERNAL); } size_t const n = TRI_LengthVector(&source->json->_value._objects); if (n == 0) { TRI_V8_RETURN_UNDEFINED(); } TRI_json_t* sourceItem = static_cast(TRI_AtVector(&source->json->_value._objects, n - 1)); KeySpaceElement* dest = nullptr; auto it2 = _hash.find(keyTo); if (it2 != _hash.end()) { dest = (*it2).second; } if (dest == nullptr) { TRI_json_t* list = TRI_CreateArrayJson(1); if (list == nullptr) { TRI_V8_THROW_EXCEPTION_MEMORY(); } TRI_PushBack2ArrayJson(list, sourceItem); try { auto element = new KeySpaceElement(keyTo.c_str(), keyTo.size(), list); _hash.emplace(keyTo, element); // hack: decrease the vector size TRI_SetLengthVector(¤t->_value._objects, TRI_LengthVector(¤t->_value._objects) - 1); TRI_V8_RETURN(TRI_ObjectJson(isolate, sourceItem)); } catch (...) { TRI_V8_THROW_EXCEPTION_MEMORY(); } } TRI_ASSERT(dest != nullptr); // cppcheck-suppress * if (!TRI_IsArrayJson(dest->json)) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_INTERNAL); } // cppcheck-suppress * TRI_PushBack2ArrayJson(dest->json, sourceItem); // hack: decrease the vector size TRI_SetLengthVector(¤t->_value._objects, TRI_LengthVector(¤t->_value._objects) - 1); TRI_V8_RETURN(TRI_ObjectJson(isolate, sourceItem)); } v8::Handle keyKeys(v8::Isolate* isolate, std::string const& key) { v8::Handle result; { READ_LOCKER(readLocker, _lock); auto it = _hash.find(key); if (it == _hash.end()) { result = v8::Undefined(isolate); } else { result = TRI_KeysJson(isolate, (*it).second->json); } } return result; } v8::Handle keyValues(v8::Isolate* isolate, std::string const& key) { v8::EscapableHandleScope scope(isolate); v8::Handle result; { READ_LOCKER(readLocker, _lock); auto it = _hash.find(key); if (it == _hash.end()) { result = v8::Undefined(isolate); } else { result = TRI_ValuesJson(isolate, (*it).second->json); } } return scope.Escape(result); } void keyGetAt(v8::FunctionCallbackInfo const& args, std::string const& key, int64_t index) { v8::Isolate* isolate = args.GetIsolate(); v8::HandleScope scope(isolate); v8::Handle result; { READ_LOCKER(readLocker, _lock); auto it = _hash.find(key); if (it == _hash.end()) { result = v8::Undefined(isolate); } else { KeySpaceElement* found = (*it).second; if (!TRI_IsArrayJson(found->json)) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_INTERNAL); } size_t const n = TRI_LengthArrayJson(found->json); if (index < 0) { index = static_cast(n) + index; } if (index >= static_cast(n)) { result = v8::Undefined(isolate); } else { auto item = static_cast( TRI_AtVector(&found->json->_value._objects, static_cast(index))); result = TRI_ObjectJson(isolate, item); } } } TRI_V8_RETURN(result); } bool keySetAt(v8::Isolate* isolate, std::string const& key, int64_t index, v8::Handle const& value) { WRITE_LOCKER(writeLocker, _lock); auto it = _hash.find(key); if (it == _hash.end()) { return false; } else { KeySpaceElement* found = (*it).second; if (!TRI_IsArrayJson(found->json)) { return false; } size_t const n = TRI_LengthArrayJson(found->json); if (index < 0) { return false; } auto json = TRI_ObjectToJson(isolate, value); if (json == nullptr) { return false; } if (index >= static_cast(n)) { // insert new element TRI_InsertVector(&found->json->_value._objects, json, static_cast(index)); } else { // overwrite existing element auto item = static_cast( TRI_AtVector(&found->json->_value._objects, static_cast(index))); if (item != nullptr) { TRI_DestroyJson(item); } TRI_SetVector(&found->json->_value._objects, static_cast(index), json); } // only free pointer to json, but not its internal structures TRI_Free(json); } return true; } char const* keyType(std::string const& key) { READ_LOCKER(readLocker, _lock); auto it = _hash.find(key); if (it != _hash.end()) { KeySpaceElement* found = (*it).second; TRI_json_t const* value = static_cast(found)->json; switch (value->_type) { case TRI_JSON_NULL: return "null"; case TRI_JSON_BOOLEAN: return "boolean"; case TRI_JSON_NUMBER: return "number"; case TRI_JSON_STRING: case TRI_JSON_STRING_REFERENCE: return "string"; case TRI_JSON_ARRAY: return "list"; case TRI_JSON_OBJECT: return "object"; case TRI_JSON_UNUSED: break; } } return "undefined"; } void keyMerge(v8::FunctionCallbackInfo const& args, std::string const& key, v8::Handle const& value, bool nullMeansRemove) { v8::Isolate* isolate = args.GetIsolate(); v8::HandleScope scope(isolate); if (!value->IsObject() || value->IsArray()) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_INTERNAL); } WRITE_LOCKER(writeLocker, _lock); auto it = _hash.find(key); if (it == _hash.end()) { auto element = new KeySpaceElement(key.c_str(), key.size(), TRI_ObjectToJson(isolate, value)); _hash.emplace(key, element); TRI_V8_RETURN(value); } KeySpaceElement* found = (*it).second; if (!TRI_IsObjectJson(found->json)) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_INTERNAL); } TRI_json_t* other = TRI_ObjectToJson(isolate, value); if (other == nullptr) { TRI_V8_THROW_EXCEPTION_MEMORY(); } TRI_json_t* merged = TRI_MergeJson(found->json, other, nullMeansRemove, false); TRI_FreeJson(other); if (merged == nullptr) { TRI_V8_THROW_EXCEPTION_MEMORY(); } found->setValue(merged); TRI_V8_RETURN(TRI_ObjectJson(isolate, merged)); } private: arangodb::basics::ReadWriteLock _lock; std::unordered_map _hash; }; struct UserStructures { struct { arangodb::basics::ReadWriteLock lock; std::unordered_map data; } hashes; }; //////////////////////////////////////////////////////////////////////////////// /// @brief get the vocbase pointer from the current V8 context //////////////////////////////////////////////////////////////////////////////// static inline TRI_vocbase_t& GetContextVocBase(v8::Isolate* isolate) { TRI_GET_GLOBALS(); TRI_ASSERT(v8g->_vocbase != nullptr); return *static_cast(v8g->_vocbase); } //////////////////////////////////////////////////////////////////////////////// /// @brief finds a hash array by name /// note that at least the read-lock must be held to use this function //////////////////////////////////////////////////////////////////////////////// static KeySpace* GetKeySpace(TRI_vocbase_t* vocbase, std::string const& name) { auto h = &(static_cast(vocbase->_userStructures)->hashes); auto it = h->data.find(name); if (it != h->data.end()) { return (*it).second; } return nullptr; } //////////////////////////////////////////////////////////////////////////////// /// @brief creates a keyspace //////////////////////////////////////////////////////////////////////////////// static void JS_KeyspaceCreate(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); if (args.Length() < 1 || !args[0]->IsString()) { TRI_V8_THROW_EXCEPTION_USAGE( "KEYSPACE_CREATE(, , )"); } auto& vocbase = GetContextVocBase(isolate); std::string const name = TRI_ObjectToString(args[0]); int64_t size = 0; if (args.Length() > 1) { size = TRI_ObjectToInt64(args[1]); if (size < 0 || size > static_cast(UINT32_MAX)) { TRI_V8_THROW_EXCEPTION_PARAMETER("invalid value for "); } } bool ignoreExisting = false; if (args.Length() > 2) { ignoreExisting = TRI_ObjectToBoolean(args[2]); } auto ptr = std::make_unique(static_cast(size)); auto h = &(static_cast(vocbase._userStructures)->hashes); { WRITE_LOCKER(writeLocker, h->lock); auto hash = GetKeySpace(&vocbase, name); if (hash != nullptr) { if (!ignoreExisting) { TRI_V8_THROW_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "hash already exists"); } TRI_V8_RETURN_FALSE(); } try { h->data.emplace(std::make_pair(name, ptr.get())); ptr.release(); } catch (...) { TRI_V8_THROW_EXCEPTION_MEMORY(); } } TRI_V8_RETURN_TRUE(); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief drops a keyspace //////////////////////////////////////////////////////////////////////////////// static void JS_KeyspaceDrop(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); if (args.Length() != 1 || !args[0]->IsString()) { TRI_V8_THROW_EXCEPTION_USAGE("KEYSPACE_DROP()"); } auto& vocbase = GetContextVocBase(isolate); std::string const name = TRI_ObjectToString(args[0]); auto h = &(static_cast(vocbase._userStructures)->hashes); { WRITE_LOCKER(writeLocker, h->lock); auto it = h->data.find(name); if (it == h->data.end()) { TRI_V8_THROW_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "Keyspace does not exist"); } delete (*it).second; h->data.erase(it); } TRI_V8_RETURN_TRUE(); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief returns the number of items in the keyspace //////////////////////////////////////////////////////////////////////////////// static void JS_KeyspaceCount(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); if (args.Length() < 1 || !args[0]->IsString()) { TRI_V8_THROW_EXCEPTION_USAGE("KEYSPACE_COUNT(, )"); } auto& vocbase = GetContextVocBase(isolate); std::string const name = TRI_ObjectToString(args[0]); auto h = &(static_cast(vocbase._userStructures)->hashes); uint32_t count; { READ_LOCKER(readLocker, h->lock); auto hash = GetKeySpace(&vocbase, name); if (hash == nullptr) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_INTERNAL); } if (args.Length() > 1) { std::string const prefix = TRI_ObjectToString(args[1]); count = hash->keyspaceCount(prefix); } else { count = hash->keyspaceCount(); } } TRI_V8_RETURN(v8::Number::New(isolate, static_cast(count))); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief returns whether a keyspace exists //////////////////////////////////////////////////////////////////////////////// static void JS_KeyspaceExists(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); if (args.Length() != 1 || !args[0]->IsString()) { TRI_V8_THROW_EXCEPTION_USAGE("KEYSPACE_EXISTS()"); } auto& vocbase = GetContextVocBase(isolate); std::string const name = TRI_ObjectToString(args[0]); auto h = &(static_cast(vocbase._userStructures)->hashes); READ_LOCKER(readLocker, h->lock); auto hash = GetKeySpace(&vocbase, name); if (hash != nullptr) { TRI_V8_RETURN_TRUE(); } TRI_V8_RETURN_FALSE(); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief returns all keys of the keyspace //////////////////////////////////////////////////////////////////////////////// static void JS_KeyspaceKeys(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); if (args.Length() < 1 || !args[0]->IsString()) { TRI_V8_THROW_EXCEPTION_USAGE("KEYSPACE_KEYS(, )"); } auto& vocbase = GetContextVocBase(isolate); std::string const name = TRI_ObjectToString(args[0]); auto h = &(static_cast(vocbase._userStructures)->hashes); READ_LOCKER(readLocker, h->lock); auto hash = GetKeySpace(&vocbase, name); if (hash == nullptr) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_INTERNAL); } if (args.Length() > 1) { std::string const prefix = TRI_ObjectToString(args[1]); TRI_V8_RETURN(hash->keyspaceKeys(isolate, prefix)); } TRI_V8_RETURN(hash->keyspaceKeys(isolate)); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief returns all data of the keyspace //////////////////////////////////////////////////////////////////////////////// static void JS_KeyspaceGet(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); if (args.Length() < 1 || !args[0]->IsString()) { TRI_V8_THROW_EXCEPTION_USAGE("KEYSPACE_GET(, )"); } auto& vocbase = GetContextVocBase(isolate); std::string const name = TRI_ObjectToString(args[0]); auto h = &(static_cast(vocbase._userStructures)->hashes); READ_LOCKER(readLocker, h->lock); auto hash = GetKeySpace(&vocbase, name); if (hash == nullptr) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_INTERNAL); } if (args.Length() > 1) { std::string const prefix = TRI_ObjectToString(args[1]); TRI_V8_RETURN(hash->keyspaceGet(isolate, prefix)); } TRI_V8_RETURN(hash->keyspaceGet(isolate)); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief removes all keys from the keyspace //////////////////////////////////////////////////////////////////////////////// static void JS_KeyspaceRemove(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); if (args.Length() < 1 || !args[0]->IsString()) { TRI_V8_THROW_EXCEPTION_USAGE("KEYSPACE_REMOVE(, )"); } auto& vocbase = GetContextVocBase(isolate); std::string const name = TRI_ObjectToString(args[0]); auto h = &(static_cast(vocbase._userStructures)->hashes); READ_LOCKER(readLocker, h->lock); auto hash = GetKeySpace(&vocbase, name); if (hash == nullptr) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_INTERNAL); } if (args.Length() > 1) { std::string const prefix = TRI_ObjectToString(args[1]); TRI_V8_RETURN(hash->keyspaceRemove(isolate, prefix)); } TRI_V8_RETURN(hash->keyspaceRemove(isolate)); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief returns the value for a key in the keyspace //////////////////////////////////////////////////////////////////////////////// static void JS_KeyGet(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); if (args.Length() < 2 || !args[0]->IsString() || !args[1]->IsString()) { TRI_V8_THROW_EXCEPTION_USAGE("KEY_GET(, )"); } auto& vocbase = GetContextVocBase(isolate); std::string const name = TRI_ObjectToString(args[0]); std::string const key = TRI_ObjectToString(args[1]); auto h = &(static_cast(vocbase._userStructures)->hashes); v8::Handle result; { READ_LOCKER(readLocker, h->lock); auto hash = GetKeySpace(&vocbase, name); if (hash == nullptr) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_INTERNAL); } result = hash->keyGet(isolate, key); } TRI_V8_RETURN(result); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief set the value for a key in the keyspace //////////////////////////////////////////////////////////////////////////////// static void JS_KeySet(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); if (args.Length() < 3 || !args[0]->IsString() || !args[1]->IsString()) { TRI_V8_THROW_EXCEPTION_USAGE("KEY_SET(, , , )"); } auto& vocbase = GetContextVocBase(isolate); std::string const name = TRI_ObjectToString(args[0]); std::string const key = TRI_ObjectToString(args[1]); bool replace = true; if (args.Length() > 3) { replace = TRI_ObjectToBoolean(args[3]); } auto h = &(static_cast(vocbase._userStructures)->hashes); bool result; { READ_LOCKER(readLocker, h->lock); auto hash = GetKeySpace(&vocbase, name); if (hash == nullptr) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_INTERNAL); } result = hash->keySet(isolate, key, args[2], replace); } if (result) { TRI_V8_RETURN_TRUE(); } TRI_V8_RETURN_FALSE(); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief calls global.KEY_SET('queue-control', 'databases-expire', 0); //////////////////////////////////////////////////////////////////////////////// void TRI_ExpireFoxxQueueDatabaseCache(TRI_vocbase_t* vocbase) { TRI_ASSERT(vocbase->isSystem()); std::string const name = "queue-control"; std::string const key = "databases-expire"; auto h = &(static_cast(vocbase->_userStructures)->hashes); bool result; { READ_LOCKER(readLocker, h->lock); auto hash = GetKeySpace(vocbase, name); if (hash == nullptr) { THROW_ARANGO_EXCEPTION(TRI_ERROR_INTERNAL); } result = hash->keySet(key, 0); } if (!result) { THROW_ARANGO_EXCEPTION(TRI_ERROR_INTERNAL); } } //////////////////////////////////////////////////////////////////////////////// /// @brief conditionally set the value for a key in the keyspace //////////////////////////////////////////////////////////////////////////////// static void JS_KeySetCas(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); if (args.Length() < 4 || !args[0]->IsString() || !args[1]->IsString()) { TRI_V8_THROW_EXCEPTION_USAGE( "KEY_SET_CAS(, , , )"); } auto& vocbase = GetContextVocBase(isolate); std::string const name = TRI_ObjectToString(args[0]); std::string const key = TRI_ObjectToString(args[1]); if (args[2]->IsUndefined()) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_INTERNAL); } auto h = &(static_cast(vocbase._userStructures)->hashes); int res; bool match = false; { READ_LOCKER(readLocker, h->lock); auto hash = GetKeySpace(&vocbase, name); if (hash == nullptr) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_INTERNAL); } res = hash->keyCas(isolate, key, args[2], args[3], match); } if (res != TRI_ERROR_NO_ERROR) { TRI_V8_THROW_EXCEPTION(res); } if (match) { TRI_V8_RETURN_TRUE(); } TRI_V8_RETURN_FALSE(); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief remove the value for a key in the keyspace //////////////////////////////////////////////////////////////////////////////// static void JS_KeyRemove(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); if (args.Length() < 2 || !args[0]->IsString() || !args[1]->IsString()) { TRI_V8_THROW_EXCEPTION_USAGE("KEY_REMOVE(, )"); } auto& vocbase = GetContextVocBase(isolate); std::string const name = TRI_ObjectToString(args[0]); std::string const key = TRI_ObjectToString(args[1]); auto h = &(static_cast(vocbase._userStructures)->hashes); bool result; { READ_LOCKER(readLocker, h->lock); auto hash = GetKeySpace(&vocbase, name); if (hash == nullptr) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_INTERNAL); } result = hash->keyRemove(key); } if (result) { TRI_V8_RETURN_TRUE(); } TRI_V8_RETURN_FALSE(); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief checks if a key exists in the keyspace //////////////////////////////////////////////////////////////////////////////// static void JS_KeyExists(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); if (args.Length() < 2 || !args[0]->IsString() || !args[1]->IsString()) { TRI_V8_THROW_EXCEPTION_USAGE("KEY_EXISTS(, )"); } auto& vocbase = GetContextVocBase(isolate); std::string const name = TRI_ObjectToString(args[0]); std::string const key = TRI_ObjectToString(args[1]); auto h = &(static_cast(vocbase._userStructures)->hashes); bool result; { READ_LOCKER(readLocker, h->lock); auto hash = GetKeySpace(&vocbase, name); if (hash == nullptr) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_INTERNAL); } result = hash->keyExists(key); } if (result) { TRI_V8_RETURN_TRUE(); } TRI_V8_RETURN_FALSE(); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief increase or decrease the value for a key in a keyspace //////////////////////////////////////////////////////////////////////////////// static void JS_KeyIncr(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); if (args.Length() < 2 || !args[0]->IsString() || !args[1]->IsString()) { TRI_V8_THROW_EXCEPTION_USAGE("KEY_INCR(, , )"); } if (args.Length() >= 3 && !args[2]->IsNumber()) { TRI_V8_THROW_EXCEPTION_USAGE("KEY_INCR(, , )"); } auto& vocbase = GetContextVocBase(isolate); std::string const name = TRI_ObjectToString(args[0]); std::string const key = TRI_ObjectToString(args[1]); double incr = 1.0; if (args.Length() >= 3) { incr = TRI_ObjectToDouble(args[2]); } double result; auto h = &(static_cast(vocbase._userStructures)->hashes); { READ_LOCKER(readLocker, h->lock); auto hash = GetKeySpace(&vocbase, name); if (hash == nullptr) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_INTERNAL); } int res = hash->keyIncr(key, incr, result); if (res != TRI_ERROR_NO_ERROR) { TRI_V8_THROW_EXCEPTION(res); } } TRI_V8_RETURN(v8::Number::New(isolate, result)); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief merges an object into the object with the specified key //////////////////////////////////////////////////////////////////////////////// static void JS_KeyUpdate(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); if (args.Length() < 3 || !args[0]->IsString() || !args[1]->IsString()) { TRI_V8_THROW_EXCEPTION_USAGE( "KEY_UPDATE(, , , )"); } auto& vocbase = GetContextVocBase(isolate); std::string const name = TRI_ObjectToString(args[0]); std::string const key = TRI_ObjectToString(args[1]); bool nullMeansRemove = false; if (args.Length() > 3) { nullMeansRemove = TRI_ObjectToBoolean(args[3]); } auto h = &(static_cast(vocbase._userStructures)->hashes); READ_LOCKER(readLocker, h->lock); auto hash = GetKeySpace(&vocbase, name); if (hash == nullptr) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_INTERNAL); } hash->keyMerge(args, key, args[2], nullMeansRemove); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief returns all keys of the key //////////////////////////////////////////////////////////////////////////////// static void JS_KeyKeys(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); if (args.Length() < 2 || !args[0]->IsString() || !args[1]->IsString()) { TRI_V8_THROW_EXCEPTION_USAGE("KEY_KEYS(, )"); } auto& vocbase = GetContextVocBase(isolate); std::string const name = TRI_ObjectToString(args[0]); std::string const key = TRI_ObjectToString(args[1]); auto h = &(static_cast(vocbase._userStructures)->hashes); READ_LOCKER(readLocker, h->lock); auto hash = GetKeySpace(&vocbase, name); if (hash == nullptr) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_INTERNAL); } TRI_V8_RETURN(hash->keyKeys(isolate, key)); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief returns all value of the hash array //////////////////////////////////////////////////////////////////////////////// static void JS_KeyValues(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); if (args.Length() < 2 || !args[0]->IsString() || !args[1]->IsString()) { TRI_V8_THROW_EXCEPTION_USAGE("KEY_VALUES(, )"); } auto& vocbase = GetContextVocBase(isolate); std::string const name = TRI_ObjectToString(args[0]); std::string const key = TRI_ObjectToString(args[1]); auto h = &(static_cast(vocbase._userStructures)->hashes); READ_LOCKER(readLocker, h->lock); auto hash = GetKeySpace(&vocbase, name); if (hash == nullptr) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_INTERNAL); } TRI_V8_RETURN(hash->keyValues(isolate, key)); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief right-pushes an element into a list value //////////////////////////////////////////////////////////////////////////////// static void JS_KeyPush(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); if (args.Length() < 3 || !args[0]->IsString() || !args[1]->IsString()) { TRI_V8_THROW_EXCEPTION_USAGE("KEY_PUSH(, , )"); } auto& vocbase = GetContextVocBase(isolate); std::string const name = TRI_ObjectToString(args[0]); std::string const key = TRI_ObjectToString(args[1]); auto h = &(static_cast(vocbase._userStructures)->hashes); READ_LOCKER(readLocker, h->lock); auto hash = GetKeySpace(&vocbase, name); if (hash == nullptr) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_INTERNAL); } int res = hash->keyPush(isolate, key, args[2]); if (res != TRI_ERROR_NO_ERROR) { TRI_V8_THROW_EXCEPTION(res); } TRI_V8_RETURN_TRUE(); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief pops an element from a list value //////////////////////////////////////////////////////////////////////////////// static void JS_KeyPop(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); if (args.Length() < 2 || !args[0]->IsString() || !args[1]->IsString()) { TRI_V8_THROW_EXCEPTION_USAGE("KEY_POP(, )"); } auto& vocbase = GetContextVocBase(isolate); std::string const name = TRI_ObjectToString(args[0]); std::string const key = TRI_ObjectToString(args[1]); auto h = &(static_cast(vocbase._userStructures)->hashes); READ_LOCKER(readLocker, h->lock); auto hash = GetKeySpace(&vocbase, name); if (hash == nullptr) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_INTERNAL); } hash->keyPop(args, key); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief transfer an element from a list value into another //////////////////////////////////////////////////////////////////////////////// static void JS_KeyTransfer(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); if (args.Length() < 3 || !args[0]->IsString() || !args[1]->IsString()) { TRI_V8_THROW_EXCEPTION_USAGE("KEY_TRANSFER(, , )"); } auto& vocbase = GetContextVocBase(isolate); std::string const name = TRI_ObjectToString(args[0]); std::string const keyFrom = TRI_ObjectToString(args[1]); std::string const keyTo = TRI_ObjectToString(args[2]); auto h = &(static_cast(vocbase._userStructures)->hashes); READ_LOCKER(readLocker, h->lock); auto hash = GetKeySpace(&vocbase, name); if (hash == nullptr) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_INTERNAL); } hash->keyTransfer(args, keyFrom, keyTo); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief get an element at a specific list position //////////////////////////////////////////////////////////////////////////////// static void JS_KeyGetAt(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); if (args.Length() < 3 || !args[0]->IsString() || !args[1]->IsString()) { TRI_V8_THROW_EXCEPTION_USAGE("KEY_GET_AT(, , )"); } auto& vocbase = GetContextVocBase(isolate); std::string const name = TRI_ObjectToString(args[0]); std::string const key = TRI_ObjectToString(args[1]); int64_t offset = TRI_ObjectToInt64(args[2]); auto h = &(static_cast(vocbase._userStructures)->hashes); READ_LOCKER(readLocker, h->lock); auto hash = GetKeySpace(&vocbase, name); if (hash == nullptr) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_INTERNAL); } hash->keyGetAt(args, key, offset); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief set an element at a specific list position //////////////////////////////////////////////////////////////////////////////// static void JS_KeySetAt(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); if (args.Length() < 4 || !args[0]->IsString() || !args[1]->IsString()) { TRI_V8_THROW_EXCEPTION_USAGE("KEY_SET_AT(, , , )"); } auto& vocbase = GetContextVocBase(isolate); std::string const name = TRI_ObjectToString(args[0]); std::string const key = TRI_ObjectToString(args[1]); int64_t offset = TRI_ObjectToInt64(args[2]); auto h = &(static_cast(vocbase._userStructures)->hashes); READ_LOCKER(readLocker, h->lock); auto hash = GetKeySpace(&vocbase, name); if (hash == nullptr) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_INTERNAL); } int res = hash->keySetAt(isolate, key, offset, args[3]); if (res != TRI_ERROR_NO_ERROR) { TRI_V8_THROW_EXCEPTION(res); } TRI_V8_RETURN_TRUE(); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief returns the type of the value for a key //////////////////////////////////////////////////////////////////////////////// static void JS_KeyType(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); if (args.Length() < 2 || !args[0]->IsString() || !args[1]->IsString()) { TRI_V8_THROW_EXCEPTION_USAGE("KEY_TYPE(, )"); } auto& vocbase = GetContextVocBase(isolate); std::string const name = TRI_ObjectToString(args[0]); std::string const key = TRI_ObjectToString(args[1]); auto h = &(static_cast(vocbase._userStructures)->hashes); char const* result; { READ_LOCKER(readLocker, h->lock); auto hash = GetKeySpace(&vocbase, name); if (hash == nullptr) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_INTERNAL); } result = hash->keyType(key); } TRI_V8_RETURN_STRING(result); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief returns the number of items in a compound value //////////////////////////////////////////////////////////////////////////////// static void JS_KeyCount(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); if (args.Length() < 2 || !args[0]->IsString() || !args[1]->IsString()) { TRI_V8_THROW_EXCEPTION_USAGE("KEY_COUNT(, )"); } auto& vocbase = GetContextVocBase(isolate); std::string const name = TRI_ObjectToString(args[0]); std::string const key = TRI_ObjectToString(args[1]); auto h = &(static_cast(vocbase._userStructures)->hashes); uint32_t result; bool valid; { READ_LOCKER(readLocker, h->lock); auto hash = GetKeySpace(&vocbase, name); if (hash == nullptr) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_INTERNAL); } valid = hash->keyCount(key, result); } if (valid) { TRI_V8_RETURN(v8::Number::New(isolate, result)); } TRI_V8_RETURN_UNDEFINED(); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief creates the user structures for a database //////////////////////////////////////////////////////////////////////////////// void TRI_CreateUserStructuresVocBase(TRI_vocbase_t* vocbase) { TRI_ASSERT(vocbase != nullptr); TRI_ASSERT(vocbase->_userStructures == nullptr); vocbase->_userStructures = new UserStructures; } //////////////////////////////////////////////////////////////////////////////// /// @brief drops the user structures for a database //////////////////////////////////////////////////////////////////////////////// void TRI_FreeUserStructuresVocBase(TRI_vocbase_t* vocbase) { if (vocbase->_userStructures != nullptr) { auto us = static_cast(vocbase->_userStructures); for (auto& hash : us->hashes.data) { if (hash.second != nullptr) { delete hash.second; } } delete us; } } //////////////////////////////////////////////////////////////////////////////// /// @brief creates the user structures functions //////////////////////////////////////////////////////////////////////////////// void TRI_InitV8UserStructures(v8::Isolate* isolate, v8::Handle context) { v8::HandleScope scope(isolate); // NOTE: the following functions are all experimental and might // change without further notice TRI_AddGlobalFunctionVocbase(isolate, TRI_V8_ASCII_STRING(isolate, "KEYSPACE_CREATE"), JS_KeyspaceCreate, true); TRI_AddGlobalFunctionVocbase(isolate, TRI_V8_ASCII_STRING(isolate, "KEYSPACE_DROP"), JS_KeyspaceDrop, true); TRI_AddGlobalFunctionVocbase(isolate, TRI_V8_ASCII_STRING(isolate, "KEYSPACE_COUNT"), JS_KeyspaceCount, true); TRI_AddGlobalFunctionVocbase(isolate, TRI_V8_ASCII_STRING(isolate, "KEYSPACE_EXISTS"), JS_KeyspaceExists, true); TRI_AddGlobalFunctionVocbase(isolate, TRI_V8_ASCII_STRING(isolate, "KEYSPACE_KEYS"), JS_KeyspaceKeys, true); TRI_AddGlobalFunctionVocbase(isolate, TRI_V8_ASCII_STRING(isolate, "KEYSPACE_REMOVE"), JS_KeyspaceRemove, true); TRI_AddGlobalFunctionVocbase(isolate, TRI_V8_ASCII_STRING(isolate, "KEYSPACE_GET"), JS_KeyspaceGet, true); TRI_AddGlobalFunctionVocbase(isolate, TRI_V8_ASCII_STRING(isolate, "KEY_SET"), JS_KeySet, true); TRI_AddGlobalFunctionVocbase(isolate, TRI_V8_ASCII_STRING(isolate, "KEY_SET_CAS"), JS_KeySetCas, true); TRI_AddGlobalFunctionVocbase(isolate, TRI_V8_ASCII_STRING(isolate, "KEY_GET"), JS_KeyGet, true); TRI_AddGlobalFunctionVocbase(isolate, TRI_V8_ASCII_STRING(isolate, "KEY_REMOVE"), JS_KeyRemove, true); TRI_AddGlobalFunctionVocbase(isolate, TRI_V8_ASCII_STRING(isolate, "KEY_EXISTS"), JS_KeyExists, true); TRI_AddGlobalFunctionVocbase(isolate, TRI_V8_ASCII_STRING(isolate, "KEY_TYPE"), JS_KeyType, true); // numeric functions TRI_AddGlobalFunctionVocbase(isolate, TRI_V8_ASCII_STRING(isolate, "KEY_INCR"), JS_KeyIncr, true); // list / array functions TRI_AddGlobalFunctionVocbase(isolate, TRI_V8_ASCII_STRING(isolate, "KEY_UPDATE"), JS_KeyUpdate, true); TRI_AddGlobalFunctionVocbase(isolate, TRI_V8_ASCII_STRING(isolate, "KEY_KEYS"), JS_KeyKeys, true); TRI_AddGlobalFunctionVocbase(isolate, TRI_V8_ASCII_STRING(isolate, "KEY_VALUES"), JS_KeyValues, true); TRI_AddGlobalFunctionVocbase(isolate, TRI_V8_ASCII_STRING(isolate, "KEY_COUNT"), JS_KeyCount, true); TRI_AddGlobalFunctionVocbase(isolate, TRI_V8_ASCII_STRING(isolate, "KEY_PUSH"), JS_KeyPush, true); TRI_AddGlobalFunctionVocbase(isolate, TRI_V8_ASCII_STRING(isolate, "KEY_POP"), JS_KeyPop, true); TRI_AddGlobalFunctionVocbase(isolate, TRI_V8_ASCII_STRING(isolate, "KEY_TRANSFER"), JS_KeyTransfer, true); TRI_AddGlobalFunctionVocbase(isolate, TRI_V8_ASCII_STRING(isolate, "KEY_GET_AT"), JS_KeyGetAt, true); TRI_AddGlobalFunctionVocbase(isolate, TRI_V8_ASCII_STRING(isolate, "KEY_SET_AT"), JS_KeySetAt, true); }