diff --git a/arangod/Aql/ExecutionBlock.cpp b/arangod/Aql/ExecutionBlock.cpp index 11fe4ed9da..6a1b55a719 100644 --- a/arangod/Aql/ExecutionBlock.cpp +++ b/arangod/Aql/ExecutionBlock.cpp @@ -999,7 +999,8 @@ IndexRangeBlock::IndexRangeBlock (ExecutionEngine* engine, _condition(new IndexOrCondition()), _posInRanges(0), _sortCoords(), - _freeCondition(true) { + _freeCondition(true), + _hasV8Expression(false) { auto trxCollection = _trx->trxCollection(_collection->cid()); if (trxCollection != nullptr) { @@ -1031,7 +1032,7 @@ IndexRangeBlock::IndexRangeBlock (ExecutionEngine* engine, isConstant &= r.isConstant(); } _anyBoundVariable |= ! isConstant; - _allBoundsConstant.push_back(isConstant); + _allBoundsConstant.emplace_back(isConstant); } } @@ -1053,6 +1054,191 @@ IndexRangeBlock::~IndexRangeBlock () { delete _edgeIndexIterator; } +bool IndexRangeBlock::hasV8Expression () const { + for (auto expression : _allVariableBoundExpressions) { + TRI_ASSERT(expression != nullptr); + if (expression->isV8()) { + return true; + } + } + return false; +} + +void IndexRangeBlock::buildExpressions () { + auto en = static_cast(getPlanNode()); + size_t posInExpressions = 0; + + // The following are needed to evaluate expressions with local data from + // the current incoming item: + AqlItemBlock* cur = _buffer.front(); + vector& data(cur->getData()); + vector& docColls(cur->getDocumentCollections()); + RegisterId nrRegs = cur->getNrRegs(); + + + IndexOrCondition* newCondition = nullptr; + + for (size_t i = 0; i < en->_ranges.size(); i++) { + size_t const n = en->_ranges[i].size(); + // prefill with n default-constructed vectors + std::vector> collector(n); + + // collect the evaluated bounds here + for (size_t k = 0; k < n; k++) { + auto r = en->_ranges[i][k]; + // First create a new RangeInfo containing only the constant + // low and high bound of r: + RangeInfo riConst(r._var, r._attr, r._lowConst, r._highConst, + r.is1ValueRangeInfo()); + collector[k].emplace_back(riConst); + + // Now work the actual values of the variable lows and highs into + // this constant range: + for (auto l : r._lows) { + Expression* e = _allVariableBoundExpressions[posInExpressions]; + TRI_ASSERT(e != nullptr); + TRI_document_collection_t const* myCollection = nullptr; + AqlValue a = e->execute(_trx, docColls, data, nrRegs * _pos, + _inVars[posInExpressions], + _inRegs[posInExpressions], + &myCollection); + posInExpressions++; + + Json bound; + if (a._type == AqlValue::JSON) { + bound = *(a._json); + a.destroy(); // the TRI_json_t* of a._json has been stolen + } + else if (a._type == AqlValue::SHAPED || a._type == AqlValue::DOCVEC) { + bound = a.toJson(_trx, myCollection); + a.destroy(); // the TRI_json_t* of a._json has been stolen + } + else { + THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, + "AQL: computed a variable bound and got non-JSON"); + } + + if (! bound.isArray()) { + auto b(bound.copy()); + RangeInfo ri(r._var, + r._attr, + RangeInfoBound(l.inclusive(), true, b), // will steal b's JSON + RangeInfoBound(), + false); + + for (size_t j = 0; j < collector[k].size(); j++) { + collector[k][j].fuse(ri); + } + } + else { + std::vector riv; + riv.reserve(bound.size()); + + for (size_t j = 0; j < bound.size(); j++) { + auto b1(bound.at(static_cast(j)).copy()); // first instance of bound + auto b2(bound.at(static_cast(j)).copy()); // second instance of same bound + + riv.emplace_back(RangeInfo(r._var, + r._attr, + RangeInfoBound(l.inclusive(), true, b1), // will steal b1's JSON + RangeInfoBound(l.inclusive(), true, b2), // will steal b2's JSON + true)); + } + + collector[k] = andCombineRangeInfoVecs(collector[k], riv); + } + } + + for (auto h : r._highs) { + Expression* e = _allVariableBoundExpressions[posInExpressions]; + TRI_ASSERT(e != nullptr); + TRI_document_collection_t const* myCollection = nullptr; + AqlValue a = e->execute(_trx, docColls, data, nrRegs * _pos, + _inVars[posInExpressions], + _inRegs[posInExpressions], + &myCollection); + posInExpressions++; + + Json bound; + if (a._type == AqlValue::JSON) { + bound = *(a._json); + a.destroy(); // the TRI_json_t* of a._json has been stolen + } + else if (a._type == AqlValue::SHAPED || a._type == AqlValue::DOCVEC) { + bound = a.toJson(_trx, myCollection); + a.destroy(); // the TRI_json_t* of a._json has been stolen + } + else { + THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, + "AQL: computed a variable bound and got non-JSON"); + } + if (! bound.isArray()) { + auto b(bound.copy()); + RangeInfo ri(r._var, + r._attr, + RangeInfoBound(), + RangeInfoBound(h.inclusive(), true, b), // will steal b's JSON + false); + + for (size_t j = 0; j < collector[k].size(); j++) { + collector[k][j].fuse(ri); + } + } + else { + std::vector riv; + riv.reserve(bound.size()); + + for (size_t j = 0; j < bound.size(); j++) { + auto b1(bound.at(static_cast(j)).copy()); // first instance of bound + auto b2(bound.at(static_cast(j)).copy()); // second instance of same bound + + riv.emplace_back(RangeInfo(r._var, + r._attr, + RangeInfoBound(h.inclusive(), true, b1), // will steal b1's JSON + RangeInfoBound(h.inclusive(), true, b2), // will steal b2's JSON + true)); + } + collector[k] = andCombineRangeInfoVecs(collector[k], riv); + } + } + } + + bool isEmpty = false; + for (auto x: collector) { + if (x.empty()) { + isEmpty = true; + break; + } + } + + if (! isEmpty) { + // otherwise the condition is impossible to fulfill + // the elements of the direct product of the collector are and + // conditions which should be added to newCondition + auto indexAnds = cartesian(collector); + + if (newCondition != nullptr) { + for (auto indexAnd: *indexAnds) { + newCondition->push_back(indexAnd); + } + delete indexAnds; + } + else { + newCondition = indexAnds; + } + } + } + //_condition = newCondition.release(); + if (newCondition != nullptr) { + freeCondition(); + _condition = newCondition; + _freeCondition = true; + } + + // remove duplicates . . . + removeOverlapsIndexOr(*_condition); +} + int IndexRangeBlock::initialize () { ENTER_BLOCK int res = ExecutionBlock::initialize(); @@ -1063,6 +1249,8 @@ int IndexRangeBlock::initialize () { } } + _allVariableBoundExpressions.clear(); + // instanciate expressions: auto instanciateExpression = [&] (RangeInfoBound& b) -> void { AstNode const* a = b.getExpressionAst(_engine->getQuery()->ast()); @@ -1078,6 +1266,7 @@ int IndexRangeBlock::initialize () { delete e; throw; } + // Prepare _inVars and _inRegs: _inVars.emplace_back(); std::vector& inVarsCur = _inVars.back(); @@ -1123,6 +1312,8 @@ int IndexRangeBlock::initialize () { } } } + + _hasV8Expression = hasV8Expression(); return res; LEAVE_BLOCK; @@ -1147,206 +1338,43 @@ int IndexRangeBlock::initialize () { bool IndexRangeBlock::initRanges () { ENTER_BLOCK _flag = true; - auto en = static_cast(getPlanNode()); - - TRI_ASSERT(en->_index != nullptr); // Find out about the actual values for the bounds in the variable bound case: if (_anyBoundVariable) { - size_t posInExpressions = 0; - - // The following are needed to evaluate expressions with local data from - // the current incoming item: - AqlItemBlock* cur = _buffer.front(); - vector& data(cur->getData()); - vector& docColls(cur->getDocumentCollections()); - RegisterId nrRegs = cur->getNrRegs(); + if (_hasV8Expression) { + // must have a V8 context here to protect Expression::execute() + auto engine = _engine; + triagens::basics::ScopeGuard guard{ + [&engine]() -> void { + engine->getQuery()->enterContext(); + }, + [&]() -> void { + // must invalidate the expression now as we might be called from + // different threads + if (triagens::arango::ServerState::instance()->isRunningInCluster()) { + for (auto e : _allVariableBoundExpressions) { + e->invalidate(); + } + } + + engine->getQuery()->exitContext(); + } + }; + + ISOLATE; + v8::HandleScope scope(isolate); // do not delete this! - // must have a V8 context here to protect Expression::execute() - auto engine = _engine; - triagens::basics::ScopeGuard guard{ - [&engine]() -> void { - engine->getQuery()->enterContext(); - }, - [&]() -> void { - - // must invalidate the expression now as we might be called from - // different threads - if (triagens::arango::ServerState::instance()->isRunningInCluster()) { - for (auto e : _allVariableBoundExpressions) { - e->invalidate(); - } - } - - engine->getQuery()->exitContext(); - } - }; - - ISOLATE; - v8::HandleScope scope(isolate); // do not delete this! - - IndexOrCondition* newCondition = nullptr; - for (size_t i = 0; i < en->_ranges.size(); i++) { - std::vector> collector; - //collect the evaluated bounds here - for (size_t k = 0; k < en->_ranges[i].size(); k++) { - auto r = en->_ranges[i][k]; - collector.emplace_back(std::vector()); - // First create a new RangeInfo containing only the constant - // low and high bound of r: - RangeInfo riConst(r._var, r._attr, r._lowConst, r._highConst, - r.is1ValueRangeInfo()); - collector[k].emplace_back(riConst); - - // Now work the actual values of the variable lows and highs into - // this constant range: - for (auto l : r._lows) { - Expression* e = _allVariableBoundExpressions[posInExpressions]; - TRI_ASSERT(e != nullptr); - TRI_document_collection_t const* myCollection = nullptr; - AqlValue a = e->execute(_trx, docColls, data, nrRegs * _pos, - _inVars[posInExpressions], - _inRegs[posInExpressions], - &myCollection); - posInExpressions++; - - Json bound; - if (a._type == AqlValue::JSON) { - bound = *(a._json); - a.destroy(); // the TRI_json_t* of a._json has been stolen - } - else if (a._type == AqlValue::SHAPED || a._type == AqlValue::DOCVEC) { - bound = a.toJson(_trx, myCollection); - a.destroy(); // the TRI_json_t* of a._json has been stolen - } - else { - THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, - "AQL: computed a variable bound and got non-JSON"); - } - if (! bound.isArray()) { - Json json(Json::Object, 3); - json("include", Json(l.inclusive())) - ("isConstant", Json(true)) - ("bound", bound.copy()); - RangeInfo ri = RangeInfo(r._var, - r._attr, - RangeInfoBound(json), - RangeInfoBound(), - false); - for (size_t j = 0; j < collector[k].size(); j++) { - collector[k][j].fuse(ri); - } - } - else { - std::vector riv; - for (size_t j = 0; j < bound.size(); j++) { - Json json(Json::Object, 3); - json("include", Json(l.inclusive())) - ("isConstant", Json(true)) - ("bound", bound.at(static_cast(j)).copy()); - - riv.emplace_back(RangeInfo(r._var, - r._attr, - RangeInfoBound(json), - RangeInfoBound(json), - true)); - } - collector[k] = andCombineRangeInfoVecs(collector[k], riv); - } - } - - for (auto h : r._highs) { - Expression* e = _allVariableBoundExpressions[posInExpressions]; - TRI_ASSERT(e != nullptr); - TRI_document_collection_t const* myCollection = nullptr; - AqlValue a = e->execute(_trx, docColls, data, nrRegs * _pos, - _inVars[posInExpressions], - _inRegs[posInExpressions], - &myCollection); - posInExpressions++; - - Json bound; - if (a._type == AqlValue::JSON) { - bound = *(a._json); - a.destroy(); // the TRI_json_t* of a._json has been stolen - } - else if (a._type == AqlValue::SHAPED || a._type == AqlValue::DOCVEC) { - bound = a.toJson(_trx, myCollection); - a.destroy(); // the TRI_json_t* of a._json has been stolen - } - else { - THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, - "AQL: computed a variable bound and got non-JSON"); - } - if (! bound.isArray()) { - Json json(Json::Object, 3); - json("include", Json(h.inclusive())) - ("isConstant", Json(true)) - ("bound", bound.copy()); - RangeInfo ri = RangeInfo(r._var, - r._attr, - RangeInfoBound(), - RangeInfoBound(json), - false); - for (size_t j = 0; j < collector[k].size(); j++) { - collector[k][j].fuse(ri); - } - } - else { - std::vector riv; - for (size_t j = 0; j < bound.size(); j++) { - Json json(Json::Object, 3); - json("include", Json(h.inclusive())) - ("isConstant", Json(true)) - ("bound", bound.at(static_cast(j)).copy()); - - riv.emplace_back(RangeInfo(r._var, - r._attr, - RangeInfoBound(json), - RangeInfoBound(json), - true)); - } - collector[k] = andCombineRangeInfoVecs(collector[k], riv); - } - } - } - - bool isEmpty = false; - for (auto x: collector) { - if (x.empty()) { - isEmpty = true; - break; - } - } - - if (! isEmpty) { - // otherwise the condition is impossible to fulfil - // the elements of the direct product of the collector are and - // conditions which should be added to newCondition - auto indexAnds = cartesian(collector); - - if (newCondition != nullptr) { - for (auto indexAnd: *indexAnds) { - newCondition->push_back(indexAnd); - } - delete indexAnds; - } - else { - newCondition = indexAnds; - } - } + buildExpressions(); } - //_condition = newCondition.release(); - if (newCondition != nullptr) { - freeCondition(); - _condition = newCondition; - _freeCondition = true; + else { + // no V8 context required! + buildExpressions(); } - - // remove duplicates . . . - removeOverlapsIndexOr(*_condition); } + + auto en = static_cast(getPlanNode()); + TRI_ASSERT(en->_index != nullptr); if (en->_index->type == TRI_IDX_TYPE_PRIMARY_INDEX) { return true; //no initialization here! diff --git a/arangod/Aql/ExecutionBlock.h b/arangod/Aql/ExecutionBlock.h index 9a983f912f..65d802c534 100644 --- a/arangod/Aql/ExecutionBlock.h +++ b/arangod/Aql/ExecutionBlock.h @@ -595,6 +595,18 @@ namespace triagens { private: +//////////////////////////////////////////////////////////////////////////////// +/// @brief whether or not one of the bounds expressions requires V8 +//////////////////////////////////////////////////////////////////////////////// + + bool hasV8Expression () const; + +//////////////////////////////////////////////////////////////////////////////// +/// @brief build the bounds expressions +//////////////////////////////////////////////////////////////////////////////// + + void buildExpressions (); + //////////////////////////////////////////////////////////////////////////////// /// @brief free _condition if it belongs to us //////////////////////////////////////////////////////////////////////////////// @@ -829,6 +841,7 @@ namespace triagens { bool _freeCondition; + bool _hasV8Expression; }; diff --git a/arangod/Aql/Query.cpp b/arangod/Aql/Query.cpp index 571a7856d8..29f288643f 100644 --- a/arangod/Aql/Query.cpp +++ b/arangod/Aql/Query.cpp @@ -652,22 +652,29 @@ QueryResult Query::execute (QueryRegistry* registry) { triagens::basics::Json jsonResult(triagens::basics::Json::Array, 16); triagens::basics::Json stats; - AqlItemBlock* value; + AqlItemBlock* value = nullptr; - while (nullptr != (value = _engine->getSome(1, ExecutionBlock::DefaultBatchSize))) { - auto doc = value->getDocumentCollection(0); - size_t const n = value->size(); - // reserve space for n additional results at once - jsonResult.reserve(n); + try { + while (nullptr != (value = _engine->getSome(1, ExecutionBlock::DefaultBatchSize))) { + auto doc = value->getDocumentCollection(0); + size_t const n = value->size(); + // reserve space for n additional results at once + jsonResult.reserve(n); - for (size_t i = 0; i < n; ++i) { - AqlValue val = value->getValue(i, 0); + for (size_t i = 0; i < n; ++i) { + AqlValue val = value->getValue(i, 0); - if (! val.isEmpty()) { - jsonResult.add(val.toJson(_trx, doc)); + if (! val.isEmpty()) { + jsonResult.add(val.toJson(_trx, doc)); + } } + delete value; + value = nullptr; } + } + catch (...) { delete value; + throw; } stats = _engine->_stats.toJson(); @@ -726,22 +733,28 @@ QueryResultV8 Query::executeV8 (v8::Isolate* isolate, QueryRegistry* registry) { result.result = v8::Array::New(isolate); triagens::basics::Json stats; - AqlItemBlock* value; + AqlItemBlock* value = nullptr; - while (nullptr != (value = _engine->getSome(1, ExecutionBlock::DefaultBatchSize))) { - auto doc = value->getDocumentCollection(0); - size_t const n = value->size(); - // reserve space for n additional results at once - /// json.reserve(n); - - for (size_t i = 0; i < n; ++i) { - AqlValue val = value->getValueReference(i, 0); + try { + while (nullptr != (value = _engine->getSome(1, ExecutionBlock::DefaultBatchSize))) { + auto doc = value->getDocumentCollection(0); - if (! val.isEmpty()) { - result.result->Set(j++, val.toV8(isolate, _trx, doc)); + size_t const n = value->size(); + + for (size_t i = 0; i < n; ++i) { + AqlValue val = value->getValueReference(i, 0); + + if (! val.isEmpty()) { + result.result->Set(j++, val.toV8(isolate, _trx, doc)); + } } + delete value; + value = nullptr; } + } + catch (...) { delete value; + throw; } stats = _engine->_stats.toJson(); diff --git a/arangod/Aql/RangeInfo.h b/arangod/Aql/RangeInfo.h index e24a66f410..49fa0acf1b 100644 --- a/arangod/Aql/RangeInfo.h +++ b/arangod/Aql/RangeInfo.h @@ -28,11 +28,9 @@ #ifndef ARANGODB_AQL_RANGE_INFO_H #define ARANGODB_AQL_RANGE_INFO_H 1 -#include -#include - +#include "Basics/Common.h" #include "Aql/AstNode.h" - +#include "Basics/JsonHelper.h" #include "Basics/json-utilities.h" namespace triagens { @@ -98,6 +96,21 @@ namespace triagens { _defined = true; } } + + RangeInfoBound (bool include, + bool isConstant, + triagens::basics::Json& json) + : _bound(), + _include(include), + _isConstant(isConstant), + _defined(false), + _expressionAst(nullptr) { + + if (! json.isEmpty()) { + _bound = triagens::basics::Json(TRI_UNKNOWN_MEM_ZONE, json.steal()); + _defined = true; + } + } RangeInfoBound () : _bound(),