From 930b09cd93325e3d7f4d8787351d02334bb310c6 Mon Sep 17 00:00:00 2001 From: Andrey Abramov Date: Wed, 27 Mar 2019 18:33:16 +0300 Subject: [PATCH] [3.4] bug-fix/issue-#8294 (#8430) (#8585) * [3.4] bug-fix/issue-#8294 (#8430) * fix invalid optimizations for multi-valued attributes * fix broken optimizations for multivalued attributes * adjust tests * add `IN_RANGE` function * add tests * add some shallow integration tests * fix optimization for IN operator # Conflicts: # arangod/IResearch/IResearchFeature.cpp # arangod/IResearch/IResearchViewOptimizerRules.cpp # tests/IResearch/IResearchFilterBoolean-test.cpp * fix compilation errors * fix another compilation issue * address compilation errors * fix tests --- arangod/Aql/Condition.cpp | 122 +- arangod/Aql/Condition.h | 8 +- arangod/IResearch/IResearchFeature.cpp | 16 +- arangod/IResearch/IResearchFilterFactory.cpp | 127 +- arangod/IResearch/IResearchView.h | 12 +- .../IResearch/IResearchViewOptimizerRules.cpp | 14 +- tests/CMakeLists.txt | 2 + .../IResearch/IResearchFilterBoolean-test.cpp | 282 +- .../IResearchFilterFunction-test.cpp | 193 +- .../IResearch/IResearchQueryInRange-test.cpp | 711 ++ .../IResearchQueryOptimization-test.cpp | 8501 +++++++++++++++++ tests/IResearch/IResearchQueryScorer-test.cpp | 30 +- tests/IResearch/common.cpp | 54 + tests/IResearch/common.h | 10 +- .../aql/aql-view-arangosearch-cluster.js | 18 + .../aql/aql-view-arangosearch-noncluster.js | 18 + 16 files changed, 9960 insertions(+), 158 deletions(-) create mode 100644 tests/IResearch/IResearchQueryInRange-test.cpp create mode 100644 tests/IResearch/IResearchQueryOptimization-test.cpp diff --git a/arangod/Aql/Condition.cpp b/arangod/Aql/Condition.cpp index ad2a4bbb32..6e0ef529c4 100644 --- a/arangod/Aql/Condition.cpp +++ b/arangod/Aql/Condition.cpp @@ -113,8 +113,10 @@ struct PermutationState { size_t const n; }; -} // namespace +//------------------------------------------------------------------------ +// Rules for single-valued variables +//------------------------------------------------------------------------ // | | a == y | a != y | a < y | a <= y | a >= y | a > y // -------|------------------|--------|--------|--------|--------|-------- // x < y | | IMP | OIS | OIS | OIS | IMP | IMP @@ -140,6 +142,7 @@ struct PermutationState { // x < y | | SIO | DIJ | DIJ | DIJ | SIO | SIO // x == y | a > x | IMP | OIS | IMP | IMP | OIS | OIS // x > y | | IMP | OIS | IMP | IMP | OIS | OIS +//------------------------------------------------------------------------ // the 7th column is here as fallback if the operation is not in the table // above. // IMP -> IMPOSSIBLE -> empty result -> the complete AND set of conditions can @@ -155,7 +158,7 @@ struct PermutationState { // larger than that of B // -> A can be dropped. -ConditionPartCompareResult const ConditionPart::ResultsTable[3][7][7] = { +ConditionPartCompareResult const ResultsTable[3][7][7] = { {// X < Y {IMPOSSIBLE, OTHER_CONTAINED_IN_SELF, OTHER_CONTAINED_IN_SELF, OTHER_CONTAINED_IN_SELF, IMPOSSIBLE, IMPOSSIBLE, DISJOINT}, @@ -199,6 +202,96 @@ ConditionPartCompareResult const ConditionPart::ResultsTable[3][7][7] = { OTHER_CONTAINED_IN_SELF, OTHER_CONTAINED_IN_SELF, DISJOINT}, {DISJOINT, DISJOINT, DISJOINT, DISJOINT, DISJOINT, DISJOINT, DISJOINT}}}; +//------------------------------------------------------------------------ +// Rules for multi-valued variables +//------------------------------------------------------------------------ +// | | a == y | a != y | a < y | a <= y | a >= y | a > y +// -------|------------------|--------|--------|--------|--------|-------- +// x < y | | DIJ | DIJ | OIS | OIS | DIJ | DIJ +// x == y | a == x | OIS | IMP | DIJ | OIS | OIS | DIJ +// x > y | | DIJ | DIJ | DIJ | DIJ | OIS | OIS +// -------|------------------|--------|--------|--------|--------|-------- +// x < y | | DIJ | DIJ | DIJ | DIJ | DIJ | DIJ +// x == y | a != x | IMP | OIS | DIJ | DIJ | DIJ | DIJ +// x > y | | DIJ | DIJ | DIJ | DIJ | DIJ | DIJ +// -------|------------------|--------|--------|--------|--------|-------- +// x < y | | DIJ | DIJ | OIS | OIS | DIJ | DIJ +// x == y | a < x | DIJ | DIJ | OIS | OIS | DIJ | DIJ +// x > y | | SIO | DIJ | SIO | SIO | DIJ | DIJ +// -------|------------------|--------|--------|--------|--------|-------- +// x < y | | DIJ | DIJ | OIS | OIS | DIJ | DIJ +// x == y | a <= x | SIO | DIJ | SIO | OIS | DIJ | DIJ +// x > y | | SIO | DIJ | SIO | SIO | DIJ | DIJ +// -------|------------------|--------|--------|--------|--------|-------- +// x < y | | SIO | DIJ | DIJ | DIJ | SIO | SIO +// x == y | a >= x | SIO | DIJ | DIJ | DIJ | OIS | SIO +// x > y | | DIJ | DIJ | DIJ | DIJ | OIS | OIS +// -------|------------------|--------|--------|--------|--------|-------- +// x < y | | SIO | DIJ | DIJ | DIJ | SIO | SIO +// x == y | a > x | DIJ | DIJ | DIJ | DIJ | OIS | OIS +// x > y | | DIJ | DIJ | DIJ | DIJ | OIS | OIS +//------------------------------------------------------------------------ +// the 7th column is here as fallback if the operation is not in the table +// above. +// IMP -> IMPOSSIBLE -> empty result -> the complete AND set of conditions can +// be dropped. +// CEQ -> CONVERT_EQUAL -> both conditions can be combined to a equals x. +// DIJ -> DISJOINT -> neither condition is a consequence of the other -> both +// have to stay in place. +// SIO -> SELF_CONTAINED_IN_OTHER -> the left condition is a consequence of the +// right condition +// OIS -> OTHER_CONTAINED_IN_SELF -> the right condition is a consequence of the +// left condition +// If a condition (A) is a consequence of another (B), the solution set of A is +// larger than that of B +// -> A can be dropped. + +ConditionPartCompareResult const ResultsTableMultiValued[3][7][7] = { + {// X < Y + {DISJOINT, DISJOINT, OTHER_CONTAINED_IN_SELF, + OTHER_CONTAINED_IN_SELF, DISJOINT, DISJOINT, DISJOINT}, + {DISJOINT, DISJOINT, DISJOINT, DISJOINT, + DISJOINT, DISJOINT, DISJOINT}, + {DISJOINT, DISJOINT, OTHER_CONTAINED_IN_SELF, + OTHER_CONTAINED_IN_SELF, DISJOINT, DISJOINT, DISJOINT}, + {DISJOINT, DISJOINT, OTHER_CONTAINED_IN_SELF, + OTHER_CONTAINED_IN_SELF, DISJOINT, DISJOINT, DISJOINT}, + {SELF_CONTAINED_IN_OTHER, DISJOINT, DISJOINT, DISJOINT, + SELF_CONTAINED_IN_OTHER, SELF_CONTAINED_IN_OTHER, DISJOINT}, + {SELF_CONTAINED_IN_OTHER, DISJOINT, DISJOINT, DISJOINT, + SELF_CONTAINED_IN_OTHER, SELF_CONTAINED_IN_OTHER, DISJOINT}, + {DISJOINT, DISJOINT, DISJOINT, DISJOINT, DISJOINT, DISJOINT, DISJOINT}}, + {// X == Y + {OTHER_CONTAINED_IN_SELF, IMPOSSIBLE, DISJOINT, OTHER_CONTAINED_IN_SELF, + OTHER_CONTAINED_IN_SELF, DISJOINT, DISJOINT}, + {IMPOSSIBLE, OTHER_CONTAINED_IN_SELF, DISJOINT, DISJOINT, + DISJOINT, DISJOINT, DISJOINT}, + {DISJOINT, DISJOINT, OTHER_CONTAINED_IN_SELF, + OTHER_CONTAINED_IN_SELF, DISJOINT, DISJOINT, DISJOINT}, + {SELF_CONTAINED_IN_OTHER, DISJOINT, SELF_CONTAINED_IN_OTHER, + OTHER_CONTAINED_IN_SELF, DISJOINT, DISJOINT, DISJOINT}, + {SELF_CONTAINED_IN_OTHER, DISJOINT, DISJOINT, DISJOINT, + OTHER_CONTAINED_IN_SELF, SELF_CONTAINED_IN_OTHER, DISJOINT}, + {DISJOINT, DISJOINT, DISJOINT, DISJOINT, + OTHER_CONTAINED_IN_SELF, OTHER_CONTAINED_IN_SELF, DISJOINT}, + {DISJOINT, DISJOINT, DISJOINT, DISJOINT, DISJOINT, DISJOINT, DISJOINT}}, + {// X > Y + {DISJOINT, DISJOINT, DISJOINT, DISJOINT, + OTHER_CONTAINED_IN_SELF, OTHER_CONTAINED_IN_SELF, DISJOINT}, + {DISJOINT, DISJOINT, DISJOINT, + DISJOINT, DISJOINT, DISJOINT, DISJOINT}, + {SELF_CONTAINED_IN_OTHER, DISJOINT, SELF_CONTAINED_IN_OTHER, + SELF_CONTAINED_IN_OTHER, DISJOINT, DISJOINT, DISJOINT}, + {SELF_CONTAINED_IN_OTHER, DISJOINT, SELF_CONTAINED_IN_OTHER, + SELF_CONTAINED_IN_OTHER, DISJOINT, DISJOINT, DISJOINT}, + {DISJOINT, DISJOINT, DISJOINT, DISJOINT, + OTHER_CONTAINED_IN_SELF, OTHER_CONTAINED_IN_SELF, DISJOINT}, + {DISJOINT, DISJOINT, DISJOINT, DISJOINT, + OTHER_CONTAINED_IN_SELF, OTHER_CONTAINED_IN_SELF, DISJOINT}, + {DISJOINT, DISJOINT, DISJOINT, DISJOINT, DISJOINT, DISJOINT, DISJOINT}}}; + +} // namespace + ConditionPart::ConditionPart(Variable const* variable, std::string const& attributeName, AstNode const* operatorNode, AttributeSideType side, void* data) : variable(variable), @@ -271,7 +364,7 @@ bool ConditionPart::isCoveredBy(ConditionPart const& other, bool isReversed) con auto w = other.valueNode->getMemberUnchecked(j); ConditionPartCompareResult res = - ConditionPart::ResultsTable[CompareAstNodes(v, w, true) + 1][0][0]; + ResultsTable[CompareAstNodes(v, w, true) + 1][0][0]; if (res != CompareResult::OTHER_CONTAINED_IN_SELF && res != CompareResult::CONVERT_EQUAL && res != CompareResult::IMPOSSIBLE) { @@ -333,8 +426,8 @@ bool ConditionPart::isCoveredBy(ConditionPart const& other, bool isReversed) con // Results are -1, 0, 1, move to 0, 1, 2 for the lookup: ConditionPartCompareResult res = - ConditionPart::ResultsTable[CompareAstNodes(other.valueNode, valueNode, true) + 1] - [other.whichCompareOperation()][whichCompareOperation()]; + ResultsTable[CompareAstNodes(other.valueNode, valueNode, true) + 1] + [other.whichCompareOperation()][whichCompareOperation()]; if (res == CompareResult::OTHER_CONTAINED_IN_SELF || res == CompareResult::CONVERT_EQUAL || res == CompareResult::IMPOSSIBLE) { @@ -521,7 +614,7 @@ std::vector> Condition::getConstAtt /// @brief normalize the condition /// this will convert the condition into its disjunctive normal form -void Condition::normalize(ExecutionPlan* plan) { +void Condition::normalize(ExecutionPlan* plan, bool multivalued /*= false*/) { if (_isNormalized) { // already normalized return; @@ -531,7 +624,7 @@ void Condition::normalize(ExecutionPlan* plan) { _root = transformNodePostorder(_root); _root = fixRoot(_root, 0); - optimize(plan); + optimize(plan, multivalued); #ifdef ARANGODB_ENABLE_MAINTAINER_MODE if (_root != nullptr) { @@ -802,7 +895,7 @@ bool Condition::removeInvalidVariables(arangodb::HashSet const& } /// @brief optimize the condition expression tree -void Condition::optimize(ExecutionPlan* plan) { +void Condition::optimize(ExecutionPlan* plan, bool multivalued) { if (_root == nullptr) { return; } @@ -822,6 +915,10 @@ void Condition::optimize(ExecutionPlan* plan) { size_t n = _root->numMembers(); size_t r = 0; + const auto* resultsTable = multivalued + ? ResultsTableMultiValued + : ResultsTable; + while (r < n) { // foreach OR-Node bool retry = false; auto oldAnd = _root->getMemberUnchecked(r); @@ -981,7 +1078,8 @@ void Condition::optimize(ExecutionPlan* plan) { // IN-merging if (leftNode->type == NODE_TYPE_OPERATOR_BINARY_IN && - leftNode->getMemberUnchecked(1)->isConstant()) { + leftNode->getMemberUnchecked(1)->isConstant() && + !multivalued) { TRI_ASSERT(leftNode->numMembers() == 2); if (rightNode->type == NODE_TYPE_OPERATOR_BINARY_IN && @@ -1007,8 +1105,8 @@ void Condition::optimize(ExecutionPlan* plan) { for (size_t k = 0; k < values->numMembers(); ++k) { auto value = values->getMemberUnchecked(k); ConditionPartCompareResult res = - ConditionPart::ResultsTable[CompareAstNodes(value, other.valueNode, true) + 1][0 /*NODE_TYPE_OPERATOR_BINARY_EQ*/] - [other.whichCompareOperation()]; + ResultsTable[CompareAstNodes(value, other.valueNode, true) + 1][0 /*NODE_TYPE_OPERATOR_BINARY_EQ*/] + [other.whichCompareOperation()]; bool const keep = (res == CompareResult::OTHER_CONTAINED_IN_SELF || res == CompareResult::CONVERT_EQUAL); @@ -1036,7 +1134,7 @@ void Condition::optimize(ExecutionPlan* plan) { // end of IN-merging // Results are -1, 0, 1, move to 0, 1, 2 for the lookup: - ConditionPartCompareResult res = ConditionPart::ResultsTable + ConditionPartCompareResult res = resultsTable [CompareAstNodes(current.valueNode, other.valueNode, true) + 1] [current.whichCompareOperation()][other.whichCompareOperation()]; diff --git a/arangod/Aql/Condition.h b/arangod/Aql/Condition.h index afacd9e0f9..0a39bcbdcd 100644 --- a/arangod/Aql/Condition.h +++ b/arangod/Aql/Condition.h @@ -56,8 +56,6 @@ enum ConditionPartCompareResult { enum AttributeSideType { ATTRIBUTE_LEFT, ATTRIBUTE_RIGHT }; struct ConditionPart { - static ConditionPartCompareResult const ResultsTable[3][7][7]; - ConditionPart() = delete; ConditionPart(Variable const*, std::string const&, AstNode const*, @@ -215,7 +213,9 @@ class Condition { /// @brief normalize the condition /// this will convert the condition into its disjunctive normal form - void normalize(ExecutionPlan*); + /// @param mutlivalued attributes may have more than one value + /// (ArangoSearch view case) + void normalize(ExecutionPlan*, bool multivalued = false); /// @brief normalize the condition /// this will convert the condition into its disjunctive normal form @@ -251,7 +251,7 @@ class Condition { bool sortOrs(Variable const*, std::vector&); /// @brief optimize the condition expression tree - void optimize(ExecutionPlan*); + void optimize(ExecutionPlan*, bool multivalued); /// @brief registers an attribute access for a particular (collection) /// variable diff --git a/arangod/IResearch/IResearchFeature.cpp b/arangod/IResearch/IResearchFeature.cpp index 23c348ac6c..096f1e31ab 100644 --- a/arangod/IResearch/IResearchFeature.cpp +++ b/arangod/IResearch/IResearchFeature.cpp @@ -137,7 +137,7 @@ arangodb::aql::AqlValue dummyFilterFunc(arangodb::aql::ExpressionContext*, arangodb::SmallVector const&) { THROW_ARANGO_EXCEPTION_MESSAGE( TRI_ERROR_NOT_IMPLEMENTED, - "ArangoSearch filter functions EXISTS, STARTS_WITH, PHRASE, MIN_MATCH, " + "ArangoSearch filter functions EXISTS, STARTS_WITH, IN_RANGE, PHRASE, MIN_MATCH, " "BOOST and ANALYZER " " are designed to be used only within a corresponding SEARCH statement " "of ArangoSearch view." @@ -346,15 +346,11 @@ void registerFilters(arangodb::aql::AqlFunctionFeature& functions) { arangodb::aql::Function::makeFlags(arangodb::aql::Function::Flags::Deterministic, arangodb::aql::Function::Flags::Cacheable, arangodb::aql::Function::Flags::CanRunOnDBServer); - addFunction(functions, {"EXISTS", ".|.,.", flags, &dummyFilterFunc}); // (attribute, [ - // "analyzer"|"type"|"string"|"numeric"|"bool"|"null" - // ]) + addFunction(functions, {"EXISTS", ".|.,.", flags, &dummyFilterFunc}); // (attribute, [ // "analyzer"|"type"|"string"|"numeric"|"bool"|"null" // ]) addFunction(functions, {"STARTS_WITH", ".,.|.", flags, &dummyFilterFunc}); // (attribute, prefix, scoring-limit) - addFunction(functions, {"PHRASE", ".,.|.+", flags, - &dummyFilterFunc}); // (attribute, input [, offset, - // input... ] [, analyzer]) - addFunction(functions, {"MIN_MATCH", ".,.|.+", flags, &dummyFilterFunc}); // (filter expression [, filter expression, - // ... ], min match count) + addFunction(functions, {"PHRASE", ".,.|.+", flags, &dummyFilterFunc}); // (attribute, input [, offset, input... ] [, analyzer]) + addFunction(functions, {"IN_RANGE", ".,.,.,.,.", flags, &dummyFilterFunc}); // (attribute, lower, upper, include lower, include upper) + addFunction(functions, {"MIN_MATCH", ".,.|.+", flags, &dummyFilterFunc}); // (filter expression [, filter expression, ... ], min match count) addFunction(functions, {"BOOST", ".,.", flags, &dummyFilterFunc}); // (filter expression, boost) addFunction(functions, {"ANALYZER", ".,.", flags, &dummyFilterFunc}); // (filter expression, analyzer) } @@ -1097,4 +1093,4 @@ void IResearchFeature::validateOptions(std::shared_ptr(irs::null_token_stream::value_null()); range.include(minInclude); - ; range.term(irs::null_token_stream::value_null()); range.include(maxInclude); - ; } return true; @@ -981,6 +987,7 @@ bool fromNegation(irs::boolean_filter* filter, QueryContext const& ctx, return ::filter(filter, ctx, subFilterCtx, *member); } +/* bool rangeFromBinaryAnd(irs::boolean_filter* filter, QueryContext const& ctx, FilterContext const& filterCtx, arangodb::aql::AstNode const& node) { @@ -1029,6 +1036,7 @@ bool rangeFromBinaryAnd(irs::boolean_filter* filter, QueryContext const& ctx, // unable to create range return false; } +*/ template bool fromGroup(irs::boolean_filter* filter, QueryContext const& ctx, @@ -1048,12 +1056,6 @@ bool fromGroup(irs::boolean_filter* filter, QueryContext const& ctx, // Note: cannot optimize for single member in AND/OR since 'a OR NOT b' // translates to 'a OR (OR NOT b)' - if (std::is_same::value && 2 == n && - rangeFromBinaryAnd(filter, ctx, filterCtx, node)) { - // range case - return true; - } - if (filter) { filter = &filter->add(); filter->boost(filterCtx.boost); @@ -1821,6 +1823,103 @@ bool fromFuncStartsWith(irs::boolean_filter* filter, QueryContext const& ctx, return true; } +// IN_RANGE(, , , , ) +bool fromFuncInRange(irs::boolean_filter* filter, QueryContext const& ctx, + FilterContext const& filterCtx, + arangodb::aql::AstNode const& args) { + if (!args.isDeterministic()) { + LOG_TOPIC("dff45", WARN, arangodb::iresearch::TOPIC) + << "Unable to handle non-deterministic arguments for 'IN_RANGE' " + "function"; + return false; // nondeterministic + } + + auto const argc = args.numMembers(); + + if (argc != 5) { + LOG_TOPIC("2f5a8", WARN, arangodb::iresearch::TOPIC) + << "'IN_RANGE' AQL function: Invalid number of arguments passed " + "(should be 5)"; + return false; + } + + // 1st argument defines a field + auto const* field = + arangodb::iresearch::checkAttributeAccess(args.getMemberUnchecked(0), *ctx.ref); + + if (!field) { + LOG_TOPIC("7c56a", WARN, arangodb::iresearch::TOPIC) + << "'IN_RANGE' AQL function: Unable to parse 1st argument as an " + "attribute identifier"; + return false; + } + + ScopedAqlValue includeValue; + auto getInclusion = [&ctx, filter, &includeValue]( + arangodb::aql::AstNode const* arg, + bool& include, + irs::string_ref const& argName) -> bool { + if (!arg) { + LOG_TOPIC("8ec00", WARN, arangodb::iresearch::TOPIC) + << "'IN_RANGE' AQL function: " << argName << " argument is invalid"; + return false; + } + + includeValue.reset(*arg); + + if (filter || includeValue.isConstant()) { + if (!includeValue.execute(ctx)) { + LOG_TOPIC("32f3b", WARN, arangodb::iresearch::TOPIC) + << "'IN_RANGE' AQL function: Failed to evaluate " << argName << " argument"; + return false; + } + + if (arangodb::iresearch::SCOPED_VALUE_TYPE_BOOL != includeValue.type()) { + LOG_TOPIC("57a29", WARN, arangodb::iresearch::TOPIC) + << "'IN_RANGE' AQL function: " << argName << " argument has invalid type '" + << includeValue.type() << "' (boolean expected)"; + return false; + } + + include = includeValue.getBoolean(); + } + + return true; + }; + + // 2nd argument defines a lower boundary + auto const* lhsArg = args.getMemberUnchecked(1); + + if (!lhsArg) { + LOG_TOPIC("f1167", WARN, arangodb::iresearch::TOPIC) + << "'IN_RANGE' AQL function: 2nd argument is invalid"; + return false; + } + + // 3rd argument defines an upper boundary + auto const* rhsArg = args.getMemberUnchecked(2); + + if (!rhsArg) { + LOG_TOPIC("d5fe6", WARN, arangodb::iresearch::TOPIC) + << "'IN_RANGE' AQL function: 3rd argument is invalid"; + return false; + } + + // 4th argument defines inclusion of lower boundary + bool lhsInclude = false; + if (!getInclusion(args.getMemberUnchecked(3), lhsInclude, "4th")) { + return false; + } + + // 5th argument defines inclusion of upper boundary + bool rhsInclude = false; + if (!getInclusion(args.getMemberUnchecked(4), rhsInclude, "5th")) { + return false; + } + + return byRange(filter, *field, *lhsArg, lhsInclude, *rhsArg, rhsInclude, ctx, filterCtx); +} + std::map const FCallUserConvertionHandlers; bool fromFCallUser(irs::boolean_filter* filter, QueryContext const& ctx, @@ -1868,7 +1967,9 @@ bool fromFCallUser(irs::boolean_filter* filter, QueryContext const& ctx, std::map const FCallSystemConvertionHandlers{ {"PHRASE", fromFuncPhrase}, {"STARTS_WITH", fromFuncStartsWith}, {"EXISTS", fromFuncExists}, {"BOOST", fromFuncBoost}, - {"ANALYZER", fromFuncAnalyzer}, {"MIN_MATCH", fromFuncMinMatch}}; + {"ANALYZER", fromFuncAnalyzer}, {"MIN_MATCH", fromFuncMinMatch}, + {"IN_RANGE", fromFuncInRange} +}; bool fromFCall(irs::boolean_filter* filter, QueryContext const& ctx, FilterContext const& filterCtx, arangodb::aql::AstNode const& node) { @@ -1987,4 +2088,4 @@ namespace iresearch { // ----------------------------------------------------------------------------- // --SECTION-- END-OF-FILE -// ----------------------------------------------------------------------------- \ No newline at end of file +// ----------------------------------------------------------------------------- diff --git a/arangod/IResearch/IResearchView.h b/arangod/IResearch/IResearchView.h index a49ded087f..24136f4471 100644 --- a/arangod/IResearch/IResearchView.h +++ b/arangod/IResearch/IResearchView.h @@ -177,6 +177,11 @@ class IResearchView final: public arangodb::LogicalView { /////////////////////////////////////////////////////////////////////////////// bool visitCollections(CollectionVisitor const& visitor) const override; + ////////////////////////////////////////////////////////////////////////////// + /// @brief persist data store states for all known links to permanent storage + ////////////////////////////////////////////////////////////////////////////// + arangodb::Result commit(); + protected: ////////////////////////////////////////////////////////////////////////////// @@ -219,11 +224,6 @@ class IResearchView final: public arangodb::LogicalView { IResearchViewMeta&& meta // view meta ); - ////////////////////////////////////////////////////////////////////////////// - /// @brief persist data store states for all known links to permanent storage - ////////////////////////////////////////////////////////////////////////////// - arangodb::Result commit(); - ////////////////////////////////////////////////////////////////////////////// /// @brief called when a view's properties are updated (i.e. delta-modified) ////////////////////////////////////////////////////////////////////////////// @@ -241,4 +241,4 @@ class IResearchView final: public arangodb::LogicalView { } // iresearch } // arangodb -#endif \ No newline at end of file +#endif diff --git a/arangod/IResearch/IResearchViewOptimizerRules.cpp b/arangod/IResearch/IResearchViewOptimizerRules.cpp index f25ede5ecd..e775adf428 100644 --- a/arangod/IResearch/IResearchViewOptimizerRules.cpp +++ b/arangod/IResearch/IResearchViewOptimizerRules.cpp @@ -80,7 +80,7 @@ bool optimizeSearchCondition(IResearchViewNode& viewNode, Query& query, Executio if (!viewNode.filterConditionIsEmpty()) { searchCondition.andCombine(&viewNode.filterCondition()); - searchCondition.normalize(&plan); // normalize the condition + searchCondition.normalize(&plan, true); // normalize the condition if (searchCondition.isEmpty()) { // condition is always false @@ -102,11 +102,11 @@ bool optimizeSearchCondition(IResearchViewNode& viewNode, Query& query, Executio } // check filter condition - auto const conditionValid = - !searchCondition.root() || - FilterFactory::filter(nullptr, - { query.trx(), nullptr, nullptr, nullptr, &viewNode.outVariable() }, - *searchCondition.root()); + auto const conditionValid = !searchCondition.root() || FilterFactory::filter( + nullptr, + { query.trx(), nullptr, nullptr, nullptr, &viewNode.outVariable() }, + *searchCondition.root() + ); if (!conditionValid) { THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_QUERY_PARSE, @@ -308,4 +308,4 @@ void scatterViewInClusterRule(arangodb::aql::Optimizer* opt, // ----------------------------------------------------------------------------- // --SECTION-- END-OF-FILE -// ----------------------------------------------------------------------------- \ No newline at end of file +// ----------------------------------------------------------------------------- diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 32a1627218..d0fd20311b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -47,10 +47,12 @@ if (USE_IRESEARCH) IResearch/IResearchQueryNullTerm-test.cpp IResearch/IResearchQueryNumericTerm-test.cpp IResearch/IResearchQueryOr-test.cpp + IResearch/IResearchQueryOptimization-test.cpp IResearch/IResearchQueryPhrase-test.cpp IResearch/IResearchQueryScorer-test.cpp IResearch/IResearchQuerySelectAll-test.cpp IResearch/IResearchQueryStartsWith-test.cpp + IResearch/IResearchQueryInRange-test.cpp IResearch/IResearchQueryStringTerm-test.cpp IResearch/IResearchQueryTokens-test.cpp IResearch/IResearchQueryValue-test.cpp diff --git a/tests/IResearch/IResearchFilterBoolean-test.cpp b/tests/IResearch/IResearchFilterBoolean-test.cpp index de966183f1..0cbe911315 100644 --- a/tests/IResearch/IResearchFilterBoolean-test.cpp +++ b/tests/IResearch/IResearchFilterBoolean-test.cpp @@ -2160,9 +2160,12 @@ SECTION("BinaryAnd") { irs::numeric_token_stream maxTerm; maxTerm.reset(40.); irs::Or expected; - auto& range = expected.add(); - range.field(mangleNumeric("a.b.c")) - .include(false).insert(minTerm) + auto& root = expected.add(); + root.add() + .field(mangleNumeric("a.b.c")) + .include(false).insert(minTerm); + root.add() + .field(mangleNumeric("a.b.c")) .include(false).insert(maxTerm); assertFilterSuccess("FOR d IN collection FILTER d.a.b.c > 15 and d.a.b.c < 40 RETURN d", expected); @@ -2188,11 +2191,16 @@ SECTION("BinaryAnd") { irs::numeric_token_stream maxTerm; maxTerm.reset(40.); irs::Or expected; - auto& range = expected.add(); - range.boost(1.5); - range.field(mangleNumeric("a.b.c")) - .include(false).insert(minTerm) - .include(false).insert(maxTerm); + auto& root = expected.add(); + root.boost(1.5); + root.add() + .field(mangleNumeric("a.b.c")) + .include(false) + .insert(minTerm); + root.add() + .field(mangleNumeric("a.b.c")) + .include(false) + .insert(maxTerm); assertFilterSuccess("FOR d IN collection FILTER boost(d.a.b.c > 15 and d.a.b.c < 40, 1.5) RETURN d", expected); } @@ -2445,10 +2453,15 @@ SECTION("BinaryAnd") { irs::numeric_token_stream maxTerm; maxTerm.reset(40.); irs::Or expected; - auto& range = expected.add(); - range.field(mangleNumeric("a.b[42].c")) - .include(false).insert(minTerm) - .include(false).insert(maxTerm); + auto& root = expected.add(); + root.add() + .field(mangleNumeric("a.b[42].c")) + .include(false) + .insert(minTerm); + root.add() + .field(mangleNumeric("a.b[42].c")) + .include(false) + .insert(maxTerm); assertFilterSuccess("FOR d IN collection FILTER d.a.b[42].c > 15 and d.a.b[42].c < 40 RETURN d", expected); assertFilterSuccess("FOR d IN collection FILTER d['a'].b[42].c > 15 and d['a']['b'][42]['c'] < 40 RETURN d", expected); @@ -2473,10 +2486,15 @@ SECTION("BinaryAnd") { irs::numeric_token_stream maxTerm; maxTerm.reset(40.); irs::Or expected; - auto& range = expected.add(); - range.field(mangleNumeric("a.b.c")) - .include(true).insert(minTerm) - .include(false).insert(maxTerm); + auto& root = expected.add(); + root.add() + .field(mangleNumeric("a.b.c")) + .include(true) + .insert(minTerm); + root.add() + .field(mangleNumeric("a.b.c")) + .include(false) + .insert(maxTerm); assertFilterSuccess("FOR d IN collection FILTER d.a.b.c >= 15 and d.a.b.c < 40 RETURN d", expected); assertFilterSuccess("FOR d IN collection FILTER d.a['b']['c'] >= 15 and d['a']['b']['c'] < 40 RETURN d", expected); @@ -2497,10 +2515,15 @@ SECTION("BinaryAnd") { irs::numeric_token_stream maxTerm; maxTerm.reset(40.); irs::Or expected; - auto& range = expected.add(); - range.field(mangleNumeric("a.b.c")) - .include(true).insert(minTerm) - .include(true).insert(maxTerm); + auto& root = expected.add(); + root.add() + .field(mangleNumeric("a.b.c")) + .include(true) + .insert(minTerm); + root.add() + .field(mangleNumeric("a.b.c")) + .include(true) + .insert(maxTerm); assertFilterSuccess("FOR d IN collection FILTER d.a.b.c >= 15 and d.a.b.c <= 40 RETURN d", expected); assertFilterSuccess("FOR d IN collection FILTER d.a['b']['c'] >= 15 and d.a.b.c <= 40 RETURN d", expected); @@ -2612,10 +2635,15 @@ SECTION("BinaryAnd") { irs::numeric_token_stream maxTerm; maxTerm.reset(40.); irs::Or expected; - auto& range = expected.add(); - range.field(mangleNumeric("a.b.c")) - .include(false).insert(minTerm) - .include(true).insert(maxTerm); + auto& root = expected.add(); + root.add() + .field(mangleNumeric("a.b.c")) + .include(false) + .insert(minTerm); + root.add() + .field(mangleNumeric("a.b.c")) + .include(true) + .insert(maxTerm); assertFilterSuccess("FOR d IN collection FILTER d.a.b.c > 15 and d.a.b.c <= 40 RETURN d", expected); assertFilterSuccess("FOR d IN collection FILTER d['a'].b.c > 15 and d.a.b.c <= 40 RETURN d", expected); @@ -2733,10 +2761,15 @@ SECTION("BinaryAnd") { irs::numeric_token_stream maxTerm; maxTerm.reset(40.); irs::Or expected; - auto& range = expected.add(); - range.field(mangleNumeric("a.b.c.e[4].f[5].g[3].g.a")) - .include(false).insert(minTerm) - .include(true).insert(maxTerm); + auto& root = expected.add(); + root.add() + .field(mangleNumeric("a.b.c.e[4].f[5].g[3].g.a")) + .include(false) + .insert(minTerm); + root.add() + .field(mangleNumeric("a.b.c.e[4].f[5].g[3].g.a")) + .include(true) + .insert(maxTerm); assertFilterSuccess("LET a='a' LET c='c' LET offsetInt=4 LET offsetDbl=5.6 FOR d IN collection FILTER d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_FORWARD_('a')] > 15 && d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_FORWARD_('a')] <= 40 RETURN d", expected, &ctx); assertFilterSuccess("LET a='a' LET c='c' LET offsetInt=4 LET offsetDbl=5.6 FOR d IN collection FILTER 15 < d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_FORWARD_('a')] && 40 >= d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_FORWARD_('a')] RETURN d", expected, &ctx); @@ -2777,10 +2810,15 @@ SECTION("BinaryAnd") { // string range { irs::Or expected; - auto& range = expected.add(); - range.field(mangleStringIdentity("a.b.c")) - .include(false).term("15") - .include(false).term("40"); + auto& root = expected.add(); + root.add() + .field(mangleStringIdentity("a.b.c")) + .include(false) + .term("15"); + root.add() + .field(mangleStringIdentity("a.b.c")) + .include(false) + .term("40"); assertFilterSuccess("FOR d IN collection FILTER d.a.b.c > '15' and d.a.b.c < '40' RETURN d", expected); assertFilterSuccess("FOR d IN collection FILTER d['a']['b']['c'] > '15' and d.a.b.c < '40' RETURN d", expected); @@ -2795,10 +2833,15 @@ SECTION("BinaryAnd") { // string range { irs::Or expected; - auto& range = expected.add(); - range.field(mangleStringIdentity("a.b.c")) - .include(true).term("15") - .include(false).term("40"); + auto& root = expected.add(); + root.add() + .field(mangleStringIdentity("a.b.c")) + .include(true) + .term("15"); + root.add() + .field(mangleStringIdentity("a.b.c")) + .include(false) + .term("40"); assertFilterSuccess("FOR d IN collection FILTER d.a.b.c >= '15' and d.a.b.c < '40' RETURN d", expected); assertFilterSuccess("FOR d IN collection FILTER d['a']['b'].c >= '15' and d['a']['b']['c'] < '40' RETURN d", expected); @@ -2813,11 +2856,16 @@ SECTION("BinaryAnd") { // string range, boost, analyzer { irs::Or expected; - auto& range = expected.add(); - range.boost(0.5); - range.field(mangleString("a.b.c", "testVocbase::test_analyzer")) - .include(true).term("15") - .include(false).term("40"); + auto& root = expected.add(); + root.boost(0.5); + root.add() + .field(mangleString("a.b.c", "testVocbase::test_analyzer")) + .include(true) + .term("15"); + root.add() + .field(mangleString("a.b.c", "testVocbase::test_analyzer")) + .include(false) + .term("40"); assertFilterSuccess("FOR d IN collection FILTER analyzer(boost(d.a.b.c >= '15' and d.a.b.c < '40', 0.5), 'test_analyzer') RETURN d", expected); assertFilterSuccess("FOR d IN collection FILTER boost(analyzer(d['a']['b'].c >= '15' and d['a']['b']['c'] < '40', 'test_analyzer'), 0.5) RETURN d", expected); @@ -2826,10 +2874,15 @@ SECTION("BinaryAnd") { // string range { irs::Or expected; - auto& range = expected.add(); - range.field(mangleStringIdentity("a.b.c")) - .include(true).term("15") - .include(true).term("40"); + auto& root = expected.add(); + root.add() + .field(mangleStringIdentity("a.b.c")) + .include(true) + .term("15"); + root.add() + .field(mangleStringIdentity("a.b.c")) + .include(true) + .term("40"); assertFilterSuccess("FOR d IN collection FILTER d.a.b.c >= '15' and d.a.b.c <= '40' RETURN d", expected); assertFilterSuccess("FOR d IN collection FILTER d['a']['b']['c'] >= '15' and d.a.b.c <= '40' RETURN d", expected); @@ -2896,10 +2949,15 @@ SECTION("BinaryAnd") { // string range { irs::Or expected; - auto& range = expected.add(); - range.field(mangleStringIdentity("a.b.c")) - .include(false).term("15") - .include(true).term("40"); + auto& root = expected.add(); + root.add() + .field(mangleStringIdentity("a.b.c")) + .include(false) + .term("15"); + root.add() + .field(mangleStringIdentity("a.b.c")) + .include(true) + .term("40"); assertFilterSuccess("FOR d IN collection FILTER d.a.b.c > '15' and d.a.b.c <= '40' RETURN d", expected); assertFilterSuccess("FOR d IN collection FILTER d.a.b.c > '15' and d.a.b.c <= '40' RETURN d", expected); @@ -2917,10 +2975,15 @@ SECTION("BinaryAnd") { ctx.vars.emplace("numVal", arangodb::aql::AqlValue(arangodb::aql::AqlValueHintInt(2))); irs::Or expected; - auto& range = expected.add(); - range.field(mangleStringIdentity("a.b.c.e.f")) - .include(false).term("15") - .include(true).term("40"); + auto& root = expected.add(); + root.add() + .field(mangleStringIdentity("a.b.c.e.f")) + .include(false) + .term("15"); + root.add() + .field(mangleStringIdentity("a.b.c.e.f")) + .include(true) + .term("40"); assertFilterSuccess( "LET numVal=2 FOR d IN collection FILTER d.a.b.c.e.f > TO_STRING(numVal+13) && d.a.b.c.e.f <= TO_STRING(numVal+38) RETURN d", @@ -2941,11 +3004,16 @@ SECTION("BinaryAnd") { ctx.vars.emplace("numVal", arangodb::aql::AqlValue(arangodb::aql::AqlValueHintInt(2))); irs::Or expected; - auto& range = expected.add(); - range.boost(2.f); - range.field(mangleString("a.b.c.e.f", "testVocbase::test_analyzer")) - .include(false).term("15") - .include(true).term("40"); + auto& root = expected.add(); + root.boost(2.f); + root.add() + .field(mangleString("a.b.c.e.f", "testVocbase::test_analyzer")) + .include(false) + .term("15"); + root.add() + .field(mangleString("a.b.c.e.f", "testVocbase::test_analyzer")) + .include(true) + .term("40"); assertFilterSuccess( "LET numVal=2 FOR d IN collection FILTER boost(analyzer(d.a.b.c.e.f > TO_STRING(numVal+13) && d.a.b.c.e.f <= TO_STRING(numVal+38), 'test_analyzer'), numVal) RETURN d", @@ -2969,10 +3037,15 @@ SECTION("BinaryAnd") { ctx.vars.emplace("offsetDbl", arangodb::aql::AqlValue(arangodb::aql::AqlValue(arangodb::aql::AqlValueHintDouble{5.6}))); irs::Or expected; - auto& range = expected.add(); - range.field(mangleStringIdentity("a.b.c.e[4].f[5].g[3].g.a")) - .include(false).term("15") - .include(true).term("40"); + auto& root = expected.add(); + root.add() + .field(mangleStringIdentity("a.b.c.e[4].f[5].g[3].g.a")) + .include(false) + .term("15"); + root.add() + .field(mangleStringIdentity("a.b.c.e[4].f[5].g[3].g.a")) + .include(true) + .term("40"); assertFilterSuccess("LET a='a' LET c='c' LET offsetInt=4 LET offsetDbl=5.6 FOR d IN collection FILTER d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_FORWARD_('a')] > '15' && d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_FORWARD_('a')] <= '40' RETURN d", expected, &ctx); assertFilterSuccess("LET a='a' LET c='c' LET offsetInt=4 LET offsetDbl=5.6 FOR d IN collection FILTER '15' < d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_FORWARD_('a')] && '40' >= d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_FORWARD_('a')] RETURN d", expected, &ctx); @@ -3113,10 +3186,15 @@ SECTION("BinaryAnd") { irs::numeric_token_stream maxTerm; maxTerm.reset(40.); irs::Or expected; - expected.add() - .field(mangleNumeric("a.b.c")) - .include(true).insert(minTerm) - .include(true).insert(maxTerm); + auto& root = expected.add(); + root.add() + .field(mangleNumeric("a.b.c")) + .include(true) + .insert(minTerm); + root.add() + .field(mangleNumeric("a.b.c")) + .include(false) + .insert(maxTerm); assertFilterSuccess("FOR d IN collection FILTER d.a.b.c >= 15.5 and d.a.b.c < 40 RETURN d", expected); assertFilterSuccess("FOR d IN collection FILTER d['a']['b'].c >= 15.5 and d['a']['b'].c < 40 RETURN d", expected); @@ -3414,10 +3492,15 @@ SECTION("BinaryAnd") { ctx.vars.emplace("numVal", arangodb::aql::AqlValue(arangodb::aql::AqlValueHintInt(2))); irs::Or expected; - auto& range = expected.add(); - range.field(mangleBool("a.b.c.e.f")) - .include(true).term(irs::boolean_token_stream::value_true()) - .include(true).term(irs::boolean_token_stream::value_true()); + auto& root = expected.add(); + root.add() + .field(mangleBool("a.b.c.e.f")) + .include(true) + .term(irs::boolean_token_stream::value_true()); + root.add() + .field(mangleBool("a.b.c.e.f")) + .include(true) + .term(irs::boolean_token_stream::value_true()); assertFilterSuccess( "LET numVal=2 FOR d IN collection FILTER d.a.b.c.e.f >= (numVal < 13) && d.a.b.c.e.f <= (numVal > 1) RETURN d", @@ -3438,11 +3521,16 @@ SECTION("BinaryAnd") { ctx.vars.emplace("numVal", arangodb::aql::AqlValue(arangodb::aql::AqlValueHintInt(2))); irs::Or expected; - auto& range = expected.add(); - range.boost(1.5); - range.field(mangleBool("a.b.c.e.f")) - .include(true).term(irs::boolean_token_stream::value_true()) - .include(true).term(irs::boolean_token_stream::value_true()); + auto& root = expected.add(); + root.boost(1.5); + root.add() + .field(mangleBool("a.b.c.e.f")) + .include(true) + .term(irs::boolean_token_stream::value_true()); + root.add() + .field(mangleBool("a.b.c.e.f")) + .include(true) + .term(irs::boolean_token_stream::value_true()); assertFilterSuccess( "LET numVal=2 FOR d IN collection FILTER boost(d.a.b.c.e.f >= (numVal < 13) && d.a.b.c.e.f <= (numVal > 1), 1.5) RETURN d", @@ -3492,10 +3580,16 @@ SECTION("BinaryAnd") { ctx.vars.emplace("nullVal", arangodb::aql::AqlValue(arangodb::aql::AqlValueHintNull{})); irs::Or expected; - auto& range = expected.add(); - range.field(mangleNull("a.b.c.e.f")) - .include(true).term(irs::null_token_stream::value_null()) - .include(true).term(irs::null_token_stream::value_null()); + auto& root = expected.add(); + root.add() + .field(mangleNull("a.b.c.e.f")) + .include(true) + .term(irs::null_token_stream::value_null()); + root.add() + .field(mangleNull("a.b.c.e.f")) + .include(true) + .term(irs::null_token_stream::value_null()); + assertFilterSuccess( "LET nullVal=null FOR d IN collection FILTER d.a.b.c.e.f >= (nullVal && true) && d.a.b.c.e.f <= (nullVal && false) RETURN d", @@ -3516,11 +3610,16 @@ SECTION("BinaryAnd") { ctx.vars.emplace("nullVal", arangodb::aql::AqlValue(arangodb::aql::AqlValueHintNull{})); irs::Or expected; - auto& range = expected.add(); - range.boost(1.5); - range.field(mangleNull("a.b.c.e.f")) - .include(true).term(irs::null_token_stream::value_null()) - .include(true).term(irs::null_token_stream::value_null()); + auto& root = expected.add(); + root.boost(1.5); + root.add() + .field(mangleNull("a.b.c.e.f")) + .include(true) + .term(irs::null_token_stream::value_null()); + root.add() + .field(mangleNull("a.b.c.e.f")) + .include(true) + .term(irs::null_token_stream::value_null()); assertFilterSuccess( "LET nullVal=null FOR d IN collection FILTER boost(d.a.b.c.e.f >= (nullVal && true) && d.a.b.c.e.f <= (nullVal && false), 1.5) RETURN d", @@ -3544,10 +3643,15 @@ SECTION("BinaryAnd") { ctx.vars.emplace("numVal", arangodb::aql::AqlValue(arangodb::aql::AqlValueHintInt(2))); irs::Or expected; - expected.add() - .field(mangleNumeric("a.b.c.e.f")) - .include(true).insert(minTerm) - .include(false).insert(maxTerm); + auto& root = expected.add(); + root.add() + .field(mangleNumeric("a.b.c.e.f")) + .include(true) + .insert(minTerm); + root.add() + .field(mangleNumeric("a.b.c.e.f")) + .include(false) + .insert(maxTerm); assertFilterSuccess( "LET numVal=2 FOR d IN collection FILTER d.a['b'].c.e.f >= (numVal + 13.5) && d.a.b.c.e.f < (numVal + 38) RETURN d", @@ -3653,4 +3757,4 @@ SECTION("BinaryAnd") { // ----------------------------------------------------------------------------- // --SECTION-- END-OF-FILE -// ----------------------------------------------------------------------------- \ No newline at end of file +// ----------------------------------------------------------------------------- diff --git a/tests/IResearch/IResearchFilterFunction-test.cpp b/tests/IResearch/IResearchFilterFunction-test.cpp index 40e357852d..b7918a8410 100644 --- a/tests/IResearch/IResearchFilterFunction-test.cpp +++ b/tests/IResearch/IResearchFilterFunction-test.cpp @@ -2283,8 +2283,199 @@ SECTION("StartsWith") { assertFilterFail("FOR d IN myView FILTER starts_with(d.name, 'abc', RAND() ? 128 : 10) RETURN d"); } +SECTION("IN_RANGE") { + // d.name > 'a' && d.name < 'z' + { + irs::Or expected; + auto& range = expected.add(); + range.field(mangleStringIdentity("name")) + .include(false).term("a") + .include(false).term("z"); + + assertFilterSuccess("FOR d IN myView FILTER in_range(d['name'], 'a', 'z', false, false) RETURN d", expected); + assertFilterSuccess("FOR d IN myView FILTER in_range(d.name, 'a', 'z', false, false) RETURN d", expected); + } + + // BOOST(d.name >= 'a' && d.name <= 'z', 1.5) + { + irs::Or expected; + auto& range = expected.add(); + range.boost(1.5); + range.field(mangleStringIdentity("name")) + .include(true).term("a") + .include(true).term("z"); + + assertFilterSuccess("FOR d IN myView FILTER boost(in_range(d['name'], 'a', 'z', true, true), 1.5) RETURN d", expected); + assertFilterSuccess("FOR d IN myView FILTER boost(in_range(d.name, 'a', 'z', true, true), 1.5) RETURN d", expected); + } + + // ANALYZER(BOOST(d.name > 'a' && d.name <= 'z', 1.5), "testVocbase::test_analyzer") + { + irs::Or expected; + auto& range = expected.add(); + range.boost(1.5); + range.field(mangleString("name", "testVocbase::test_analyzer")) + .include(false).term("a") + .include(true).term("z"); + + assertFilterSuccess("FOR d IN myView FILTER analyzer(boost(in_range(d['name'], 'a', 'z', false, true), 1.5), 'testVocbase::test_analyzer') RETURN d", expected); + assertFilterSuccess("FOR d IN myView FILTER analyzer(boost(in_range(d.name, 'a', 'z', false, true), 1.5), 'testVocbase::test_analyzer') RETURN d", expected); + } + + // dynamic complex attribute field + { + ExpressionContextMock ctx; + ctx.vars.emplace("a", arangodb::aql::AqlValue(arangodb::aql::AqlValue{"a"})); + ctx.vars.emplace("c", arangodb::aql::AqlValue(arangodb::aql::AqlValue{"c"})); + ctx.vars.emplace("offsetInt", arangodb::aql::AqlValue(arangodb::aql::AqlValue(arangodb::aql::AqlValueHintInt{4}))); + ctx.vars.emplace("offsetDbl", arangodb::aql::AqlValue(arangodb::aql::AqlValue(arangodb::aql::AqlValueHintDouble{5.6}))); + + irs::Or expected; + auto& range = expected.add(); + range.field(mangleStringIdentity("a.b.c.e[4].f[5].g[3].g.a")) + .include(true).term("abc") + .include(false).term("bce"); + + assertFilterSuccess("LET a='a' LET c='c' LET offsetInt=4 LET offsetDbl=5.6 FOR d IN collection FILTER in_range(d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_FORWARD_('a')], 'abc', 'bce', true, false) RETURN d", expected, &ctx); + assertFilterSuccess("LET a='a' LET c='c' LET offsetInt=4 LET offsetDbl=5.6 FOR d IN collection FILTER in_range(d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_FORWARD_('a')], CONCAT(_FORWARD_('a'), _FORWARD_('bc')), CONCAT(_FORWARD_('bc'), _FORWARD_('e')), _FORWARD_(5) > _FORWARD_(4), _FORWARD_(5) > _FORWARD_(6)) RETURN d", expected, &ctx); + } + + // invalid dynamic attribute name (null value) + { + ExpressionContextMock ctx; + ctx.vars.emplace("a", arangodb::aql::AqlValue(arangodb::aql::AqlValueHintNull{})); // invalid value type + ctx.vars.emplace("c", arangodb::aql::AqlValue(arangodb::aql::AqlValue{"c"})); + ctx.vars.emplace("offsetInt", arangodb::aql::AqlValue(arangodb::aql::AqlValue(arangodb::aql::AqlValueHintInt{4}))); + ctx.vars.emplace("offsetDbl", arangodb::aql::AqlValue(arangodb::aql::AqlValue(arangodb::aql::AqlValueHintDouble{5.6}))); + + assertFilterExecutionFail("LET a=null LET c='c' LET offsetInt=4 LET offsetDbl=5.6 FOR d IN collection FILTER in_range(d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_FORWARD_('a')], 'abc', 'bce', true, false) RETURN d", &ctx); + } + + // boolean expression in range, boost + { + ExpressionContextMock ctx; + ctx.vars.emplace("numVal", arangodb::aql::AqlValue(arangodb::aql::AqlValueHintInt(2))); + + irs::Or expected; + auto& range = expected.add(); + range.boost(1.5); + range.field(mangleBool("a.b.c.e.f")) + .include(true).term(irs::boolean_token_stream::value_true()) + .include(true).term(irs::boolean_token_stream::value_true()); + + assertFilterSuccess( + "LET numVal=2 FOR d IN collection FILTER boost(in_rangE(d.a.b.c.e.f, (numVal < 13), (numVal > 1), true, true), 1.5) RETURN d", + expected, + &ctx // expression context + ); + + assertFilterSuccess( + "LET numVal=2 FOR d IN collection FILTER boost(in_rangE(d.a.b.c.e.f, (numVal < 13), (numVal > 1), true, true), 1.5) RETURN d", + expected, + &ctx // expression context + ); + } + + // null expression in range, boost + { + ExpressionContextMock ctx; + ctx.vars.emplace("nullVal", arangodb::aql::AqlValue(arangodb::aql::AqlValueHintNull{})); + + irs::Or expected; + auto& range = expected.add(); + range.boost(1.5); + range.field(mangleNull("a.b.c.e.f")) + .include(true).term(irs::null_token_stream::value_null()) + .include(true).term(irs::null_token_stream::value_null()); + + assertFilterSuccess( + "LET nullVal=null FOR d IN collection FILTER BOOST(in_range(d.a.b.c.e.f, (nullVal && true), (nullVal && false), true, true), 1.5) RETURN d", + expected, + &ctx // expression context + ); + + assertFilterSuccess( + "LET nullVal=null FOR d IN collection FILTER bOoST(in_range(d.a.b.c.e.f, (nullVal && false), (nullVal && true), true, true), 1.5) RETURN d", + expected, + &ctx // expression context + ); + } + + // numeric expression in range, boost + { + irs::numeric_token_stream minTerm; minTerm.reset(15.5); + irs::numeric_token_stream maxTerm; maxTerm.reset(40.); + + ExpressionContextMock ctx; + ctx.vars.emplace("numVal", arangodb::aql::AqlValue(arangodb::aql::AqlValueHintInt(2))); + + irs::Or expected; + auto& range = expected.add(); + range.boost(1.5); + range.field(mangleNumeric("a.b.c.e.f")) + .include(true).insert(minTerm) + .include(false).insert(maxTerm); + + assertFilterSuccess( + "LET numVal=2 FOR d IN collection FILTER boost(in_range(d.a['b'].c.e.f, (numVal + 13.5), (numVal + 38), true, false), 1.5) RETURN d", + expected, + &ctx // expression context + ); + + assertFilterSuccess( + "LET numVal=2 FOR d IN collection FILTER boost(IN_RANGE(d.a.b.c.e.f, (numVal + 13.5), (numVal + 38), true, false), 1.5) RETURN d", + expected, + &ctx // expression context + ); + + assertFilterSuccess( + "LET numVal=2 FOR d IN collection FILTER analyzer(boost(in_range(d.a.b.c.e.f, (numVal + 13.5), (numVal + 38), true, false), 1.5), 'test_analyzer') RETURN d", + expected, + &ctx // expression context + ); + } + + // invalid attribute access + assertFilterFail("FOR d IN myView FILTER in_range(['d'], 'abc', true, 'z', false) RETURN d"); + assertFilterFail("FOR d IN myView FILTER in_range([d], 'abc', true, 'z', false) RETURN d"); + assertFilterFail("FOR d IN myView FILTER in_range(d, 'abc', true, 'z', false) RETURN d"); + assertFilterFail("FOR d IN myView FILTER in_range(d[*], 'abc', true, 'z', false) RETURN d"); + assertFilterFail("FOR d IN myView FILTER in_range(d.a[*].c, 'abc', true, 'z', false) RETURN d"); + assertFilterFail("FOR d IN myView FILTER in_range('d.name', 'abc', true, 'z', false) RETURN d"); + assertFilterFail("FOR d IN myView FILTER in_range(123, 'abc', true, 'z', false) RETURN d"); + assertFilterFail("FOR d IN myView FILTER in_range(123.5, 'abc', true, 'z', false) RETURN d"); + assertFilterFail("FOR d IN myView FILTER in_range(null, 'abc', true, 'z', false) RETURN d"); + assertFilterFail("FOR d IN myView FILTER in_range(true, 'abc', true, 'z', false) RETURN d"); + assertFilterFail("FOR d IN myView FILTER in_range(false, 'abc', true, 'z', false) RETURN d"); + + // invalid type of inclusion argument + assertFilterFail("FOR d IN myView FILTER in_range(d.name, 'abc', true, 'z', 'false') RETURN d"); + assertFilterFail("FOR d IN myView FILTER in_range(d.name, 'abc', true, 'z', 0) RETURN d"); + assertFilterFail("FOR d IN myView FILTER in_range(d.name, 'abc', true, 'z', null) RETURN d"); + assertFilterFail("FOR d IN myView FILTER in_range(d.name, 'abc', 'true', 'z', false) RETURN d"); + assertFilterFail("FOR d IN myView FILTER in_range(d.name, 'abc', 1, 'z', false) RETURN d"); + assertFilterFail("FOR d IN myView FILTER in_range(d.name, 'abc', null, 'z', false) RETURN d"); + + // non-deterministic argument + assertFilterFail("FOR d IN myView FILTER in_range(d[RAND() ? 'name' : 'x'], 'abc', true, 'z', false) RETURN d"); + assertFilterFail("FOR d IN myView FILTER in_range(d.name, RAND() ? 'abc' : 'def', true, 'z', false) RETURN d"); + assertFilterFail("FOR d IN myView FILTER in_range(d.name, 'abc', RAND() ? true : false, 'z', false) RETURN d"); + assertFilterFail("FOR d IN myView FILTER in_range(d.name, 'abc', true, RAND() ? 'z' : 'x', false) RETURN d"); + assertFilterFail("FOR d IN myView FILTER in_range(d.name, 'abc', true, 'z', RAND() ? false : true) RETURN d"); + + // lower/upper boundary type mismatch + assertFilterFail("FOR d IN myView FILTER in_range(d.name, 1, true, 'z', false) RETURN d"); + assertFilterFail("FOR d IN myView FILTER in_range(d.name, 'abc', true, null, false) RETURN d"); + assertFilterFail("FOR d IN myView FILTER in_range(d.name, bool, true, null, false) RETURN d"); + assertFilterFail("FOR d IN myView FILTER in_range(d.name, bool, true, 1, false) RETURN d"); + + // wrong number of arguments + assertFilterParseFail("FOR d IN myView FILTER in_range(d.name, 'abc', true, 'z') RETURN d"); + assertFilterParseFail("FOR d IN myView FILTER in_range(d.name, 'abc', true, 'z', false, false) RETURN d"); +} + } // ----------------------------------------------------------------------------- // --SECTION-- END-OF-FILE -// ----------------------------------------------------------------------------- \ No newline at end of file +// ----------------------------------------------------------------------------- diff --git a/tests/IResearch/IResearchQueryInRange-test.cpp b/tests/IResearch/IResearchQueryInRange-test.cpp new file mode 100644 index 0000000000..125323a954 --- /dev/null +++ b/tests/IResearch/IResearchQueryInRange-test.cpp @@ -0,0 +1,711 @@ +//////////////////////////////////////////////////////////////////////////////// +/// DISCLAIMER +/// +/// Copyright 2017 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 Andrey Abramov +/// @author Vasiliy Nabatchikov +//////////////////////////////////////////////////////////////////////////////// + +#include "catch.hpp" +#include "common.h" + +#include "../Mocks/StorageEngineMock.h" + +#if USE_ENTERPRISE + #include "Enterprise/Ldap/LdapFeature.h" +#endif + +#include "Cluster/ClusterFeature.h" +#include "V8Server/V8DealerFeature.h" +#include "Aql/AqlFunctionFeature.h" +#include "Aql/Ast.h" +#include "Aql/OptimizerRulesFeature.h" +#include "Aql/Query.h" +#include "Basics/VelocyPackHelper.h" +#include "GeneralServer/AuthenticationFeature.h" +#include "IResearch/IResearchCommon.h" +#include "IResearch/IResearchFeature.h" +#include "IResearch/IResearchFilterFactory.h" +#include "IResearch/IResearchView.h" +#include "IResearch/IResearchAnalyzerFeature.h" +#include "Logger/Logger.h" +#include "Logger/LogTopic.h" +#include "StorageEngine/EngineSelectorFeature.h" +#include "RestServer/DatabasePathFeature.h" +#include "RestServer/ViewTypesFeature.h" +#include "RestServer/AqlFeature.h" +#include "RestServer/DatabaseFeature.h" +#include "RestServer/QueryRegistryFeature.h" +#include "RestServer/SystemDatabaseFeature.h" +#include "RestServer/TraverserEngineRegistryFeature.h" +#include "Sharding/ShardingFeature.h" +#include "V8/v8-globals.h" +#include "VocBase/LogicalCollection.h" +#include "VocBase/LogicalView.h" + +#include "3rdParty/iresearch/tests/tests_config.hpp" +#include "Transaction/StandaloneContext.h" +#include "Utils/SingleCollectionTransaction.h" + +#include "IResearch/VelocyPackHelper.h" +#include "analysis/analyzers.hpp" +#include "analysis/token_attributes.hpp" +#include "utils/utf8_path.hpp" + +#include + +extern const char* ARGV0; // defined in main.cpp + +NS_LOCAL + +// ----------------------------------------------------------------------------- +// --SECTION-- setup / tear-down +// ----------------------------------------------------------------------------- + +struct IResearchQueryInRangeSetup { + StorageEngineMock engine; + arangodb::application_features::ApplicationServer server; + std::unique_ptr system; + std::vector> features; + + IResearchQueryInRangeSetup(): engine(server), server(nullptr, nullptr) { + arangodb::EngineSelectorFeature::ENGINE = &engine; + + arangodb::tests::init(true); + + // suppress INFO {authentication} Authentication is turned on (system only), authentication for unix sockets is turned on + arangodb::LogTopic::setLogLevel(arangodb::Logger::AUTHENTICATION.name(), arangodb::LogLevel::WARN); + + // suppress log messages since tests check error conditions + arangodb::LogTopic::setLogLevel(arangodb::Logger::FIXME.name(), arangodb::LogLevel::ERR); // suppress WARNING DefaultCustomTypeHandler called + arangodb::LogTopic::setLogLevel(arangodb::iresearch::TOPIC.name(), arangodb::LogLevel::FATAL); + irs::logger::output_le(iresearch::logger::IRL_FATAL, stderr); + + // setup required application features + features.emplace_back(new arangodb::V8DealerFeature(server), false); // required for DatabaseFeature::createDatabase(...) + features.emplace_back(new arangodb::ViewTypesFeature(server), true); + features.emplace_back(new arangodb::AuthenticationFeature(server), true); + features.emplace_back(new arangodb::DatabasePathFeature(server), false); + features.emplace_back(new arangodb::DatabaseFeature(server), false); + features.emplace_back(new arangodb::ShardingFeature(server), false); // + features.emplace_back(new arangodb::QueryRegistryFeature(server), false); // must be first + arangodb::application_features::ApplicationServer::server->addFeature(features.back().first); // need QueryRegistryFeature feature to be added now in order to create the system database + system = irs::memory::make_unique(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 0, TRI_VOC_SYSTEM_DATABASE); + features.emplace_back(new arangodb::SystemDatabaseFeature(server, system.get()), false); // required for IResearchAnalyzerFeature + features.emplace_back(new arangodb::TraverserEngineRegistryFeature(server), false); // must be before AqlFeature + features.emplace_back(new arangodb::AqlFeature(server), true); + features.emplace_back(new arangodb::aql::OptimizerRulesFeature(server), true); + features.emplace_back(new arangodb::aql::AqlFunctionFeature(server), true); // required for IResearchAnalyzerFeature + features.emplace_back(new arangodb::iresearch::IResearchAnalyzerFeature(server), true); + features.emplace_back(new arangodb::iresearch::IResearchFeature(server), true); + + #if USE_ENTERPRISE + features.emplace_back(new arangodb::LdapFeature(server), false); // required for AuthenticationFeature with USE_ENTERPRISE + #endif + + // required for V8DealerFeature::prepare(), ClusterFeature::prepare() not required + arangodb::application_features::ApplicationServer::server->addFeature( + new arangodb::ClusterFeature(server) + ); + + for (auto& f : features) { + arangodb::application_features::ApplicationServer::server->addFeature(f.first); + } + + for (auto& f : features) { + f.first->prepare(); + } + + for (auto& f : features) { + if (f.second) { + f.first->start(); + } + } + + TRI_vocbase_t* vocbase; + + auto* dbFeature = arangodb::application_features::ApplicationServer::lookupFeature< + arangodb::DatabaseFeature + >("Database"); + dbFeature->createDatabase(1, "testVocbase", vocbase); // required for IResearchAnalyzerFeature::emplace(...) + + auto* analyzers = arangodb::application_features::ApplicationServer::lookupFeature< + arangodb::iresearch::IResearchAnalyzerFeature + >(); + + arangodb::iresearch::IResearchAnalyzerFeature::EmplaceResult result; + analyzers->emplace( + result, + "testVocbase::test_csv_analyzer", + "TestDelimAnalyzer", + "," + ); // cache analyzer + + analyzers->emplace( + result, + "testVocbase::test_analyzer", + "TestAnalyzer", + "abc" + ); // cache analyzer + + auto* dbPathFeature = arangodb::application_features::ApplicationServer::getFeature("DatabasePath"); + arangodb::tests::setDatabasePath(*dbPathFeature); // ensure test data is stored in a unique directory + } + + ~IResearchQueryInRangeSetup() { + system.reset(); // destroy before reseting the 'ENGINE' + arangodb::AqlFeature(server).stop(); // unset singleton instance + arangodb::LogTopic::setLogLevel(arangodb::iresearch::TOPIC.name(), arangodb::LogLevel::DEFAULT); + arangodb::LogTopic::setLogLevel(arangodb::Logger::FIXME.name(), arangodb::LogLevel::DEFAULT); + arangodb::application_features::ApplicationServer::server = nullptr; + + // destroy application features + for (auto& f : features) { + if (f.second) { + f.first->stop(); + } + } + + for (auto& f : features) { + f.first->unprepare(); + } + + arangodb::LogTopic::setLogLevel(arangodb::Logger::AUTHENTICATION.name(), arangodb::LogLevel::DEFAULT); + arangodb::EngineSelectorFeature::ENGINE = nullptr; + } +}; // IResearchQueryInRangeSetup + +NS_END + +// ----------------------------------------------------------------------------- +// --SECTION-- test suite +// ----------------------------------------------------------------------------- + +//////////////////////////////////////////////////////////////////////////////// +/// @brief setup +//////////////////////////////////////////////////////////////////////////////// + +TEST_CASE("IResearchQueryInRange", "[iresearch][iresearch-query]") { + IResearchQueryInRangeSetup s; + UNUSED(s); + + TRI_vocbase_t vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); + std::vector insertedDocs; + arangodb::LogicalView* view; + + // create collection0 + { + auto createJson = arangodb::velocypack::Parser::fromJson("{ \"name\": \"testCollection0\" }"); + auto collection = vocbase.createCollection(createJson->slice()); + REQUIRE((nullptr != collection)); + + std::vector> docs { + arangodb::velocypack::Parser::fromJson("{ \"seq\": -6, \"value\": null }"), + arangodb::velocypack::Parser::fromJson("{ \"seq\": -5, \"value\": true }"), + arangodb::velocypack::Parser::fromJson("{ \"seq\": -4, \"value\": \"abc\" }"), + arangodb::velocypack::Parser::fromJson("{ \"seq\": -3, \"value\": [ 3.14, -3.14 ] }"), + arangodb::velocypack::Parser::fromJson("{ \"seq\": -2, \"value\": [ 1, \"abc\" ] }"), + arangodb::velocypack::Parser::fromJson("{ \"seq\": -1, \"value\": { \"a\": 7, \"b\": \"c\" } }"), + }; + + arangodb::OperationOptions options; + options.returnNew = true; + arangodb::SingleCollectionTransaction trx( + arangodb::transaction::StandaloneContext::Create(vocbase), + *collection, + arangodb::AccessMode::Type::WRITE + ); + CHECK((trx.begin().ok())); + + for (auto& entry: docs) { + auto res = trx.insert(collection->name(), entry->slice(), options); + CHECK((res.ok())); + insertedDocs.emplace_back(res.slice().get("new")); + } + + CHECK((trx.commit().ok())); + } + + // create collection1 + { + auto createJson = arangodb::velocypack::Parser::fromJson("{ \"name\": \"testCollection1\" }"); + auto collection = vocbase.createCollection(createJson->slice()); + REQUIRE((nullptr != collection)); + + irs::utf8_path resource; + resource/=irs::string_ref(IResearch_test_resource_dir); + resource/=irs::string_ref("simple_sequential.json"); + + auto builder = arangodb::basics::VelocyPackHelper::velocyPackFromFile(resource.utf8()); + auto slice = builder.slice(); + REQUIRE(slice.isArray()); + + arangodb::OperationOptions options; + options.returnNew = true; + arangodb::SingleCollectionTransaction trx( + arangodb::transaction::StandaloneContext::Create(vocbase), + *collection, + arangodb::AccessMode::Type::WRITE + ); + CHECK((trx.begin().ok())); + + for (arangodb::velocypack::ArrayIterator itr(slice); itr.valid(); ++itr) { + auto res = trx.insert(collection->name(), itr.value(), options); + CHECK((res.ok())); + insertedDocs.emplace_back(res.slice().get("new")); + } + + CHECK((trx.commit().ok())); + } + + // create view + { + auto createJson = arangodb::velocypack::Parser::fromJson("{ \"name\": \"testView\", \"type\": \"arangosearch\" }"); + auto logicalView = vocbase.createView(createJson->slice()); + REQUIRE((false == !logicalView)); + + view = logicalView.get(); + auto* impl = dynamic_cast(view); + REQUIRE((false == !impl)); + + auto updateJson = arangodb::velocypack::Parser::fromJson( + "{ \"links\": {" + "\"testCollection0\": { \"analyzers\": [ \"test_analyzer\", \"identity\" ], \"includeAllFields\": true, \"trackListPositions\": false, \"storeValues\":\"id\" }," + "\"testCollection1\": { \"analyzers\": [ \"test_analyzer\", \"identity\" ], \"includeAllFields\": true, \"storeValues\":\"id\" }" + "}}" + ); + + CHECK((impl->properties(updateJson->slice(), true).ok())); + std::set cids; + impl->visitCollections([&cids](TRI_voc_cid_t cid)->bool { cids.emplace(cid); return true; }); + CHECK((2 == cids.size())); + CHECK(impl->commit().ok()); + } + + // d.value > false && d.value <= true + { + std::vector expected = { + insertedDocs[1].slice(), + }; + auto result = arangodb::tests::executeQuery( + vocbase, + "FOR d IN testView SEARCH IN_RANGE(d.value, false, true, false, true) SORT d.seq RETURN d" + ); + REQUIRE(TRI_ERROR_NO_ERROR == result.code); + auto slice = result.result->slice(); + CHECK(slice.isArray()); + size_t i = 0; + + for (arangodb::velocypack::ArrayIterator itr(slice); itr.valid(); ++itr) { + auto const resolved = itr.value().resolveExternals(); + + CHECK((i < expected.size())); + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(expected[i++], resolved, true))); + } + + CHECK((i == expected.size())); + } + + // d.value >= null && d.value <= null + { + std::vector expected = { + insertedDocs[0].slice(), + }; + auto result = arangodb::tests::executeQuery( + vocbase, + "FOR d IN testView SEARCH IN_RANGE(d.value, null, null, true, true) SORT d.seq RETURN d" + ); + REQUIRE(TRI_ERROR_NO_ERROR == result.code); + auto slice = result.result->slice(); + CHECK(slice.isArray()); + size_t i = 0; + + for (arangodb::velocypack::ArrayIterator itr(slice); itr.valid(); ++itr) { + auto const resolved = itr.value().resolveExternals(); + + CHECK((i < expected.size())); + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(expected[i++], resolved, true))); + } + + CHECK((i == expected.size())); + } + + // d.value > null && d.value <= null + { + std::vector expected = { + }; + auto result = arangodb::tests::executeQuery( + vocbase, + "FOR d IN testView SEARCH IN_RANGE(d.value, null, null, false, true) SORT d.seq RETURN d" + ); + REQUIRE(TRI_ERROR_NO_ERROR == result.code); + auto slice = result.result->slice(); + CHECK(slice.isArray()); + size_t i = 0; + + for (arangodb::velocypack::ArrayIterator itr(slice); itr.valid(); ++itr) { + auto const resolved = itr.value().resolveExternals(); + + CHECK((i < expected.size())); + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(expected[i++], resolved, true))); + } + + CHECK((i == expected.size())); + } + + // d.name >= 'A' && d.name <= 'A' + { + std::vector expected = { + insertedDocs[6].slice(), + }; + auto result = arangodb::tests::executeQuery( + vocbase, + "FOR d IN testView SEARCH IN_RANGE(d.name, 'A', 'A', true, true) SORT d.seq RETURN d" + ); + REQUIRE(TRI_ERROR_NO_ERROR == result.code); + auto slice = result.result->slice(); + CHECK(slice.isArray()); + size_t i = 0; + + for (arangodb::velocypack::ArrayIterator itr(slice); itr.valid(); ++itr) { + auto const resolved = itr.value().resolveExternals(); + + CHECK((i < expected.size())); + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(expected[i++], resolved, true))); + } + + CHECK((i == expected.size())); + } + + // d.name >= 'B' && d.name <= 'A' + { + std::vector expected = { + }; + auto result = arangodb::tests::executeQuery( + vocbase, + "FOR d IN testView SEARCH IN_RANGE(d.name, 'B', 'A', true, true) SORT d.seq RETURN d" + ); + REQUIRE(TRI_ERROR_NO_ERROR == result.code); + auto slice = result.result->slice(); + CHECK(slice.isArray()); + size_t i = 0; + + for (arangodb::velocypack::ArrayIterator itr(slice); itr.valid(); ++itr) { + auto const resolved = itr.value().resolveExternals(); + + CHECK((i < expected.size())); + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(expected[i++], resolved, true))); + } + + CHECK((i == expected.size())); + } + + // d.name >= 'A' && d.name <= 'E' + { + std::vector expected = { + insertedDocs[6].slice(), + insertedDocs[7].slice(), + insertedDocs[8].slice(), + insertedDocs[9].slice(), + insertedDocs[10].slice(), + }; + auto result = arangodb::tests::executeQuery( + vocbase, + "FOR d IN testView SEARCH IN_RANGE(d.name, 'A', 'E', true, true) SORT d.seq RETURN d" + ); + REQUIRE(TRI_ERROR_NO_ERROR == result.code); + auto slice = result.result->slice(); + CHECK(slice.isArray()); + size_t i = 0; + + for (arangodb::velocypack::ArrayIterator itr(slice); itr.valid(); ++itr) { + auto const resolved = itr.value().resolveExternals(); + + CHECK((i < expected.size())); + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(expected[i++], resolved, true))); + } + + CHECK((i == expected.size())); + } + + // d.name >= 'A' && d.name < 'E' + { + std::vector expected = { + insertedDocs[6].slice(), + insertedDocs[7].slice(), + insertedDocs[8].slice(), + insertedDocs[9].slice(), + }; + auto result = arangodb::tests::executeQuery( + vocbase, + "FOR d IN testView SEARCH IN_RANGE(d.name, 'A', 'E', true, false) SORT d.seq RETURN d" + ); + REQUIRE(TRI_ERROR_NO_ERROR == result.code); + auto slice = result.result->slice(); + CHECK(slice.isArray()); + size_t i = 0; + + for (arangodb::velocypack::ArrayIterator itr(slice); itr.valid(); ++itr) { + auto const resolved = itr.value().resolveExternals(); + + CHECK((i < expected.size())); + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(expected[i++], resolved, true))); + } + + CHECK((i == expected.size())); + } + + // d.name > 'A' && d.name <= 'E' + { + std::vector expected = { + insertedDocs[7].slice(), + insertedDocs[8].slice(), + insertedDocs[9].slice(), + insertedDocs[10].slice(), + }; + auto result = arangodb::tests::executeQuery( + vocbase, + "FOR d IN testView SEARCH IN_RANGE(d.name, 'A', 'E', false, true) SORT d.seq RETURN d" + ); + REQUIRE(TRI_ERROR_NO_ERROR == result.code); + auto slice = result.result->slice(); + CHECK(slice.isArray()); + size_t i = 0; + + for (arangodb::velocypack::ArrayIterator itr(slice); itr.valid(); ++itr) { + auto const resolved = itr.value().resolveExternals(); + + CHECK((i < expected.size())); + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(expected[i++], resolved, true))); + } + + CHECK((i == expected.size())); + } + + // d.name > 'A' && d.name < 'E' + { + std::vector expected = { + insertedDocs[7].slice(), + insertedDocs[8].slice(), + insertedDocs[9].slice(), + }; + auto result = arangodb::tests::executeQuery( + vocbase, + "FOR d IN testView SEARCH IN_RANGE(d.name, 'A', 'E', false, false) SORT d.seq RETURN d" + ); + REQUIRE(TRI_ERROR_NO_ERROR == result.code); + auto slice = result.result->slice(); + CHECK(slice.isArray()); + size_t i = 0; + + for (arangodb::velocypack::ArrayIterator itr(slice); itr.valid(); ++itr) { + auto const resolved = itr.value().resolveExternals(); + + CHECK((i < expected.size())); + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(expected[i++], resolved, true))); + } + + CHECK((i == expected.size())); + } + + // d.seq >= 5 && d.seq <= -1 + { + std::vector expected = { + }; + auto result = arangodb::tests::executeQuery( + vocbase, + "FOR d IN testView SEARCH IN_RANGE(d.seq, 5, -1, true, true) SORT d.seq RETURN d" + ); + REQUIRE(TRI_ERROR_NO_ERROR == result.code); + auto slice = result.result->slice(); + CHECK(slice.isArray()); + size_t i = 0; + + for (arangodb::velocypack::ArrayIterator itr(slice); itr.valid(); ++itr) { + auto const resolved = itr.value().resolveExternals(); + + CHECK((i < expected.size())); + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(expected[i++], resolved, true))); + } + + CHECK((i == expected.size())); + } + + // d.seq >= 1 && d.seq <= 5 + { + std::vector expected = { + insertedDocs[7].slice(), + insertedDocs[8].slice(), + insertedDocs[9].slice(), + insertedDocs[10].slice(), + insertedDocs[11].slice(), + }; + auto result = arangodb::tests::executeQuery( + vocbase, + "FOR d IN testView SEARCH IN_RANGE(d.seq, 1, 5, true, true) SORT d.seq RETURN d" + ); + REQUIRE(TRI_ERROR_NO_ERROR == result.code); + auto slice = result.result->slice(); + CHECK(slice.isArray()); + size_t i = 0; + + for (arangodb::velocypack::ArrayIterator itr(slice); itr.valid(); ++itr) { + auto const resolved = itr.value().resolveExternals(); + + CHECK((i < expected.size())); + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(expected[i++], resolved, true))); + } + + CHECK((i == expected.size())); + } + + // d.seq > -2 && d.seq <= 5 + { + std::vector expected = { + insertedDocs[5].slice(), + insertedDocs[6].slice(), + insertedDocs[7].slice(), + insertedDocs[8].slice(), + insertedDocs[9].slice(), + insertedDocs[10].slice(), + insertedDocs[11].slice(), + }; + auto result = arangodb::tests::executeQuery( + vocbase, + "FOR d IN testView SEARCH IN_RANGE(d.seq, -2, 5, false, true) SORT d.seq RETURN d" + ); + REQUIRE(TRI_ERROR_NO_ERROR == result.code); + auto slice = result.result->slice(); + CHECK(slice.isArray()); + size_t i = 0; + + for (arangodb::velocypack::ArrayIterator itr(slice); itr.valid(); ++itr) { + auto const resolved = itr.value().resolveExternals(); + + CHECK((i < expected.size())); + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(expected[i++], resolved, true))); + } + + CHECK((i == expected.size())); + } + + // d.seq > 1 && d.seq < 5 + { + std::vector expected = { + insertedDocs[8].slice(), + insertedDocs[9].slice(), + insertedDocs[10].slice(), + }; + auto result = arangodb::tests::executeQuery( + vocbase, + "FOR d IN testView SEARCH IN_RANGE(d.seq, 1, 5, false, false) SORT d.seq RETURN d" + ); + REQUIRE(TRI_ERROR_NO_ERROR == result.code); + auto slice = result.result->slice(); + CHECK(slice.isArray()); + size_t i = 0; + + for (arangodb::velocypack::ArrayIterator itr(slice); itr.valid(); ++itr) { + auto const resolved = itr.value().resolveExternals(); + + CHECK((i < expected.size())); + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(expected[i++], resolved, true))); + } + + CHECK((i == expected.size())); + } + + // d.seq >= 1 && d.seq < 5 + { + std::vector expected = { + insertedDocs[7].slice(), + insertedDocs[8].slice(), + insertedDocs[9].slice(), + insertedDocs[10].slice(), + }; + auto result = arangodb::tests::executeQuery( + vocbase, + "FOR d IN testView SEARCH IN_RANGE(d.seq, 1, 5, true, false) SORT d.seq RETURN d" + ); + REQUIRE(TRI_ERROR_NO_ERROR == result.code); + auto slice = result.result->slice(); + CHECK(slice.isArray()); + size_t i = 0; + + for (arangodb::velocypack::ArrayIterator itr(slice); itr.valid(); ++itr) { + auto const resolved = itr.value().resolveExternals(); + + CHECK((i < expected.size())); + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(expected[i++], resolved, true))); + } + + CHECK((i == expected.size())); + } + + // d.value > 3 && d.value < 4 + { + std::vector expected = { + insertedDocs[3].slice(), + }; + auto result = arangodb::tests::executeQuery( + vocbase, + "FOR d IN testView SEARCH IN_RANGE(d.value, 3, 4, false, false) SORT d.seq RETURN d" + ); + REQUIRE(TRI_ERROR_NO_ERROR == result.code); + auto slice = result.result->slice(); + CHECK(slice.isArray()); + size_t i = 0; + + for (arangodb::velocypack::ArrayIterator itr(slice); itr.valid(); ++itr) { + auto const resolved = itr.value().resolveExternals(); + + CHECK((i < expected.size())); + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(expected[i++], resolved, true))); + } + + CHECK((i == expected.size())); + } + + // d.value > -4 && d.value < -3 + { + std::vector expected = { + insertedDocs[3].slice(), + }; + auto result = arangodb::tests::executeQuery( + vocbase, + "FOR d IN testView SEARCH IN_RANGE(d.value, -4, -3, false, false) SORT d.seq RETURN d" + ); + REQUIRE(TRI_ERROR_NO_ERROR == result.code); + auto slice = result.result->slice(); + CHECK(slice.isArray()); + size_t i = 0; + + for (arangodb::velocypack::ArrayIterator itr(slice); itr.valid(); ++itr) { + auto const resolved = itr.value().resolveExternals(); + + CHECK((i < expected.size())); + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(expected[i++], resolved, true))); + } + + CHECK((i == expected.size())); + } +} + +// ----------------------------------------------------------------------------- +// --SECTION-- END-OF-FILE +// ----------------------------------------------------------------------------- diff --git a/tests/IResearch/IResearchQueryOptimization-test.cpp b/tests/IResearch/IResearchQueryOptimization-test.cpp new file mode 100644 index 0000000000..45c7c3c6cb --- /dev/null +++ b/tests/IResearch/IResearchQueryOptimization-test.cpp @@ -0,0 +1,8501 @@ +//////////////////////////////////////////////////////////////////////////////// +/// DISCLAIMER +/// +/// Copyright 2017 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 Andrey Abramov +/// @author Vasiliy Nabatchikov +//////////////////////////////////////////////////////////////////////////////// + +#include "catch.hpp" +#include "common.h" + +#include "../Mocks/StorageEngineMock.h" + +#if USE_ENTERPRISE + #include "Enterprise/Ldap/LdapFeature.h" +#endif + +#include "Aql/Ast.h" +#include "Aql/ExpressionContext.h" +#include "Aql/ExecutionNode.h" +#include "Aql/ExecutionPlan.h" +#include "Aql/QueryRegistry.h" +#include "Aql/Query.h" +#include "V8/v8-globals.h" +#include "VocBase/LogicalCollection.h" +#include "VocBase/LogicalView.h" +#include "VocBase/ManagedDocumentResult.h" +#include "Transaction/StandaloneContext.h" +#include "Utils/OperationOptions.h" +#include "Transaction/Methods.h" +#include "Aql/AqlFunctionFeature.h" +#include "Aql/OptimizerRulesFeature.h" +#include "GeneralServer/AuthenticationFeature.h" +#include "IResearch/IResearchCommon.h" +#include "IResearch/IResearchFeature.h" +#include "IResearch/IResearchFilterFactory.h" +#include "IResearch/IResearchView.h" +#include "IResearch/IResearchAnalyzerFeature.h" +#include "Logger/Logger.h" +#include "Logger/LogTopic.h" +#include "StorageEngine/EngineSelectorFeature.h" +#include "RestServer/DatabasePathFeature.h" +#include "RestServer/ViewTypesFeature.h" +#include "RestServer/AqlFeature.h" +#include "RestServer/DatabaseFeature.h" +#include "RestServer/QueryRegistryFeature.h" +#include "RestServer/SystemDatabaseFeature.h" +#include "RestServer/TraverserEngineRegistryFeature.h" +#include "Sharding/ShardingFeature.h" +#include "Basics/VelocyPackHelper.h" +#include "Basics/SmallVector.h" +#include "3rdParty/iresearch/tests/tests_config.hpp" + +#include "IResearch/VelocyPackHelper.h" +#include "analysis/analyzers.hpp" +#include "analysis/token_attributes.hpp" +#include "search/boolean_filter.hpp" +#include "search/term_filter.hpp" +#include "search/range_filter.hpp" +#include "utils/utf8_path.hpp" + +#include + +extern const char* ARGV0; // defined in main.cpp + +NS_LOCAL + +bool findEmptyNodes( + TRI_vocbase_t& vocbase, + std::string const& queryString, + std::shared_ptr bindVars = nullptr +) { + auto options = arangodb::velocypack::Parser::fromJson( +// "{ \"tracing\" : 1 }" + "{ }" + ); + + arangodb::aql::Query query( + false, + vocbase, + arangodb::aql::QueryString(queryString), + bindVars, + options, + arangodb::aql::PART_MAIN + ); + + query.prepare(arangodb::QueryRegistryFeature::registry()); + + arangodb::SmallVector::allocator_type::arena_type a; + arangodb::SmallVector nodes{a}; + + // try to find `EnumerateViewNode`s and process corresponding filters and sorts + query.plan()->findNodesOfType(nodes, arangodb::aql::ExecutionNode::NORESULTS, true); + return !nodes.empty(); +} + +// ----------------------------------------------------------------------------- +// --SECTION-- setup / tear-down +// ----------------------------------------------------------------------------- + +struct IResearchQueryStringTermSetup { + StorageEngineMock engine; + arangodb::application_features::ApplicationServer server; + std::unique_ptr system; + std::vector> features; + + IResearchQueryStringTermSetup(): engine(server), server(nullptr, nullptr) { + arangodb::EngineSelectorFeature::ENGINE = &engine; + arangodb::aql::AqlFunctionFeature* functions = nullptr; + + arangodb::tests::init(true); + + // suppress INFO {authentication} Authentication is turned on (system only), authentication for unix sockets is turned on + arangodb::LogTopic::setLogLevel(arangodb::Logger::AUTHENTICATION.name(), arangodb::LogLevel::WARN); + + // suppress log messages since tests check error conditions + arangodb::LogTopic::setLogLevel(arangodb::Logger::FIXME.name(), arangodb::LogLevel::ERR); // suppress WARNING DefaultCustomTypeHandler called + arangodb::LogTopic::setLogLevel(arangodb::iresearch::TOPIC.name(), arangodb::LogLevel::FATAL); + irs::logger::output_le(iresearch::logger::IRL_FATAL, stderr); + + // setup required application features + features.emplace_back(new arangodb::ViewTypesFeature(server), true); + features.emplace_back(new arangodb::AuthenticationFeature(server), true); + features.emplace_back(new arangodb::DatabasePathFeature(server), false); + features.emplace_back(new arangodb::DatabaseFeature(server), false); + features.emplace_back(new arangodb::ShardingFeature(server), false); + features.emplace_back(new arangodb::QueryRegistryFeature(server), false); // must be first + arangodb::application_features::ApplicationServer::server->addFeature(features.back().first); // need QueryRegistryFeature feature to be added now in order to create the system database + system = irs::memory::make_unique(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 0, TRI_VOC_SYSTEM_DATABASE); + features.emplace_back(new arangodb::SystemDatabaseFeature(server, system.get()), false); // required for IResearchAnalyzerFeature + features.emplace_back(new arangodb::TraverserEngineRegistryFeature(server), false); // must be before AqlFeature + features.emplace_back(new arangodb::AqlFeature(server), true); + features.emplace_back(new arangodb::aql::OptimizerRulesFeature(server), true); + features.emplace_back(functions = new arangodb::aql::AqlFunctionFeature(server), true); // required for IResearchAnalyzerFeature + features.emplace_back(new arangodb::iresearch::IResearchAnalyzerFeature(server), true); + features.emplace_back(new arangodb::iresearch::IResearchFeature(server), true); + + #if USE_ENTERPRISE + features.emplace_back(new arangodb::LdapFeature(server), false); // required for AuthenticationFeature with USE_ENTERPRISE + #endif + + for (auto& f : features) { + arangodb::application_features::ApplicationServer::server->addFeature(f.first); + } + + for (auto& f : features) { + f.first->prepare(); + } + + for (auto& f : features) { + if (f.second) { + f.first->start(); + } + } + + // register fake non-deterministic function in order to suppress optimizations + functions->add(arangodb::aql::Function{ + "_NONDETERM_", + ".", + arangodb::aql::Function::makeFlags( + // fake non-deterministic + arangodb::aql::Function::Flags::CanRunOnDBServer + ), + [](arangodb::aql::ExpressionContext*, arangodb::transaction::Methods*, arangodb::aql::VPackFunctionParameters const& params) { + TRI_ASSERT(!params.empty()); + return params[0]; + }}); + + // register fake non-deterministic function in order to suppress optimizations + functions->add(arangodb::aql::Function{ + "_FORWARD_", + ".", + arangodb::aql::Function::makeFlags( + // fake deterministic + arangodb::aql::Function::Flags::Deterministic, + arangodb::aql::Function::Flags::Cacheable, + arangodb::aql::Function::Flags::CanRunOnDBServer + ), + [](arangodb::aql::ExpressionContext*, arangodb::transaction::Methods*, arangodb::aql::VPackFunctionParameters const& params) { + TRI_ASSERT(!params.empty()); + return params[0]; + }}); + + auto* analyzers = arangodb::application_features::ApplicationServer::lookupFeature< + arangodb::iresearch::IResearchAnalyzerFeature + >(); + + analyzers->emplace("test_analyzer", "TestAnalyzer", "abc"); // cache analyzer + analyzers->emplace("test_csv_analyzer", "TestDelimAnalyzer", ","); // cache analyzer + + auto* dbPathFeature = arangodb::application_features::ApplicationServer::getFeature("DatabasePath"); + arangodb::tests::setDatabasePath(*dbPathFeature); // ensure test data is stored in a unique directory + } + + ~IResearchQueryStringTermSetup() { + system.reset(); // destroy before reseting the 'ENGINE' + arangodb::AqlFeature(server).stop(); // unset singleton instance + arangodb::LogTopic::setLogLevel(arangodb::iresearch::TOPIC.name(), arangodb::LogLevel::DEFAULT); + arangodb::LogTopic::setLogLevel(arangodb::Logger::FIXME.name(), arangodb::LogLevel::DEFAULT); + arangodb::application_features::ApplicationServer::server = nullptr; + arangodb::EngineSelectorFeature::ENGINE = nullptr; + + // destroy application features + for (auto& f : features) { + if (f.second) { + f.first->stop(); + } + } + + for (auto& f : features) { + f.first->unprepare(); + } + + arangodb::LogTopic::setLogLevel(arangodb::Logger::AUTHENTICATION.name(), arangodb::LogLevel::DEFAULT); + } +}; // IResearchQuerySetup + +NS_END + +// ----------------------------------------------------------------------------- +// --SECTION-- test suite +// ----------------------------------------------------------------------------- + +//////////////////////////////////////////////////////////////////////////////// +/// @brief setup +//////////////////////////////////////////////////////////////////////////////// + +// dedicated to https://github.com/arangodb/arangodb/issues/8294 +TEST_CASE("IResearchQueryTestOptimization", "[iresearch][iresearch-query]") { + IResearchQueryStringTermSetup s; + UNUSED(s); + + static std::vector const EMPTY; + + auto createJson = arangodb::velocypack::Parser::fromJson("{ \ + \"name\": \"testView\", \ + \"type\": \"arangosearch\" \ + }"); + + TRI_vocbase_t vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); + std::shared_ptr logicalCollection1; + std::shared_ptr logicalCollection2; + + // add collection_1 + { + auto collectionJson = arangodb::velocypack::Parser::fromJson("{ \"name\": \"collection_1\" }"); + logicalCollection1 = vocbase.createCollection(collectionJson->slice()); + REQUIRE((nullptr != logicalCollection1)); + } + + // add view + auto view = std::dynamic_pointer_cast( + vocbase.createView(createJson->slice()) + ); + REQUIRE((false == !view)); + + // add link to collection + { + auto updateJson = arangodb::velocypack::Parser::fromJson( + "{ \"links\" : {" + "\"collection_1\" : { \"includeAllFields\" : true }" + "}}" + ); + CHECK((view->properties(updateJson->slice(), true).ok())); + + arangodb::velocypack::Builder builder; + + builder.openObject(); + view->properties(builder, true, false); + builder.close(); + + auto slice = builder.slice(); + CHECK(slice.isObject()); + CHECK(slice.get("name").copyString() == "testView"); + CHECK(slice.get("type").copyString() == arangodb::iresearch::DATA_SOURCE_TYPE.name()); + CHECK(slice.get("deleted").isNone()); // no system properties + auto tmpSlice = slice.get("links"); + CHECK((true == tmpSlice.isObject() && 1 == tmpSlice.length())); + } + + std::deque insertedDocs; + + // populate view with the data + { + arangodb::OperationOptions opt; + TRI_voc_tick_t tick; + + arangodb::transaction::Methods trx( + arangodb::transaction::StandaloneContext::Create(vocbase), + EMPTY, EMPTY, EMPTY, + arangodb::transaction::Options() + ); + CHECK((trx.begin().ok())); + + // insert into collection + auto builder = arangodb::velocypack::Parser::fromJson( + "[{ \"values\" : [ \"A\", \"C\", \"B\" ] }]"); + + auto root = builder->slice(); + REQUIRE(root.isArray()); + + for (auto doc : arangodb::velocypack::ArrayIterator(root)) { + insertedDocs.emplace_back(); + auto const res = logicalCollection1->insert(&trx, doc, insertedDocs.back(), opt, tick, false); + CHECK(res.ok()); + } + + CHECK((trx.commit().ok())); + CHECK(view->commit().ok()); + } + + // a IN [ x ] && a == y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values IN [ '@', 'A' ] AND d.values == 'C' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + { + auto& sub = root.add(); + sub.add().field(mangleStringIdentity("values")).term("C"); + sub.add().field(mangleStringIdentity("values")).term("A"); + } + root.add().field(mangleStringIdentity("values")).term("C"); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a IN [ x ] && a == y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values IN [ 'C', 'B', 'A' ] AND d.values == 'A' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + //FIXME + // check structure + { + irs::Or expected; + auto& root = expected.add(); + { + auto& sub = root.add(); + sub.add().field(mangleStringIdentity("values")).term("C"); + sub.add().field(mangleStringIdentity("values")).term("B"); + sub.add().field(mangleStringIdentity("values")).term("A"); + } + root.add().field(mangleStringIdentity("values")).term("A"); + } + //{ + // irs::Or expected; + // auto& root = expected.add(); + // { + // auto& sub = root.add(); + // sub.add().field(mangleStringIdentity("values")).term("C"); + // sub.add().field(mangleStringIdentity("values")).term("B"); + // } + // root.add().field(mangleStringIdentity("values")).term("A"); + //} + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a IN [ x ] && a == y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values IN [ 'C', 'B' ] AND d.values == 'A' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + { + auto& sub = root.add(); + sub.add().field(mangleStringIdentity("values")).term("C"); + sub.add().field(mangleStringIdentity("values")).term("B"); + } + root.add().field(mangleStringIdentity("values")).term("A"); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a IN [ x ] && a == y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values IN [ '@', 'A' ] AND d.values != 'D' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + { + auto& sub = root.add(); + sub.add().field(mangleStringIdentity("values")).term("@"); + sub.add().field(mangleStringIdentity("values")).term("A"); + } + root.add().filter().field(mangleStringIdentity("values")).term("B"); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a IN [ x ] && a == y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values IN [ '@', 'A' ] AND d.values != 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + { + auto& sub = root.add(); + sub.add().field(mangleStringIdentity("values")).term("@"); + sub.add().field(mangleStringIdentity("values")).term("A"); + } + root.add().filter().field(mangleStringIdentity("values")).term("B"); + } + + std::vector expectedDocs { + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a IN [ x ] && a == y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values IN [ 'C', 'D' ] AND d.values != 'D' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // FIXME + // check structure + { + irs::Or expected; + auto& root = expected.add(); + { + auto& sub = root.add(); + sub.add().field(mangleStringIdentity("values")).term("C"); + sub.add().field(mangleStringIdentity("values")).term("A"); + } + root.add().filter().field(mangleStringIdentity("values")).term("A"); + } + //{ + // irs::Or expected; + // auto& root = expected.add(); + // { + // auto& sub = root.add(); + // sub.add().field(mangleStringIdentity("values")).term("C"); + // } + // root.add().filter().field(mangleStringIdentity("values")).term("A"); + //} + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + /* + //FIXME + // a IN [ x ] && a == y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values IN [ 'A', 'A' ] AND d.values != 'A' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(findEmptyNodes(vocbase, query)); + + std::vector expectedDocs { + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + */ + + // a IN [ x ] && a != y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values IN [ 'C', 'B' ] AND d.values != 'A' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + { + auto& sub = root.add(); + sub.add().field(mangleStringIdentity("values")).term("C"); + sub.add().field(mangleStringIdentity("values")).term("B"); + } + root.add().filter().field(mangleStringIdentity("values")).term("A"); + } + + std::vector expectedDocs { + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a IN [ x ] && a != y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values IN [ 'C', 'B' ] AND d.values != '@' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + { + auto& sub = root.add(); + sub.add().field(mangleStringIdentity("values")).term("C"); + sub.add().field(mangleStringIdentity("values")).term("B"); + } + root.add().filter().field(mangleStringIdentity("values")).term("@"); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a IN [ x ] && a < y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values IN [ 'A', 'B' ] AND d.values < 'C' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + //FIXME + // check structure + { + irs::Or expected; + auto& root = expected.add(); + { + auto& sub = root.add(); + sub.add().field(mangleStringIdentity("values")).term("A"); + sub.add().field(mangleStringIdentity("values")).term("B"); + } + root.add() + .field(mangleStringIdentity("values")) + .include(false) + .term("C"); + } + //{ + // irs::Or expected; + // { + // auto& sub = root.add(); + // sub.add().field(mangleStringIdentity("values")).term("A"); + // sub.add().field(mangleStringIdentity("values")).term("B"); + // } + //} + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a IN [ x ] && a < y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values IN [ 'A', 'C' ] AND d.values < 'C' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + { + auto& sub = root.add(); + sub.add().field(mangleStringIdentity("values")).term("A"); + sub.add().field(mangleStringIdentity("values")).term("C"); + } + root.add() + .field(mangleStringIdentity("values")) + .include(false) + .term("C"); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a IN [ x ] && a < y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values IN [ 'D', 'C' ] AND d.values < 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + { + auto& sub = root.add(); + sub.add().field(mangleStringIdentity("values")).term("D"); + sub.add().field(mangleStringIdentity("values")).term("C"); + } + root.add() + .field(mangleStringIdentity("values")) + .include(false) + .term("B"); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a IN [ x ] && a <= y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values IN [ 'B', 'C' ] AND d.values <= 'D' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + //FIXME + // check structure + { + irs::Or expected; + auto& root = expected.add(); + { + auto& sub = root.add(); + sub.add().field(mangleStringIdentity("values")).term("B"); + sub.add().field(mangleStringIdentity("values")).term("C"); + } + root.add() + .field(mangleStringIdentity("values")) + .include(true) + .term("D"); + } + //{ + // irs::Or expected; + // auto& root = expected.add(); + // { + // auto& sub = root.add(); + // sub.add().field(mangleStringIdentity("values")).term("B"); + // sub.add().field(mangleStringIdentity("values")).term("D"); + // } + //} + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a IN [ x ] && a <= y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values IN [ 'B', 'C' ] AND d.values <= 'C' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + //FIXME + // check structure + { + irs::Or expected; + auto& root = expected.add(); + { + auto& sub = root.add(); + sub.add().field(mangleStringIdentity("values")).term("B"); + sub.add().field(mangleStringIdentity("values")).term("C"); + } + root.add() + .field(mangleStringIdentity("values")) + .include(true) + .term("C"); + } + //{ + // irs::Or expected; + // auto& root = expected.add(); + // { + // auto& sub = root.add(); + // sub.add().field(mangleStringIdentity("values")).term("B"); + // sub.add().field(mangleStringIdentity("values")).term("C"); + // } + //} + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a IN [ x ] && a <= y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values IN [ 'B', 'C' ] AND d.values <= 'A' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + { + auto& sub = root.add(); + sub.add().field(mangleStringIdentity("values")).term("B"); + sub.add().field(mangleStringIdentity("values")).term("C"); + } + root.add() + .field(mangleStringIdentity("values")) + .include(true) + .term("A"); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a IN [ x ] && a >= y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values IN [ '@', 'A' ] AND d.values >= 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + { + auto& sub = root.add(); + sub.add().field(mangleStringIdentity("values")).term("@"); + sub.add().field(mangleStringIdentity("values")).term("A"); + } + root.add() + .field(mangleStringIdentity("values")) + .include(true) + .term("B"); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a IN [ x ] && a >= y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values IN [ '@', 'A' ] AND d.values >= 'A' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + //FIXME + // check structure + { + irs::Or expected; + auto& root = expected.add(); + { + auto& sub = root.add(); + sub.add().field(mangleStringIdentity("values")).term("@"); + sub.add().field(mangleStringIdentity("values")).term("A"); + } + root.add() + .field(mangleStringIdentity("values")) + .include(true) + .term("A"); + } + //{ + // irs::Or expected; + // auto& root = expected.add(); + // root.add().field(mangleStringIdentity("values")).term("A"); + //} + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a IN [ x ] && a >= y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values IN [ 'C', 'D' ] AND d.values >= 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + //FIXME + // check structure + { + irs::Or expected; + auto& root = expected.add(); + { + auto& sub = root.add(); + sub.add().field(mangleStringIdentity("values")).term("C"); + sub.add().field(mangleStringIdentity("values")).term("D"); + } + root.add() + .field(mangleStringIdentity("values")) + .include(true) + .term("B"); + } + //{ + // irs::Or expected; + // auto& root = expected.add(); + // { + // auto& sub = root.add(); + // sub.add().field(mangleStringIdentity("values")).term("C"); + // sub.add().field(mangleStringIdentity("values")).term("D"); + // } + //} + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a IN [ x ] && a > y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values IN [ '@', 'A' ] AND d.values > 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + { + auto& sub = root.add(); + sub.add().field(mangleStringIdentity("values")).term("@"); + sub.add().field(mangleStringIdentity("values")).term("A"); + } + root.add() + .field(mangleStringIdentity("values")) + .include(false) + .term("B"); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a IN [ x ] && a > y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values IN [ 'C', 'B' ] AND d.values > 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + //FIXME + // check structure + { + irs::Or expected; + auto& root = expected.add(); + { + auto& sub = root.add(); + sub.add().field(mangleStringIdentity("values")).term("C"); + sub.add().field(mangleStringIdentity("values")).term("B"); + } + root.add() + .field(mangleStringIdentity("values")) + .include(false) + .term("B"); + } + //{ + // irs::Or expected; + // auto& root = expected.add(); + // root.add().field(mangleStringIdentity("values")).term("C"); + //} + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a IN [ x ] && a > y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values IN [ 'C', 'D' ] AND d.values > 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + //FIXME + // check structure + { + irs::Or expected; + auto& root = expected.add(); + { + auto& sub = root.add(); + sub.add().field(mangleStringIdentity("values")).term("C"); + sub.add().field(mangleStringIdentity("values")).term("D"); + } + root.add() + .field(mangleStringIdentity("values")) + .include(false) + .term("B"); + } + //{ + // irs::Or expected; + // auto& root = expected.add(); + // { + // auto& sub = root.add(); + // sub.add().field(mangleStringIdentity("values")).term("C"); + // sub.add().field(mangleStringIdentity("values")).term("D"); + // } + //} + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + + // a IN [ x ] && a IN [ y ] + { + std::string const query = + "FOR d IN testView SEARCH d.values IN [ 'A', 'B' ] AND d.values IN [ 'A', 'B', 'C' ] RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // FIXME optimize + // check structure + { + irs::Or expected; + auto& root = expected.add(); + { + auto& sub = root.add(); + sub.add().field(mangleStringIdentity("values")).term("A"); + sub.add().field(mangleStringIdentity("values")).term("B"); + } + { + auto& sub = root.add(); + sub.add().field(mangleStringIdentity("values")).term("A"); + sub.add().field(mangleStringIdentity("values")).term("B"); + sub.add().field(mangleStringIdentity("values")).term("C"); + } + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + + // a IN [ x ] && a == y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values IN [ 'B' ] AND d.values == 'C' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).term("B"); + root.add().field(mangleStringIdentity("values")).term("C"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a IN [ x ] && a == y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values IN [ 'C' ] AND d.values == 'C' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).term("C"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a IN [ x ] && a == y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values IN [ 'C' ] AND d.values == 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).term("C"); + root.add().field(mangleStringIdentity("values")).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a IN [ x ] && a != y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values IN [ 'A' ] AND d.values != 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("B"); + root.add().field(mangleStringIdentity("values")).term("A"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a IN [ x ] && a != y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values IN [ 'C' ] AND d.values != 'C' RETURN d"; + + //FIXME + //CHECK(arangodb::tests::assertRules( + // vocbase, query, { + // arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + // } + //)); + + CHECK(findEmptyNodes(vocbase, query)); + + std::vector expectedDocs { + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a IN [ x ] && a != y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values IN ['B'] AND d.values != 'C' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("C"); + root.add().field(mangleStringIdentity("values")).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a IN [ x ] && a < y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values IN [ 'B' ] AND d.values < 'C' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a IN [ x ] && a < y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values IN [ 'C' ] AND d.values < 'C' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).term("C"); + root.add().field(mangleStringIdentity("values")).include(false).term("C"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a IN [ x ] && a < y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values IN [ 'C' ] AND d.values < 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).term("C"); + root.add().field(mangleStringIdentity("values")).include(false).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a IN [ x ] && a <= y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values IN [ 'B' ] AND d.values <= 'C' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a IN [x] && a <= y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values IN [ 'B' ] AND d.values <= 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a IN [ x ] && a <= y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values IN [ 'C' ] AND d.values <= 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).term("C"); + root.add().field(mangleStringIdentity("values")).include(true).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a IN [ x ] && a >= y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values IN [ 'A' ] AND d.values >= 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(true).term("B"); + root.add().field(mangleStringIdentity("values")).term("A"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a IN [ x ] && a >= y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values IN [ 'B' ] AND d.values >= 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a IN [x] && a >= y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values IN ['C'] AND d.values >= 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).term("C"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a IN [x] && a > y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values IN ['A'] AND d.values > 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(false).term("B"); + root.add().field(mangleStringIdentity("values")).term("A"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a IN [x] && a > y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values IN ['B'] AND d.values > 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(false).term("B"); + root.add().field(mangleStringIdentity("values")).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a IN [x] && a > y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values IN ['C'] AND d.values > 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).term("C"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a == x && a == y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values == 'B' AND d.values == 'C' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).term("B"); + root.add().field(mangleStringIdentity("values")).term("C"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a == x && a == y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values == 'C' AND d.values == 'C' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).term("C"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a == x && a == y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values == 'C' AND d.values == 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).term("C"); + root.add().field(mangleStringIdentity("values")).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a == x && a != y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values == 'A' AND d.values != 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("B"); + root.add().field(mangleStringIdentity("values")).term("A"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a == x && a != y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values == 'C' AND d.values != 'C' RETURN d"; + + //FIXME + //CHECK(arangodb::tests::assertRules( + // vocbase, query, { + // arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + // } + //)); + + CHECK(findEmptyNodes(vocbase, query)); + + std::vector expectedDocs { + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a == x && a != y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values == 'B' AND d.values != 'C' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("C"); + root.add().field(mangleStringIdentity("values")).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a == x && a < y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values == 'B' AND d.values < 'C' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a == x && a < y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values == 'C' AND d.values < 'C' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).term("C"); + root.add().field(mangleStringIdentity("values")).include(false).term("C"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a == x && a < y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values == 'C' AND d.values < 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).term("C"); + root.add().field(mangleStringIdentity("values")).include(false).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a == x && a <= y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values == 'B' AND d.values <= 'C' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a == x && a <= y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values == 'B' AND d.values <= 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a == x && a <= y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values == 'C' AND d.values <= 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).term("C"); + root.add().field(mangleStringIdentity("values")).include(true).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a == x && a >= y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values == 'A' AND d.values >= 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(true).term("B"); + root.add().field(mangleStringIdentity("values")).term("A"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a == x && a >= y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values == 'B' AND d.values >= 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a == x && a >= y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values == 'C' AND d.values >= 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).term("C"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a == x && a > y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values == 'A' AND d.values > 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(false).term("B"); + root.add().field(mangleStringIdentity("values")).term("A"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a == x && a > y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values == 'B' AND d.values > 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(false).term("B"); + root.add().field(mangleStringIdentity("values")).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a == x && a > y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values == 'C' AND d.values > 'A' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).term("C"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a != x && a == y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values != '@' AND d.values == 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("@"); + root.add().field(mangleStringIdentity("values")).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a != x && a == y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values != 'A' AND d.values == 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("A"); + root.add().field(mangleStringIdentity("values")).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a != x && a == y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values != 'A' AND d.values == 'A' RETURN d"; + + //FIXME + //CHECK(arangodb::tests::assertRules( + // vocbase, query, { + // arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + // } + //)); + + CHECK(findEmptyNodes(vocbase, query)); + + std::vector expectedDocs { + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a != x && a == y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values != 'D' AND d.values == 'A' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("D"); + root.add().field(mangleStringIdentity("values")).term("A"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a != x && a == y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values != 'B' AND d.values == 'A' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("B"); + root.add().field(mangleStringIdentity("values")).term("A"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a != x && a != y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values != '@' AND d.values != 'D' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("@"); + root.add().filter().field(mangleStringIdentity("values")).term("D"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a != x && a != y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values != 'A' AND d.values != 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("A"); + root.add().filter().field(mangleStringIdentity("values")).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a != x && a != y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values != 'D' AND d.values != 'D' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("D"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a != x && a != y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values != 'A' AND d.values != 'A' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("A"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a != x && a != y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values != 'B' AND d.values != 'A' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("B"); + root.add().filter().field(mangleStringIdentity("values")).term("A"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a != x && a < y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values != '0' AND d.values < 'C' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("0"); + root.add().field(mangleStringIdentity("values")).include(false).term("C"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a != x && a < y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values != 'A' AND d.values < 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("A"); + root.add().field(mangleStringIdentity("values")).include(false).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a != x && a < y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values != '@' AND d.values < 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("@"); + root.add().field(mangleStringIdentity("values")).include(false).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a != x && a < y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values != 'D' AND d.values < 'D' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("D"); + root.add().field(mangleStringIdentity("values")).include(false).term("D"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a != x && a < y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values != 'D' AND d.values < 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("D"); + root.add().field(mangleStringIdentity("values")).include(false).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a != x && a < y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values != 'C' AND d.values < 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("C"); + root.add().field(mangleStringIdentity("values")).include(false).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a != x && a <= y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values != '0' AND d.values <= 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("0"); + root.add().field(mangleStringIdentity("values")).include(true).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a != x && a <= y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values != 'A' AND d.values <= 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("A"); + root.add().field(mangleStringIdentity("values")).include(true).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a != x && a <= y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values != 'D' AND d.values <= 'D' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("D"); + root.add().field(mangleStringIdentity("values")).include(true).term("D"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a != x && a <= y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values != 'B' AND d.values <= 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("B"); + root.add().field(mangleStringIdentity("values")).include(true).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a != x && a <= y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values != 'D' AND d.values <= 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("D"); + root.add().field(mangleStringIdentity("values")).include(true).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a != x && a <= y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values != 'C' AND d.values <= 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("C"); + root.add().field(mangleStringIdentity("values")).include(true).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a != x && a >= y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values != '0' AND d.values >= 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("0"); + root.add().field(mangleStringIdentity("values")).include(true).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a != x && a >= y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values != 'A' AND d.values >= 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("A"); + root.add().field(mangleStringIdentity("values")).include(true).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a != x && a >= y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values != '0' AND d.values >= '0' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("0"); + root.add().field(mangleStringIdentity("values")).include(true).term("0"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a != x && a >= y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values != 'A' AND d.values >= 'A' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("A"); + root.add().field(mangleStringIdentity("values")).include(true).term("A"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a != x && a >= y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values != 'D' AND d.values >= 'A' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("D"); + root.add().field(mangleStringIdentity("values")).include(true).term("A"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a != x && a >= y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values != 'C' AND d.values >= 'A' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("C"); + root.add().field(mangleStringIdentity("values")).include(true).term("A"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a != x && a > y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values != '0' AND d.values > 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("0"); + root.add().field(mangleStringIdentity("values")).include(false).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a != x && a > y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values != 'A' AND d.values > 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("A"); + root.add().field(mangleStringIdentity("values")).include(false).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a != x && a > y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values != '0' AND d.values > '0' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("0"); + root.add().field(mangleStringIdentity("values")).include(false).term("0"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a != x && a > y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values != 'A' AND d.values > 'A' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("A"); + root.add().field(mangleStringIdentity("values")).include(false).term("A"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a != x && a > y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values != 'D' AND d.values > 'A' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("D"); + root.add().field(mangleStringIdentity("values")).include(false).term("A"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a != x && a > y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values != 'C' AND d.values > 'A' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("C"); + root.add().field(mangleStringIdentity("values")).include(false).term("A"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a < x && a == y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values < 'B' AND d.values == 'C' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).term("C"); + root.add().field(mangleStringIdentity("values")).include(false).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a < x && a == y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values < 'B' AND d.values == 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).term("B"); + root.add().field(mangleStringIdentity("values")).include(false).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a < x && a == y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values < 'C' AND d.values == 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a < x && a != y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values < 'B' AND d.values != 'D' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("D"); + root.add().field(mangleStringIdentity("values")).include(false).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a < x && a != y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values < 'B' AND d.values != 'C' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("C"); + root.add().field(mangleStringIdentity("values")).include(false).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a < x && a != y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values < 'D' AND d.values != 'D' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("D"); + root.add().field(mangleStringIdentity("values")).include(false).term("D"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a < x && a != y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values < 'B' AND d.values != 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("B"); + root.add().field(mangleStringIdentity("values")).include(false).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a < x && a != y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values < 'C' AND d.values != '0' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("0"); + root.add().field(mangleStringIdentity("values")).include(false).term("C"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a < x && a != y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values < 'C' AND d.values != 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("B"); + root.add().field(mangleStringIdentity("values")).include(false).term("C"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a < x && a < y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values < 'B' AND d.values < 'C' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(false).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a < x && a < y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values < 'B' AND d.values < 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(false).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a < x && a < y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values < 'C' AND d.values < 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(false).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a < x && a <= y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values < 'B' AND d.values <= 'C' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(false).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a < x && a <= y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values < 'C' AND d.values <= 'C' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(false).term("C"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a < x && a <= y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values < 'C' AND d.values <= 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(true).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a < x && a >= y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values < 'B' AND d.values >= 'C' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(true).term("C"); + root.add().field(mangleStringIdentity("values")).include(false).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a < x && a >= y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values < 'B' AND d.values >= 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(true).term("B"); + root.add().field(mangleStringIdentity("values")).include(false).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a < x && a >= y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values < 'C' AND d.values >= 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(true).term("B"); + root.add().field(mangleStringIdentity("values")).include(false).term("C"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a < x && a > y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values < 'B' AND d.values > 'C' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(false).term("C"); + root.add().field(mangleStringIdentity("values")).include(false).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a < x && a > y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values < 'B' AND d.values > 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(false).term("B"); + root.add().field(mangleStringIdentity("values")).include(false).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a < x && a > y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values < 'C' AND d.values > 'A' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(false).term("A"); + root.add().field(mangleStringIdentity("values")).include(false).term("C"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a <= x && a == y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values <= 'A' AND d.values == 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).term("B"); + root.add().field(mangleStringIdentity("values")).include(true).term("A"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a <= x && a == y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values <= 'A' AND d.values == 'A' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).term("A"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a <= x && a == y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values <= 'B' AND d.values == 'A' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).term("A"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a <= x && a != y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values <= 'A' AND d.values != 'D' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("D"); + root.add().field(mangleStringIdentity("values")).include(true).term("A"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a <= x && a != y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values <= 'A' AND d.values != 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("B"); + root.add().field(mangleStringIdentity("values")).include(true).term("A"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a <= x && a != y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values <= 'B' AND d.values != 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("B"); + root.add().field(mangleStringIdentity("values")).include(true).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a <= x && a != y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values <= 'D' AND d.values != 'D' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("D"); + root.add().field(mangleStringIdentity("values")).include(true).term("D"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a <= x && a != y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values <= 'C' AND d.values != '@' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("@"); + root.add().field(mangleStringIdentity("values")).include(true).term("C"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a <= x && a != y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values <= 'C' AND d.values != 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("B"); + root.add().field(mangleStringIdentity("values")).include(true).term("C"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a <= x && a < y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values <= 'A' AND d.values < 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(true).term("A"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a <= x && a < y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values <= 'B' AND d.values < 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(false).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a <= x && a < y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values <= 'C' AND d.values < 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(false).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a <= x && a <= y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values <= 'A' AND d.values <= 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(true).term("A"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a <= x && a <= y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values <= 'B' AND d.values <= 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(true).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a <= x && a <= y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values <= 'C' AND d.values <= 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(true).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a <= x && a >= y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values <= 'A' AND d.values >= 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(true).term("B"); + root.add().field(mangleStringIdentity("values")).include(true).term("A"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a <= x && a >= y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values <= 'A' AND d.values >= 'A' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(true).term("A"); + root.add().field(mangleStringIdentity("values")).include(true).term("A"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a <= x && a >= y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values <= 'C' AND d.values >= 'A' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(true).term("A"); + root.add().field(mangleStringIdentity("values")).include(true).term("C"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a <= x && a > y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values <= 'A' AND d.values > 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(false).term("B"); + root.add().field(mangleStringIdentity("values")).include(true).term("A"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a <= x && a > y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values <= 'A' AND d.values > 'A' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(false).term("A"); + root.add().field(mangleStringIdentity("values")).include(true).term("A"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a <= x && a > y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values <= 'C' AND d.values > 'A' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(false).term("A"); + root.add().field(mangleStringIdentity("values")).include(true).term("C"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a >= x && a == y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values >= 'A' AND d.values == 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a >= x && a == y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values >= 'A' AND d.values == 'A' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).term("A"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a >= x && a == y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values >= 'B' AND d.values == 'A' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(true).term("B"); + root.add().field(mangleStringIdentity("values")).term("A"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a >= x && a != y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values >= 'A' AND d.values != 'D' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("D"); + root.add().field(mangleStringIdentity("values")).include(true).term("A"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a >= x && a != y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values >= 'A' AND d.values != 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("B"); + root.add().field(mangleStringIdentity("values")).include(true).term("A"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a >= x && a != y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values >= '@' AND d.values != '@' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("@"); + root.add().field(mangleStringIdentity("values")).include(true).term("@"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a >= x && a != y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values >= 'A' AND d.values != 'A' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("A"); + root.add().field(mangleStringIdentity("values")).include(true).term("A"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a >= x && a != y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values >= 'B' AND d.values != 'D' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("D"); + root.add().field(mangleStringIdentity("values")).include(true).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a >= x && a != y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values >= 'B' AND d.values != 'A' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("A"); + root.add().field(mangleStringIdentity("values")).include(true).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a >= x && a < y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values >= 'A' AND d.values < 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(true).term("A"); + root.add().field(mangleStringIdentity("values")).include(false).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a >= x && a < y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values >= 'B' AND d.values < 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(true).term("B"); + root.add().field(mangleStringIdentity("values")).include(false).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a >= x && a < y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values >= 'C' AND d.values < 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(true).term("C"); + root.add().field(mangleStringIdentity("values")).include(false).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a >= x && a <= y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values >= 'A' AND d.values <= 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(true).term("A"); + root.add().field(mangleStringIdentity("values")).include(true).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a >= x && a <= y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values >= 'B' AND d.values <= 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(true).term("B"); + root.add().field(mangleStringIdentity("values")).include(true).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a >= x && a <= y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values >= 'C' AND d.values <= 'A' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(true).term("C"); + root.add().field(mangleStringIdentity("values")).include(true).term("A"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a >= x && a >= y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values >= 'A' AND d.values >= 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(true).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a >= x && a >= y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values >= 'B' AND d.values >= 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(true).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a >= x && a >= y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values >= 'C' AND d.values >= 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(true).term("C"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a >= x && a > y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values >= 'A' AND d.values > 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(false).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a >= x && a > y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values >= 'B' AND d.values > 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(false).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a >= x && a > y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values >= 'B' AND d.values > 'A' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(true).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a > x && a == y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values > 'A' AND d.values == 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a > x && a == y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values > 'B' AND d.values == 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(false).term("B"); + root.add().field(mangleStringIdentity("values")).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a > x && a == y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values > 'B' AND d.values == 'A' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(false).term("B"); + root.add().field(mangleStringIdentity("values")).term("A"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a > x && a != y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values > 'A' AND d.values != 'D' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("D"); + root.add().field(mangleStringIdentity("values")).include(false).term("A"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a > x && a != y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values > 'A' AND d.values != 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("B"); + root.add().field(mangleStringIdentity("values")).include(false).term("A"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a > x && a != y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values > '@' AND d.values != '@' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("@"); + root.add().field(mangleStringIdentity("values")).include(false).term("@"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a > x && a != y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values > 'A' AND d.values != 'A' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("A"); + root.add().field(mangleStringIdentity("values")).include(false).term("A"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a > x && a != y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values > 'B' AND d.values != '@' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("@"); + root.add().field(mangleStringIdentity("values")).include(false).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a > x && a != y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values > 'B' AND d.values != 'A' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().filter().field(mangleStringIdentity("values")).term("A"); + root.add().field(mangleStringIdentity("values")).include(false).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a > x && a < y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values > 'A' AND d.values < 'C' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(false).term("A"); + root.add().field(mangleStringIdentity("values")).include(false).term("C"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a > x && a < y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values > 'B' AND d.values < 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(false).term("B"); + root.add().field(mangleStringIdentity("values")).include(false).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + CHECK(!findEmptyNodes(vocbase, query)); + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a > x && a < y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values > 'C' AND d.values < 'A' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(false).term("C"); + root.add().field(mangleStringIdentity("values")).include(false).term("A"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a > x && a <= y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values > 'A' AND d.values <= 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(false).term("A"); + root.add().field(mangleStringIdentity("values")).include(true).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a > x && a <= y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values > 'B' AND d.values <= 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(false).term("B"); + root.add().field(mangleStringIdentity("values")).include(true).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a > x && a <= y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values > 'B' AND d.values <= 'A' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(false).term("B"); + root.add().field(mangleStringIdentity("values")).include(true).term("A"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a > x && a >= y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values > 'A' AND d.values >= 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(true).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a > x && a >= y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values > 'B' AND d.values >= 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(false).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a > x && a >= y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values > 'B' AND d.values >= 'A' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(false).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a > x && a > y, x < y + { + std::string const query = + "FOR d IN testView SEARCH d.values > 'A' AND d.values > 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(false).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a > x && a > y, x == y + { + std::string const query = + "FOR d IN testView SEARCH d.values > 'B' AND d.values > 'B' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(false).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } + + // a > x && a > y, x > y + { + std::string const query = + "FOR d IN testView SEARCH d.values > 'B' AND d.values > 'A' RETURN d"; + + CHECK(arangodb::tests::assertRules( + vocbase, query, { + arangodb::aql::OptimizerRule::handleArangoSearchViewsRule + } + )); + + CHECK(!findEmptyNodes(vocbase, query)); + + // check structure + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("values")).include(false).term("B"); + assertFilterOptimized(vocbase, query, expected); + } + + std::vector expectedDocs { + arangodb::velocypack::Slice(insertedDocs[0].vpack()), + }; + + auto queryResult = arangodb::tests::executeQuery(vocbase, query); + REQUIRE(TRI_ERROR_NO_ERROR == queryResult.code); + + auto result = queryResult.result->slice(); + CHECK(result.isArray()); + + arangodb::velocypack::ArrayIterator resultIt(result); + REQUIRE(expectedDocs.size() == resultIt.size()); + + // Check documents + auto expectedDoc = expectedDocs.begin(); + for (;resultIt.valid(); resultIt.next(), ++expectedDoc) { + auto const actualDoc = resultIt.value(); + auto const resolved = actualDoc.resolveExternals(); + + CHECK((0 == arangodb::basics::VelocyPackHelper::compare(arangodb::velocypack::Slice(*expectedDoc), resolved, true))); + } + CHECK(expectedDoc == expectedDocs.end()); + } +} + +// ----------------------------------------------------------------------------- +// --SECTION-- END-OF-FILE +// ----------------------------------------------------------------------------- diff --git a/tests/IResearch/IResearchQueryScorer-test.cpp b/tests/IResearch/IResearchQueryScorer-test.cpp index 1314139a7c..36f3118a0a 100644 --- a/tests/IResearch/IResearchQueryScorer-test.cpp +++ b/tests/IResearch/IResearchQueryScorer-test.cpp @@ -366,7 +366,7 @@ TEST_CASE("IResearchQueryScorer", "[iresearch][iresearch-query]") { "LET arr = [0,1] " "FOR i in 0..1 " " LET rnd = _NONDETERM_(i) " - " FOR d IN testView SEARCH d.name >= 'A' AND d.name <= 'C' " + " FOR d IN testView SEARCH IN_RANGE(d.name, 'A', 'C', true, true) " "LIMIT 10 " "RETURN { d, score: d.seq + 3*customscorer(d, arr[TO_NUMBER(rnd != 0)]) }"; @@ -478,7 +478,7 @@ TEST_CASE("IResearchQueryScorer", "[iresearch][iresearch-query]") { { std::string const queryString = "LET i = 1" - "FOR d IN testView SEARCH d.name >= 'A' AND d.name < 'B' " + "FOR d IN testView SEARCH IN_RANGE(d.name, 'A', 'B', true, false) " "RETURN [ customscorer(d, i), customscorer(d, 1) ] "; CHECK(arangodb::tests::assertRules( @@ -584,7 +584,7 @@ TEST_CASE("IResearchQueryScorer", "[iresearch][iresearch-query]") { { std::string const queryString = "LET obj = _NONDETERM_({ value : 2 }) " - "FOR d IN testView SEARCH d.name >= 'A' AND d.name <= 'C' " + "FOR d IN testView SEARCH IN_RANGE(d.name, 'A', 'C', true, true) " "RETURN [ customscorer(d, obj.value), customscorer(d, obj.value) ] "; CHECK(arangodb::tests::assertRules( @@ -695,7 +695,7 @@ TEST_CASE("IResearchQueryScorer", "[iresearch][iresearch-query]") { { std::string const queryString = "LET obj = _NONDETERM_({ value : 2 }) " - "FOR d IN testView SEARCH d.name >= 'A' AND d.name <= 'C' " + "FOR d IN testView SEARCH IN_RANGE(d.name, 'A', 'C', true, true) " "RETURN [ customscorer(d, obj.value+1), customscorer(d, obj.value+1) ] "; CHECK(arangodb::tests::assertRules( @@ -806,7 +806,7 @@ TEST_CASE("IResearchQueryScorer", "[iresearch][iresearch-query]") { { std::string const queryString = "LET obj = _NONDETERM_([ 2, 5 ]) " - "FOR d IN testView SEARCH d.name >= 'A' AND d.name <= 'C' " + "FOR d IN testView SEARCH IN_RANGE(d.name, 'A', 'C', true, true) " "RETURN [ customscorer(d, obj[1]), customscorer(d, obj[1]) ] "; CHECK(arangodb::tests::assertRules( @@ -917,7 +917,7 @@ TEST_CASE("IResearchQueryScorer", "[iresearch][iresearch-query]") { { std::string const queryString = "LET obj = _NONDETERM_([ 2, 5 ]) " - "FOR d IN testView SEARCH d.name >= 'A' AND d.name <= 'C' " + "FOR d IN testView SEARCH IN_RANGE(d.name, 'A', 'C', true, true) " "RETURN [ customscorer(d, obj[0] > obj[1] ? 1 : 2), customscorer(d, obj[0] > obj[1] ? 1 : 2) ] "; CHECK(arangodb::tests::assertRules( @@ -1028,7 +1028,7 @@ TEST_CASE("IResearchQueryScorer", "[iresearch][iresearch-query]") { { std::string const queryString = "LET obj = _NONDETERM_([ 2, 5 ]) " - "FOR d IN testView SEARCH d.name >= 'A' AND d.name <= 'C' " + "FOR d IN testView SEARCH IN_RANGE(d.name, 'A', 'C', true, true) " "RETURN [ customscorer(d, obj[0] > obj[1] ? 1 : 2), customscorer(d, obj[1] > obj[2] ? 1 : 2) ] "; CHECK(arangodb::tests::assertRules( @@ -1148,7 +1148,7 @@ TEST_CASE("IResearchQueryScorer", "[iresearch][iresearch-query]") { { std::string const queryString = "LET obj = _NONDETERM_([ 2, 5 ]) " - "FOR d IN testView SEARCH d.name >= 'A' AND d.name <= 'C' " + "FOR d IN testView SEARCH IN_RANGE(d.name, 'A', 'C', true, true) " "RETURN [ customscorer(d, 5*obj[0]*TO_NUMBER(obj[1] > obj[2])/obj[1] - 1), customscorer(d, 5*obj[0]*TO_NUMBER(obj[1] > obj[2])/obj[1] - 1) ] "; CHECK(arangodb::tests::assertRules( @@ -1259,7 +1259,7 @@ TEST_CASE("IResearchQueryScorer", "[iresearch][iresearch-query]") { { std::string const queryString = "LET obj = _NONDETERM_([ 2, 5 ]) " - "FOR d IN testView SEARCH d.name >= 'A' AND d.name <= 'C' " + "FOR d IN testView SEARCH IN_RANGE(d.name, 'A', 'C', true, true) " "RETURN [ customscorer(d, { [ CONCAT(obj[0], obj[1]) ] : 1 }), customscorer(d, { [ CONCAT(obj[0], obj[1]) ] : 1 }) ]"; CHECK(arangodb::tests::assertRules( @@ -1346,7 +1346,7 @@ TEST_CASE("IResearchQueryScorer", "[iresearch][iresearch-query]") { { std::string const queryString = "LET obj = _NONDETERM_([ 2, 5 ]) " - "FOR d IN testView SEARCH d.name >= 'A' AND d.name <= 'C' " + "FOR d IN testView SEARCH IN_RANGE(d.name, 'A', 'C', true, true) " "RETURN [ customscorer(d, { foo : obj[1] }), customscorer(d, { foo : obj[1] }) ]"; CHECK(arangodb::tests::assertRules( @@ -1433,7 +1433,7 @@ TEST_CASE("IResearchQueryScorer", "[iresearch][iresearch-query]") { { std::string const queryString = "LET obj = _NONDETERM_([ 2, 5 ]) " - "FOR d IN testView SEARCH d.name >= 'A' AND d.name <= 'C' " + "FOR d IN testView SEARCH IN_RANGE(d.name, 'A', 'C', true, true) " "RETURN [ customscorer(d, 5*obj[0]*TO_NUMBER(obj[1] > obj[2])/obj[1] - 1), customscorer(d, 5*obj[0]*TO_NUMBER(obj[1] > obj[2])/obj[1] - 2) ] "; CHECK(arangodb::tests::assertRules( @@ -1553,7 +1553,7 @@ TEST_CASE("IResearchQueryScorer", "[iresearch][iresearch-query]") { { std::string const queryString = "LET obj = _NONDETERM_([ 2, 5 ]) " - "FOR d IN testView SEARCH d.name >= 'A' AND d.name <= 'C' " + "FOR d IN testView SEARCH IN_RANGE(d.name, 'A', 'C', true, true) " "RETURN [ customscorer(d, obj any == 3), customscorer(d, obj any == 3) ]"; CHECK(arangodb::tests::assertRules( @@ -1640,7 +1640,7 @@ TEST_CASE("IResearchQueryScorer", "[iresearch][iresearch-query]") { { std::string const queryString = "LET obj = _NONDETERM_([ 2, 5 ]) " - "FOR d IN testView SEARCH d.name >= 'A' AND d.name <= 'C' " + "FOR d IN testView SEARCH IN_RANGE(d.name, 'A', 'C', true, true) " "RETURN [ customscorer(d, obj any == 3), customscorer(d, obj all == 3) ]"; CHECK(arangodb::tests::assertRules( @@ -1724,7 +1724,7 @@ TEST_CASE("IResearchQueryScorer", "[iresearch][iresearch-query]") { // con't deduplicate scorers with default values { std::string const queryString = - "FOR d IN testView SEARCH d.name >= 'A' AND d.name <= 'C' " + "FOR d IN testView SEARCH IN_RANGE(d.name, 'A', 'C', true, true) " "RETURN [ tfidf(d), tfidf(d, false) ] "; CHECK(arangodb::tests::assertRules( @@ -1828,4 +1828,4 @@ TEST_CASE("IResearchQueryScorer", "[iresearch][iresearch-query]") { // ----------------------------------------------------------------------------- // --SECTION-- END-OF-FILE -// ----------------------------------------------------------------------------- \ No newline at end of file +// ----------------------------------------------------------------------------- diff --git a/tests/IResearch/common.cpp b/tests/IResearch/common.cpp index d586ee83c5..00bdd415b9 100644 --- a/tests/IResearch/common.cpp +++ b/tests/IResearch/common.cpp @@ -42,6 +42,7 @@ #include "IResearch/AqlHelper.h" #include "IResearch/ExpressionFilter.h" #include "IResearch/IResearchFilterFactory.h" +#include "IResearch/IResearchViewNode.h" #include "IResearch/IResearchKludge.h" #include "IResearch/VelocyPackHelper.h" #include "tests/Basics/icu-helper.h" @@ -440,6 +441,59 @@ std::string mangleStringIdentity(std::string name) { return name; } +void assertFilterOptimized( + TRI_vocbase_t& vocbase, + std::string const& queryString, + irs::filter const& expectedFilter, + arangodb::aql::ExpressionContext* exprCtx /*= nullptr*/, + std::shared_ptr bindVars /* = nullptr */ +) { + auto options = arangodb::velocypack::Parser::fromJson( +// "{ \"tracing\" : 1 }" + "{ }" + ); + + arangodb::aql::Query query( + false, + vocbase, + arangodb::aql::QueryString(queryString), + bindVars, + options, + arangodb::aql::PART_MAIN + ); + + query.prepare(arangodb::QueryRegistryFeature::registry()); + CHECK(query.plan()); + auto& plan = *query.plan(); + + arangodb::SmallVector::allocator_type::arena_type a; + arangodb::SmallVector nodes{a}; + plan.findNodesOfType(nodes, arangodb::aql::ExecutionNode::ENUMERATE_IRESEARCH_VIEW, true); + + CHECK(nodes.size() == 1); + + auto* viewNode = arangodb::aql::ExecutionNode::castTo(nodes.front()); + + CHECK(viewNode); + + // execution time + { + arangodb::transaction ::Methods trx( + arangodb::transaction::StandaloneContext::Create(vocbase), + {}, + {}, + {}, + arangodb::transaction::Options() + ); + + irs::Or actualFilter; + arangodb::iresearch::QueryContext const ctx{ &trx, &plan, plan.getAst(), exprCtx, &viewNode->outVariable() }; + CHECK(arangodb::iresearch::FilterFactory::filter(&actualFilter, ctx, viewNode->filterCondition())); + CHECK(!actualFilter.empty()); + CHECK(expectedFilter == *actualFilter.begin()); + } +} + void assertExpressionFilter( std::string const& queryString, irs::boost::boost_t boost /*= irs::boost::no_boost()*/, diff --git a/tests/IResearch/common.h b/tests/IResearch/common.h index ab3fc3f017..8cc532e67e 100644 --- a/tests/IResearch/common.h +++ b/tests/IResearch/common.h @@ -132,6 +132,14 @@ void assertFilterBoost( irs::filter const& actual ); +void assertFilterOptimized( + TRI_vocbase_t& vocbase, + std::string const& queryString, + irs::filter const& expectedFilter, + arangodb::aql::ExpressionContext* exprCtx = nullptr, + std::shared_ptr bindVars = nullptr +); + void assertFilter( bool parseOk, bool execOk, @@ -169,4 +177,4 @@ void assertFilterParseFail( std::shared_ptr bindVars = nullptr ); -#endif \ No newline at end of file +#endif diff --git a/tests/js/common/aql/aql-view-arangosearch-cluster.js b/tests/js/common/aql/aql-view-arangosearch-cluster.js index 24c089ace6..cc43592baf 100644 --- a/tests/js/common/aql/aql-view-arangosearch-cluster.js +++ b/tests/js/common/aql/aql-view-arangosearch-cluster.js @@ -834,6 +834,24 @@ function IResearchAqlTestSuite(args) { linksView.drop(); entities.drop(); links.drop(); + }, + + testAttributeInRangeOpenInterval : function () { + var result = db._query("FOR doc IN UnitTestsView SEARCH IN_RANGE(doc.c, 1, 3, false, false) OPTIONS { waitForSync : true } RETURN doc").toArray(); + + assertEqual(result.length, 4); + result.forEach(function(res) { + assertTrue(res.c > 1 && res.c < 3); + }); + }, + + testAttributeInRangeClosedInterval : function () { + var result = db._query("FOR doc IN UnitTestsView SEARCH IN_RANGE(doc.c, 1, 3, true, true) OPTIONS { waitForSync : true } RETURN doc").toArray(); + + assertEqual(result.length, 12); + result.forEach(function(res) { + assertTrue(res.c >= 1 && res.c <= 3); + }); } }; } diff --git a/tests/js/common/aql/aql-view-arangosearch-noncluster.js b/tests/js/common/aql/aql-view-arangosearch-noncluster.js index d28d3f12bd..2ec4fc907e 100644 --- a/tests/js/common/aql/aql-view-arangosearch-noncluster.js +++ b/tests/js/common/aql/aql-view-arangosearch-noncluster.js @@ -857,6 +857,24 @@ function iResearchAqlTestSuite () { linksView.drop(); entities.drop(); links.drop(); + }, + + testAttributeInRangeOpenInterval : function () { + var result = db._query("FOR doc IN UnitTestsView SEARCH IN_RANGE(doc.c, 1, 3, false, false) OPTIONS { waitForSync : true } RETURN doc").toArray(); + + assertEqual(result.length, 4); + result.forEach(function(res) { + assertTrue(res.c > 1 && res.c < 3); + }); + }, + + testAttributeInRangeClosedInterval : function () { + var result = db._query("FOR doc IN UnitTestsView SEARCH IN_RANGE(doc.c, 1, 3, true, true) OPTIONS { waitForSync : true } RETURN doc").toArray(); + + assertEqual(result.length, 12); + result.forEach(function(res) { + assertTrue(res.c >= 1 && res.c <= 3); + }); } }; }