//////////////////////////////////////////////////////////////////////////////// /// 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 Dr. Frank Celler //////////////////////////////////////////////////////////////////////////////// #include "V8Server/v8-voccursor.h" #include "Aql/QueryCursor.h" #include "Aql/QueryResult.h" #include "Basics/StringUtils.h" #include "Basics/VelocyPackHelper.h" #include "Basics/conversions.h" #include "Basics/ScopeGuard.h" #include "Transaction/Context.h" #include "Transaction/V8Context.h" #include "Utils/CollectionNameResolver.h" #include "Utils/Cursor.h" #include "Utils/CursorRepository.h" #include "V8/v8-conv.h" #include "V8/v8-globals.h" #include "V8/v8-utils.h" #include "V8/v8-vpack.h" #include "v8-vocbaseprivate.h" #include #include #include #include "Logger/Logger.h" using namespace arangodb; using namespace arangodb::basics; //////////////////////////////////////////////////////////////////////////////// /// @brief generates a general cursor from an array //////////////////////////////////////////////////////////////////////////////// static void JS_CreateCursor(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); auto& vocbase = GetContextVocBase(isolate); if (args.Length() < 1) { TRI_V8_THROW_EXCEPTION_USAGE("CREATE_CURSOR(, , )"); } if (!args[0]->IsArray()) { TRI_V8_THROW_TYPE_ERROR(" must be an array"); } // extract objects v8::Handle array = v8::Handle::Cast(args[0]); auto builder = std::make_shared(); int res = TRI_V8ToVPack(isolate, *builder, array, false); if (res != TRI_ERROR_NO_ERROR) { TRI_V8_THROW_TYPE_ERROR("cannot convert to JSON"); } // maximum number of results to return at once uint32_t batchSize = 1000; if (args.Length() >= 2) { int64_t maxValue = TRI_ObjectToInt64(isolate, args[1]); if (maxValue > 0 && maxValue < (int64_t)UINT32_MAX) { batchSize = static_cast(maxValue); } } double ttl = 0.0; if (args.Length() >= 3) { ttl = TRI_ObjectToDouble(isolate, args[2]); } if (ttl <= 0.0) { ttl = 30.0; // default ttl } auto* cursors = vocbase.cursorRepository(); // create a cursor arangodb::aql::QueryResult result(TRI_ERROR_NO_ERROR); result.data = builder; result.cached = false; result.context = transaction::V8Context::Create(vocbase, false); TRI_ASSERT(builder.get() != nullptr); try { arangodb::Cursor* cursor = cursors->createFromQueryResult(std::move(result), static_cast(batchSize), ttl, true); TRI_ASSERT(cursor != nullptr); TRI_DEFER(cursors->release(cursor)); // need to fetch id before release() as release() might delete the cursor CursorId id = cursor->id(); auto result = TRI_V8UInt64String(isolate, id); TRI_V8_RETURN(result); } catch (...) { TRI_V8_THROW_EXCEPTION_MEMORY(); } TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief generates a JSON object from the specified cursor //////////////////////////////////////////////////////////////////////////////// static void JS_JsonCursor(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); auto& vocbase = GetContextVocBase(isolate); if (args.Length() != 1) { TRI_V8_THROW_EXCEPTION_USAGE("JSON_CURSOR()"); } std::string const id = TRI_ObjectToString(isolate, args[0]); auto cursorId = static_cast(arangodb::basics::StringUtils::uint64(id)); // find the cursor auto cursors = vocbase.cursorRepository(); TRI_ASSERT(cursors != nullptr); bool busy; Cursor* cursor = cursors->find(cursorId, Cursor::CURSOR_VPACK, busy); TRI_DEFER(cursors->release(cursor)); if (cursor == nullptr) { if (busy) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_CURSOR_BUSY); } TRI_V8_THROW_EXCEPTION(TRI_ERROR_CURSOR_NOT_FOUND); } VPackOptions* opts = cursor->context()->getVPackOptions(); VPackBuilder builder(opts); builder.openObject(true); // conversion uses sequential iterator, no indexing Result r = cursor->dumpSync(builder); if (r.fail()) { TRI_V8_THROW_EXCEPTION_MEMORY(); // for compatibility } builder.close(); v8::Handle result = TRI_VPackToV8(isolate, builder.slice()); TRI_V8_RETURN(result); TRI_V8_TRY_CATCH_END } // ............................................................................. // cursor v8 class // ............................................................................. struct V8Cursor final { static constexpr uint16_t CID = 4956; V8Cursor(v8::Isolate* isolate, v8::Handle holder, TRI_vocbase_t& vocbase, CursorId cursorId) : _isolate(isolate), _cursorId(cursorId), _resolver(vocbase), _cte(transaction::Context::createCustomTypeHandler(vocbase, _resolver)) { // sanity checks TRI_ASSERT(_handle.IsEmpty()); TRI_ASSERT(holder->InternalFieldCount() > 0); // create a new persistent handle holder->SetAlignedPointerInInternalField(0, this); _handle.Reset(_isolate, holder); _handle.SetWrapperClassId(CID); // and make it weak, so that we can garbage collect _handle.SetWeak(this, weakCallback, v8::WeakCallbackType::kParameter); _options.customTypeHandler = _cte.get(); } ~V8Cursor() { if (!_handle.IsEmpty()) { TRI_ASSERT(_handle.IsNearDeath()); _handle.ClearWeak(); _handle.Reset(); } if (_isolate) { TRI_GET_GLOBALS2(_isolate); TRI_vocbase_t* vocbase = v8g->_vocbase; if (vocbase) { CursorRepository* cursors = vocbase->cursorRepository(); cursors->remove(_cursorId, Cursor::CURSOR_VPACK); } } } ////////////////////////////////////////////////////////////////////////////// /// @brief unwraps a structure ////////////////////////////////////////////////////////////////////////////// static V8Cursor* unwrap(v8::Local handle) { TRI_ASSERT(handle->InternalFieldCount() > 0); return static_cast(handle->GetAlignedPointerFromInternalField(0)); } /// @brief return false on error bool maybeFetchBatch(v8::Isolate* isolate) { if (_dataIterator == nullptr && _hasMore) { // fetch more data TRI_GET_GLOBALS(); CursorRepository* cursors = v8g->_vocbase->cursorRepository(); bool busy; Cursor* cc = cursors->find(_cursorId, Cursor::CURSOR_VPACK, busy); if (busy || cc == nullptr) { TRI_V8_SET_ERROR(TRI_errno_string(TRI_ERROR_CURSOR_BUSY)); return false; // someone else is using it } TRI_DEFER(cc->release()); Result r = fetchData(cc); if (r.fail()) { TRI_CreateErrorObject(isolate, r); return false; } } return true; // still got some data } /// @brief fetch the next batch Result fetchData(Cursor* cursor) { TRI_ASSERT(cursor != nullptr && cursor->isUsed()); TRI_ASSERT(_hasMore); TRI_ASSERT(_dataIterator == nullptr); _dataSlice = VPackSlice::noneSlice(); _extraSlice = VPackSlice::noneSlice(); _tmpResult.clear(); _tmpResult.openObject(); Result r = cursor->dumpSync(_tmpResult); if (r.fail()) { return r; } _tmpResult.close(); TRI_ASSERT(_tmpResult.slice().isObject()); // TODO as an optimization for (auto pair : VPackObjectIterator(_tmpResult.slice(), true)) { if (pair.key.isEqualString("result")) { _dataSlice = pair.value; TRI_ASSERT(_dataSlice.isArray()); if (!_dataSlice.isEmptyArray()) { _dataIterator = std::make_unique(_dataSlice); } } else if (pair.key.isEqualString("hasMore")) { _hasMore = pair.value.getBool(); } else if (pair.key.isEqualString("extra")) { _extraSlice = pair.value; } } // cursor should delete itself TRI_ASSERT(_hasMore || cursor->isDeleted()); return Result{}; } //////////////////////////////////////////////////////////////////////////////// /// @brief constructs a new streaming cursor from arguments //////////////////////////////////////////////////////////////////////////////// static void New(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); TRI_GET_GLOBALS(); if (!args.IsConstructCall()) { // if not call as a constructor call it TRI_V8_THROW_EXCEPTION_USAGE("only instance-able by constructor"); } if (args.Length() < 1 || args.Length() > 3) { TRI_V8_THROW_EXCEPTION_USAGE( "ArangoQueryStreamCursor(, , )"); } // get the query string if (!args[0]->IsString()) { TRI_V8_THROW_TYPE_ERROR("expecting string for "); } std::string const queryString(TRI_ObjectToString(isolate, args[0])); // bind parameters std::shared_ptr bindVars; if (args.Length() > 1) { if (!args[1]->IsUndefined() && !args[1]->IsNull() && !args[1]->IsObject()) { TRI_V8_THROW_TYPE_ERROR("expecting object for "); } if (args[1]->IsObject()) { bindVars.reset(new VPackBuilder); int res = TRI_V8ToVPack(isolate, *(bindVars.get()), args[1], false); if (res != TRI_ERROR_NO_ERROR) { TRI_V8_THROW_EXCEPTION(res); } } } // options auto options = std::make_shared(); if (args.Length() > 2) { // we have options! yikes! if (!args[2]->IsObject()) { TRI_V8_THROW_TYPE_ERROR("expecting object for "); } int res = TRI_V8ToVPack(isolate, *options, args[2], false); if (res != TRI_ERROR_NO_ERROR) { TRI_V8_THROW_EXCEPTION(res); } } else { VPackObjectBuilder guard(options.get()); } size_t batchSize = VelocyPackHelper::getNumericValue(options->slice(), "batchSize", 1000); const bool contextOwnedByExterior = transaction::V8Context::isEmbedded(); TRI_vocbase_t* vocbase = v8g->_vocbase; TRI_ASSERT(vocbase != nullptr); auto* cursors = vocbase->cursorRepository(); // create a cursor double ttl = std::numeric_limits::max(); // specify ID 0 so it uses the external V8 context auto cc = cursors->createQueryStream(queryString, std::move(bindVars), std::move(options), batchSize, ttl, contextOwnedByExterior, /*trxCtx*/ nullptr); TRI_DEFER(cc->release()); // args.Holder() is supposedly better than args.This() auto self = std::make_unique(isolate, args.Holder(), *vocbase, cc->id()); Result r = self->fetchData(cc); self.release(); // args.Holder() owns the pointer if (r.fail()) { TRI_V8_THROW_EXCEPTION(r); } else { TRI_V8_RETURN(args.This()); } // do not delete self, its owned by V8 now TRI_V8_RETURN(args.This()); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief ArangoQueryStreamCursor.prototype.toArray = ... //////////////////////////////////////////////////////////////////////////////// static void toArray(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::Isolate* isolate = args.GetIsolate(); v8::HandleScope scope(isolate); V8Cursor* self = V8Cursor::unwrap(args.Holder()); if (self == nullptr) { TRI_V8_RETURN_UNDEFINED(); } v8::Handle resArray = v8::Array::New(isolate); // iterate over result and return it uint32_t j = 0; while (self->maybeFetchBatch(isolate)) { if (!self->_dataIterator) { break; } if (V8PlatformFeature::isOutOfMemory(isolate)) { TRI_V8_SET_EXCEPTION_MEMORY(); break; } while (self->_dataIterator->valid()) { VPackSlice s = self->_dataIterator->value(); resArray->Set(j++, TRI_VPackToV8(isolate, s, &self->_options)); ++(*self->_dataIterator); } // reset so that the next one can fetch again self->_dataIterator.reset(); } TRI_V8_RETURN(resArray); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief ArangoQueryStreamCursor.prototype.getExtra = ... //////////////////////////////////////////////////////////////////////////////// static void getExtra(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); V8Cursor* self = V8Cursor::unwrap(args.Holder()); if (self == nullptr) { TRI_V8_RETURN(v8::Undefined(isolate)); } // we always need to fetch if (!self->maybeFetchBatch(isolate)) { // sets exception return; } if (self->_extraSlice.isObject()) { TRI_V8_RETURN(TRI_VPackToV8(isolate, self->_extraSlice)); } TRI_V8_THROW_EXCEPTION_MESSAGE( TRI_ERROR_BAD_PARAMETER, "getExtra() is only valid after all data has been fetched"); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief ArangoQueryStreamCursor.prototype.hasNext = ... //////////////////////////////////////////////////////////////////////////////// static void hasNext(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); V8Cursor* self = V8Cursor::unwrap(args.Holder()); if (self == nullptr) { TRI_V8_RETURN_UNDEFINED(); } // we always need to fetch if (!self->maybeFetchBatch(isolate)) { // sets exception return; } if (self->_dataIterator != nullptr) { TRI_V8_RETURN_TRUE(); } else { TRI_V8_RETURN_FALSE(); } TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief ArangoQueryStreamCursor.prototype.next = ... //////////////////////////////////////////////////////////////////////////////// static void next(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); V8Cursor* self = V8Cursor::unwrap(args.Holder()); if (self == nullptr) { TRI_V8_RETURN_UNDEFINED(); } // we always need to fetch if (!self->maybeFetchBatch(isolate)) { // sets exception return; } if (self->_dataIterator) { // got a current batch TRI_ASSERT(self->_dataIterator->valid()); VPackSlice s = self->_dataIterator->value(); v8::Local val = TRI_VPackToV8(isolate, s, &self->_options); ++(*self->_dataIterator); // reset so that the next one can fetch again if (!self->_dataIterator->valid()) { self->_dataIterator.reset(); } TRI_V8_RETURN(val); } TRI_V8_RETURN_UNDEFINED(); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief ArangoQueryStreamCursor.prototype.count = ... //////////////////////////////////////////////////////////////////////////////// static void count(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); TRI_V8_RETURN_UNDEFINED(); // always undefined TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief explicitly discard cursor, mostly relevant for testing //////////////////////////////////////////////////////////////////////////////// static void dispose(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); V8Cursor* self = V8Cursor::unwrap(args.Holder()); if (self != nullptr) { TRI_GET_GLOBALS(); CursorRepository* cursors = v8g->_vocbase->cursorRepository(); cursors->remove(self->_cursorId, Cursor::CURSOR_VPACK); self->_hasMore = false; self->_dataSlice = VPackSlice::noneSlice(); self->_extraSlice = VPackSlice::noneSlice(); self->_dataIterator.reset(); self->_tmpResult.clear(); } TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief ArangoQueryStreamCursor.prototype.id = ... //////////////////////////////////////////////////////////////////////////////// static void id(v8::FunctionCallbackInfo const& args) { v8::Isolate* isolate = args.GetIsolate(); v8::HandleScope scope(isolate); V8Cursor* self = V8Cursor::unwrap(args.Holder()); if (self == nullptr) { TRI_V8_RETURN(v8::Undefined(isolate)); } TRI_V8_RETURN(TRI_V8UInt64String(isolate, self->_cursorId)); } private: /// called when GC deletes the value static void weakCallback(const v8::WeakCallbackInfo& data) { auto obj = data.GetParameter(); delete obj; } private: /// @brief persistent handle for V8 object v8::Persistent _handle; /// @brief isolate v8::Isolate* _isolate; /// @brief temporary result buffer VPackBuilder _tmpResult; /// @brief id of cursor CursorId _cursorId; /// cache has more variable bool _hasMore = true; VPackSlice _dataSlice = VPackSlice::noneSlice(); /// cached extras which might be attached to the stream VPackSlice _extraSlice = VPackSlice::noneSlice(); /// @brief pointer to the current result std::unique_ptr _dataIterator; CollectionNameResolver _resolver; std::shared_ptr _cte; VPackOptions _options = VPackOptions::Defaults; }; // ............................................................................. // generate the general cursor template // ............................................................................. void TRI_InitV8cursor(v8::Handle context, TRI_v8_global_t* v8g) { v8::Isolate* isolate = v8::Isolate::GetCurrent(); // cursor functions. not intended to be used by end users // these cursor functions are the APIs implemented in js/actions/api-simple.js TRI_AddGlobalFunctionVocbase(isolate, TRI_V8_ASCII_STRING(isolate, "CREATE_CURSOR"), JS_CreateCursor, true); TRI_AddGlobalFunctionVocbase(isolate, TRI_V8_ASCII_STRING(isolate, "JSON_CURSOR"), JS_JsonCursor, true); // streaming query cursor class, intended to be used via // ArangoStatement.execute v8::Handle rt; v8::Handle ft; ft = v8::FunctionTemplate::New(isolate, V8Cursor::New); ft->SetClassName(TRI_V8_ASCII_STRING(isolate, "ArangoQueryStreamCursor")); rt = ft->InstanceTemplate(); rt->SetInternalFieldCount(1); ft->PrototypeTemplate()->Set(TRI_V8_ASCII_STRING(isolate, "isArangoResultSet"), v8::True(isolate)); TRI_V8_AddProtoMethod(isolate, ft, TRI_V8_ASCII_STRING(isolate, "toArray"), V8Cursor::toArray); TRI_V8_AddProtoMethod(isolate, ft, TRI_V8_ASCII_STRING(isolate, "getExtra"), V8Cursor::getExtra); TRI_V8_AddProtoMethod(isolate, ft, TRI_V8_ASCII_STRING(isolate, "hasNext"), V8Cursor::hasNext); TRI_V8_AddProtoMethod(isolate, ft, TRI_V8_ASCII_STRING(isolate, "next"), V8Cursor::next); TRI_V8_AddProtoMethod(isolate, ft, TRI_V8_ASCII_STRING(isolate, "count"), V8Cursor::count); TRI_V8_AddProtoMethod(isolate, ft, TRI_V8_ASCII_STRING(isolate, "dispose"), V8Cursor::dispose); TRI_V8_AddProtoMethod(isolate, ft, TRI_V8_ASCII_STRING(isolate, "id"), V8Cursor::id); v8g->StreamQueryCursorTempl.Reset(isolate, ft); v8::MaybeLocal ctor = ft->GetFunction(context); if (ctor.IsEmpty()) { THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "error creating v8 stream cursor"); } // ft->SetClassName(TRI_V8_ASCII_STRING(isolate, // "ArangoStreamQueryCursorCtor")); TRI_AddGlobalFunctionVocbase(isolate, TRI_V8_ASCII_STRING(isolate, "ArangoQueryStreamCursor"), ctor.ToLocalChecked(), true); }