//////////////////////////////////////////////////////////////////////////////// /// 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 Max Neunhoeffer //////////////////////////////////////////////////////////////////////////////// #include "EnumerateListBlock.h" #include "Aql/ExecutionEngine.h" #include "Basics/Exceptions.h" #include "VocBase/vocbase.h" using namespace triagens::aql; using Json = triagens::basics::Json; EnumerateListBlock::EnumerateListBlock(ExecutionEngine* engine, EnumerateListNode const* en) : ExecutionBlock(engine, en), _index(0), _thisBlock(0), _seen(0), _docVecSize(0), _collection(nullptr), _inVarRegId(ExecutionNode::MaxRegisterId) { auto it = en->getRegisterPlan()->varInfo.find(en->_inVariable->id); if (it == en->getRegisterPlan()->varInfo.end()) { THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "variable not found"); } _inVarRegId = (*it).second.registerId; TRI_ASSERT(_inVarRegId < ExecutionNode::MaxRegisterId); } EnumerateListBlock::~EnumerateListBlock() {} //////////////////////////////////////////////////////////////////////////////// /// @brief initialize, here we get the inVariable //////////////////////////////////////////////////////////////////////////////// int EnumerateListBlock::initialize() { return ExecutionBlock::initialize(); } int EnumerateListBlock::initializeCursor(AqlItemBlock* items, size_t pos) { int res = ExecutionBlock::initializeCursor(items, pos); if (res != TRI_ERROR_NO_ERROR) { return res; } // handle local data (if any) _index = 0; // index in _inVariable for next run _thisBlock = 0; // the current block in the _inVariable DOCVEC _seen = 0; // the sum of the sizes of the blocks in the _inVariable // DOCVEC that preceed _thisBlock return TRI_ERROR_NO_ERROR; } AqlItemBlock* EnumerateListBlock::getSome(size_t, size_t atMost) { if (_done) { return nullptr; } std::unique_ptr res(nullptr); do { // repeatedly try to get more stuff from upstream // note that the value of the variable we have to loop over // can contain zero entries, in which case we have to // try again! if (_buffer.empty()) { size_t toFetch = (std::min)(DefaultBatchSize, atMost); if (!ExecutionBlock::getBlock(toFetch, toFetch)) { _done = true; return nullptr; } _pos = 0; // this is in the first block } // if we make it here, then _buffer.front() exists AqlItemBlock* cur = _buffer.front(); // get the thing we are looping over AqlValue inVarReg = cur->getValueReference(_pos, _inVarRegId); size_t sizeInVar = 0; // to shut up compiler // get the size of the thing we are looping over _collection = nullptr; switch (inVarReg._type) { case AqlValue::JSON: { if (!inVarReg._json->isArray()) { throwArrayExpectedException(); } sizeInVar = inVarReg._json->size(); break; } case AqlValue::RANGE: { sizeInVar = inVarReg._range->size(); break; } case AqlValue::DOCVEC: { if (_index == 0) { // this is a (maybe) new DOCVEC _docVecSize = 0; // we require the total number of items for (size_t i = 0; i < inVarReg._vector->size(); i++) { _docVecSize += inVarReg._vector->at(i)->size(); } } sizeInVar = _docVecSize; if (sizeInVar > 0) { _collection = inVarReg._vector->at(0)->getDocumentCollection(0); } break; } case AqlValue::SHAPED: case AqlValue::EMPTY: { throwArrayExpectedException(); } } if (sizeInVar == 0) { res = nullptr; } else { size_t toSend = (std::min)(atMost, sizeInVar - _index); // create the result res.reset(new AqlItemBlock( toSend, getPlanNode()->getRegisterPlan()->nrRegs[getPlanNode()->getDepth()])); inheritRegisters(cur, res.get(), _pos); // we might have a collection: res->setDocumentCollection(cur->getNrRegs(), _collection); for (size_t j = 0; j < toSend; j++) { if (j > 0) { // re-use already copied aqlvalues for (RegisterId i = 0; i < cur->getNrRegs(); i++) { res->setValue(j, i, res->getValueReference(0, i)); // Note that if this throws, all values will be // deleted properly, since the first row is. } } // add the new register value . . . AqlValue a = getAqlValue(inVarReg); // deep copy of the inVariable.at(_pos) with correct memory // requirements // Note that _index has been increased by 1 by getAqlValue! try { TRI_IF_FAILURE("EnumerateListBlock::getSome") { THROW_ARANGO_EXCEPTION(TRI_ERROR_DEBUG); } res->setValue(j, cur->getNrRegs(), a); } catch (...) { a.destroy(); throw; } } } if (_index == sizeInVar) { _index = 0; _thisBlock = 0; _seen = 0; // advance read position in the current block . . . if (++_pos == cur->size()) { delete cur; _buffer.pop_front(); // does not throw _pos = 0; } } } while (res.get() == nullptr); // Clear out registers no longer needed later: clearRegisters(res.get()); return res.release(); } size_t EnumerateListBlock::skipSome(size_t atLeast, size_t atMost) { if (_done) { return 0; } size_t skipped = 0; while (skipped < atLeast) { if (_buffer.empty()) { size_t toFetch = (std::min)(DefaultBatchSize, atMost); if (!ExecutionBlock::getBlock(toFetch, toFetch)) { _done = true; return skipped; } _pos = 0; // this is in the first block } // if we make it here, then _buffer.front() exists AqlItemBlock* cur = _buffer.front(); // get the thing we are looping over AqlValue inVarReg = cur->getValue(_pos, _inVarRegId); size_t sizeInVar = 0; // to shut up compiler // get the size of the thing we are looping over switch (inVarReg._type) { case AqlValue::JSON: { if (!inVarReg._json->isArray()) { throwArrayExpectedException(); } sizeInVar = inVarReg._json->size(); break; } case AqlValue::RANGE: { sizeInVar = inVarReg._range->size(); break; } case AqlValue::DOCVEC: { if (_index == 0) { // this is a (maybe) new DOCVEC _docVecSize = 0; // we require the total number of items for (size_t i = 0; i < inVarReg._vector->size(); i++) { _docVecSize += inVarReg._vector->at(i)->size(); } } sizeInVar = _docVecSize; if (sizeInVar > 0) { _collection = inVarReg._vector->at(0)->getDocumentCollection(0); } break; } case AqlValue::SHAPED: case AqlValue::EMPTY: { throwArrayExpectedException(); } } if (atMost < sizeInVar - _index) { // eat just enough of inVariable . . . _index += atMost; skipped = atMost; } else { // eat the whole of the current inVariable and proceed . . . skipped += (sizeInVar - _index); _index = 0; _thisBlock = 0; _seen = 0; delete cur; _buffer.pop_front(); _pos = 0; } } return skipped; } //////////////////////////////////////////////////////////////////////////////// /// @brief create an AqlValue from the inVariable using the current _index //////////////////////////////////////////////////////////////////////////////// AqlValue EnumerateListBlock::getAqlValue(AqlValue const& inVarReg) { TRI_IF_FAILURE("EnumerateListBlock::getAqlValue") { THROW_ARANGO_EXCEPTION(TRI_ERROR_DEBUG); } switch (inVarReg._type) { case AqlValue::JSON: { // FIXME: is this correct? What if the copy works, but the // new throws? Is this then a leak? What if the new works // but the AqlValue temporary cannot be made? return AqlValue( new Json(inVarReg._json->at(static_cast(_index++)).copy())); } case AqlValue::RANGE: { return AqlValue( new Json(static_cast(inVarReg._range->at(_index++)))); } case AqlValue::DOCVEC: { // incoming doc vec has a single column auto& block = inVarReg._vector->at(_thisBlock); AqlValue out = block->getValue(_index - _seen, 0).clone(); if (++_index == block->size() + _seen) { _seen += block->size(); _thisBlock++; } return out; } case AqlValue::SHAPED: case AqlValue::EMPTY: { // error break; } } throwArrayExpectedException(); TRI_ASSERT(false); // cannot be reached. function call above will always throw an exception return AqlValue(); } void EnumerateListBlock::throwArrayExpectedException() { THROW_ARANGO_EXCEPTION_MESSAGE( TRI_ERROR_QUERY_ARRAY_EXPECTED, TRI_errno_string(TRI_ERROR_QUERY_ARRAY_EXPECTED) + std::string(" as operand to FOR loop")); }