diff --git a/arangod/Aql/Query.cpp b/arangod/Aql/Query.cpp index c132cadb8d..858049000d 100644 --- a/arangod/Aql/Query.cpp +++ b/arangod/Aql/Query.cpp @@ -463,152 +463,119 @@ void Query::registerWarning(int code, char const* details) { /// execute calls it internally. The purpose of this separate method is /// to be able to only prepare a query from VelocyPack and then store it in the /// QueryRegistry. -QueryResult Query::prepare(QueryRegistry* registry) { +void Query::prepare(QueryRegistry* registry) { LOG_TOPIC(DEBUG, Logger::QUERIES) << TRI_microtime() - _startTime << " " << "Query::prepare" << " this: " << (uintptr_t) this; TRI_ASSERT(registry != nullptr); - try { - init(); - enterState(PARSING); + init(); + enterState(PARSING); - auto parser = std::make_unique(this); - std::unique_ptr plan; + auto parser = std::make_unique(this); + std::unique_ptr plan; - if (_queryString != nullptr) { - parser->parse(false); - // put in bind parameters - parser->ast()->injectBindParameters(_bindParameters); - } - - _isModificationQuery = parser->isModificationQuery(); - - // create the transaction object, but do not start it yet - AqlTransaction* trx = new AqlTransaction( - createTransactionContext(), _collections.collections(), - _part == PART_MAIN); - _trx = trx; - - try { - bool planRegisters; - // As soon as we start du instantiate the plan we have to clean it - // up before killing the unique_ptr - if (_queryString != nullptr) { - // we have an AST - // optimize the ast - enterState(AST_OPTIMIZATION); - - parser->ast()->validateAndOptimize(); - - enterState(LOADING_COLLECTIONS); - - int res = trx->begin(); - - if (res != TRI_ERROR_NO_ERROR) { - return transactionError(res); - } - - enterState(PLAN_INSTANTIATION); - plan.reset(ExecutionPlan::instantiateFromAst(parser->ast())); - - if (plan.get() == nullptr) { - // oops - return QueryResult(TRI_ERROR_INTERNAL, - "failed to create query execution engine"); - } - - // Run the query optimizer: - enterState(PLAN_OPTIMIZATION); - arangodb::aql::Optimizer opt(maxNumberOfPlans()); - // get enabled/disabled rules - opt.createPlans(plan.release(), getRulesFromOptions(), - inspectSimplePlans()); - // Now plan and all derived plans belong to the optimizer - plan.reset(opt.stealBest()); // Now we own the best one again - planRegisters = true; - } else { // no queryString, we are instantiating from _queryBuilder - enterState(PARSING); - - VPackSlice const querySlice = _queryBuilder->slice(); - ExecutionPlan::getCollectionsFromVelocyPack(parser->ast(), querySlice); - - parser->ast()->variables()->fromVelocyPack(querySlice); - // creating the plan may have produced some collections - // we need to add them to the transaction now (otherwise the query will - // fail) - - enterState(LOADING_COLLECTIONS); - - int res = trx->addCollections(*_collections.collections()); - - if (res == TRI_ERROR_NO_ERROR) { - res = trx->begin(); - } - - if (res != TRI_ERROR_NO_ERROR) { - return transactionError(res); - } - - enterState(PLAN_INSTANTIATION); - - // we have an execution plan in VelocyPack format - plan.reset(ExecutionPlan::instantiateFromVelocyPack( - parser->ast(), _queryBuilder->slice())); - if (plan.get() == nullptr) { - // oops - return QueryResult(TRI_ERROR_INTERNAL); - } - - planRegisters = false; - } - - TRI_ASSERT(plan.get() != nullptr); - - // varsUsedLater and varsValid are unordered_sets and so their orders - // are not the same in the serialized and deserialized plans - - // return the V8 context - exitContext(); - - enterState(EXECUTION); - ExecutionEngine* engine(ExecutionEngine::instantiateFromPlan( - registry, this, plan.get(), planRegisters)); - - // If all went well so far, then we keep _plan and _trx and - // return: - _plan = std::move(plan); - _engine = engine; - return QueryResult(); - } catch (arangodb::basics::Exception const& ex) { - cleanupPlanAndEngine(ex.code()); - return QueryResult(ex.code(), ex.message() + getStateString()); - } catch (std::bad_alloc const&) { - cleanupPlanAndEngine(TRI_ERROR_OUT_OF_MEMORY); - return QueryResult( - TRI_ERROR_OUT_OF_MEMORY, - TRI_errno_string(TRI_ERROR_OUT_OF_MEMORY) + getStateString()); - } catch (std::exception const& ex) { - cleanupPlanAndEngine(TRI_ERROR_INTERNAL); - return QueryResult(TRI_ERROR_INTERNAL, ex.what() + getStateString()); - } catch (...) { - cleanupPlanAndEngine(TRI_ERROR_INTERNAL); - return QueryResult(TRI_ERROR_INTERNAL, - TRI_errno_string(TRI_ERROR_INTERNAL) + getStateString()); - } - } catch (arangodb::basics::Exception const& ex) { - return QueryResult(ex.code(), ex.message() + getStateString()); - } catch (std::bad_alloc const&) { - return QueryResult( - TRI_ERROR_OUT_OF_MEMORY, - TRI_errno_string(TRI_ERROR_OUT_OF_MEMORY) + getStateString()); - } catch (std::exception const& ex) { - return QueryResult(TRI_ERROR_INTERNAL, ex.what() + getStateString()); - } catch (...) { - return QueryResult(TRI_ERROR_INTERNAL, - TRI_errno_string(TRI_ERROR_INTERNAL) + getStateString()); + if (_queryString != nullptr) { + parser->parse(false); + // put in bind parameters + parser->ast()->injectBindParameters(_bindParameters); } + _isModificationQuery = parser->isModificationQuery(); + + // create the transaction object, but do not start it yet + AqlTransaction* trx = new AqlTransaction( + createTransactionContext(), _collections.collections(), + _part == PART_MAIN); + _trx = trx; + + bool planRegisters; + // As soon as we start du instantiate the plan we have to clean it + // up before killing the unique_ptr + if (_queryString != nullptr) { + // we have an AST + // optimize the ast + enterState(AST_OPTIMIZATION); + + parser->ast()->validateAndOptimize(); + + enterState(LOADING_COLLECTIONS); + + int res = trx->begin(); + + if (res != TRI_ERROR_NO_ERROR) { + THROW_ARANGO_EXCEPTION_MESSAGE(res, buildErrorMessage(res)); + } + + enterState(PLAN_INSTANTIATION); + plan.reset(ExecutionPlan::instantiateFromAst(parser->ast())); + + if (plan.get() == nullptr) { + // oops + THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "failed to create query execution engine"); + } + + // Run the query optimizer: + enterState(PLAN_OPTIMIZATION); + arangodb::aql::Optimizer opt(maxNumberOfPlans()); + // get enabled/disabled rules + opt.createPlans(plan.release(), getRulesFromOptions(), + inspectSimplePlans()); + // Now plan and all derived plans belong to the optimizer + plan.reset(opt.stealBest()); // Now we own the best one again + planRegisters = true; + } else { // no queryString, we are instantiating from _queryBuilder + enterState(PARSING); + + VPackSlice const querySlice = _queryBuilder->slice(); + ExecutionPlan::getCollectionsFromVelocyPack(parser->ast(), querySlice); + + parser->ast()->variables()->fromVelocyPack(querySlice); + // creating the plan may have produced some collections + // we need to add them to the transaction now (otherwise the query will + // fail) + + enterState(LOADING_COLLECTIONS); + + int res = trx->addCollections(*_collections.collections()); + + if (res == TRI_ERROR_NO_ERROR) { + res = trx->begin(); + } + + if (res != TRI_ERROR_NO_ERROR) { + THROW_ARANGO_EXCEPTION_MESSAGE(res, buildErrorMessage(res)); + } + + enterState(PLAN_INSTANTIATION); + + // we have an execution plan in VelocyPack format + plan.reset(ExecutionPlan::instantiateFromVelocyPack( + parser->ast(), _queryBuilder->slice())); + if (plan.get() == nullptr) { + // oops + THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "could not create plan from vpack"); + } + + planRegisters = false; + } + + TRI_ASSERT(plan.get() != nullptr); + + // varsUsedLater and varsValid are unordered_sets and so their orders + // are not the same in the serialized and deserialized plans + + // return the V8 context + exitContext(); + + enterState(EXECUTION); + ExecutionEngine* engine(ExecutionEngine::instantiateFromPlan( + registry, this, plan.get(), planRegisters)); + + // If all went well so far, then we keep _plan and _trx and + // return: + _plan = std::move(plan); + _engine = engine; } /// @brief execute an AQL query @@ -635,7 +602,7 @@ QueryResult Query::execute(QueryRegistry* registry) { if (cacheEntry != nullptr) { // got a result from the query cache - QueryResult res(TRI_ERROR_NO_ERROR); + QueryResult res; // we don't have yet a transaction when we're here, so let's create // a mimimal context to build the result res.context = std::make_shared(_vocbase); @@ -648,11 +615,8 @@ QueryResult Query::execute(QueryRegistry* registry) { } } - QueryResult result = prepare(registry); - - if (result.code != TRI_ERROR_NO_ERROR) { - return result; - } + // will throw if it fails + prepare(registry); if (_queryString == nullptr) { // we don't have query string... now pass query id to WorkMonitor @@ -741,7 +705,8 @@ QueryResult Query::execute(QueryRegistry* registry) { } _trx->commit(); - + + QueryResult result; result.context = _trx->transactionContext(); _engine->_stats.setExecutionTime(TRI_microtime() - _startTime); @@ -749,7 +714,7 @@ QueryResult Query::execute(QueryRegistry* registry) { cleanupPlanAndEngine(TRI_ERROR_NO_ERROR, stats.get()); enterState(FINALIZATION); - + result.warnings = warningsToVelocyPack(); result.result = resultBuilder; result.stats = stats; @@ -811,26 +776,23 @@ QueryResultV8 Query::executeV8(v8::Isolate* isolate, QueryRegistry* registry) { if (cacheEntry != nullptr) { // got a result from the query cache - QueryResultV8 res(TRI_ERROR_NO_ERROR); + QueryResultV8 result; // we don't have yet a transaction when we're here, so let's create // a mimimal context to build the result - res.context = std::make_shared(_vocbase); + result.context = std::make_shared(_vocbase); v8::Handle values = TRI_VPackToV8(isolate, cacheEntry->_queryResult->slice(), - res.context->getVPackOptions()); + result.context->getVPackOptions()); TRI_ASSERT(values->IsArray()); - res.result = v8::Handle::Cast(values); - res.cached = true; - return res; + result.result = v8::Handle::Cast(values); + result.cached = true; + return result; } } - QueryResultV8 result = prepare(registry); - - if (result.code != TRI_ERROR_NO_ERROR) { - return result; - } + // will throw if it fails + prepare(registry); work.reset(new AqlWorkStack(_vocbase, _id, _queryString, _queryLength)); @@ -841,6 +803,7 @@ QueryResultV8 Query::executeV8(v8::Isolate* isolate, QueryRegistry* registry) { useQueryCache = false; } + QueryResultV8 result; result.result = v8::Array::New(isolate); TRI_ASSERT(_engine != nullptr); @@ -1014,7 +977,7 @@ QueryResult Query::explain() { int res = _trx->begin(); if (res != TRI_ERROR_NO_ERROR) { - return transactionError(res); + THROW_ARANGO_EXCEPTION_MESSAGE(res, buildErrorMessage(res)); } enterState(PLAN_INSTANTIATION); @@ -1299,16 +1262,17 @@ bool Query::canUseQueryCache() const { return false; } -/// @brief neatly format transaction error to the user. -QueryResult Query::transactionError(int errorCode) const { +/// @brief neatly format exception messages for the users +std::string Query::buildErrorMessage(int errorCode) const { std::string err(TRI_errno_string(errorCode)); if (_queryString != nullptr && verboseErrors()) { - err += - std::string("\nwhile executing:\n") + _queryString + std::string("\n"); + err += "\nwhile executing:\n"; + err.append(_queryString, _queryLength); + err += "\n"; } - return QueryResult(errorCode, err); + return err; } /// @brief read the "optimizer.inspectSimplePlans" section from the options diff --git a/arangod/Aql/Query.h b/arangod/Aql/Query.h index 5836fba8c2..914b170bf3 100644 --- a/arangod/Aql/Query.h +++ b/arangod/Aql/Query.h @@ -242,7 +242,7 @@ class Query { /// execute calls it internally. The purpose of this separate method is /// to be able to only prepare a query from VelocyPack and then store it in the /// QueryRegistry. - QueryResult prepare(QueryRegistry*); + void prepare(QueryRegistry*); /// @brief execute an AQL query QueryResult execute(QueryRegistry*); @@ -370,8 +370,8 @@ class Query { /// @brief read the "optimizer.rules" section from the options std::vector getRulesFromOptions() const; - /// @brief neatly format transaction errors to the user. - QueryResult transactionError(int errorCode) const; + /// @brief neatly format exception messages for the users + std::string buildErrorMessage(int errorCode) const; /// @brief enter a new state void enterState(ExecutionState); diff --git a/arangod/Aql/QueryResultV8.h b/arangod/Aql/QueryResultV8.h index 09b4a7a17b..1864176e66 100644 --- a/arangod/Aql/QueryResultV8.h +++ b/arangod/Aql/QueryResultV8.h @@ -44,6 +44,7 @@ struct QueryResultV8 : public QueryResult { QueryResultV8(int code, std::string const& details) : QueryResult(code, details), result() {} + QueryResultV8() : QueryResult(TRI_ERROR_NO_ERROR) {} explicit QueryResultV8(int code) : QueryResult(code, ""), result() {} v8::Handle result; diff --git a/arangod/Aql/RestAqlHandler.cpp b/arangod/Aql/RestAqlHandler.cpp index 0f9e70b0e2..7dcb7ddcb7 100644 --- a/arangod/Aql/RestAqlHandler.cpp +++ b/arangod/Aql/RestAqlHandler.cpp @@ -95,14 +95,18 @@ void RestAqlHandler::createQueryFromVelocyPack() { VelocyPackHelper::getStringValue(querySlice, "part", ""); auto planBuilder = std::make_shared(VPackBuilder::clone(plan)); - auto query = new Query(false, _vocbase, planBuilder, options, - (part == "main" ? PART_MAIN : PART_DEPENDENT)); - QueryResult res = query->prepare(_queryRegistry); - if (res.code != TRI_ERROR_NO_ERROR) { - LOG_TOPIC(ERR, arangodb::Logger::FIXME) << "failed to instantiate the query: " << res.details; - generateError(rest::ResponseCode::BAD, - TRI_ERROR_QUERY_BAD_JSON_PLAN, res.details); - delete query; + auto query = std::make_unique(false, _vocbase, planBuilder, options, + (part == "main" ? PART_MAIN : PART_DEPENDENT)); + + try { + query->prepare(_queryRegistry); + } catch (std::exception const& ex) { + LOG_TOPIC(ERR, arangodb::Logger::FIXME) << "failed to instantiate the query: " << ex.what(); + generateError(rest::ResponseCode::BAD, TRI_ERROR_QUERY_BAD_JSON_PLAN, ex.what()); + return; + } catch (...) { + LOG_TOPIC(ERR, arangodb::Logger::FIXME) << "failed to instantiate the query"; + generateError(rest::ResponseCode::BAD, TRI_ERROR_QUERY_BAD_JSON_PLAN); return; } @@ -116,14 +120,15 @@ void RestAqlHandler::createQueryFromVelocyPack() { } _qId = TRI_NewTickServer(); + auto transactionContext = query->trx()->transactionContext().get(); try { - _queryRegistry->insert(_qId, query, ttl); + _queryRegistry->insert(_qId, query.get(), ttl); + query.release(); } catch (...) { LOG_TOPIC(ERR, arangodb::Logger::FIXME) << "could not keep query in registry"; generateError(rest::ResponseCode::BAD, TRI_ERROR_INTERNAL, "could not keep query in registry"); - delete query; return; } @@ -139,8 +144,7 @@ void RestAqlHandler::createQueryFromVelocyPack() { return; } - sendResponse(rest::ResponseCode::ACCEPTED, answerBody.slice(), - query->trx()->transactionContext().get()); + sendResponse(rest::ResponseCode::ACCEPTED, answerBody.slice(), transactionContext); } // POST method for /_api/aql/parse (internal) @@ -306,15 +310,19 @@ void RestAqlHandler::createQueryFromString() { auto options = std::make_shared( VPackBuilder::clone(querySlice.get("options"))); - auto query = new Query(false, _vocbase, queryString.c_str(), + auto query = std::make_unique(false, _vocbase, queryString.c_str(), queryString.size(), bindVars, options, (part == "main" ? PART_MAIN : PART_DEPENDENT)); - QueryResult res = query->prepare(_queryRegistry); - if (res.code != TRI_ERROR_NO_ERROR) { - LOG_TOPIC(ERR, arangodb::Logger::FIXME) << "failed to instantiate the query: " << res.details; - generateError(rest::ResponseCode::BAD, - TRI_ERROR_QUERY_BAD_JSON_PLAN, res.details); - delete query; + + try { + query->prepare(_queryRegistry); + } catch (std::exception const& ex) { + LOG_TOPIC(ERR, arangodb::Logger::FIXME) << "failed to instantiate the query: " << ex.what(); + generateError(rest::ResponseCode::BAD, TRI_ERROR_QUERY_BAD_JSON_PLAN, ex.what()); + return; + } catch (...) { + LOG_TOPIC(ERR, arangodb::Logger::FIXME) << "failed to instantiate the query"; + generateError(rest::ResponseCode::BAD, TRI_ERROR_QUERY_BAD_JSON_PLAN); return; } @@ -327,15 +335,16 @@ void RestAqlHandler::createQueryFromString() { ttl = arangodb::basics::StringUtils::doubleDecimal(ttlstring); } + auto transactionContext = query->trx()->transactionContext().get(); _qId = TRI_NewTickServer(); try { - _queryRegistry->insert(_qId, query, ttl); + _queryRegistry->insert(_qId, query.get(), ttl); + query.release(); } catch (...) { LOG_TOPIC(ERR, arangodb::Logger::FIXME) << "could not keep query in registry"; generateError(rest::ResponseCode::BAD, TRI_ERROR_INTERNAL, "could not keep query in registry"); - delete query; return; } @@ -351,8 +360,7 @@ void RestAqlHandler::createQueryFromString() { return; } - sendResponse(rest::ResponseCode::ACCEPTED, answerBody.slice(), - query->trx()->transactionContext().get()); + sendResponse(rest::ResponseCode::ACCEPTED, answerBody.slice(), transactionContext); } // PUT method for /_api/aql//, (internal)