//////////////////////////////////////////////////////////////////////////////// /// @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-voccursor.h" #include "v8-vocbaseprivate.h" #include "VocBase/general-cursor.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; // ----------------------------------------------------------------------------- // --SECTION-- GENERAL CURSORS // ----------------------------------------------------------------------------- // ----------------------------------------------------------------------------- // --SECTION-- private functions // ----------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////////// /// @brief weak reference callback for general cursors //////////////////////////////////////////////////////////////////////////////// static void WeakGeneralCursorCallback (v8::Isolate* isolate, v8::Persistent object, void* parameter) { v8::HandleScope scope; // do not remove, will fail otherwise!! TRI_v8_global_t* v8g = static_cast(v8::Isolate::GetCurrent()->GetData()); v8g->_hasDeadObjects = true; TRI_general_cursor_t* cursor = (TRI_general_cursor_t*) parameter; TRI_ReleaseGeneralCursor(cursor); // decrease the reference-counter for the database TRI_ReleaseVocBase(cursor->_vocbase); // dispose and clear the persistent handle object.Dispose(isolate); object.Clear(); } //////////////////////////////////////////////////////////////////////////////// /// @brief stores a general cursor in a V8 object //////////////////////////////////////////////////////////////////////////////// v8::Handle TRI_WrapGeneralCursor (void* cursor) { v8::HandleScope scope; v8::TryCatch tryCatch; TRI_ASSERT(cursor != nullptr); v8::Isolate* isolate = v8::Isolate::GetCurrent(); TRI_v8_global_t* v8g = static_cast(isolate->GetData()); v8::Handle result = v8g->GeneralCursorTempl->NewInstance(); if (! result.IsEmpty()) { TRI_general_cursor_t* c = (TRI_general_cursor_t*) cursor; TRI_UseGeneralCursor(c); // increase the reference-counter for the database TRI_UseVocBase(c->_vocbase); v8::Persistent persistent = v8::Persistent::New(isolate, v8::External::New(cursor)); if (tryCatch.HasCaught()) { return scope.Close(v8::Undefined()); } result->SetInternalField(SLOT_CLASS_TYPE, v8::Integer::New(WRP_GENERAL_CURSOR_TYPE)); result->SetInternalField(SLOT_CLASS, persistent); persistent.MakeWeak(isolate, cursor, WeakGeneralCursorCallback); } return scope.Close(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 v8::Handle JS_CreateCursor (v8::Arguments const& argv) { v8::HandleScope scope; TRI_vocbase_t* vocbase = GetContextVocBase(); if (vocbase == nullptr) { TRI_V8_EXCEPTION(scope, TRI_ERROR_ARANGO_DATABASE_NOT_FOUND); } if (argv.Length() < 1) { TRI_V8_EXCEPTION_USAGE(scope, "CREATE_CURSOR(, , , )"); } if (! argv[0]->IsArray()) { TRI_V8_TYPE_ERROR(scope, " must be a list"); } // extract objects v8::Handle array = v8::Handle::Cast(argv[0]); TRI_json_t* json = TRI_ObjectToJson(array); if (json == nullptr) { TRI_V8_TYPE_ERROR(scope, "cannot convert to JSON"); } // return number of total records in cursor? bool doCount = false; if (argv.Length() >= 2) { doCount = TRI_ObjectToBoolean(argv[1]); } // maximum number of results to return at once uint32_t batchSize = 1000; if (argv.Length() >= 3) { int64_t maxValue = TRI_ObjectToInt64(argv[2]); if (maxValue > 0 && maxValue < (int64_t) UINT32_MAX) { batchSize = (uint32_t) maxValue; } } double ttl = 0.0; if (argv.Length() >= 4) { ttl = TRI_ObjectToDouble(argv[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_EXCEPTION_MEMORY(scope); } TRI_general_cursor_t* cursor = TRI_CreateGeneralCursor(vocbase, cursorResult, doCount, batchSize, ttl, nullptr); if (cursor == nullptr) { TRI_FreeCursorResult(cursorResult); TRI_V8_EXCEPTION_MEMORY(scope); } v8::Handle cursorObject = TRI_WrapGeneralCursor(cursor); if (cursorObject.IsEmpty()) { TRI_V8_EXCEPTION_MEMORY(scope); } return scope.Close(cursorObject); } //////////////////////////////////////////////////////////////////////////////// /// @brief destroys a general cursor //////////////////////////////////////////////////////////////////////////////// static v8::Handle JS_DisposeGeneralCursor (v8::Arguments const& argv) { v8::HandleScope scope; if (argv.Length() != 0) { TRI_V8_EXCEPTION_USAGE(scope, "dispose()"); } bool found = TRI_DropGeneralCursor(UnwrapGeneralCursor(argv.Holder())); return scope.Close(v8::Boolean::New(found)); } //////////////////////////////////////////////////////////////////////////////// /// @brief returns the id of a general cursor //////////////////////////////////////////////////////////////////////////////// static v8::Handle JS_IdGeneralCursor (v8::Arguments const& argv) { v8::HandleScope scope; if (argv.Length() != 0) { TRI_V8_EXCEPTION_USAGE(scope, "id()"); } TRI_voc_tick_t id = TRI_IdGeneralCursor(UnwrapGeneralCursor(argv.Holder())); if (id != 0) { return scope.Close(V8TickId(id)); } TRI_V8_EXCEPTION(scope, TRI_ERROR_CURSOR_NOT_FOUND); } //////////////////////////////////////////////////////////////////////////////// /// @brief returns the number of results //////////////////////////////////////////////////////////////////////////////// static v8::Handle JS_CountGeneralCursor (v8::Arguments const& argv) { v8::HandleScope scope; if (argv.Length() != 0) { TRI_V8_EXCEPTION_USAGE(scope, "count()"); } size_t length = TRI_CountGeneralCursor(UnwrapGeneralCursor(argv.Holder())); return scope.Close(v8::Number::New((double) length)); } //////////////////////////////////////////////////////////////////////////////// /// @brief returns the next result from the general cursor //////////////////////////////////////////////////////////////////////////////// static v8::Handle JS_NextGeneralCursor (v8::Arguments const& argv) { v8::HandleScope scope; if (argv.Length() != 0) { TRI_V8_EXCEPTION_USAGE(scope, "next()"); } v8::Handle value; TRI_general_cursor_t* cursor; cursor = (TRI_general_cursor_t*) TRI_UseGeneralCursor(UnwrapGeneralCursor(argv.Holder())); if (cursor != 0) { bool result = false; TRI_LockGeneralCursor(cursor); if (cursor->_length == 0) { TRI_UnlockGeneralCursor(cursor); TRI_ReleaseGeneralCursor(cursor); return scope.Close(v8::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 == 0) { value = v8::Undefined(); } else { value = TRI_ObjectJson((TRI_json_t*) row); result = true; } } catch (...) { } TRI_UnlockGeneralCursor(cursor); TRI_ReleaseGeneralCursor(cursor); if (result && ! tryCatch.HasCaught()) { return scope.Close(value); } if (tryCatch.HasCaught()) { if (tryCatch.CanContinue()) { return scope.Close(v8::ThrowException(tryCatch.Exception())); } else { TRI_v8_global_t* v8g = static_cast(v8::Isolate::GetCurrent()->GetData()); v8g->_canceled = true; return scope.Close(v8::Undefined()); } } } TRI_V8_EXCEPTION(scope, TRI_ERROR_CURSOR_NOT_FOUND); } //////////////////////////////////////////////////////////////////////////////// /// @brief persist the general cursor for usage in subsequent requests //////////////////////////////////////////////////////////////////////////////// static v8::Handle JS_PersistGeneralCursor (v8::Arguments const& argv) { v8::HandleScope scope; if (argv.Length() != 0) { TRI_V8_EXCEPTION_USAGE(scope, "persist()"); } TRI_vocbase_t* vocbase = GetContextVocBase(); TRI_PersistGeneralCursor(vocbase, UnwrapGeneralCursor(argv.Holder())); return scope.Close(v8::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 v8::Handle JS_ToArrayGeneralCursor (v8::Arguments const& argv) { v8::HandleScope scope; if (argv.Length() != 0) { TRI_V8_EXCEPTION_USAGE(scope, "toArray()"); } v8::Handle rows = v8::Array::New(); TRI_general_cursor_t* cursor = TRI_UseGeneralCursor(UnwrapGeneralCursor(argv.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 v8::TryCatch tryCatch; 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((TRI_json_t*) row)); } result = true; } catch (...) { } TRI_UnlockGeneralCursor(cursor); TRI_ReleaseGeneralCursor(cursor); if (result && ! tryCatch.HasCaught()) { return scope.Close(rows); } if (tryCatch.HasCaught()) { if (tryCatch.CanContinue()) { return scope.Close(v8::ThrowException(tryCatch.Exception())); } else { TRI_v8_global_t* v8g = static_cast(v8::Isolate::GetCurrent()->GetData()); v8g->_canceled = true; return scope.Close(v8::Undefined()); } } } TRI_V8_EXCEPTION(scope, TRI_ERROR_CURSOR_NOT_FOUND); } //////////////////////////////////////////////////////////////////////////////// /// @brief alias for toArray() /// @deprecated //////////////////////////////////////////////////////////////////////////////// static v8::Handle JS_GetRowsGeneralCursor (v8::Arguments const& argv) { return JS_ToArrayGeneralCursor(argv); } //////////////////////////////////////////////////////////////////////////////// /// @brief return max number of results per transfer for cursor //////////////////////////////////////////////////////////////////////////////// static v8::Handle JS_GetBatchSizeGeneralCursor (v8::Arguments const& argv) { v8::HandleScope scope; if (argv.Length() != 0) { TRI_V8_EXCEPTION_USAGE(scope, "getBatchSize()"); } TRI_general_cursor_t* cursor = TRI_UseGeneralCursor(UnwrapGeneralCursor(argv.Holder())); if (cursor != nullptr) { TRI_LockGeneralCursor(cursor); uint32_t max = cursor->getBatchSize(cursor); TRI_UnlockGeneralCursor(cursor); TRI_ReleaseGeneralCursor(cursor); return scope.Close(v8::Number::New((double) max)); } TRI_V8_EXCEPTION(scope, TRI_ERROR_CURSOR_NOT_FOUND); } //////////////////////////////////////////////////////////////////////////////// /// @brief return extra data for cursor //////////////////////////////////////////////////////////////////////////////// static v8::Handle JS_GetExtraGeneralCursor (v8::Arguments const& argv) { v8::HandleScope scope; if (argv.Length() != 0) { TRI_V8_EXCEPTION_USAGE(scope, "getExtra()"); } TRI_general_cursor_t* cursor = TRI_UseGeneralCursor(UnwrapGeneralCursor(argv.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); return scope.Close(TRI_ObjectJson(extra)); } TRI_UnlockGeneralCursor(cursor); TRI_ReleaseGeneralCursor(cursor); return scope.Close(v8::Undefined()); } TRI_V8_EXCEPTION(scope, TRI_ERROR_CURSOR_NOT_FOUND); } //////////////////////////////////////////////////////////////////////////////// /// @brief return if count flag was set for cursor //////////////////////////////////////////////////////////////////////////////// static v8::Handle JS_HasCountGeneralCursor (v8::Arguments const& argv) { v8::HandleScope scope; if (argv.Length() != 0) { TRI_V8_EXCEPTION_USAGE(scope, "hasCount()"); } TRI_general_cursor_t* cursor = TRI_UseGeneralCursor(UnwrapGeneralCursor(argv.Holder())); if (cursor != nullptr) { TRI_LockGeneralCursor(cursor); bool hasCount = cursor->hasCount(cursor); TRI_UnlockGeneralCursor(cursor); TRI_ReleaseGeneralCursor(cursor); return scope.Close(hasCount ? v8::True() : v8::False()); } TRI_V8_EXCEPTION(scope, TRI_ERROR_CURSOR_NOT_FOUND); } //////////////////////////////////////////////////////////////////////////////// /// @brief checks if the cursor is exhausted //////////////////////////////////////////////////////////////////////////////// static v8::Handle JS_HasNextGeneralCursor (v8::Arguments const& argv) { v8::HandleScope scope; if (argv.Length() != 0) { TRI_V8_EXCEPTION_USAGE(scope, "hasNext()"); } TRI_general_cursor_t* cursor = TRI_UseGeneralCursor(UnwrapGeneralCursor(argv.Holder())); if (cursor != nullptr) { TRI_LockGeneralCursor(cursor); bool hasNext = cursor->hasNext(cursor); TRI_UnlockGeneralCursor(cursor); TRI_ReleaseGeneralCursor(cursor); return scope.Close(v8::Boolean::New(hasNext)); } TRI_V8_EXCEPTION(scope, TRI_ERROR_CURSOR_NOT_FOUND); } //////////////////////////////////////////////////////////////////////////////// /// @brief get a (persistent) cursor by its id //////////////////////////////////////////////////////////////////////////////// static v8::Handle JS_Cursor (v8::Arguments const& argv) { v8::HandleScope scope; if (argv.Length() != 1) { TRI_V8_EXCEPTION_USAGE(scope, "CURSOR()"); } TRI_vocbase_t* vocbase = GetContextVocBase(); if (vocbase == nullptr) { TRI_V8_EXCEPTION(scope, TRI_ERROR_ARANGO_DATABASE_NOT_FOUND); } // get the id v8::Handle idArg = argv[0]->ToString(); if (! idArg->IsString()) { TRI_V8_TYPE_ERROR(scope, "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_EXCEPTION(scope, TRI_ERROR_CURSOR_NOT_FOUND); } v8::Handle cursorObject = TRI_WrapGeneralCursor(cursor); if (cursorObject.IsEmpty()) { TRI_V8_EXCEPTION_MEMORY(scope); } return scope.Close(cursorObject); } //////////////////////////////////////////////////////////////////////////////// /// @brief delete a (persistent) cursor by its id //////////////////////////////////////////////////////////////////////////////// static v8::Handle JS_DeleteCursor (v8::Arguments const& argv) { v8::HandleScope scope; if (argv.Length() != 1) { TRI_V8_EXCEPTION_USAGE(scope, "DELETE_CURSOR()"); } TRI_vocbase_t* vocbase = GetContextVocBase(); if (vocbase == nullptr) { TRI_V8_EXCEPTION(scope, TRI_ERROR_ARANGO_DATABASE_NOT_FOUND); } // get the id v8::Handle idArg = argv[0]->ToString(); if (! idArg->IsString()) { TRI_V8_TYPE_ERROR(scope, "expecting a string for )"); } const string idString = TRI_ObjectToString(idArg); uint64_t id = TRI_UInt64String(idString.c_str()); bool found = TRI_RemoveGeneralCursor(vocbase, id); return scope.Close(v8::Boolean::New(found)); } // ............................................................................. // generate the general cursor template // ............................................................................. void TRI_InitV8cursor (v8::Handle context, TRI_server_t* server, TRI_vocbase_t* vocbase, JSLoader* loader, const size_t threadNumber, TRI_v8_global_t* v8g){ v8::Handle rt; v8::Handle ft; v8::Isolate* isolate = v8::Isolate::GetCurrent(); ft = v8::FunctionTemplate::New(); ft->SetClassName(TRI_V8_SYMBOL("ArangoCursor")); rt = ft->InstanceTemplate(); rt->SetInternalFieldCount(2); TRI_AddMethodVocbase(rt, "count", JS_CountGeneralCursor); TRI_AddMethodVocbase(rt, "dispose", JS_DisposeGeneralCursor); TRI_AddMethodVocbase(rt, "getBatchSize", JS_GetBatchSizeGeneralCursor); TRI_AddMethodVocbase(rt, "getExtra", JS_GetExtraGeneralCursor); TRI_AddMethodVocbase(rt, "getRows", JS_GetRowsGeneralCursor, true); // DEPRECATED, use toArray TRI_AddMethodVocbase(rt, "hasCount", JS_HasCountGeneralCursor); TRI_AddMethodVocbase(rt, "hasNext", JS_HasNextGeneralCursor); TRI_AddMethodVocbase(rt, "id", JS_IdGeneralCursor); TRI_AddMethodVocbase(rt, "next", JS_NextGeneralCursor); TRI_AddMethodVocbase(rt, "persist", JS_PersistGeneralCursor); TRI_AddMethodVocbase(rt, "toArray", JS_ToArrayGeneralCursor); v8g->GeneralCursorTempl = v8::Persistent::New(isolate, rt); TRI_AddGlobalFunctionVocbase(context, "ArangoCursor", ft->GetFunction()); // cursor functions. not intended to be used by end users TRI_AddGlobalFunctionVocbase(context, "CURSOR", JS_Cursor, true); TRI_AddGlobalFunctionVocbase(context, "CREATE_CURSOR", JS_CreateCursor, true); TRI_AddGlobalFunctionVocbase(context, "DELETE_CURSOR", JS_DeleteCursor, true); }