//////////////////////////////////////////////////////////////////////////////// /// @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-vocbaseprivate.h" #include "VocBase/general-cursor.h" #include "v8-voccursor.h" #include "Basics/conversions.h" #include "V8/v8-conv.h" #include "Utils/transactions.h" using namespace std; using namespace triagens::basics; using namespace triagens::arango; using namespace triagens::rest; // ----------------------------------------------------------------------------- // --SECTION-- forward declarations // ----------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////////// /// @brief wrapped class for general cursors /// /// Layout: /// - SLOT_CLASS_TYPE /// - SLOT_CLASS //////////////////////////////////////////////////////////////////////////////// static int32_t const WRP_GENERAL_CURSOR_TYPE = 3; static int const SLOT_CURSOR = 3; // ----------------------------------------------------------------------------- // --SECTION-- GENERAL CURSORS // ----------------------------------------------------------------------------- // ----------------------------------------------------------------------------- // --SECTION-- private functions // ----------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////////// /// @brief weak reference callback for general cursors //////////////////////////////////////////////////////////////////////////////// static void WeakGeneralCursorCallback (const v8::WeakCallbackData>& data) { auto isolate = data.GetIsolate(); auto persistent = data.GetParameter(); auto myCursor = v8::Local::New(isolate, *persistent); auto cursor = static_cast(myCursor->Value()); v8::HandleScope scope(isolate); TRI_GET_GLOBALS(); v8g->_hasDeadObjects = true; TRI_ReleaseGeneralCursor(cursor); // decrease the reference-counter for the database TRI_ReleaseVocBase(cursor->_vocbase); // find the persistent handle #if TRI_ENABLE_MAINTAINER_MODE map>::iterator it = v8g->JSCursors.find(cursor); TRI_ASSERT(it != v8g->JSCursors.end()) #endif // dispose and clear the persistent handle v8g->JSCursors[cursor].Reset(); v8g->JSCursors.erase(cursor); } //////////////////////////////////////////////////////////////////////////////// /// @brief stores a general cursor in a V8 object //////////////////////////////////////////////////////////////////////////////// void TRI_WrapGeneralCursor (const v8::FunctionCallbackInfo& args, TRI_general_cursor_t* cursor) { v8::Isolate* isolate = args.GetIsolate(); v8::TryCatch tryCatch; v8::HandleScope scope(isolate); TRI_ASSERT(cursor != nullptr); TRI_GET_GLOBALS(); TRI_GET_GLOBAL(GeneralCursorTempl, v8::ObjectTemplate); v8::Handle result = GeneralCursorTempl->NewInstance(); if (! result.IsEmpty()) { TRI_UseGeneralCursor(cursor); result->SetInternalField(SLOT_CLASS_TYPE, v8::Integer::New(isolate, WRP_GENERAL_CURSOR_TYPE)); result->SetInternalField(SLOT_CLASS, v8::External::New(isolate, cursor)); map>::iterator it = v8g->JSCursors.find(cursor); if (it == v8g->JSCursors.end()) { // increase the reference-counter for the database TRI_UseVocBase(cursor->_vocbase); auto externalCursor = v8::External::New(isolate, cursor); result->SetInternalField(SLOT_CURSOR, externalCursor); v8g->JSCursors[cursor].Reset(isolate, externalCursor); v8g->JSCursors[cursor].SetWeak(&v8g->JSCursors[cursor], WeakGeneralCursorCallback); } else { auto myCursor = v8::Local::New(isolate, it->second); result->SetInternalField(SLOT_CURSOR, myCursor); } if (tryCatch.HasCaught()) { TRI_V8_RETURN_UNDEFINED(); } } if (result.IsEmpty()) { TRI_V8_THROW_EXCEPTION_MEMORY(); } TRI_V8_RETURN(result); } //////////////////////////////////////////////////////////////////////////////// /// @brief extracts a cursor from a V8 object //////////////////////////////////////////////////////////////////////////////// static TRI_general_cursor_t* UnwrapGeneralCursor (v8::Handle cursorObject) { return TRI_UnwrapClass(cursorObject, WRP_GENERAL_CURSOR_TYPE); } //////////////////////////////////////////////////////////////////////////////// /// @brief generates a general cursor from a list //////////////////////////////////////////////////////////////////////////////// static void JS_CreateCursor (const v8::FunctionCallbackInfo& args) { 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); } if (args.Length() < 1) { TRI_V8_THROW_EXCEPTION_USAGE("CREATE_CURSOR(, , , )"); } if (! args[0]->IsArray()) { TRI_V8_THROW_TYPE_ERROR(" must be a list"); } // extract objects v8::Handle array = v8::Handle::Cast(args[0]); TRI_json_t* json = TRI_ObjectToJson(isolate, array); if (json == nullptr) { TRI_V8_THROW_TYPE_ERROR("cannot convert to JSON"); } // return number of total records in cursor? bool doCount = false; if (args.Length() >= 2) { doCount = TRI_ObjectToBoolean(args[1]); } // maximum number of results to return at once uint32_t batchSize = 1000; if (args.Length() >= 3) { int64_t maxValue = TRI_ObjectToInt64(args[2]); if (maxValue > 0 && maxValue < (int64_t) UINT32_MAX) { batchSize = (uint32_t) maxValue; } } double ttl = 0.0; if (args.Length() >= 4) { ttl = TRI_ObjectToDouble(args[3]); } if (ttl <= 0.0) { ttl = 30.0; // default ttl } // create a cursor TRI_general_cursor_result_t* cursorResult = TRI_CreateResultGeneralCursor(json); if (cursorResult == nullptr) { TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, json); TRI_V8_THROW_EXCEPTION_MEMORY(); } TRI_general_cursor_t* cursor = TRI_CreateGeneralCursor(vocbase, cursorResult, doCount, batchSize, ttl, nullptr); if (cursor == nullptr) { TRI_FreeCursorResult(cursorResult); TRI_V8_THROW_EXCEPTION_MEMORY(); } TRI_WrapGeneralCursor(args, cursor); } //////////////////////////////////////////////////////////////////////////////// /// @brief destroys a general cursor //////////////////////////////////////////////////////////////////////////////// static void JS_DisposeGeneralCursor (const v8::FunctionCallbackInfo& args) { v8::Isolate* isolate = args.GetIsolate(); v8::HandleScope scope(isolate); if (args.Length() != 0) { TRI_V8_THROW_EXCEPTION_USAGE("dispose()"); } bool found = TRI_DropGeneralCursor(UnwrapGeneralCursor(args.Holder())); if (found) { TRI_V8_RETURN_TRUE(); } else { TRI_V8_RETURN_FALSE(); } } //////////////////////////////////////////////////////////////////////////////// /// @brief returns the id of a general cursor //////////////////////////////////////////////////////////////////////////////// static void JS_IdGeneralCursor (const v8::FunctionCallbackInfo& args) { v8::Isolate* isolate = args.GetIsolate(); v8::HandleScope scope(isolate); if (args.Length() != 0) { TRI_V8_THROW_EXCEPTION_USAGE("id()"); } TRI_voc_tick_t id = TRI_IdGeneralCursor(UnwrapGeneralCursor(args.Holder())); if (id != 0) { TRI_V8_RETURN(V8TickId(isolate, id)); } TRI_V8_THROW_EXCEPTION(TRI_ERROR_CURSOR_NOT_FOUND); } //////////////////////////////////////////////////////////////////////////////// /// @brief returns the number of results //////////////////////////////////////////////////////////////////////////////// static void JS_CountGeneralCursor (const v8::FunctionCallbackInfo& args) { v8::Isolate* isolate = args.GetIsolate(); v8::HandleScope scope(isolate); if (args.Length() != 0) { TRI_V8_THROW_EXCEPTION_USAGE("count()"); } size_t length = TRI_CountGeneralCursor(UnwrapGeneralCursor(args.Holder())); TRI_V8_RETURN(v8::Number::New(isolate, (double) length)); } //////////////////////////////////////////////////////////////////////////////// /// @brief returns the next result from the general cursor //////////////////////////////////////////////////////////////////////////////// static void JS_NextGeneralCursor (const v8::FunctionCallbackInfo& args) { v8::Isolate* isolate = args.GetIsolate(); v8::HandleScope scope(isolate); if (args.Length() != 0) { TRI_V8_THROW_EXCEPTION_USAGE("next()"); } v8::Handle value; TRI_general_cursor_t* cursor; cursor = (TRI_general_cursor_t*) TRI_UseGeneralCursor(UnwrapGeneralCursor(args.Holder())); if (cursor != 0) { bool result = false; TRI_LockGeneralCursor(cursor); if (cursor->_length == 0) { TRI_UnlockGeneralCursor(cursor); TRI_ReleaseGeneralCursor(cursor); TRI_V8_RETURN_UNDEFINED(); } // exceptions must be caught in the following part because we hold an exclusive // lock that might otherwise not be freed v8::TryCatch tryCatch; try { TRI_general_cursor_row_t row = cursor->next(cursor); if (row == nullptr) { value = v8::Undefined(isolate); } else { value = TRI_ObjectJson(isolate, (TRI_json_t*) row); result = true; } } catch (...) { } TRI_UnlockGeneralCursor(cursor); TRI_ReleaseGeneralCursor(cursor); if (result && ! tryCatch.HasCaught()) { TRI_V8_RETURN(value); } if (tryCatch.HasCaught()) { if (tryCatch.CanContinue()) { tryCatch.ReThrow(); TRI_V8_RETURN_UNDEFINED(); } else { TRI_GET_GLOBALS(); v8g->_canceled = true; TRI_V8_RETURN_UNDEFINED(); } } } TRI_V8_THROW_EXCEPTION(TRI_ERROR_CURSOR_NOT_FOUND); } //////////////////////////////////////////////////////////////////////////////// /// @brief persist the general cursor for usage in subsequent requests //////////////////////////////////////////////////////////////////////////////// static void JS_PersistGeneralCursor (const v8::FunctionCallbackInfo& args) { v8::Isolate* isolate = args.GetIsolate(); v8::HandleScope scope(isolate); if (args.Length() != 0) { TRI_V8_THROW_EXCEPTION_USAGE("persist()"); } TRI_vocbase_t* vocbase = GetContextVocBase(isolate); TRI_PersistGeneralCursor(vocbase, UnwrapGeneralCursor(args.Holder())); TRI_V8_RETURN_TRUE(); } //////////////////////////////////////////////////////////////////////////////// /// @brief return all following rows from the cursor in one go /// /// This function constructs multiple rows at once and should be preferred over /// hasNext()...next() when iterating over bigger result sets //////////////////////////////////////////////////////////////////////////////// static void JS_ToArrayGeneralCursor (const v8::FunctionCallbackInfo& args) { v8::Isolate* isolate = args.GetIsolate(); v8::TryCatch tryCatch; v8::HandleScope scope(isolate); if (args.Length() != 0) { TRI_V8_THROW_EXCEPTION_USAGE("toArray()"); } v8::Handle rows = v8::Array::New(isolate); TRI_general_cursor_t* cursor = TRI_UseGeneralCursor(UnwrapGeneralCursor(args.Holder())); if (cursor != nullptr) { bool result = false; TRI_LockGeneralCursor(cursor); // exceptions must be caught in the following part because we hold an exclusive // lock that might otherwise not be freed try { uint32_t max = (uint32_t) cursor->getBatchSize(cursor); for (uint32_t i = 0; i < max; ++i) { TRI_general_cursor_row_t row = cursor->next(cursor); if (row == nullptr) { break; } rows->Set(i, TRI_ObjectJson(isolate, (TRI_json_t*) row)); } result = true; } catch (...) { } TRI_UnlockGeneralCursor(cursor); TRI_ReleaseGeneralCursor(cursor); if (result && ! tryCatch.HasCaught()) { TRI_V8_RETURN(rows); } if (tryCatch.HasCaught()) { if (tryCatch.CanContinue()) { tryCatch.ReThrow(); TRI_V8_RETURN_UNDEFINED(); } else { TRI_GET_GLOBALS(); v8g->_canceled = true; TRI_V8_RETURN_UNDEFINED(); } } } TRI_V8_THROW_EXCEPTION(TRI_ERROR_CURSOR_NOT_FOUND); } //////////////////////////////////////////////////////////////////////////////// /// @brief alias for toArray() /// @deprecated //////////////////////////////////////////////////////////////////////////////// static void JS_GetRowsGeneralCursor (const v8::FunctionCallbackInfo& args) { return JS_ToArrayGeneralCursor(args); } //////////////////////////////////////////////////////////////////////////////// /// @brief return max number of results per transfer for cursor //////////////////////////////////////////////////////////////////////////////// static void JS_GetBatchSizeGeneralCursor (const v8::FunctionCallbackInfo& args) { v8::Isolate* isolate = args.GetIsolate(); v8::HandleScope scope(isolate); if (args.Length() != 0) { TRI_V8_THROW_EXCEPTION_USAGE("getBatchSize()"); } TRI_general_cursor_t* cursor = TRI_UseGeneralCursor(UnwrapGeneralCursor(args.Holder())); if (cursor != nullptr) { TRI_LockGeneralCursor(cursor); uint32_t max = cursor->getBatchSize(cursor); TRI_UnlockGeneralCursor(cursor); TRI_ReleaseGeneralCursor(cursor); TRI_V8_RETURN(v8::Number::New(isolate, (double) max)); } TRI_V8_THROW_EXCEPTION(TRI_ERROR_CURSOR_NOT_FOUND); } //////////////////////////////////////////////////////////////////////////////// /// @brief return extra data for cursor //////////////////////////////////////////////////////////////////////////////// static void JS_GetExtraGeneralCursor (const v8::FunctionCallbackInfo& args) { v8::Isolate* isolate = args.GetIsolate(); v8::HandleScope scope(isolate); if (args.Length() != 0) { TRI_V8_THROW_EXCEPTION_USAGE("getExtra()"); } TRI_general_cursor_t* cursor = TRI_UseGeneralCursor(UnwrapGeneralCursor(args.Holder())); if (cursor != nullptr) { TRI_LockGeneralCursor(cursor); TRI_json_t* extra = cursor->getExtra(cursor); if (extra != nullptr && extra->_type == TRI_JSON_ARRAY) { TRI_UnlockGeneralCursor(cursor); TRI_ReleaseGeneralCursor(cursor); TRI_V8_RETURN(TRI_ObjectJson(isolate, extra)); } TRI_UnlockGeneralCursor(cursor); TRI_ReleaseGeneralCursor(cursor); TRI_V8_RETURN_UNDEFINED(); } TRI_V8_THROW_EXCEPTION(TRI_ERROR_CURSOR_NOT_FOUND); } //////////////////////////////////////////////////////////////////////////////// /// @brief return if count flag was set for cursor //////////////////////////////////////////////////////////////////////////////// static void JS_HasCountGeneralCursor (const v8::FunctionCallbackInfo& args) { v8::Isolate* isolate = args.GetIsolate(); v8::HandleScope scope(isolate); if (args.Length() != 0) { TRI_V8_THROW_EXCEPTION_USAGE("hasCount()"); } TRI_general_cursor_t* cursor = TRI_UseGeneralCursor(UnwrapGeneralCursor(args.Holder())); if (cursor != nullptr) { TRI_LockGeneralCursor(cursor); bool hasCount = cursor->hasCount(cursor); TRI_UnlockGeneralCursor(cursor); TRI_ReleaseGeneralCursor(cursor); if (hasCount) { TRI_V8_RETURN_TRUE(); } else { TRI_V8_RETURN_FALSE(); } } TRI_V8_THROW_EXCEPTION(TRI_ERROR_CURSOR_NOT_FOUND); } //////////////////////////////////////////////////////////////////////////////// /// @brief checks if the cursor is exhausted //////////////////////////////////////////////////////////////////////////////// static void JS_HasNextGeneralCursor (const v8::FunctionCallbackInfo& args) { v8::Isolate* isolate = args.GetIsolate(); v8::HandleScope scope(isolate); if (args.Length() != 0) { TRI_V8_THROW_EXCEPTION_USAGE("hasNext()"); } TRI_general_cursor_t* cursor = TRI_UseGeneralCursor(UnwrapGeneralCursor(args.Holder())); if (cursor != nullptr) { TRI_LockGeneralCursor(cursor); bool hasNext = cursor->hasNext(cursor); TRI_UnlockGeneralCursor(cursor); TRI_ReleaseGeneralCursor(cursor); if (hasNext) { TRI_V8_RETURN_TRUE(); } else { TRI_V8_RETURN_FALSE(); } } TRI_V8_THROW_EXCEPTION(TRI_ERROR_CURSOR_NOT_FOUND); } //////////////////////////////////////////////////////////////////////////////// /// @brief get a (persistent) cursor by its id //////////////////////////////////////////////////////////////////////////////// static void JS_Cursor (const v8::FunctionCallbackInfo& args) { v8::Isolate* isolate = args.GetIsolate(); v8::HandleScope scope(isolate); if (args.Length() != 1) { TRI_V8_THROW_EXCEPTION_USAGE("CURSOR()"); } TRI_vocbase_t* vocbase = GetContextVocBase(isolate); if (vocbase == nullptr) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_ARANGO_DATABASE_NOT_FOUND); } // get the id v8::Handle idArg = args[0]->ToString(); if (! idArg->IsString()) { TRI_V8_THROW_TYPE_ERROR("expecting a string for )"); } const string idString = TRI_ObjectToString(idArg); uint64_t id = TRI_UInt64String(idString.c_str()); TRI_general_cursor_t* cursor = TRI_FindGeneralCursor(vocbase, (TRI_voc_tick_t) id); if (cursor == nullptr) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_CURSOR_NOT_FOUND); } TRI_WrapGeneralCursor(args, cursor); } //////////////////////////////////////////////////////////////////////////////// /// @brief delete a (persistent) cursor by its id //////////////////////////////////////////////////////////////////////////////// static void JS_DeleteCursor (const v8::FunctionCallbackInfo& args) { v8::Isolate* isolate = args.GetIsolate(); v8::HandleScope scope(isolate); if (args.Length() != 1) { TRI_V8_THROW_EXCEPTION_USAGE("DELETE_CURSOR()"); } TRI_vocbase_t* vocbase = GetContextVocBase(isolate); if (vocbase == nullptr) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_ARANGO_DATABASE_NOT_FOUND); } // get the id v8::Handle idArg = args[0]->ToString(); if (! idArg->IsString()) { TRI_V8_THROW_TYPE_ERROR("expecting a string for )"); } const string idString = TRI_ObjectToString(idArg); uint64_t id = TRI_UInt64String(idString.c_str()); bool found = TRI_RemoveGeneralCursor(vocbase, id); if (found) { TRI_V8_RETURN_TRUE(); } else { TRI_V8_RETURN_FALSE(); } } // ............................................................................. // generate the general cursor template // ............................................................................. void TRI_InitV8cursor (v8::Handle context, TRI_v8_global_t* v8g){ v8::Handle rt; v8::Handle ft; v8::Isolate* isolate = v8::Isolate::GetCurrent(); ft = v8::FunctionTemplate::New(isolate); ft->SetClassName(TRI_V8_ASCII_STRING("ArangoCursor")); rt = ft->InstanceTemplate(); rt->SetInternalFieldCount(4); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING("count"), JS_CountGeneralCursor); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING("dispose"), JS_DisposeGeneralCursor); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING("getBatchSize"), JS_GetBatchSizeGeneralCursor); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING("getExtra"), JS_GetExtraGeneralCursor); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING("getRows"), JS_GetRowsGeneralCursor, true); // DEPRECATED, use toArray TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING("hasCount"), JS_HasCountGeneralCursor); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING("hasNext"), JS_HasNextGeneralCursor); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING("id"), JS_IdGeneralCursor); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING("next"), JS_NextGeneralCursor); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING("persist"), JS_PersistGeneralCursor); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING("toArray"), JS_ToArrayGeneralCursor); v8g->GeneralCursorTempl.Reset(isolate, rt); TRI_AddGlobalFunctionVocbase(isolate, context, TRI_V8_ASCII_STRING("ArangoCursor"), ft->GetFunction()); // cursor functions. not intended to be used by end users TRI_AddGlobalFunctionVocbase(isolate, context, TRI_V8_ASCII_STRING("CURSOR"), JS_Cursor, true); TRI_AddGlobalFunctionVocbase(isolate, context, TRI_V8_ASCII_STRING("CREATE_CURSOR"), JS_CreateCursor, true); TRI_AddGlobalFunctionVocbase(isolate, context, TRI_V8_ASCII_STRING("DELETE_CURSOR"), JS_DeleteCursor, true); }