1
0
Fork 0
arangodb/arangod/V8Server/v8-voccursor.cpp

699 lines
22 KiB
C++

////////////////////////////////////////////////////////////////////////////////
/// @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<v8::External, v8::Persistent<v8::External>>& data) {
auto isolate = data.GetIsolate();
auto persistent = data.GetParameter();
auto myCursor = v8::Local<v8::External>::New(isolate, *persistent);
auto cursor = static_cast<TRI_general_cursor_t*>(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<void*, v8::Persistent<v8::External>>::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<v8::Value>& 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<v8::Object> 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<void*, v8::Persistent<v8::External>>::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<v8::External>::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<v8::Object> cursorObject) {
return TRI_UnwrapClass<TRI_general_cursor_t>(cursorObject, WRP_GENERAL_CURSOR_TYPE);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief generates a general cursor from a list
////////////////////////////////////////////////////////////////////////////////
static void JS_CreateCursor (const v8::FunctionCallbackInfo<v8::Value>& 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(<list>, <doCount>, <batchSize>, <ttl>)");
}
if (! args[0]->IsArray()) {
TRI_V8_THROW_TYPE_ERROR("<list> must be a list");
}
// extract objects
v8::Handle<v8::Array> array = v8::Handle<v8::Array>::Cast(args[0]);
TRI_json_t* json = TRI_ObjectToJson(isolate, array);
if (json == nullptr) {
TRI_V8_THROW_TYPE_ERROR("cannot convert <list> 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<v8::Value>& 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<v8::Value>& 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<v8::Value>& 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<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
v8::HandleScope scope(isolate);
if (args.Length() != 0) {
TRI_V8_THROW_EXCEPTION_USAGE("next()");
}
v8::Handle<v8::Value> 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<v8::Value>& 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<v8::Value>& 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<v8::Array> 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<v8::Value>& args) {
return JS_ToArrayGeneralCursor(args);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief return max number of results per transfer for cursor
////////////////////////////////////////////////////////////////////////////////
static void JS_GetBatchSizeGeneralCursor (const v8::FunctionCallbackInfo<v8::Value>& 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<v8::Value>& 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<v8::Value>& 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<v8::Value>& 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<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
v8::HandleScope scope(isolate);
if (args.Length() != 1) {
TRI_V8_THROW_EXCEPTION_USAGE("CURSOR(<cursor-identifier>)");
}
TRI_vocbase_t* vocbase = GetContextVocBase(isolate);
if (vocbase == nullptr) {
TRI_V8_THROW_EXCEPTION(TRI_ERROR_ARANGO_DATABASE_NOT_FOUND);
}
// get the id
v8::Handle<v8::Value> idArg = args[0]->ToString();
if (! idArg->IsString()) {
TRI_V8_THROW_TYPE_ERROR("expecting a string for <cursor-identifier>)");
}
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<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
v8::HandleScope scope(isolate);
if (args.Length() != 1) {
TRI_V8_THROW_EXCEPTION_USAGE("DELETE_CURSOR(<cursor-identifier>)");
}
TRI_vocbase_t* vocbase = GetContextVocBase(isolate);
if (vocbase == nullptr) {
TRI_V8_THROW_EXCEPTION(TRI_ERROR_ARANGO_DATABASE_NOT_FOUND);
}
// get the id
v8::Handle<v8::Value> idArg = args[0]->ToString();
if (! idArg->IsString()) {
TRI_V8_THROW_TYPE_ERROR("expecting a string for <cursor-identifier>)");
}
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<v8::Context> context,
TRI_v8_global_t* v8g){
v8::Handle<v8::ObjectTemplate> rt;
v8::Handle<v8::FunctionTemplate> 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);
}