diff --git a/arangod/Aql/IResearchViewExecutor.cpp b/arangod/Aql/IResearchViewExecutor.cpp index 78aba56625..d30f84d997 100644 --- a/arangod/Aql/IResearchViewExecutor.cpp +++ b/arangod/Aql/IResearchViewExecutor.cpp @@ -155,7 +155,7 @@ IResearchViewExecutorInfos::IResearchViewExecutorInfos( RegisterId numScoreRegisters, Query& query, std::vector const& scorers, - IResearchViewSort const* sort, + std::pair const& sort, ExecutionPlan const& plan, Variable const& outVariable, aql::AstNode const& filterCondition, @@ -688,9 +688,11 @@ size_t IResearchViewExecutor::skip(size_t limit) { template IResearchViewMergeExecutor::IResearchViewMergeExecutor(Fetcher& fetcher, Infos& infos) : Base{fetcher, infos}, - _heap_it{ MinHeapContext{ *infos.sort(), _segments } } { - TRI_ASSERT(infos.sort()); - TRI_ASSERT(!infos.sort()->empty()); + _heap_it{ MinHeapContext{ *infos.sort().first, infos.sort().second, _segments } } { + TRI_ASSERT(infos.sort().first); + TRI_ASSERT(!infos.sort().first->empty()); + TRI_ASSERT(infos.sort().first->size() >= infos.sort().second); + TRI_ASSERT(infos.sort().second); TRI_ASSERT(ordered == (infos.getNumScoreRegisters() != 0)); } diff --git a/arangod/Aql/IResearchViewExecutor.h b/arangod/Aql/IResearchViewExecutor.h index 9947b5dd65..8b73dc45c8 100644 --- a/arangod/Aql/IResearchViewExecutor.h +++ b/arangod/Aql/IResearchViewExecutor.h @@ -63,7 +63,7 @@ class IResearchViewExecutorInfos : public ExecutorInfos { RegisterId numScoreRegisters, Query& query, std::vector const& scorers, - iresearch::IResearchViewSort const* sort, + std::pair const& sort, ExecutionPlan const& plan, Variable const& outVariable, aql::AstNode const& filterCondition, @@ -83,7 +83,10 @@ class IResearchViewExecutorInfos : public ExecutorInfos { int getDepth() const noexcept { return _depth; } bool volatileSort() const noexcept { return _volatileSort; } bool volatileFilter() const noexcept { return _volatileFilter; } - iresearch::IResearchViewSort const* sort() const noexcept { return _sort; } + + // first - sort + // second - number of sort conditions to take into account + std::pair const& sort() const noexcept { return _sort; } bool isScoreReg(RegisterId reg) const noexcept { return getOutputRegister() < reg && reg <= getOutputRegister() + getNumScoreRegisters(); @@ -95,7 +98,7 @@ class IResearchViewExecutorInfos : public ExecutorInfos { std::shared_ptr const _reader; Query& _query; std::vector const& _scorers; - iresearch::IResearchViewSort const* _sort{}; + std::pair _sort; ExecutionPlan const& _plan; Variable const& _outVariable; aql::AstNode const& _filterCondition; @@ -449,8 +452,9 @@ class IResearchViewMergeExecutor : public IResearchViewExecutorBase& segments) noexcept - : _less(sort), + : _less(sort, sortBuckets), _segments(&segments) { } diff --git a/arangod/Aql/IResearchViewNode.cpp b/arangod/Aql/IResearchViewNode.cpp index 55110568b9..f713095541 100644 --- a/arangod/Aql/IResearchViewNode.cpp +++ b/arangod/Aql/IResearchViewNode.cpp @@ -787,7 +787,31 @@ IResearchViewNode::IResearchViewNode(aql::ExecutionPlan& plan, velocypack::Slice } if (!primarySort.empty()) { - _sort = &primarySort; // set sort from corresponding view + size_t primarySortBuckets = primarySort.size(); + + auto const primarySortBucketsSlice = base.get("primarySortBuckets"); + + if (!primarySortBucketsSlice.isNone()) { + if (!primarySortBucketsSlice.isNumber()) { + THROW_ARANGO_EXCEPTION_MESSAGE( + TRI_ERROR_BAD_PARAMETER, + "invalid vpack format: 'primarySortBuckets' attribute is intended to be a number"); + } + + primarySortBuckets = primarySortBucketsSlice.getNumber(); + + if (primarySortBuckets > primarySort.size()) { + THROW_ARANGO_EXCEPTION_MESSAGE( + TRI_ERROR_BAD_PARAMETER, + "invalid vpack format: value of 'primarySortBuckets' attribute '" + std::to_string(primarySortBuckets) + + "' is greater than number of buckets specified in 'primarySort' attribute '" + std::to_string(primarySort.size()) + + "' of the view '" + _view->name() + "'"); + } + } + + // set sort from corresponding view + _sort.first = &primarySort; + _sort.second = primarySortBuckets; } } } @@ -872,11 +896,15 @@ void IResearchViewNode::toVelocyPackHelper(VPackBuilder& nodes, unsigned flags) nodes.add("volatility", VPackValue(_volatilityMask)); // primarySort - if (_sort && !_sort->empty()) { - VPackArrayBuilder arrayScope(&nodes, "primarySort"); - _sort->toVelocyPack(nodes); + if (_sort.first && !_sort.first->empty()) { + { + VPackArrayBuilder arrayScope(&nodes, "primarySort"); + _sort.first->toVelocyPack(nodes); + } + nodes.add("primarySortBuckets", VPackValue(_sort.second)); } + nodes.close(); } @@ -1112,8 +1140,8 @@ std::unique_ptr IResearchViewNode::createBlock( getRegisterPlan()->varInfo, getDepth()}; - if (_sort) { - TRI_ASSERT(!_sort->empty()); // guaranteed by optimizer rule + if (_sort.first) { + TRI_ASSERT(!_sort.first->empty()); // guaranteed by optimizer rule if (!ordered) { return std::make_unique>>( diff --git a/arangod/Aql/IResearchViewNode.h b/arangod/Aql/IResearchViewNode.h index 054ff88239..e44f570994 100644 --- a/arangod/Aql/IResearchViewNode.h +++ b/arangod/Aql/IResearchViewNode.h @@ -132,13 +132,14 @@ class IResearchViewNode final : public arangodb::aql::ExecutionNode { } /// @return sort condition satisfied by a sorted index - IResearchViewSort const* sort() const noexcept { + std::pair const& sort() const noexcept { return _sort; } /// @brief set sort condition satisfied by a sorted index - void sort(IResearchViewSort const* sort) noexcept { - _sort = sort; + void sort(IResearchViewSort const* sort, size_t size) noexcept { + _sort.first = sort; + _sort.second = sort ? std::min(size, sort->size()) : 0; } /// @brief getVariablesUsedHere, modifying the set in-place @@ -182,7 +183,9 @@ class IResearchViewNode final : public arangodb::aql::ExecutionNode { aql::AstNode const* _filterCondition; /// @brief sort condition covered by the view - IResearchViewSort const* _sort{}; + /// first - sort condition + /// second - number of sort buckets to use + std::pair _sort{}; /// @brief scorers related to the view std::vector _scorers; diff --git a/arangod/Aql/IResearchViewOptimizerRules.cpp b/arangod/Aql/IResearchViewOptimizerRules.cpp index 865bb17fce..ff653b9075 100644 --- a/arangod/Aql/IResearchViewOptimizerRules.cpp +++ b/arangod/Aql/IResearchViewOptimizerRules.cpp @@ -248,7 +248,7 @@ bool optimizeSort(IResearchViewNode& viewNode, ExecutionPlan* plan) { } assert(!primarySort.empty()); - viewNode.sort(&primarySort); + viewNode.sort(&primarySort, sortElements.size()); sortNode->_reinsertInCluster = false; if (!arangodb::ServerState::instance()->isCoordinator()) { diff --git a/arangod/IResearch/IResearchAnalyzerFeature.cpp b/arangod/IResearch/IResearchAnalyzerFeature.cpp index 52eb6f730f..90df65570d 100644 --- a/arangod/IResearch/IResearchAnalyzerFeature.cpp +++ b/arangod/IResearch/IResearchAnalyzerFeature.cpp @@ -147,15 +147,9 @@ arangodb::aql::AqlValue aqlFnTokens(arangodb::aql::ExpressionContext* expression auto data = arangodb::iresearch::getStringRef(args[0].slice()); auto name = arangodb::iresearch::getStringRef(args[1].slice()); auto* analyzers = - arangodb::application_features::ApplicationServer::lookupFeature(); + arangodb::application_features::ApplicationServer::getFeature(); - if (!analyzers) { - irs::string_ref const message = "failure to find feature 'arangosearch' while " - "computing result for function 'TOKENS'"; - - LOG_TOPIC("fbd91", WARN, arangodb::iresearch::TOPIC) << message; - THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, message); - } + TRI_ASSERT(analyzers); arangodb::iresearch::IResearchAnalyzerFeature::AnalyzerPool::ptr pool; diff --git a/arangod/IResearch/IResearchFeature.cpp b/arangod/IResearch/IResearchFeature.cpp index fb12c54508..c7e223b3be 100644 --- a/arangod/IResearch/IResearchFeature.cpp +++ b/arangod/IResearch/IResearchFeature.cpp @@ -566,15 +566,9 @@ void registerUpgradeTasks() { void registerViewFactory() { auto& viewType = arangodb::iresearch::DATA_SOURCE_TYPE; auto* viewTypes = - arangodb::application_features::ApplicationServer::lookupFeature(); + arangodb::application_features::ApplicationServer::getFeature(); - if (!viewTypes) { - THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, - std::string("failed to find feature '") + - arangodb::ViewTypesFeature::name() + - "' while registering view type '" + - viewType.name() + "'"); - } + TRI_ASSERT(viewTypes); arangodb::Result res; diff --git a/arangod/IResearch/IResearchVPackComparer.cpp b/arangod/IResearch/IResearchVPackComparer.cpp index 5249066ef2..d749cf407e 100644 --- a/arangod/IResearch/IResearchVPackComparer.cpp +++ b/arangod/IResearch/IResearchVPackComparer.cpp @@ -39,18 +39,19 @@ namespace arangodb { namespace iresearch { VPackComparer::VPackComparer() - : _sort(&IResearchViewMeta::DEFAULT()._primarySort) { + : VPackComparer(IResearchViewMeta::DEFAULT()._primarySort) { } bool VPackComparer::less(const irs::bytes_ref& lhs, const irs::bytes_ref& rhs) const { TRI_ASSERT(_sort); + TRI_ASSERT(_sort->size() >= _size); TRI_ASSERT(!lhs.empty()); TRI_ASSERT(!rhs.empty()); VPackSlice lhsSlice(lhs.c_str()); VPackSlice rhsSlice(rhs.c_str()); - for (size_t i = 0, size = _sort->size(); i < size; ++i) { + for (size_t i = 0; i < _size; ++i) { TRI_ASSERT(!lhsSlice.isNone()); TRI_ASSERT(!rhsSlice.isNone()); diff --git a/arangod/IResearch/IResearchVPackComparer.h b/arangod/IResearch/IResearchVPackComparer.h index 7e2817592f..a1a77b8e05 100644 --- a/arangod/IResearch/IResearchVPackComparer.h +++ b/arangod/IResearch/IResearchVPackComparer.h @@ -36,15 +36,20 @@ class VPackComparer final : public irs::comparer { VPackComparer(); explicit VPackComparer(IResearchViewSort const& sort) noexcept - : _sort(&sort) { + : _sort(&sort), _size(sort.size()) { + } + + VPackComparer(IResearchViewSort const& sort, size_t size) noexcept + : _sort(&sort), _size(std::min(sort.size(), size)) { } void reset(IResearchViewSort const& sort) noexcept { _sort = &sort; + _size = sort.size(); } bool empty() const noexcept { - return _sort->empty(); + return 0 == _size; } protected: @@ -53,6 +58,7 @@ class VPackComparer final : public irs::comparer { private: IResearchViewSort const* _sort; + size_t _size; // number of buckets to compare }; // VPackComparer } // iresearch diff --git a/arangod/V8Server/v8-analyzers.cpp b/arangod/V8Server/v8-analyzers.cpp index 4a028cbcb8..fb0668dffd 100644 --- a/arangod/V8Server/v8-analyzers.cpp +++ b/arangod/V8Server/v8-analyzers.cpp @@ -291,16 +291,11 @@ void JS_Create(v8::FunctionCallbackInfo const& args) { PREVENT_EMBEDDED_TRANSACTION(); - auto* analyzers = arangodb::application_features::ApplicationServer::lookupFeature< // find feature - arangodb::iresearch::IResearchAnalyzerFeature // feature type + auto* analyzers = arangodb::application_features::ApplicationServer::getFeature< + arangodb::iresearch::IResearchAnalyzerFeature >(); - if (!analyzers) { - TRI_V8_THROW_EXCEPTION_MESSAGE( - TRI_ERROR_INTERNAL, - "failure to find feature '" + arangodb::iresearch::IResearchAnalyzerFeature::name() + "'" - ); - } + TRI_ASSERT(analyzers); auto* sysDatabase = arangodb::application_features::ApplicationServer::lookupFeature< arangodb::SystemDatabaseFeature @@ -444,16 +439,11 @@ void JS_Get(v8::FunctionCallbackInfo const& args) { PREVENT_EMBEDDED_TRANSACTION(); - auto* analyzers = arangodb::application_features::ApplicationServer::lookupFeature< // find feature - arangodb::iresearch::IResearchAnalyzerFeature // feature type + auto* analyzers = arangodb::application_features::ApplicationServer::getFeature< + arangodb::iresearch::IResearchAnalyzerFeature >(); - if (!analyzers) { - TRI_V8_THROW_EXCEPTION_MESSAGE( // exception - TRI_ERROR_INTERNAL, // code - std::string("failure to find feature '") + arangodb::iresearch::IResearchAnalyzerFeature::name() + "'" // message - ); - } + TRI_ASSERT(analyzers); auto* sysDatabase = arangodb::application_features::ApplicationServer::lookupFeature< // find feature arangodb::SystemDatabaseFeature // featue type @@ -515,16 +505,11 @@ void JS_List(v8::FunctionCallbackInfo const& args) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_ARANGO_DATABASE_NOT_FOUND); } - auto* analyzers = arangodb::application_features::ApplicationServer::lookupFeature< // find feature - arangodb::iresearch::IResearchAnalyzerFeature // feature type + auto* analyzers = arangodb::application_features::ApplicationServer::getFeature< + arangodb::iresearch::IResearchAnalyzerFeature >(); - if (!analyzers) { - TRI_V8_THROW_EXCEPTION_MESSAGE( // exception - TRI_ERROR_INTERNAL, // code - std::string("failure to find feature '") + arangodb::iresearch::IResearchAnalyzerFeature::name() + "'" // message - ); - } + TRI_ASSERT(analyzers); auto* sysDatabase = arangodb::application_features::ApplicationServer::lookupFeature< // find feature arangodb::SystemDatabaseFeature // featue type @@ -611,16 +596,11 @@ void JS_Remove(v8::FunctionCallbackInfo const& args) { PREVENT_EMBEDDED_TRANSACTION(); - auto* analyzers = arangodb::application_features::ApplicationServer::lookupFeature< // find feature - arangodb::iresearch::IResearchAnalyzerFeature // feature type + auto* analyzers = arangodb::application_features::ApplicationServer::getFeature< + arangodb::iresearch::IResearchAnalyzerFeature >(); - if (!analyzers) { - TRI_V8_THROW_EXCEPTION_MESSAGE( // exception - TRI_ERROR_INTERNAL, // code - std::string("failure to find feature '") + arangodb::iresearch::IResearchAnalyzerFeature::name() + "'" // message - ); - } + TRI_ASSERT(analyzers); auto* sysDatabase = arangodb::application_features::ApplicationServer::lookupFeature< // find feature arangodb::SystemDatabaseFeature // featue type diff --git a/tests/IResearch/IResearchViewNode-test.cpp b/tests/IResearch/IResearchViewNode-test.cpp index 8f20f6c37b..bc87c7f96e 100644 --- a/tests/IResearch/IResearchViewNode-test.cpp +++ b/tests/IResearch/IResearchViewNode-test.cpp @@ -174,6 +174,151 @@ class IResearchViewNodeTest : public ::testing::Test { } // namespace +TEST_F(IResearchViewNodeTest, constructSortedView) { + TRI_vocbase_t vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, + "testVocbase"); + // create view + auto createJson = arangodb::velocypack::Parser::fromJson( + "{ " + " \"name\": \"testView\", " + " \"type\": \"arangosearch\", " + " \"primarySort\": [ " + " { \"field\": \"my.nested.Fields\", \"asc\": false }, " + " { \"field\": \"another.field\", \"asc\": true } ] " + "}"); + auto logicalView = vocbase.createView(createJson->slice()); + ASSERT_TRUE(logicalView); + + // dummy query + arangodb::aql::Query query(false, vocbase, arangodb::aql::QueryString("RETURN 1"), + nullptr, arangodb::velocypack::Parser::fromJson("{}"), + arangodb::aql::PART_MAIN); + query.prepare(arangodb::QueryRegistryFeature::registry()); + arangodb::aql::Variable const outVariable("variable", 0); + + { + auto json = arangodb::velocypack::Parser::fromJson( + "{ \"id\":42, \"depth\":0, \"totalNrRegs\":0, \"varInfoList\":[], " + "\"nrRegs\":[], \"nrRegsHere\":[], \"regsToClear\":[], " + "\"varsUsedLater\":[], \"varsValid\":[], \"outVariable\": { " + "\"name\":\"variable\", \"id\":0 }, \"options\": { \"waitForSync\" : " + "true, \"collections\":[42] }, \"viewId\": \"" + std::to_string(logicalView->id()) + "\", " + "\"primarySort\": [ { \"field\": \"my.nested.Fields\", \"asc\": false}, { \"field\": \"another.field\", \"asc\":true } ] }"); + + arangodb::iresearch::IResearchViewNode node(*query.plan(), // plan + json->slice()); + + EXPECT_TRUE(node.empty()); // view has no links + EXPECT_TRUE(node.collections().empty()); // view has no links + EXPECT_TRUE(node.shards().empty()); + EXPECT_TRUE(node.sort().first); // primary sort is set + EXPECT_EQ(2, node.sort().second); + + EXPECT_TRUE(arangodb::aql::ExecutionNode::ENUMERATE_IRESEARCH_VIEW == node.getType()); + EXPECT_TRUE(outVariable.id == node.outVariable().id); + EXPECT_TRUE(outVariable.name == node.outVariable().name); + EXPECT_TRUE(query.plan() == node.plan()); + EXPECT_TRUE(42 == node.id()); + EXPECT_TRUE(logicalView == node.view()); + EXPECT_TRUE(node.scorers().empty()); + EXPECT_TRUE(!node.volatility().first); // filter volatility + EXPECT_TRUE(!node.volatility().second); // sort volatility + arangodb::HashSet usedHere; + node.getVariablesUsedHere(usedHere); + EXPECT_TRUE(usedHere.empty()); + auto const setHere = node.getVariablesSetHere(); + EXPECT_TRUE(1 == setHere.size()); + EXPECT_TRUE(outVariable.id == setHere[0]->id); + EXPECT_TRUE(outVariable.name == setHere[0]->name); + EXPECT_TRUE(true == node.options().forceSync); + EXPECT_TRUE(true == node.options().restrictSources); + EXPECT_TRUE(1 == node.options().sources.size()); + EXPECT_TRUE(42 == *node.options().sources.begin()); + + EXPECT_TRUE(0. == node.getCost().estimatedCost); // no dependencies + EXPECT_TRUE(0 == node.getCost().estimatedNrItems); // no dependencies + } + + { + auto json = arangodb::velocypack::Parser::fromJson( + "{ \"id\":42, \"depth\":0, \"totalNrRegs\":0, \"varInfoList\":[], " + "\"nrRegs\":[], \"nrRegsHere\":[], \"regsToClear\":[], " + "\"varsUsedLater\":[], \"varsValid\":[], \"outVariable\": { " + "\"name\":\"variable\", \"id\":0 }, \"options\": { \"waitForSync\" : " + "true, \"collections\":[42] }, \"viewId\": \"" + std::to_string(logicalView->id()) + "\", " + "\"primarySort\": [ { \"field\": \"my.nested.Fields\", \"asc\": false}, { \"field\": \"another.field\", \"asc\":true } ], \"primarySortBuckets\": 1 }"); + + arangodb::iresearch::IResearchViewNode node(*query.plan(), // plan + json->slice()); + + EXPECT_TRUE(node.empty()); // view has no links + EXPECT_TRUE(node.collections().empty()); // view has no links + EXPECT_TRUE(node.shards().empty()); + EXPECT_TRUE(node.sort().first); // primary sort is set + EXPECT_EQ(1, node.sort().second); + + EXPECT_TRUE(arangodb::aql::ExecutionNode::ENUMERATE_IRESEARCH_VIEW == node.getType()); + EXPECT_TRUE(outVariable.id == node.outVariable().id); + EXPECT_TRUE(outVariable.name == node.outVariable().name); + EXPECT_TRUE(query.plan() == node.plan()); + EXPECT_TRUE(42 == node.id()); + EXPECT_TRUE(logicalView == node.view()); + EXPECT_TRUE(node.scorers().empty()); + EXPECT_TRUE(!node.volatility().first); // filter volatility + EXPECT_TRUE(!node.volatility().second); // sort volatility + arangodb::HashSet usedHere; + node.getVariablesUsedHere(usedHere); + EXPECT_TRUE(usedHere.empty()); + auto const setHere = node.getVariablesSetHere(); + EXPECT_TRUE(1 == setHere.size()); + EXPECT_TRUE(outVariable.id == setHere[0]->id); + EXPECT_TRUE(outVariable.name == setHere[0]->name); + EXPECT_TRUE(true == node.options().forceSync); + EXPECT_TRUE(true == node.options().restrictSources); + EXPECT_TRUE(1 == node.options().sources.size()); + EXPECT_TRUE(42 == *node.options().sources.begin()); + + EXPECT_TRUE(0. == node.getCost().estimatedCost); // no dependencies + EXPECT_TRUE(0 == node.getCost().estimatedNrItems); // no dependencies + } + + // invalid 'primarySortBuckets' specified + { + auto json = arangodb::velocypack::Parser::fromJson( + "{ \"id\":42, \"depth\":0, \"totalNrRegs\":0, \"varInfoList\":[], " + "\"nrRegs\":[], \"nrRegsHere\":[], \"regsToClear\":[], " + "\"varsUsedLater\":[], \"varsValid\":[], \"outVariable\": { " + "\"name\":\"variable\", \"id\":0 }, \"viewId\": \"" + std::to_string(logicalView->id()) + "\", " + "\"primarySort\": [ { \"field\": \"my.nested.Fields\", \"asc\": false}, { \"field\": \"another.field\", \"asc\":true } ], \"primarySortBuckets\": false }"); + + try { + arangodb::iresearch::IResearchViewNode node(*query.plan(), // plan + json->slice()); + EXPECT_TRUE(false); + } catch (arangodb::basics::Exception const& ex) { + EXPECT_TRUE(TRI_ERROR_BAD_PARAMETER == ex.code()); + } + } + + // invalid 'primarySortBuckets' specified + { + auto json = arangodb::velocypack::Parser::fromJson( + "{ \"id\":42, \"depth\":0, \"totalNrRegs\":0, \"varInfoList\":[], " + "\"nrRegs\":[], \"nrRegsHere\":[], \"regsToClear\":[], " + "\"varsUsedLater\":[], \"varsValid\":[], \"outVariable\": { " + "\"name\":\"variable\", \"id\":0 }, \"viewId\": \"" + std::to_string(logicalView->id()) + "\", " + "\"primarySort\": [ { \"field\": \"my.nested.Fields\", \"asc\": false}, { \"field\": \"another.field\", \"asc\":true } ], \"primarySortBuckets\": 3 }"); + + try { + arangodb::iresearch::IResearchViewNode node(*query.plan(), // plan + json->slice()); + EXPECT_TRUE(false); + } catch (arangodb::basics::Exception const& ex) { + EXPECT_TRUE(TRI_ERROR_BAD_PARAMETER == ex.code()); + } + } +} + TEST_F(IResearchViewNodeTest, construct) { TRI_vocbase_t vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); @@ -207,7 +352,8 @@ TEST_F(IResearchViewNodeTest, construct) { EXPECT_TRUE(node.empty()); // view has no links EXPECT_TRUE(node.collections().empty()); // view has no links EXPECT_TRUE(node.shards().empty()); - EXPECT_TRUE(!node.sort()); // primary sort is not set by default + EXPECT_TRUE(!node.sort().first); // primary sort is not set by default + EXPECT_EQ(0, node.sort().second); // primary sort is not set by default EXPECT_TRUE(arangodb::aql::ExecutionNode::ENUMERATE_IRESEARCH_VIEW == node.getType()); EXPECT_TRUE(&outVariable == &node.outVariable()); @@ -445,7 +591,88 @@ TEST_F(IResearchViewNodeTest, constructFromVPackSingleServer) { EXPECT_TRUE(node.empty()); // view has no links EXPECT_TRUE(node.collections().empty()); // view has no links EXPECT_TRUE(node.shards().empty()); - EXPECT_TRUE(!node.sort()); + EXPECT_TRUE(!node.sort().first); // primary sort is not set by default + EXPECT_EQ(0, node.sort().second); // primary sort is not set by default + + EXPECT_TRUE(arangodb::aql::ExecutionNode::ENUMERATE_IRESEARCH_VIEW == node.getType()); + EXPECT_TRUE(outVariable.id == node.outVariable().id); + EXPECT_TRUE(outVariable.name == node.outVariable().name); + EXPECT_TRUE(query.plan() == node.plan()); + EXPECT_TRUE(42 == node.id()); + EXPECT_TRUE(logicalView == node.view()); + EXPECT_TRUE(node.scorers().empty()); + EXPECT_TRUE(!node.volatility().first); // filter volatility + EXPECT_TRUE(!node.volatility().second); // sort volatility + arangodb::HashSet usedHere; + node.getVariablesUsedHere(usedHere); + EXPECT_TRUE(usedHere.empty()); + auto const setHere = node.getVariablesSetHere(); + EXPECT_TRUE(1 == setHere.size()); + EXPECT_TRUE(outVariable.id == setHere[0]->id); + EXPECT_TRUE(outVariable.name == setHere[0]->name); + EXPECT_TRUE(false == node.options().forceSync); + + EXPECT_TRUE(0. == node.getCost().estimatedCost); // no dependencies + EXPECT_TRUE(0 == node.getCost().estimatedNrItems); // no dependencies + } + + // no options, ignore 'primarySortBuckets' + { + auto json = arangodb::velocypack::Parser::fromJson( + "{ \"id\":42, \"depth\":0, \"totalNrRegs\":0, \"varInfoList\":[], " + "\"nrRegs\":[], \"nrRegsHere\":[], \"regsToClear\":[], " + "\"varsUsedLater\":[], \"varsValid\":[], \"outVariable\": { " + "\"name\":\"variable\", \"id\":0 }, \"viewId\": \"" + + std::to_string(logicalView->id()) + "\", \"primarySort\": [], \"primarySortBuckets\": false }"); + + arangodb::iresearch::IResearchViewNode node(*query.plan(), // plan + json->slice()); + + EXPECT_TRUE(node.empty()); // view has no links + EXPECT_TRUE(node.collections().empty()); // view has no links + EXPECT_TRUE(node.shards().empty()); + EXPECT_TRUE(!node.sort().first); // primary sort is not set by default + EXPECT_EQ(0, node.sort().second); // primary sort is not set by default + + EXPECT_TRUE(arangodb::aql::ExecutionNode::ENUMERATE_IRESEARCH_VIEW == node.getType()); + EXPECT_TRUE(outVariable.id == node.outVariable().id); + EXPECT_TRUE(outVariable.name == node.outVariable().name); + EXPECT_TRUE(query.plan() == node.plan()); + EXPECT_TRUE(42 == node.id()); + EXPECT_TRUE(logicalView == node.view()); + EXPECT_TRUE(node.scorers().empty()); + EXPECT_TRUE(!node.volatility().first); // filter volatility + EXPECT_TRUE(!node.volatility().second); // sort volatility + arangodb::HashSet usedHere; + node.getVariablesUsedHere(usedHere); + EXPECT_TRUE(usedHere.empty()); + auto const setHere = node.getVariablesSetHere(); + EXPECT_TRUE(1 == setHere.size()); + EXPECT_TRUE(outVariable.id == setHere[0]->id); + EXPECT_TRUE(outVariable.name == setHere[0]->name); + EXPECT_TRUE(false == node.options().forceSync); + + EXPECT_TRUE(0. == node.getCost().estimatedCost); // no dependencies + EXPECT_TRUE(0 == node.getCost().estimatedNrItems); // no dependencies + } + + // no options + { + auto json = arangodb::velocypack::Parser::fromJson( + "{ \"id\":42, \"depth\":0, \"totalNrRegs\":0, \"varInfoList\":[], " + "\"nrRegs\":[], \"nrRegsHere\":[], \"regsToClear\":[], " + "\"varsUsedLater\":[], \"varsValid\":[], \"outVariable\": { " + "\"name\":\"variable\", \"id\":0 }, \"viewId\": \"" + + std::to_string(logicalView->id()) + "\", \"primarySort\": [], \"primarySortBuckets\": 42 }"); + + arangodb::iresearch::IResearchViewNode node(*query.plan(), // plan + json->slice()); + + EXPECT_TRUE(node.empty()); // view has no links + EXPECT_TRUE(node.collections().empty()); // view has no links + EXPECT_TRUE(node.shards().empty()); + EXPECT_TRUE(!node.sort().first); // primary sort is not set by default + EXPECT_EQ(0, node.sort().second); // primary sort is not set by default EXPECT_TRUE(arangodb::aql::ExecutionNode::ENUMERATE_IRESEARCH_VIEW == node.getType()); EXPECT_TRUE(outVariable.id == node.outVariable().id); @@ -485,7 +712,8 @@ TEST_F(IResearchViewNodeTest, constructFromVPackSingleServer) { EXPECT_TRUE(node.empty()); // view has no links EXPECT_TRUE(node.collections().empty()); // view has no links EXPECT_TRUE(node.shards().empty()); - EXPECT_TRUE(!node.sort()); + EXPECT_TRUE(!node.sort().first); // primary sort is not set by default + EXPECT_EQ(0, node.sort().second); // primary sort is not set by default EXPECT_TRUE(arangodb::aql::ExecutionNode::ENUMERATE_IRESEARCH_VIEW == node.getType()); EXPECT_TRUE(outVariable.id == node.outVariable().id); @@ -528,7 +756,8 @@ TEST_F(IResearchViewNodeTest, constructFromVPackSingleServer) { EXPECT_TRUE(node.empty()); // view has no links EXPECT_TRUE(node.collections().empty()); // view has no links EXPECT_TRUE(node.shards().empty()); - EXPECT_TRUE(!node.sort()); + EXPECT_TRUE(!node.sort().first); // primary sort is not set by default + EXPECT_EQ(0, node.sort().second); // primary sort is not set by default EXPECT_TRUE(arangodb::aql::ExecutionNode::ENUMERATE_IRESEARCH_VIEW == node.getType()); EXPECT_TRUE(outVariable.id == node.outVariable().id); @@ -570,7 +799,8 @@ TEST_F(IResearchViewNodeTest, constructFromVPackSingleServer) { EXPECT_TRUE(node.empty()); // view has no links EXPECT_TRUE(node.collections().empty()); // view has no links EXPECT_TRUE(node.shards().empty()); - EXPECT_TRUE(!node.sort()); + EXPECT_TRUE(!node.sort().first); // primary sort is not set by default + EXPECT_EQ(0, node.sort().second); // primary sort is not set by default EXPECT_TRUE(arangodb::aql::ExecutionNode::ENUMERATE_IRESEARCH_VIEW == node.getType()); EXPECT_TRUE(outVariable.id == node.outVariable().id); @@ -1005,7 +1235,7 @@ TEST_F(IResearchViewNodeTest, clone) { nullptr, // no options {} // no scorers ); - node.sort(&sort); + node.sort(&sort, 0); EXPECT_TRUE(node.empty()); // view has no links EXPECT_TRUE(node.collections().empty()); // view has no links @@ -1310,7 +1540,7 @@ TEST_F(IResearchViewNodeTest, serializeSortedView) { nullptr, // no options {} // no sort condition ); - node.sort(&viewImpl.primarySort()); + node.sort(&viewImpl.primarySort(), 1); EXPECT_TRUE(node.empty()); // view has no links EXPECT_TRUE(node.collections().empty()); // view has no links @@ -1346,6 +1576,7 @@ TEST_F(IResearchViewNodeTest, serializeSortedView) { EXPECT_TRUE(node.volatility() == deserialized.volatility()); EXPECT_TRUE(node.options().forceSync == deserialized.options().forceSync); EXPECT_TRUE(node.sort() == deserialized.sort()); + EXPECT_EQ(1, node.sort().second); EXPECT_TRUE(node.getCost() == deserialized.getCost()); } @@ -1371,6 +1602,7 @@ TEST_F(IResearchViewNodeTest, serializeSortedView) { EXPECT_TRUE(node.volatility() == deserialized.volatility()); EXPECT_TRUE(node.options().forceSync == deserialized.options().forceSync); EXPECT_TRUE(node.sort() == deserialized.sort()); + EXPECT_EQ(1, node.sort().second); EXPECT_TRUE(node.getCost() == deserialized.getCost()); } @@ -1432,6 +1664,7 @@ TEST_F(IResearchViewNodeTest, serializeSortedView) { EXPECT_TRUE(node.volatility() == deserialized.volatility()); EXPECT_TRUE(node.options().forceSync == deserialized.options().forceSync); EXPECT_TRUE(node.sort() == deserialized.sort()); + EXPECT_EQ(0, node.sort().second); EXPECT_TRUE(node.getCost() == deserialized.getCost()); } @@ -1457,6 +1690,7 @@ TEST_F(IResearchViewNodeTest, serializeSortedView) { EXPECT_TRUE(node.volatility() == deserialized.volatility()); EXPECT_TRUE(node.options().forceSync == deserialized.options().forceSync); EXPECT_TRUE(node.sort() == deserialized.sort()); + EXPECT_EQ(0, node.sort().second); EXPECT_TRUE(node.getCost() == deserialized.getCost()); } diff --git a/tests/IResearch/IResearchViewSorted-test.cpp b/tests/IResearch/IResearchViewSorted-test.cpp index 6dd8ebc002..549a5eac3b 100644 --- a/tests/IResearch/IResearchViewSorted-test.cpp +++ b/tests/IResearch/IResearchViewSorted-test.cpp @@ -352,7 +352,8 @@ TEST_F(IResearchViewSortedTest, SingleField) { arangodb::aql::ExecutionNode::castTo( nodes.front()); ASSERT_TRUE(viewNode); - EXPECT_TRUE(viewNode->sort()); + EXPECT_TRUE(viewNode->sort().first); + EXPECT_EQ(1, viewNode->sort().second); // check query execution auto queryResult = arangodb::tests::executeQuery(vocbase, query); @@ -402,7 +403,8 @@ TEST_F(IResearchViewSortedTest, SingleField) { arangodb::aql::ExecutionNode::castTo( nodes.front()); ASSERT_TRUE(viewNode); - EXPECT_TRUE(viewNode->sort()); + EXPECT_TRUE(viewNode->sort().first); + EXPECT_EQ(1, viewNode->sort().second); // check query execution std::vector expectedDocs{ @@ -461,7 +463,8 @@ TEST_F(IResearchViewSortedTest, SingleField) { arangodb::aql::ExecutionNode::castTo( nodes.front()); ASSERT_TRUE(viewNode); - EXPECT_TRUE(viewNode->sort()); + EXPECT_TRUE(viewNode->sort().first); + EXPECT_EQ(1, viewNode->sort().second); // check query execution std::vector expectedDocs{ @@ -639,7 +642,8 @@ TEST_F(IResearchViewSortedTest, MultipleFields) { arangodb::aql::ExecutionNode::castTo( nodes.front()); ASSERT_TRUE(viewNode); - EXPECT_TRUE(viewNode->sort()); + EXPECT_TRUE(viewNode->sort().first); + EXPECT_EQ(4, viewNode->sort().second); // check query execution auto queryResult = arangodb::tests::executeQuery(vocbase, query); @@ -688,104 +692,8 @@ TEST_F(IResearchViewSortedTest, MultipleFields) { arangodb::aql::ExecutionNode::castTo( nodes.front()); ASSERT_TRUE(viewNode); - EXPECT_TRUE(viewNode->sort()); - - // check query execution - auto queryResult = arangodb::tests::executeQuery(vocbase, query); - ASSERT_TRUE(queryResult.result.ok()); - - auto result = queryResult.data->slice(); - EXPECT_TRUE(result.isArray()); - - arangodb::velocypack::ArrayIterator resultIt(result); - EXPECT_TRUE(insertedDocs.size() == resultIt.size()); - - auto expectedDoc = insertedDocs.rbegin(); - for (auto const actualDoc : resultIt) { - auto const resolved = actualDoc.resolveExternals(); - - EXPECT_TRUE(0 == arangodb::basics::VelocyPackHelper::compare( - arangodb::velocypack::Slice(expectedDoc->vpack()), resolved, true)); - ++expectedDoc; - } - EXPECT_TRUE(expectedDoc == insertedDocs.rend()); - } - - // return all - { - std::string const query = - "FOR d IN testView SORT d.same, d.same DESC RETURN d"; - - EXPECT_TRUE(arangodb::tests::assertRules( - vocbase, query, {arangodb::aql::OptimizerRule::handleArangoSearchViewsRule})); - - arangodb::SmallVector::allocator_type::arena_type a; - arangodb::SmallVector nodes{a}; - auto preparedQuery = arangodb::tests::prepareQuery(vocbase, query); - auto plan = preparedQuery->plan(); - ASSERT_TRUE(plan); - plan->findVarUsage(); - - // ensure sort node is optimized out - plan->findNodesOfType(nodes, arangodb::aql::ExecutionNode::SORT, true); - EXPECT_TRUE(nodes.empty()); - - // check sort is set - plan->findNodesOfType(nodes, arangodb::aql::ExecutionNode::ENUMERATE_IRESEARCH_VIEW, true); - EXPECT_TRUE(1 == nodes.size()); - auto* viewNode = - arangodb::aql::ExecutionNode::castTo( - nodes.front()); - ASSERT_TRUE(viewNode); - EXPECT_TRUE(viewNode->sort()); - - // check query execution - auto queryResult = arangodb::tests::executeQuery(vocbase, query); - ASSERT_TRUE(queryResult.result.ok()); - - auto result = queryResult.data->slice(); - EXPECT_TRUE(result.isArray()); - - arangodb::velocypack::ArrayIterator resultIt(result); - EXPECT_TRUE(insertedDocs.size() == resultIt.size()); - - auto expectedDoc = insertedDocs.rbegin(); - for (auto const actualDoc : resultIt) { - auto const resolved = actualDoc.resolveExternals(); - - EXPECT_TRUE(0 == arangodb::basics::VelocyPackHelper::compare( - arangodb::velocypack::Slice(expectedDoc->vpack()), resolved, true)); - ++expectedDoc; - } - EXPECT_TRUE(expectedDoc == insertedDocs.rend()); - } - - // return all - { - std::string const query = "FOR d IN testView SORT d.same RETURN d"; - - EXPECT_TRUE(arangodb::tests::assertRules( - vocbase, query, {arangodb::aql::OptimizerRule::handleArangoSearchViewsRule})); - - arangodb::SmallVector::allocator_type::arena_type a; - arangodb::SmallVector nodes{a}; - auto preparedQuery = arangodb::tests::prepareQuery(vocbase, query); - auto plan = preparedQuery->plan(); - ASSERT_TRUE(plan); - plan->findVarUsage(); - - // ensure sort node is optimized out - plan->findNodesOfType(nodes, arangodb::aql::ExecutionNode::SORT, true); - EXPECT_TRUE(nodes.empty()); - - // check sort is set - plan->findNodesOfType(nodes, arangodb::aql::ExecutionNode::ENUMERATE_IRESEARCH_VIEW, true); - EXPECT_TRUE(1 == nodes.size()); - auto* viewNode = - arangodb::aql::ExecutionNode::castTo( - nodes.front()); - ASSERT_TRUE(viewNode); - EXPECT_TRUE(viewNode->sort()); + EXPECT_TRUE(viewNode->sort().first); + EXPECT_EQ(3, viewNode->sort().second); // check query execution auto queryResult = arangodb::tests::executeQuery(vocbase, query); @@ -835,7 +743,8 @@ TEST_F(IResearchViewSortedTest, MultipleFields) { arangodb::aql::ExecutionNode::castTo( nodes.front()); ASSERT_TRUE(viewNode); - EXPECT_TRUE(viewNode->sort()); + EXPECT_TRUE(viewNode->sort().first); + EXPECT_EQ(3, viewNode->sort().second); // check query execution std::vector expectedDocs{ @@ -894,7 +803,8 @@ TEST_F(IResearchViewSortedTest, MultipleFields) { arangodb::aql::ExecutionNode::castTo( nodes.front()); ASSERT_TRUE(viewNode); - EXPECT_TRUE(viewNode->sort()); + EXPECT_TRUE(viewNode->sort().first); + EXPECT_EQ(4, viewNode->sort().second); // check query execution std::vector expectedDocs{ @@ -951,7 +861,8 @@ TEST_F(IResearchViewSortedTest, MultipleFields) { arangodb::aql::ExecutionNode::castTo( nodes.front()); ASSERT_TRUE(viewNode); - EXPECT_TRUE(viewNode->sort()); + EXPECT_TRUE(viewNode->sort().first); + EXPECT_EQ(2, viewNode->sort().second); // check query execution std::vector expectedDocs{};