mirror of https://gitee.com/bigwinds/arangodb
371 lines
13 KiB
C++
371 lines
13 KiB
C++
#include <v8.h>
|
|
#include "Transactions.h"
|
|
|
|
#include "Basics/ReadLocker.h"
|
|
#include "Basics/WriteLocker.h"
|
|
#include "Logger/Logger.h"
|
|
#include "Transaction/Methods.h"
|
|
#include "Transaction/Options.h"
|
|
#include "Transaction/V8Context.h"
|
|
#include "Utils/CursorRepository.h"
|
|
#include "V8/v8-conv.h"
|
|
#include "V8/v8-helper.h"
|
|
#include "V8/v8-vpack.h"
|
|
#include "V8Server/V8Context.h"
|
|
#include "V8Server/V8DealerFeature.h"
|
|
#include "V8Server/v8-vocbaseprivate.h"
|
|
|
|
#include <velocypack/Slice.h>
|
|
|
|
#ifdef USE_ENTERPRISE
|
|
#include "Enterprise/Transaction/IgnoreNoAccessMethods.h"
|
|
#endif
|
|
|
|
namespace arangodb {
|
|
|
|
Result executeTransaction(v8::Isolate* isolate, basics::ReadWriteLock& lock,
|
|
std::atomic<bool>& 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<v8::Value> in = TRI_VPackToV8(isolate, slice);
|
|
|
|
v8::Handle<v8::Value> result;
|
|
v8::TryCatch tryCatch(isolate);
|
|
|
|
v8::Handle<v8::Object> request = v8::Object::New(isolate);
|
|
v8::Handle<v8::Value> jsPortTypeKey =
|
|
TRI_V8_ASCII_STRING(isolate, "portType");
|
|
v8::Handle<v8::Value> 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<v8::Value>::Cast(request);
|
|
auto responseVal = v8::Handle<v8::Value>::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;
|
|
if (!tryCatch.Message().IsEmpty()) {
|
|
v8::String::Utf8Value m(isolate, tryCatch.Message()->Get());
|
|
if (*m != nullptr) {
|
|
msg = *m;
|
|
}
|
|
}
|
|
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<v8::Value> const& arg,
|
|
v8::Handle<v8::Value>& result, v8::TryCatch& tryCatch) {
|
|
v8::Local<v8::Context> context = isolate->GetCurrentContext();
|
|
Result rv;
|
|
auto& vocbase = GetContextVocBase(isolate);
|
|
|
|
// treat the value as an object from now on
|
|
v8::Handle<v8::Object> object = v8::Handle<v8::Object>::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 (TRI_HasProperty(context, isolate, object, "lockTimeout") &&
|
|
!object->Get(TRI_V8_ASCII_STRING(isolate, "lockTimeout"))->IsNumber()) {
|
|
rv.reset(TRI_ERROR_BAD_PARAMETER,
|
|
"<lockTimeout> must be a valid numeric value");
|
|
return rv;
|
|
}
|
|
if (TRI_HasProperty(context, isolate, object, WaitForSyncKey) && !object->Get(WaitForSyncKey)->IsBoolean() &&
|
|
!object->Get(WaitForSyncKey)->IsBooleanObject()) {
|
|
rv.reset(TRI_ERROR_BAD_PARAMETER, "<waitForSync> 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;
|
|
// we must use "convertFunctionsToNull" here, because "action" is most
|
|
// likey a JavaScript function
|
|
TRI_V8ToVPack(isolate, builder, object, false,
|
|
/*convertFunctionsToNull*/ true);
|
|
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, "<lockTiemout> needs to be positive");
|
|
return rv;
|
|
}
|
|
|
|
// "collections"
|
|
std::string collectionError;
|
|
|
|
if (!TRI_HasProperty(context, isolate, object, "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<v8::Object> collections = v8::Handle<v8::Object>::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<std::string> readCollections;
|
|
std::vector<std::string> writeCollections;
|
|
std::vector<std::string> exclusiveCollections;
|
|
|
|
if (TRI_HasProperty(context, isolate, collections, "allowImplicit")) {
|
|
trxOptions.allowImplicitCollections = TRI_ObjectToBoolean(isolate,
|
|
collections->Get(TRI_V8_ASCII_STRING(isolate, "allowImplicit")));
|
|
}
|
|
|
|
auto getCollections =
|
|
[&isolate, &context](v8::Handle<v8::Object> obj, std::vector<std::string>& collections,
|
|
char const* attributeName, std::string& collectionError) -> bool {
|
|
if (TRI_HasProperty(context, isolate, obj, attributeName)) {
|
|
if (obj->Get(TRI_V8_ASCII_STRING(isolate, attributeName))->IsArray()) {
|
|
v8::Handle<v8::Array> names = v8::Handle<v8::Array>::Cast(
|
|
obj->Get(TRI_V8_ASCII_STRING(isolate, attributeName)));
|
|
|
|
for (uint32_t i = 0; i < names->Length(); ++i) {
|
|
v8::Handle<v8::Value> 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(isolate, collection));
|
|
}
|
|
} else if (obj->Get(TRI_V8_ASCII_STRING(isolate, attributeName))->IsString()) {
|
|
collections.emplace_back(TRI_ObjectToString(isolate,
|
|
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 (!TRI_HasProperty(context, isolate, object, "action")) {
|
|
rv.reset(TRI_ERROR_BAD_PARAMETER, actionError);
|
|
return rv;
|
|
}
|
|
|
|
// function parameters
|
|
v8::Handle<v8::Value> params;
|
|
|
|
if (TRI_HasProperty(context, isolate, object, "params")) {
|
|
params = v8::Handle<v8::Array>::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 (TRI_HasProperty(context, isolate, object, "embed")) {
|
|
v8::Handle<v8::Value> v = v8::Handle<v8::Object>::Cast(
|
|
object->Get(TRI_V8_ASCII_STRING(isolate, "embed")));
|
|
embed = TRI_ObjectToBoolean(isolate, v);
|
|
}
|
|
|
|
v8::Handle<v8::Object> current = isolate->GetCurrentContext()->Global();
|
|
|
|
// callback function
|
|
v8::Handle<v8::Function> action;
|
|
if (object->Get(TRI_V8_ASCII_STRING(isolate, "action"))->IsFunction()) {
|
|
action = v8::Handle<v8::Function>::Cast(
|
|
object->Get(TRI_V8_ASCII_STRING(isolate, "action")));
|
|
v8::Local<v8::Value> v8_fnname = action->GetName();
|
|
std::string fnname = TRI_ObjectToString(isolate, 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<v8::Function> ctor = v8::Local<v8::Function>::Cast(
|
|
current->Get(TRI_V8_ASCII_STRING(isolate, "Function")));
|
|
|
|
// Invoke Function constructor to create function with the given body and the
|
|
// arguments
|
|
std::string body = TRI_ObjectToString(isolate,
|
|
TRI_GetProperty(context, isolate, object, "action"));
|
|
body = "return (" + body + ")(params);";
|
|
v8::Handle<v8::Value> args[2] = {TRI_V8_ASCII_STRING(isolate, "params"),
|
|
TRI_V8_STD_STRING(isolate, body)};
|
|
v8::Local<v8::Object> function = ctor->NewInstance(TRI_IGETC, 2, args).FromMaybe(v8::Local<v8::Object>());
|
|
|
|
action = v8::Local<v8::Function>::Cast(function);
|
|
if (tryCatch.HasCaught()) {
|
|
if (!tryCatch.Message().IsEmpty()) {
|
|
v8::String::Utf8Value tryCatchMessage(isolate, tryCatch.Message()->Get());
|
|
if (*tryCatchMessage != nullptr) {
|
|
actionError += " - ";
|
|
actionError += *tryCatchMessage;
|
|
}
|
|
}
|
|
auto stacktraceV8 = tryCatch.StackTrace(TRI_IGETC).FromMaybe(v8::Local<v8::Value>());
|
|
v8::String::Utf8Value tryCatchStackTrace(isolate, stacktraceV8);
|
|
if (*tryCatchStackTrace != nullptr) {
|
|
actionError += " - ";
|
|
actionError += *tryCatchStackTrace;
|
|
}
|
|
rv.reset(TRI_ERROR_BAD_PARAMETER, actionError);
|
|
tryCatch.Reset(); // reset as we have transferred 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 ctx = std::make_shared<transaction::V8Context>(vocbase, embed);
|
|
|
|
// start actual transaction
|
|
auto trx = std::make_unique<transaction::Methods>(ctx, readCollections, writeCollections,
|
|
exclusiveCollections, trxOptions);
|
|
trx->addHint(transaction::Hints::Hint::GLOBAL_MANAGED);
|
|
|
|
rv = trx->begin();
|
|
|
|
if (rv.fail()) {
|
|
return rv;
|
|
}
|
|
|
|
try {
|
|
v8::Handle<v8::Value> arguments = params;
|
|
|
|
result = action->Call(current, 1, &arguments);
|
|
|
|
if (tryCatch.HasCaught()) {
|
|
trx->abort();
|
|
|
|
std::tuple<bool, bool, Result> 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");
|
|
}
|
|
|
|
rv = trx->finish(rv);
|
|
|
|
// if we do not remove unused V8Cursors, V8Context might not reset global
|
|
// state
|
|
vocbase.cursorRepository()->garbageCollect(/*force*/ false);
|
|
|
|
return rv;
|
|
}
|
|
|
|
} // namespace arangodb
|