//////////////////////////////////////////////////////////////////////////////// /// 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 "Aql/AllRowsFetcher.h" #include "Aql/AqlCallStack.h" #include "Aql/AqlItemBlock.h" #include "Aql/CalculationExecutor.h" #include "Aql/ConstFetcher.h" #include "Aql/ConstrainedSortExecutor.h" #include "Aql/CountCollectExecutor.h" #include "Aql/DistinctCollectExecutor.h" #include "Aql/EnumerateCollectionExecutor.h" #include "Aql/EnumerateListExecutor.h" #include "Aql/ExecutionEngine.h" #include "Aql/ExecutionState.h" #include "Aql/ExecutorInfos.h" #include "Aql/FilterExecutor.h" #include "Aql/HashedCollectExecutor.h" #include "Aql/IResearchViewExecutor.h" #include "Aql/IdExecutor.h" #include "Aql/IndexExecutor.h" #include "Aql/InputAqlItemRow.h" #include "Aql/KShortestPathsExecutor.h" #include "Aql/LimitExecutor.h" #include "Aql/MaterializeExecutor.h" #include "Aql/ModificationExecutor.h" #include "Aql/MultiDependencySingleRowFetcher.h" #include "Aql/NoResultsExecutor.h" #include "Aql/ParallelUnsortedGatherExecutor.h" #include "Aql/Query.h" #include "Aql/QueryOptions.h" #include "Aql/ReturnExecutor.h" #include "Aql/ShadowAqlItemRow.h" #include "Aql/ShortestPathExecutor.h" #include "Aql/SimpleModifier.h" #include "Aql/SingleRemoteModificationExecutor.h" #include "Aql/SortExecutor.h" #include "Aql/SortRegister.h" #include "Aql/SortedCollectExecutor.h" #include "Aql/SortingGatherExecutor.h" #include "Aql/SubqueryEndExecutor.h" #include "Aql/SubqueryExecutor.h" #include "Aql/SubqueryStartExecutor.h" #include "Aql/TraversalExecutor.h" #include "Aql/UnsortedGatherExecutor.h" #include "Aql/UpsertModifier.h" #include "Basics/system-functions.h" #include "Transaction/Context.h" #include #include #include using namespace arangodb; using namespace arangodb::aql; namespace { std::string const doneString = "DONE"; std::string const hasMoreString = "HASMORE"; std::string const waitingString = "WAITING"; std::string const unknownString = "UNKNOWN"; std::string const& stateToString(aql::ExecutionState state) { switch (state) { case aql::ExecutionState::DONE: return doneString; case aql::ExecutionState::HASMORE: return hasMoreString; case aql::ExecutionState::WAITING: return waitingString; default: // just to suppress a warning .. return unknownString; } } } // namespace /* * Creates a metafunction `checkName` that tests whether a class has a method * named `methodName`, used like this: * * CREATE_HAS_MEMBER_CHECK(someMethod, hasSomeMethod); * ... * constexpr bool someClassHasSomeMethod = hasSomeMethod::value; */ #define CREATE_HAS_MEMBER_CHECK(methodName, checkName) \ template \ class checkName { \ template \ static std::true_type test(decltype(&C::methodName)); \ template \ static std::true_type test(decltype(&C::template methodName<>)); \ template \ static std::false_type test(...); \ \ public: \ static constexpr bool value = decltype(test(0))::value; \ } CREATE_HAS_MEMBER_CHECK(initializeCursor, hasInitializeCursor); CREATE_HAS_MEMBER_CHECK(skipRows, hasSkipRows); CREATE_HAS_MEMBER_CHECK(fetchBlockForPassthrough, hasFetchBlockForPassthrough); CREATE_HAS_MEMBER_CHECK(expectedNumberOfRows, hasExpectedNumberOfRows); template ExecutionBlockImpl::ExecutionBlockImpl(ExecutionEngine* engine, ExecutionNode const* node, typename Executor::Infos&& infos) : ExecutionBlock(engine, node), _dependencyProxy(_dependencies, engine->itemBlockManager(), infos.getInputRegisters(), infos.numberOfInputRegisters(), trxVpackOptions()), _rowFetcher(_dependencyProxy), _infos(std::move(infos)), _executor(_rowFetcher, _infos), _outputItemRow(), _query(*engine->getQuery()), _state(InternalState::FETCH_DATA), _lastRange{ExecutorState::HASMORE} { // already insert ourselves into the statistics results if (_profile >= PROFILE_LEVEL_BLOCKS) { _engine->_stats.nodes.try_emplace(node->id(), ExecutionStats::Node()); } } template ExecutionBlockImpl::~ExecutionBlockImpl() = default; 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) { TRI_ASSERT(atMost <= ExecutionBlock::DefaultBatchSize()); // 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 (_state == InternalState::DONE) { // We are done, so we stay done return {ExecutionState::DONE, nullptr}; } if (!_outputItemRow) { ExecutionState state; SharedAqlItemBlockPtr 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); _state = InternalState::DONE; // _rowFetcher must be DONE now already return {state, nullptr}; } TRI_ASSERT(newBlock != nullptr); TRI_ASSERT(newBlock->size() > 0); TRI_ASSERT(newBlock->size() <= atMost); _outputItemRow = createOutputRow(newBlock); } ExecutionState state = ExecutionState::HASMORE; ExecutorStats executorStats{}; TRI_ASSERT(atMost > 0); if (isInSplicedSubquery()) { // The loop has to be entered at least once! TRI_ASSERT(!_outputItemRow->isFull()); while (!_outputItemRow->isFull() && _state != InternalState::DONE) { // Assert that write-head is always pointing to a free row TRI_ASSERT(!_outputItemRow->produced()); switch (_state) { case InternalState::FETCH_DATA: { std::tie(state, executorStats) = _executor.produceRows(*_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) { _state = InternalState::FETCH_SHADOWROWS; } break; } case InternalState::FETCH_SHADOWROWS: { state = fetchShadowRowInternal(); if (state == ExecutionState::WAITING) { return {state, nullptr}; } break; } case InternalState::DONE: { TRI_ASSERT(false); // Invalid state } } } // Modify the return state. // As long as we do still have ShadowRows // We need to return HASMORE! if (_state == InternalState::DONE) { state = ExecutionState::DONE; } else { state = ExecutionState::HASMORE; } } else { // The loop has to be entered at least once! TRI_ASSERT(!_outputItemRow->isFull()); while (!_outputItemRow->isFull()) { std::tie(state, executorStats) = _executor.produceRows(*_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) { 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. // Plus, the ConstrainedSortExecutor will report an expectedNumberOfRows // according to its heap size, thus resulting in a smaller allocated output // block. However, it won't report DONE after, because a LIMIT block with // fullCount must continue to count after the sorted output. if /* constexpr */ (Executor::Properties::allowsBlockPassthrough == BlockPassthrough::Disable && !std::is_same::value) { 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 || _state == InternalState::DONE); _outputItemRow.reset(); return {state, std::move(outputBlock)}; } template std::unique_ptr ExecutionBlockImpl::createOutputRow( SharedAqlItemBlockPtr& newBlock) const { if /* constexpr */ (Executor::Properties::allowsBlockPassthrough == BlockPassthrough::Enable) { return std::make_unique(newBlock, infos().getOutputRegisters(), infos().registersToKeep(), infos().registersToClear(), OutputAqlItemRow::CopyRowBehavior::DoNotCopyInputRows); } else { return std::make_unique(newBlock, infos().getOutputRegisters(), infos().registersToKeep(), infos().registersToClear()); } } template Executor& ExecutionBlockImpl::executor() { return _executor; } template Query const& ExecutionBlockImpl::getQuery() const { return _query; } template typename ExecutionBlockImpl::Infos const& ExecutionBlockImpl::infos() const { return _infos; } namespace arangodb::aql { enum class SkipVariants { FETCHER, EXECUTOR, GET_SOME }; // Specifying the namespace here is important to MSVC. template struct ExecuteSkipVariant {}; template <> struct ExecuteSkipVariant { template static std::tuple executeSkip( Executor& executor, typename Executor::Fetcher& fetcher, size_t toSkip) { auto res = fetcher.skipRows(toSkip); return std::make_tuple(res.first, typename Executor::Stats{}, res.second); // tuple, cannot use initializer list due to build failure } }; template <> struct ExecuteSkipVariant { template static std::tuple executeSkip( Executor& executor, typename Executor::Fetcher& fetcher, size_t toSkip) { return executor.skipRows(toSkip); } }; template <> struct ExecuteSkipVariant { template static std::tuple executeSkip( Executor& executor, typename Executor::Fetcher& fetcher, size_t toSkip) { // this function should never be executed TRI_ASSERT(false); // Make MSVC happy: return std::make_tuple(ExecutionState::DONE, typename Executor::Stats{}, 0); // tuple, cannot use initializer list due to build failure } }; template static SkipVariants constexpr skipType() { bool constexpr useFetcher = Executor::Properties::allowsBlockPassthrough == BlockPassthrough::Enable && !std::is_same>::value; bool constexpr useExecutor = hasSkipRows::value; // ConstFetcher and SingleRowFetcher can skip, but // it may not be done for modification subqueries. static_assert(useFetcher == (std::is_same::value || (std::is_same>::value && !std::is_same>::value)), "Unexpected fetcher for SkipVariants::FETCHER"); static_assert(!useFetcher || hasSkipRows::value, "Fetcher is chosen for skipping, but has not skipRows method!"); static_assert( useExecutor == (std::is_same::value || std::is_same>::value || std::is_same>::value || std::is_same>::value || std::is_same>::value || std::is_same>::value || std::is_same>::value || std::is_same>::value || std::is_same>::value || std::is_same>::value || std::is_same>::value || std::is_same::value || std::is_same::value || std::is_same::value || std::is_same::value || std::is_same::value || std::is_same::value || std::is_same>::value || std::is_same>::value), "Unexpected executor for SkipVariants::EXECUTOR"); // The LimitExecutor will not work correctly with SkipVariants::FETCHER! static_assert( !std::is_same::value || useFetcher, "LimitExecutor needs to implement skipRows() to work correctly"); if (useExecutor) { return SkipVariants::EXECUTOR; } else if (useFetcher) { return SkipVariants::FETCHER; } else { return SkipVariants::GET_SOME; } } } // namespace arangodb::aql template std::pair ExecutionBlockImpl::skipSome(size_t const atMost) { traceSkipSomeBegin(atMost); auto state = ExecutionState::HASMORE; while (state == ExecutionState::HASMORE && _skipped < atMost) { auto res = skipSomeOnceWithoutTrace(atMost - _skipped); TRI_ASSERT(state != ExecutionState::WAITING || res.second == 0); state = res.first; _skipped += res.second; TRI_ASSERT(_skipped <= atMost); } size_t skipped = 0; if (state != ExecutionState::WAITING) { std::swap(skipped, _skipped); } TRI_ASSERT(skipped <= atMost); return traceSkipSomeEnd(state, skipped); } template std::pair ExecutionBlockImpl::skipSomeOnceWithoutTrace(size_t atMost) { constexpr SkipVariants customSkipType = skipType(); if (customSkipType == SkipVariants::GET_SOME) { atMost = std::min(atMost, DefaultBatchSize()); auto res = getSomeWithoutTrace(atMost); size_t skipped = 0; if (res.second != nullptr) { skipped = res.second->size(); } TRI_ASSERT(skipped <= atMost); return {res.first, skipped}; } ExecutionState state; typename Executor::Stats stats; size_t skipped; std::tie(state, stats, skipped) = ExecuteSkipVariant::executeSkip(_executor, _rowFetcher, atMost); _engine->_stats += stats; TRI_ASSERT(skipped <= atMost); return {state, skipped}; } template struct InitializeCursor {}; template <> struct InitializeCursor { template static void init(Executor& executor, typename Executor::Fetcher& rowFetcher, typename Executor::Infos& infos) { // destroy and re-create the Executor executor.~Executor(); new (&executor) Executor(rowFetcher, infos); } }; template <> struct InitializeCursor { template static void init(Executor& executor, typename Executor::Fetcher&, typename Executor::Infos&) { // re-initialize the Executor executor.initializeCursor(); } }; template std::pair ExecutionBlockImpl::initializeCursor(InputAqlItemRow const& input) { // reinitialize the DependencyProxy _dependencyProxy.reset(); _lastRange = DataRange(ExecutorState::HASMORE); // destroy and re-create the Fetcher _rowFetcher.~Fetcher(); new (&_rowFetcher) Fetcher(_dependencyProxy); TRI_ASSERT(_skipped == 0); _skipped = 0; TRI_ASSERT(_state == InternalState::DONE || _state == InternalState::FETCH_DATA); _state = InternalState::FETCH_DATA; constexpr bool customInit = hasInitializeCursor::value; // IndexExecutor and EnumerateCollectionExecutor have initializeCursor // implemented, so assert this implementation is used. static_assert(!std::is_same::value || customInit, "EnumerateCollectionExecutor is expected to implement a custom " "initializeCursor method!"); static_assert(!std::is_same::value || customInit, "IndexExecutor is expected to implement a custom " "initializeCursor method!"); static_assert(!std::is_same::value || customInit, "DistinctCollectExecutor is expected to implement a custom " "initializeCursor method!"); InitializeCursor::init(_executor, _rowFetcher, _infos); // // use this with c++17 instead of specialization below // if constexpr (std::is_same_v) { // if (items != nullptr) { // _executor._inputRegisterValues.reset( // items->slice(pos, *(_executor._infos.registersToKeep()))); // } // } return ExecutionBlock::initializeCursor(input); } template std::pair ExecutionBlockImpl::shutdown(int errorCode) { return ExecutionBlock::shutdown(errorCode); } template std::tuple ExecutionBlockImpl::execute(AqlCallStack stack) { // TODO remove this IF if (std::is_same::value) { // Only this executor is fully implemented traceExecuteBegin(stack); auto res = executeWithoutTrace(stack); traceExecuteEnd(res); return res; } // Fall back to getSome/skipSome auto myCall = stack.popCall(); TRI_ASSERT(AqlCall::IsSkipSomeCall(myCall) || AqlCall::IsGetSomeCall(myCall)); if (AqlCall::IsSkipSomeCall(myCall)) { auto const [state, skipped] = skipSome(myCall.getOffset()); if (state != ExecutionState::WAITING) { myCall.didSkip(skipped); } return {state, skipped, nullptr}; } else if (AqlCall::IsGetSomeCall(myCall)) { auto const [state, block] = getSome(myCall.getLimit()); // We do not need to count as softLimit will be overwritten, and hard cannot be set. return {state, 0, block}; } // Should never get here! THROW_ARANGO_EXCEPTION(TRI_ERROR_NOT_IMPLEMENTED); } template void ExecutionBlockImpl::traceExecuteBegin(AqlCallStack const& stack) { if (_profile >= PROFILE_LEVEL_BLOCKS) { if (_getSomeBegin <= 0.0) { _getSomeBegin = TRI_microtime(); } if (_profile >= PROFILE_LEVEL_TRACE_1) { auto const node = getPlanNode(); auto const queryId = this->_engine->getQuery()->id(); // TODO make sure this works also if stack is non relevant, e.g. passed through by outer subquery. auto const& call = stack.peek(); LOG_TOPIC("1e717", INFO, Logger::QUERIES) << "[query#" << queryId << "] " << "execute type=" << node->getTypeString() << " offset=" << call.getOffset() << " limit= " << call.getLimit() << " this=" << (uintptr_t)this << " id=" << node->id(); } } } template void ExecutionBlockImpl::traceExecuteEnd( std::tuple const& result) { if (_profile >= PROFILE_LEVEL_BLOCKS) { auto const& [state, skipped, block] = result; auto const items = block != nullptr ? block->size() : 0; ExecutionNode const* en = getPlanNode(); ExecutionStats::Node stats; stats.calls = 1; stats.items = skipped + items; if (state != ExecutionState::WAITING) { stats.runtime = TRI_microtime() - _getSomeBegin; _getSomeBegin = 0.0; } auto it = _engine->_stats.nodes.find(en->id()); if (it != _engine->_stats.nodes.end()) { it->second += stats; } else { _engine->_stats.nodes.emplace(en->id(), stats); } if (_profile >= PROFILE_LEVEL_TRACE_1) { ExecutionNode const* node = getPlanNode(); auto const queryId = this->_engine->getQuery()->id(); LOG_TOPIC("60bbc", INFO, Logger::QUERIES) << "[query#" << queryId << "] " << "execute done type=" << node->getTypeString() << " this=" << (uintptr_t)this << " id=" << node->id() << " state=" << stateToString(state) << " skipped=" << skipped << " produced=" << items; if (_profile >= PROFILE_LEVEL_TRACE_2) { if (block == nullptr) { LOG_TOPIC("9b3f4", INFO, Logger::QUERIES) << "[query#" << queryId << "] " << "execute type=" << node->getTypeString() << " result: nullptr"; } else { VPackBuilder builder; auto const options = trxVpackOptions(); block->toSimpleVPack(options, builder); LOG_TOPIC("f12f9", INFO, Logger::QUERIES) << "[query#" << queryId << "] " << "execute type=" << node->getTypeString() << " result: " << VPackDumper::toString(builder.slice(), options); } } } } } // 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::aql { // TODO -- remove this specialization when cpp 17 becomes available template <> std::pair ExecutionBlockImpl>::initializeCursor( InputAqlItemRow const& input) { // reinitialize the DependencyProxy _dependencyProxy.reset(); // destroy and re-create the Fetcher _rowFetcher.~Fetcher(); new (&_rowFetcher) Fetcher(_dependencyProxy); TRI_ASSERT(_skipped == 0); _skipped = 0; TRI_ASSERT(_state == InternalState::DONE || _state == InternalState::FETCH_DATA); _state = InternalState::FETCH_DATA; SharedAqlItemBlockPtr block = input.cloneToBlock(_engine->itemBlockManager(), *(infos().registersToKeep()), infos().numberOfOutputRegisters()); _rowFetcher.injectBlock(block); // cppcheck-suppress unreadVariable constexpr bool customInit = hasInitializeCursor::value; InitializeCursor::init(_executor, _rowFetcher, _infos); // end of default initializeCursor return ExecutionBlock::initializeCursor(input); } // TODO the shutdown specializations shall be unified! 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); } 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 subqueryResult; // shutdown is repeatable std::tie(state, subqueryResult) = this->executor().shutdown(errorCode); if (state == ExecutionState::WAITING) { return {ExecutionState::WAITING, subqueryResult}; } Result result; std::tie(state, result) = ExecutionBlock::shutdown(errorCode); if (state == ExecutionState::WAITING) { return {state, result}; } if (result.fail()) { return {state, result}; } return {state, subqueryResult}; } template <> std::pair ExecutionBlockImpl>::shutdown(int errorCode) { ExecutionState state; Result subqueryResult; // shutdown is repeatable std::tie(state, subqueryResult) = this->executor().shutdown(errorCode); if (state == ExecutionState::WAITING) { return {ExecutionState::WAITING, subqueryResult}; } Result result; std::tie(state, result) = ExecutionBlock::shutdown(errorCode); if (state == ExecutionState::WAITING) { return {state, result}; } if (result.fail()) { return {state, result}; } return {state, subqueryResult}; } template <> std::pair ExecutionBlockImpl>>::shutdown(int errorCode) { if (this->infos().isResponsibleForInitializeCursor()) { return ExecutionBlock::shutdown(errorCode); } return {ExecutionState::DONE, {errorCode}}; } } // namespace arangodb::aql namespace arangodb::aql { // The constant "PASSTHROUGH" is somehow reserved with MSVC. enum class RequestWrappedBlockVariant { DEFAULT, PASS_THROUGH, INPUTRESTRICTED }; // Specifying the namespace here is important to MSVC. template struct RequestWrappedBlock {}; template <> struct RequestWrappedBlock { /** * @brief Default requestWrappedBlock() implementation. Just get a new block * from the AqlItemBlockManager. */ template static std::pair run( #ifdef ARANGODB_ENABLE_MAINTAINER_MODE typename Executor::Infos const&, #endif Executor& executor, ExecutionEngine& engine, size_t nrItems, RegisterCount nrRegs) { return {ExecutionState::HASMORE, engine.itemBlockManager().requestBlock(nrItems, nrRegs)}; } }; template <> struct RequestWrappedBlock { /** * @brief If blocks can be passed through, we do not create new blocks. * Instead, we take the input blocks and reuse them. */ template static std::pair run( #ifdef ARANGODB_ENABLE_MAINTAINER_MODE typename Executor::Infos const& infos, #endif Executor& executor, ExecutionEngine& engine, size_t nrItems, RegisterCount nrRegs) { static_assert(Executor::Properties::allowsBlockPassthrough == BlockPassthrough::Enable, "This function can only be used with executors supporting " "`allowsBlockPassthrough`"); static_assert(hasFetchBlockForPassthrough::value, "An Executor with allowsBlockPassthrough must implement " "fetchBlockForPassthrough"); SharedAqlItemBlockPtr block; ExecutionState state; typename Executor::Stats executorStats; std::tie(state, executorStats, block) = executor.fetchBlockForPassthrough(nrItems); engine._stats += executorStats; if (state == ExecutionState::WAITING) { TRI_ASSERT(block == nullptr); return {state, nullptr}; } if (block == nullptr) { TRI_ASSERT(state == ExecutionState::DONE); return {state, nullptr}; } // Now we must have a block. TRI_ASSERT(block != nullptr); // Assert that the block has enough registers. This must be guaranteed by // the register planning. TRI_ASSERT(block->getNrRegs() == nrRegs); #ifdef ARANGODB_ENABLE_MAINTAINER_MODE // Check that all output registers are empty. for (auto const& reg : *infos.getOutputRegisters()) { for (size_t row = 0; row < block->size(); row++) { AqlValue const& val = block->getValueReference(row, reg); TRI_ASSERT(val.isEmpty()); } } #endif return {ExecutionState::HASMORE, block}; } }; template <> struct RequestWrappedBlock { /** * @brief If the executor can set an upper bound on the output size knowing * the input size, usually because size(input) >= size(output), let it * prefetch an input block to give us this upper bound. * Only then we allocate a new block with at most this upper bound. */ template static std::pair run( #ifdef ARANGODB_ENABLE_MAINTAINER_MODE typename Executor::Infos const&, #endif Executor& executor, ExecutionEngine& engine, size_t nrItems, RegisterCount nrRegs) { static_assert(Executor::Properties::inputSizeRestrictsOutputSize, "This function can only be used with executors supporting " "`inputSizeRestrictsOutputSize`"); static_assert(hasExpectedNumberOfRows::value, "An Executor with inputSizeRestrictsOutputSize must " "implement expectedNumberOfRows"); SharedAqlItemBlockPtr block; ExecutionState state; size_t expectedRows = 0; // Note: this might trigger a prefetch on the rowFetcher! std::tie(state, expectedRows) = executor.expectedNumberOfRows(nrItems); if (state == ExecutionState::WAITING) { return {state, nullptr}; } nrItems = (std::min)(expectedRows, nrItems); if (nrItems == 0) { TRI_ASSERT(state == ExecutionState::DONE); return {state, nullptr}; } block = engine.itemBlockManager().requestBlock(nrItems, nrRegs); return {ExecutionState::HASMORE, block}; } }; } // namespace arangodb::aql template std::pair ExecutionBlockImpl::requestWrappedBlock( size_t nrItems, RegisterCount nrRegs) { static_assert(Executor::Properties::allowsBlockPassthrough == BlockPassthrough::Disable || !Executor::Properties::inputSizeRestrictsOutputSize, "At most one of Properties::allowsBlockPassthrough or " "Properties::inputSizeRestrictsOutputSize should be true for " "each Executor"); static_assert( (Executor::Properties::allowsBlockPassthrough == BlockPassthrough::Enable) == hasFetchBlockForPassthrough::value, "Executors should implement the method fetchBlockForPassthrough() iff " "Properties::allowsBlockPassthrough is true"); static_assert( Executor::Properties::inputSizeRestrictsOutputSize == hasExpectedNumberOfRows::value, "Executors should implement the method expectedNumberOfRows() iff " "Properties::inputSizeRestrictsOutputSize is true"); constexpr RequestWrappedBlockVariant variant = Executor::Properties::allowsBlockPassthrough == BlockPassthrough::Enable ? RequestWrappedBlockVariant::PASS_THROUGH : Executor::Properties::inputSizeRestrictsOutputSize ? RequestWrappedBlockVariant::INPUTRESTRICTED : RequestWrappedBlockVariant::DEFAULT; // Override for spliced subqueries, this optimization does not work there. if (isInSplicedSubquery() && variant == RequestWrappedBlockVariant::INPUTRESTRICTED) { return RequestWrappedBlock::run( #ifdef ARANGODB_ENABLE_MAINTAINER_MODE infos(), #endif executor(), *_engine, nrItems, nrRegs); } return RequestWrappedBlock::run( #ifdef ARANGODB_ENABLE_MAINTAINER_MODE infos(), #endif executor(), *_engine, nrItems, nrRegs); } /// @brief request an AqlItemBlock from the memory manager template SharedAqlItemBlockPtr ExecutionBlockImpl::requestBlock(size_t nrItems, RegisterId nrRegs) { return _engine->itemBlockManager().requestBlock(nrItems, nrRegs); } // TODO: Remove this special implementations template <> std::pair ExecutionBlockImpl::getSome(size_t atMost) { AqlCallStack stack{AqlCall::SimulateGetSome(atMost)}; auto const [state, skipped, block] = execute(stack); return {state, block}; } template <> std::pair ExecutionBlockImpl::skipSome(size_t const toSkip) { AqlCallStack stack{AqlCall::SimulateSkipSome(toSkip)}; auto const [state, skipped, block] = execute(stack); return {state, skipped}; } template std::tuple ExecutionBlockImpl::executeWithoutTrace(AqlCallStack stack) { // TODO implement! TRI_ASSERT(false); THROW_ARANGO_EXCEPTION(TRI_ERROR_NOT_IMPLEMENTED); } // TODO move me up enum ExecState { SKIP, PRODUCE, FULLCOUNT, UPSTREAM, SHADOWROWS, DONE }; namespace { // This cannot return upstream call or shadowrows. ExecState NextState(AqlCall const& call) { if (call.getOffset() > 0) { // First skip return ExecState::SKIP; } if (call.getLimit() > 0) { // Then produce return ExecState::PRODUCE; } if (call.needsFullCount()) { // then fullcount return ExecState::FULLCOUNT; } // now we are done. return ExecState::DONE; } } // namespace template <> std::tuple ExecutionBlockImpl::executeWithoutTrace(AqlCallStack stack) { if (!_outputItemRow) { // TODO: FIXME Hard coded size SharedAqlItemBlockPtr newBlock = _engine->itemBlockManager().requestBlock(1000, _infos.numberOfOutputRegisters()); TRI_ASSERT(newBlock != nullptr); TRI_ASSERT(newBlock->size() == 1000); _outputItemRow = createOutputRow(newBlock); } size_t skipped = 0; // TODO: Need to make this member variable for waiting? AqlCall myCall = stack.popCall(); ExecState execState = ::NextState(myCall); if (_lastRange.hasShadowRow()) { // We have not been able to move all shadowRows into the output last time. // Continue from there. // TODO test if this works with COUNT COLLECT execState = ExecState::SHADOWROWS; } AqlCall executorRequest; while (execState != ExecState::DONE && !_outputItemRow->isFull()) { switch (execState) { case ExecState::SKIP: { auto [state, skippedLocal, call] = _executor.skipRowsRange(myCall.getOffset(), _lastRange); myCall.didSkip(skippedLocal); skipped += skippedLocal; if (state == ExecutorState::DONE) { execState = ExecState::SHADOWROWS; } else if (myCall.getOffset() > 0) { TRI_ASSERT(_upstreamState != ExecutionState::DONE); // We need to request more executorRequest = call; execState = ExecState::UPSTREAM; } else { // We are done with skipping. Skip is not allowed to request more execState = ::NextState(myCall); } break; } case ExecState::PRODUCE: { auto linesBefore = _outputItemRow->numRowsWritten(); TRI_ASSERT(myCall.getLimit() > 0); auto limit = (std::min)(myCall.getLimit(), _outputItemRow->numRowsLeft()); // Execute getSome auto const [state, stats, call] = _executor.produceRows(limit, _lastRange, *_outputItemRow); auto written = _outputItemRow->numRowsWritten() - linesBefore; _engine->_stats += stats; myCall.didProduce(written); if (state == ExecutorState::DONE) { execState = ExecState::SHADOWROWS; } else if (myCall.getLimit() > 0) { TRI_ASSERT(_upstreamState != ExecutionState::DONE); // We need to request more executorRequest = call; execState = ExecState::UPSTREAM; } else { // We are done with producing. Produce is not allowed to request more execState = ::NextState(myCall); } break; } case ExecState::FULLCOUNT: { TRI_ASSERT(false); } case ExecState::UPSTREAM: { // If this triggers the executors produceRows function has returned // HASMORE even if it new that upstream has no further rows. TRI_ASSERT(_upstreamState != ExecutionState::DONE); TRI_ASSERT(!_lastRange.hasMore()); size_t skippedLocal = 0; stack.pushCall(std::move(executorRequest)); std::tie(_upstreamState, skippedLocal, _lastRange) = _rowFetcher.execute(stack); // Do we need to call it? // myCall.didSkip(skippedLocal); skipped += skippedLocal; execState = ::NextState(myCall); break; } case ExecState::SHADOWROWS: { // TODO: Check if there is a situation where we are at this point, but at the end of a block // Or if we would not recognize this beforehand // TODO: Check if we can have the situation that we are between two shadow rows here. // E.g. LastRow is releveant shadowRow. NextRow is non-relevant shadowRow. // NOTE: I do not think this is an issue, as the Executor will always say that it cannot do anything with // an empty input. Only exception might be COLLECT COUNT. if (_lastRange.hasShadowRow()) { auto const& [state, shadowRow] = _lastRange.nextShadowRow(); TRI_ASSERT(shadowRow.isInitialized()); _outputItemRow->copyRow(shadowRow); if (shadowRow.isRelevant()) { // We found a relevant shadow Row. // We need to reset the Executor // TODO: call reset! } TRI_ASSERT(_outputItemRow->produced()); _outputItemRow->advanceRow(); if (state == ExecutorState::DONE) { if (_lastRange.hasMore()) { // TODO this state is invalid, and can just show up now if we exclude SKIP execState = ExecState::PRODUCE; } else { // Right now we cannot support to have more than one set of // ShadowRows inside of a Range. // We do not know how to continue with the above executor after a shadowrow. TRI_ASSERT(!_lastRange.hasMore()); execState = ExecState::DONE; } } } else { execState = ExecState::DONE; } break; } default: // unreachable TRI_ASSERT(false); } } auto outputBlock = _outputItemRow->stealBlock(); // This is not strictly necessary here, as we shouldn't be called again // after DONE. _outputItemRow.reset(); if (_lastRange.hasMore() || _lastRange.hasShadowRow()) { return {ExecutionState::HASMORE, skipped, std::move(outputBlock)}; } return {_upstreamState, skipped, std::move(outputBlock)}; } /// @brief reset all internal states after processing a shadow row. template void ExecutionBlockImpl::resetAfterShadowRow() { // cppcheck-suppress unreadVariable constexpr bool customInit = hasInitializeCursor::value; InitializeCursor::init(_executor, _rowFetcher, _infos); } template ExecutionState ExecutionBlockImpl::fetchShadowRowInternal() { TRI_ASSERT(_state == InternalState::FETCH_SHADOWROWS); TRI_ASSERT(!_outputItemRow->isFull()); ExecutionState state = ExecutionState::HASMORE; ShadowAqlItemRow shadowRow{CreateInvalidShadowRowHint{}}; // TODO: Add lazy evaluation in case of LIMIT "lying" on done std::tie(state, shadowRow) = _rowFetcher.fetchShadowRow(); if (state == ExecutionState::WAITING) { TRI_ASSERT(!shadowRow.isInitialized()); return state; } if (state == ExecutionState::DONE) { _state = InternalState::DONE; } if (shadowRow.isInitialized()) { _outputItemRow->copyRow(shadowRow); TRI_ASSERT(_outputItemRow->produced()); _outputItemRow->advanceRow(); } else { if (_state != InternalState::DONE) { _state = InternalState::FETCH_DATA; resetAfterShadowRow(); } } return state; } 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; // IndexTag, Insert, Remove, Update,Replace, Upsert are only tags for this one 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, InsertModifier>>; template class ::arangodb::aql::ExecutionBlockImpl>; template class ::arangodb::aql::ExecutionBlockImpl, RemoveModifier>>; template class ::arangodb::aql::ExecutionBlockImpl>; template class ::arangodb::aql::ExecutionBlockImpl, UpdateReplaceModifier>>; template class ::arangodb::aql::ExecutionBlockImpl>; template class ::arangodb::aql::ExecutionBlockImpl, UpsertModifier>>;