#include "Transactions.h" #include #include "Transaction/Options.h" #include "Transaction/UserTransaction.h" #include "Transaction/V8Context.h" #include "V8/v8-conv.h" #include "V8/v8-vpack.h" #include "V8/v8-helper.h" #include "V8Server/V8Context.h" #include "V8Server/V8DealerFeature.h" #include "V8Server/v8-vocbaseprivate.h" #include "Logger/Logger.h" #include #include "Basics/WriteLocker.h" #include "Basics/ReadLocker.h" #ifdef USE_ENTERPRISE #include "Enterprise/Transaction/IgnoreNoAccessMethods.h" #endif namespace arangodb { Result executeTransaction( v8::Isolate* isolate, basics::ReadWriteLock& lock, std::atomic& canceled, VPackSlice slice, std::string portType, VPackBuilder& builder){ // YOU NEED A TRY CATCH BLOCK like: // TRI_V8_TRY_CATCH_BEGIN(isolate); // TRI_V8_TRY_CATCH_END // outside of this function! READ_LOCKER(readLock, lock); Result rv; if(canceled){ rv.reset(TRI_ERROR_REQUEST_CANCELED,"handler canceled"); return rv; } v8::HandleScope scope(isolate); v8::Handle in = TRI_VPackToV8(isolate, slice); v8::Handle result; v8::TryCatch tryCatch; v8::Handle request = v8::Object::New(isolate); v8::Handle jsPortTypeKey= TRI_V8_ASCII_STRING(isolate, "portType"); v8::Handle jsPortTypeValue = TRI_V8_ASCII_STRING(isolate, portType.c_str()); if (!request->Set(jsPortTypeKey, jsPortTypeValue)){ rv.reset(TRI_ERROR_INTERNAL, "could not set portType"); return rv; } { auto requestVal = v8::Handle::Cast(request); auto responseVal = v8::Handle::Cast(v8::Undefined(isolate)); v8gHelper globalVars(isolate, tryCatch, requestVal, responseVal); readLock.unlock(); //unlock rv = executeTransactionJS(isolate, in, result, tryCatch); globalVars.cancel(canceled); } //do not allow the manipulation of the isolate while we are messing here READ_LOCKER(readLock2, lock); if (canceled) { //if it was ok we would already have committed if(rv.ok()) { rv.reset(TRI_ERROR_REQUEST_CANCELED,"handler canceled - result already committed"); } else { rv.reset(TRI_ERROR_REQUEST_CANCELED,"handler canceled"); } return rv; } if (rv.fail()) { return rv; } if (tryCatch.HasCaught()) { //we have some javascript error that is not an arangoError std::string msg = *v8::String::Utf8Value(tryCatch.Message()->Get()); rv.reset(TRI_ERROR_HTTP_SERVER_ERROR, msg); } if (rv.fail()) { return rv; } if(result.IsEmpty() || result->IsUndefined()) { // turn undefined to none builder.add(VPackSlice::noneSlice()); } else { TRI_V8ToVPack(isolate, builder, result, false); } return rv; } Result executeTransactionJS( v8::Isolate* isolate, v8::Handle const& arg, v8::Handle& result, v8::TryCatch& tryCatch) { Result rv; TRI_vocbase_t* vocbase = GetContextVocBase(isolate); if (vocbase == nullptr) { rv.reset(TRI_ERROR_ARANGO_DATABASE_NOT_FOUND); return rv; } // treat the value as an object from now on v8::Handle object = v8::Handle::Cast(arg); // "waitForSync" TRI_GET_GLOBALS(); TRI_GET_GLOBAL_STRING(WaitForSyncKey); // do extra sanity checking for user facing APIs, parsing // is performed in `transaction::Options::fromVelocyPack` if (object->Has(TRI_V8_ASCII_STRING(isolate, "lockTimeout")) && !object->Get(TRI_V8_ASCII_STRING(isolate, "lockTimeout"))->IsNumber()) { rv.reset(TRI_ERROR_BAD_PARAMETER, " must be a valid numeric value"); return rv; } if (object->Has(WaitForSyncKey) && !object->Get(WaitForSyncKey)->IsBoolean() && !object->Get(WaitForSyncKey)->IsBooleanObject()) { rv.reset(TRI_ERROR_BAD_PARAMETER, " must be a boolean value"); return rv; } // extract the properties from the object transaction::Options trxOptions; { // parse all other options. `allowImplicitCollections` will // be overwritten later if is contained in `object` VPackBuilder builder; TRI_V8ToVPack(isolate, builder, object, false); if (!builder.isClosed()) { builder.close(); } if (!builder.slice().isObject()) { rv.reset(TRI_ERROR_BAD_PARAMETER); return rv; } trxOptions.fromVelocyPack(builder.slice()); } if (trxOptions.lockTimeout < 0.0) { rv.reset(TRI_ERROR_BAD_PARAMETER, " needs to be positive"); return rv; } // "collections" std::string collectionError; if (!object->Has(TRI_V8_ASCII_STRING(isolate, "collections")) || !object->Get(TRI_V8_ASCII_STRING(isolate, "collections"))->IsObject()) { collectionError = "missing/invalid collections definition for transaction"; rv.reset(TRI_ERROR_BAD_PARAMETER, collectionError); return rv; } // extract collections v8::Handle collections = v8::Handle::Cast( object->Get(TRI_V8_ASCII_STRING(isolate, "collections"))); if (collections.IsEmpty()) { collectionError = "empty collections definition for transaction"; rv.reset(TRI_ERROR_BAD_PARAMETER, collectionError); return rv; } std::vector readCollections; std::vector writeCollections; std::vector exclusiveCollections; if (collections->Has(TRI_V8_ASCII_STRING(isolate, "allowImplicit"))) { trxOptions.allowImplicitCollections = TRI_ObjectToBoolean( collections->Get(TRI_V8_ASCII_STRING(isolate, "allowImplicit"))); } auto getCollections = [&isolate](v8::Handle obj, std::vector& collections, char const* attributeName, std::string &collectionError) -> bool { if (obj->Has(TRI_V8_ASCII_STRING(isolate, attributeName))) { if (obj->Get(TRI_V8_ASCII_STRING(isolate, attributeName))->IsArray()) { v8::Handle names = v8::Handle::Cast( obj->Get(TRI_V8_ASCII_STRING(isolate, attributeName))); for (uint32_t i = 0; i < names->Length(); ++i) { v8::Handle collection = names->Get(i); if (!collection->IsString()) { collectionError += std::string(" Collection name #") + std::to_string(i) + " in array '"+ attributeName + std::string("' is not a string"); return false; } collections.emplace_back(TRI_ObjectToString(collection)); } } else if (obj->Get(TRI_V8_ASCII_STRING(isolate, attributeName))->IsString()) { collections.emplace_back( TRI_ObjectToString(obj->Get(TRI_V8_ASCII_STRING(isolate, attributeName)))); } else { collectionError += std::string(" There is no array in '") + attributeName + "'"; return false; } // intentionally falls through } return true; }; collectionError = "invalid collection definition for transaction: "; // collections.read bool isValid = (getCollections(collections, readCollections, "read", collectionError) && getCollections(collections, writeCollections, "write", collectionError) && getCollections(collections, exclusiveCollections, "exclusive", collectionError)); if (!isValid) { rv.reset(TRI_ERROR_BAD_PARAMETER, collectionError); return rv; } // extract the "action" property static std::string const actionErrorPrototype = "missing/invalid action definition for transaction"; std::string actionError = actionErrorPrototype; if (!object->Has(TRI_V8_ASCII_STRING(isolate, "action"))) { rv.reset(TRI_ERROR_BAD_PARAMETER, actionError); return rv; } // function parameters v8::Handle params; if (object->Has(TRI_V8_ASCII_STRING(isolate, "params"))) { params = v8::Handle::Cast(object->Get(TRI_V8_ASCII_STRING(isolate, "params"))); } else { params = v8::Undefined(isolate); } if (params.IsEmpty()) { rv.reset(TRI_ERROR_BAD_PARAMETER, "unable to decode function parameters"); return rv; } bool embed = false; if (object->Has(TRI_V8_ASCII_STRING(isolate, "embed"))) { v8::Handle v = v8::Handle::Cast(object->Get(TRI_V8_ASCII_STRING(isolate, "embed"))); embed = TRI_ObjectToBoolean(v); } v8::Handle current = isolate->GetCurrentContext()->Global(); // callback function v8::Handle action; if (object->Get(TRI_V8_ASCII_STRING(isolate, "action"))->IsFunction()) { action = v8::Handle::Cast( object->Get(TRI_V8_ASCII_STRING(isolate, "action"))); v8::Local v8_fnname = action->GetName(); std::string fnname = TRI_ObjectToString(v8_fnname); if (fnname.length() == 0) { action->SetName(TRI_V8_ASCII_STRING(isolate, "userTransactionFunction")); } } else if (object->Get(TRI_V8_ASCII_STRING(isolate, "action"))->IsString()) { // get built-in Function constructor (see ECMA-262 5th edition 15.3.2) v8::Local ctor = v8::Local::Cast( current->Get(TRI_V8_ASCII_STRING(isolate, "Function"))); // Invoke Function constructor to create function with the given body and no // arguments std::string body = TRI_ObjectToString( object->Get(TRI_V8_ASCII_STRING(isolate, "action"))->ToString()); body = "return (" + body + ")(params);"; v8::Handle args[2] = {TRI_V8_ASCII_STRING(isolate, "params"), TRI_V8_STD_STRING(isolate, body)}; v8::Local function = ctor->NewInstance(2, args); action = v8::Local::Cast(function); if (tryCatch.HasCaught()) { actionError += " - "; actionError += *v8::String::Utf8Value(tryCatch.Message()->Get()); actionError += " - "; actionError += *v8::String::Utf8Value(tryCatch.StackTrace()); rv.reset(TRI_ERROR_BAD_PARAMETER, actionError); tryCatch.Reset(); //reset as we have transferd the error message into the Result return rv; } action->SetName(TRI_V8_ASCII_STRING(isolate, "userTransactionSource")); } else { rv.reset(TRI_ERROR_BAD_PARAMETER, actionError); return rv; } if (action.IsEmpty()) { rv.reset(TRI_ERROR_BAD_PARAMETER, actionError); return rv; } auto transactionContext = std::make_shared(vocbase, embed); // start actual transaction std::unique_ptr trx(new transaction::UserTransaction(transactionContext, readCollections, writeCollections, exclusiveCollections, trxOptions)); rv = trx->begin(); if (rv.fail()) { return rv; } try { v8::Handle arguments = params; result = action->Call(current, 1, &arguments); if (tryCatch.HasCaught()) { trx->abort(); std::tuple rvTuple = extractArangoError(isolate, tryCatch, TRI_ERROR_TRANSACTION_INTERNAL); if (std::get<1>(rvTuple)) { rv = std::get<2>(rvTuple); } else { // some general error we don't know about rv = Result(TRI_ERROR_TRANSACTION_INTERNAL, "an unknown error occured while executing the transaction"); } } } catch (arangodb::basics::Exception const& ex) { rv.reset(ex.code(), ex.what()); } catch (std::bad_alloc const&) { rv.reset(TRI_ERROR_OUT_OF_MEMORY); } catch (std::exception const& ex) { rv.reset(TRI_ERROR_INTERNAL, ex.what()); } catch (...) { rv.reset(TRI_ERROR_INTERNAL, "caught unknown exception during transaction"); } if (rv.fail()) { return rv; } return trx->commit(); } } // arangodb