//////////////////////////////////////////////////////////////////////////////// /// DISCLAIMER /// /// Copyright 2018 ArangoDB 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 Tobias Goedderz /// @author Michael Hackstein /// @author Heiko Kernbach /// @author Jan Christoph Uhde //////////////////////////////////////////////////////////////////////////////// #include "ExecutionBlockImpl.h" #include "Basics/Common.h" #include "Aql/AqlItemBlock.h" #include "Aql/ExecutionEngine.h" #include "Aql/ExecutionState.h" #include "Aql/ExecutorInfos.h" #include "Aql/InputAqlItemRow.h" #include "Aql/CalculationExecutor.h" #include "Aql/ConstrainedSortExecutor.h" #include "Aql/CountCollectExecutor.h" #include "Aql/DistinctCollectExecutor.h" #include "Aql/EnumerateCollectionExecutor.h" #include "Aql/ModificationExecutor.h" #include "Aql/ModificationExecutorTraits.h" #include "Aql/EnumerateListExecutor.h" #include "Aql/FilterExecutor.h" #include "Aql/HashedCollectExecutor.h" #include "Aql/IdExecutor.h" #include "Aql/IndexExecutor.h" #include "Aql/LimitExecutor.h" #include "Aql/NoResultsExecutor.h" #include "Aql/ReturnExecutor.h" #include "Aql/ShortestPathExecutor.h" #include "Aql/SortExecutor.h" #include "Aql/SortRegister.h" #include "Aql/SingleRemoteModificationExecutor.h" #include "Aql/SortedCollectExecutor.h" #include "Aql/SortingGatherExecutor.h" #include "Aql/TraversalExecutor.h" #include using namespace arangodb; using namespace arangodb::aql; template ExecutionBlockImpl::ExecutionBlockImpl(ExecutionEngine* engine, ExecutionNode const* node, typename Executor::Infos&& infos) : ExecutionBlock(engine, node), _blockFetcher(_dependencies, _engine->itemBlockManager(), infos.getInputRegisters(), infos.numberOfInputRegisters()), _rowFetcher(_blockFetcher), _infos(std::move(infos)), _executor(_rowFetcher, _infos), _outputItemRow(), _query(*engine->getQuery()) {} template ExecutionBlockImpl::~ExecutionBlockImpl() {} template std::pair> ExecutionBlockImpl::getSome(size_t atMost) { traceGetSomeBegin(atMost); auto result = getSomeWithoutTrace(atMost); return traceGetSomeEnd(result.first, std::move(result.second)); } template std::pair> ExecutionBlockImpl::getSomeWithoutTrace(size_t atMost) { // silence tests -- we need to introduce new failure tests for fetchers TRI_IF_FAILURE("ExecutionBlock::getOrSkipSome1") { THROW_ARANGO_EXCEPTION(TRI_ERROR_DEBUG); } TRI_IF_FAILURE("ExecutionBlock::getOrSkipSome2") { THROW_ARANGO_EXCEPTION(TRI_ERROR_DEBUG); } TRI_IF_FAILURE("ExecutionBlock::getOrSkipSome3") { THROW_ARANGO_EXCEPTION(TRI_ERROR_DEBUG); } if (getQuery().killed()) { THROW_ARANGO_EXCEPTION(TRI_ERROR_QUERY_KILLED); } if (!_outputItemRow) { ExecutionState state; std::shared_ptr newBlock; std::tie(state, newBlock) = requestWrappedBlock(atMost, _infos.numberOfOutputRegisters()); if (state == ExecutionState::WAITING) { TRI_ASSERT(newBlock == nullptr); return {state, nullptr}; } if (newBlock == nullptr) { TRI_ASSERT(state == ExecutionState::DONE); // _rowFetcher must be DONE now already return {state, nullptr}; } TRI_ASSERT(newBlock != nullptr); _outputItemRow = createOutputRow(newBlock); } // TODO It's not very obvious that `state` will be initialized, because // it's not obvious that the loop will run at least once (e.g. after a // WAITING). It should, but I'd like that to be clearer. Initializing here // won't help much because it's unclear whether the value will be correct. ExecutionState state = ExecutionState::HASMORE; ExecutorStats executorStats{}; TRI_ASSERT(atMost > 0); TRI_ASSERT(!_outputItemRow->isFull()); while (!_outputItemRow->isFull()) { std::tie(state, executorStats) = _executor.produceRow(*_outputItemRow); // Count global but executor-specific statistics, like number of filtered // rows. _engine->_stats += executorStats; if (_outputItemRow->produced()) { _outputItemRow->advanceRow(); } if (state == ExecutionState::WAITING) { return {state, nullptr}; } if (state == ExecutionState::DONE) { // TODO Does this work as expected when there was no row produced, or // we were DONE already, so we didn't build a single row? // We must return nullptr then, because empty AqlItemBlocks are not // allowed! auto outputBlock = _outputItemRow->stealBlock(); // This is not strictly necessary here, as we shouldn't be called again // after DONE. _outputItemRow.reset(); return {state, std::move(outputBlock)}; } } TRI_ASSERT(state == ExecutionState::HASMORE); // When we're passing blocks through we have no control over the size of the // output block. if /* constexpr */ (!Executor::Properties::allowsBlockPassthrough) { TRI_ASSERT(_outputItemRow->numRowsWritten() == atMost); } auto outputBlock = _outputItemRow->stealBlock(); // we guarantee that we do return a valid pointer in the HASMORE case. TRI_ASSERT(outputBlock != nullptr); // TODO OutputAqlItemRow could get "reset" and "isValid" methods and be reused _outputItemRow.reset(); return {state, std::move(outputBlock)}; } template std::unique_ptr ExecutionBlockImpl::createOutputRow( std::shared_ptr& newBlock) const { if /* constexpr */ (Executor::Properties::allowsBlockPassthrough) { return std::make_unique(newBlock, infos().getOutputRegisters(), infos().registersToKeep(), infos().registersToClear(), OutputAqlItemRow::CopyRowBehaviour::DoNotCopyInputRows); } else { return std::make_unique(newBlock, infos().getOutputRegisters(), infos().registersToKeep(), infos().registersToClear()); } } template std::pair ExecutionBlockImpl::skipSome(size_t atMost) { // TODO IMPLEMENT ME, this is a stub! traceSkipSomeBegin(atMost); auto res = getSomeWithoutTrace(atMost); size_t skipped = 0; if (res.second != nullptr) { skipped = res.second->size(); AqlItemBlock* resultPtr = res.second.get(); returnBlock(resultPtr); res.second.release(); } return traceSkipSomeEnd(res.first, skipped); } template std::pair> ExecutionBlockImpl::traceGetSomeEnd( ExecutionState state, std::unique_ptr result) { ExecutionBlock::traceGetSomeEnd(result.get(), state); return {state, std::move(result)}; } template std::pair ExecutionBlockImpl::traceSkipSomeEnd( ExecutionState state, size_t skipped) { ExecutionBlock::traceSkipSomeEnd(skipped, state); return {state, skipped}; } template std::pair ExecutionBlockImpl::initializeCursor( AqlItemBlock* items, size_t pos) { // destroy and re-create the BlockFetcher _blockFetcher.~BlockFetcher(); new (&_blockFetcher) BlockFetcher(_dependencies, _engine->itemBlockManager(), _infos.getInputRegisters(), _infos.numberOfInputRegisters()); // destroy and re-create the Fetcher _rowFetcher.~Fetcher(); new (&_rowFetcher) Fetcher(_blockFetcher); // destroy and re-create the Executor _executor.~Executor(); new (&_executor) Executor(_rowFetcher, _infos); // // use this with c++17 instead of specialisation below // if constexpr (std::is_same_v) { // if (items != nullptr) { // _executor._inputRegisterValues.reset( // items->slice(pos, *(_executor._infos.registersToKeep()))); // } // } return ExecutionBlock::initializeCursor(items, pos); } template std::pair ExecutionBlockImpl::shutdown(int errorCode) { return ExecutionBlock::shutdown(errorCode); } // Work around GCC bug: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56480 // Without the namespaces it fails with // error: specialization of 'template std::pair arangodb::aql::ExecutionBlockImpl::initializeCursor(arangodb::aql::AqlItemBlock*, size_t)' in different namespace namespace arangodb { namespace aql { // TODO -- remove this specialization when cpp 17 becomes available template <> std::pair ExecutionBlockImpl>::initializeCursor( AqlItemBlock* items, size_t pos) { // destroy and re-create the BlockFetcher _blockFetcher.~BlockFetcher(); new (&_blockFetcher) BlockFetcher(_dependencies, _engine->itemBlockManager(), infos().getInputRegisters(), infos().numberOfInputRegisters()); // destroy and re-create the Fetcher _rowFetcher.~Fetcher(); new (&_rowFetcher) Fetcher(_blockFetcher); std::unique_ptr block; if (items != nullptr) { block = std::unique_ptr( items->slice(pos, *(infos().registersToKeep()), infos().numberOfOutputRegisters())); } else { block = std::unique_ptr( _engine->itemBlockManager().requestBlock(1, infos().numberOfOutputRegisters())); } auto shell = std::make_shared(_engine->itemBlockManager(), std::move(block)); _rowFetcher.injectBlock(shell); // destroy and re-create the Executor _executor.~IdExecutor(); new (&_executor) IdExecutor(_rowFetcher, _infos); return ExecutionBlock::initializeCursor(items, pos); } template <> std::pair ExecutionBlockImpl::shutdown(int errorCode) { ExecutionState state; Result result; std::tie(state, result) = ExecutionBlock::shutdown(errorCode); if (state == ExecutionState::WAITING) { return {state, result}; } return this->executor().shutdown(errorCode); } template <> std::pair ExecutionBlockImpl::shutdown(int errorCode) { ExecutionState state; Result result; std::tie(state, result) = ExecutionBlock::shutdown(errorCode); if (state == ExecutionState::WAITING) { return {state, result}; } return this->executor().shutdown(errorCode); } } // namespace aql } // namespace arangodb template std::pair> ExecutionBlockImpl::requestWrappedBlock(size_t nrItems, RegisterId nrRegs) { std::shared_ptr blockShell; if /* constexpr */ (Executor::Properties::allowsBlockPassthrough) { // If blocks can be passed through, we do not create new blocks. // Instead, we take the input blocks from the fetcher and reuse them. ExecutionState state; std::tie(state, blockShell) = _rowFetcher.fetchBlockForPassthrough(nrItems); if (state == ExecutionState::WAITING) { TRI_ASSERT(blockShell == nullptr); return {state, nullptr}; } if (blockShell == nullptr) { TRI_ASSERT(state == ExecutionState::DONE); return {state, nullptr}; } // Now we must have a block. TRI_ASSERT(blockShell != nullptr); // Assert that the block has enough registers. This must be guaranteed by // the register planning. TRI_ASSERT(blockShell->block().getNrRegs() == nrRegs); #ifdef ARANGODB_ENABLE_MAINTAINER_MODE // Check that all output registers are empty. if (!std::is_same>::value) { for (auto const& reg : *infos().getOutputRegisters()) { for (size_t row = 0; row < blockShell->block().size(); row++) { AqlValue const& val = blockShell->block().getValueReference(row, reg); TRI_ASSERT(val.isEmpty()); } } } #endif } else if /* constexpr */ (Executor::Properties::inputSizeRestrictsOutputSize) { // The SortExecutor should refetch a block to save memory in case if only few elements to sort ExecutionState state; size_t expectedRows = 0; std::tie(state, expectedRows) = _rowFetcher.preFetchNumberOfRows(nrItems); if (state == ExecutionState::WAITING) { TRI_ASSERT(expectedRows == 0); return {state, nullptr}; } expectedRows += _executor.numberOfRowsInFlight(); nrItems = (std::min)(expectedRows, nrItems); if (nrItems == 0) { TRI_ASSERT(state == ExecutionState::DONE); return {state, nullptr}; } AqlItemBlock* block = requestBlock(nrItems, nrRegs); blockShell = std::make_shared(_engine->itemBlockManager(), std::unique_ptr{block}); } else { AqlItemBlock* block = requestBlock(nrItems, nrRegs); blockShell = std::make_shared(_engine->itemBlockManager(), std::unique_ptr{block}); } // std::unique_ptr outputBlockShell = // std::make_unique(blockShell, _infos.getOutputRegisters(), // _infos.registersToKeep()); return {ExecutionState::HASMORE, std::move(blockShell)}; } template class ::arangodb::aql::ExecutionBlockImpl>; template class ::arangodb::aql::ExecutionBlockImpl>; template class ::arangodb::aql::ExecutionBlockImpl>; template class ::arangodb::aql::ExecutionBlockImpl; template class ::arangodb::aql::ExecutionBlockImpl; template class ::arangodb::aql::ExecutionBlockImpl; template class ::arangodb::aql::ExecutionBlockImpl; template class ::arangodb::aql::ExecutionBlockImpl; template class ::arangodb::aql::ExecutionBlockImpl; template class ::arangodb::aql::ExecutionBlockImpl>; template class ::arangodb::aql::ExecutionBlockImpl>>; template class ::arangodb::aql::ExecutionBlockImpl; template class ::arangodb::aql::ExecutionBlockImpl; template class ::arangodb::aql::ExecutionBlockImpl; template class ::arangodb::aql::ExecutionBlockImpl>>; template class ::arangodb::aql::ExecutionBlockImpl>; template class ::arangodb::aql::ExecutionBlockImpl>>; template class ::arangodb::aql::ExecutionBlockImpl>; template class ::arangodb::aql::ExecutionBlockImpl>>; template class ::arangodb::aql::ExecutionBlockImpl>; template class ::arangodb::aql::ExecutionBlockImpl>>; template class ::arangodb::aql::ExecutionBlockImpl>; template class ::arangodb::aql::ExecutionBlockImpl>>; template class ::arangodb::aql::ExecutionBlockImpl>; template class ::arangodb::aql::ExecutionBlockImpl>; template class ::arangodb::aql::ExecutionBlockImpl>; template class ::arangodb::aql::ExecutionBlockImpl>; template class ::arangodb::aql::ExecutionBlockImpl>; template class ::arangodb::aql::ExecutionBlockImpl>; template class ::arangodb::aql::ExecutionBlockImpl>; template class ::arangodb::aql::ExecutionBlockImpl; template class ::arangodb::aql::ExecutionBlockImpl>; template class ::arangodb::aql::ExecutionBlockImpl>; template class ::arangodb::aql::ExecutionBlockImpl; template class ::arangodb::aql::ExecutionBlockImpl; template class ::arangodb::aql::ExecutionBlockImpl; template class ::arangodb::aql::ExecutionBlockImpl; template class ::arangodb::aql::ExecutionBlockImpl;