From cfce3d8df82fffc48b8ff35f1de5da9beff71f8c Mon Sep 17 00:00:00 2001 From: Dronplane Date: Tue, 15 Oct 2019 20:41:06 +0300 Subject: [PATCH] Bug fix/internal issue #627 (#10233) * Implemented Array IN operators * First implementation for array comparsion operators for SEARCH clause * WIP * WIP * simplified filter building * Adding Unit tests * Added tests for IN/NIN * Debuggin Array comparsion operators. Fixed ViewNode redundand register planning * Fixed log id duplicate * Fixed jslin * Fixed Mac build * Code cleanup * WIP * WIP * Code cleanup * fixed applying boost to all/empty filters * Removed redundancy in filter generation * cleanup --- arangod/Aql/IResearchViewNode.cpp | 2 +- arangod/Aql/Quantifier.cpp | 4 - arangod/Aql/Quantifier.h | 6 +- arangod/IResearch/AqlHelper.h | 2 +- arangod/IResearch/IResearchFilterFactory.cpp | 507 ++- tests/CMakeLists.txt | 2 + .../IResearch/IResearchFilterArrayIn-test.cpp | 3178 +++++++++++++++++ .../IResearchFilterArrayInterval-test.cpp | 711 ++++ tests/IResearch/IResearchViewNode-test.cpp | 190 + tests/IResearch/common.cpp | 65 + tests/IResearch/common.h | 5 + .../aql/aql-view-arangosearch-cluster.inc | 531 +++ .../aql/aql-view-arangosearch-noncluster.js | 551 ++- 13 files changed, 5686 insertions(+), 68 deletions(-) create mode 100644 tests/IResearch/IResearchFilterArrayIn-test.cpp create mode 100644 tests/IResearch/IResearchFilterArrayInterval-test.cpp diff --git a/arangod/Aql/IResearchViewNode.cpp b/arangod/Aql/IResearchViewNode.cpp index fa1f4b391a..b918be2b4c 100644 --- a/arangod/Aql/IResearchViewNode.cpp +++ b/arangod/Aql/IResearchViewNode.cpp @@ -858,7 +858,7 @@ void IResearchViewNode::planNodeRegisters( std::vector& nrRegsHere, std::vector& nrRegs, std::unordered_map& varInfo, unsigned int& totalNrRegs, unsigned int depth) const { - nrRegsHere.emplace_back(isLateMaterialized()? 0 : 1); + nrRegsHere.emplace_back(0); // create a copy of the last value here // this is requried because back returns a reference and emplace/push_back diff --git a/arangod/Aql/Quantifier.cpp b/arangod/Aql/Quantifier.cpp index ade025e5d7..fbfd942f47 100644 --- a/arangod/Aql/Quantifier.cpp +++ b/arangod/Aql/Quantifier.cpp @@ -28,10 +28,6 @@ using namespace arangodb::aql; -int64_t const Quantifier::NONE = 1; -int64_t const Quantifier::ALL = 2; -int64_t const Quantifier::ANY = 3; - /// @brief converts a quantifier string into an int equivalent int64_t Quantifier::FromString(std::string const& value) { if (value == "all") { diff --git a/arangod/Aql/Quantifier.h b/arangod/Aql/Quantifier.h index 932c827e37..3acb7c1e29 100644 --- a/arangod/Aql/Quantifier.h +++ b/arangod/Aql/Quantifier.h @@ -34,9 +34,9 @@ namespace aql { struct AstNode; struct Quantifier { - static int64_t const NONE; - static int64_t const ALL; - static int64_t const ANY; + static int64_t constexpr NONE = 1; + static int64_t constexpr ALL = 2; + static int64_t constexpr ANY = 3; /// @brief converts a quantifier string into an int equivalent static int64_t FromString(std::string const& value); diff --git a/arangod/IResearch/AqlHelper.h b/arangod/IResearch/AqlHelper.h index 1b96922f08..bf86f9f044 100644 --- a/arangod/IResearch/AqlHelper.h +++ b/arangod/IResearch/AqlHelper.h @@ -253,7 +253,7 @@ class ScopedAqlValue : private irs::util::noncopyable { } ScopedAqlValue(ScopedAqlValue&& rhs) noexcept - : _value(rhs._value), _node(rhs._node), _type(rhs._type) { + : _value(rhs._value), _node(rhs._node), _type(rhs._type), _executed(rhs._executed) { rhs._node = &INVALID_NODE; rhs._type = SCOPED_VALUE_TYPE_INVALID; rhs._destroy = false; diff --git a/arangod/IResearch/IResearchFilterFactory.cpp b/arangod/IResearch/IResearchFilterFactory.cpp index dcfb3a9e6f..46b19d4202 100644 --- a/arangod/IResearch/IResearchFilterFactory.cpp +++ b/arangod/IResearch/IResearchFilterFactory.cpp @@ -40,6 +40,7 @@ #include "Aql/Ast.h" #include "Aql/Function.h" #include "Aql/Range.h" +#include "Aql/Quantifier.h" #include "Basics/StringUtils.h" #include "IResearch/AqlHelper.h" #include "IResearch/ExpressionFilter.h" @@ -142,8 +143,7 @@ arangodb::iresearch::IResearchLinkMeta::Analyzer extractAnalyzerFromArg( irs::boolean_filter const* filter, arangodb::aql::AstNode const* analyzerArg, QueryContext const& ctx, size_t argIdx, irs::string_ref const& functionName) { static const arangodb::iresearch::IResearchLinkMeta::Analyzer invalid( // invalid analyzer - nullptr, "" - ); + nullptr, ""); if (!analyzerArg) { auto message = "'"s + std::string(functionName.c_str(), functionName.size()) + "' AQL function: " + std::to_string(argIdx) + " argument is invalid analyzer"; @@ -206,8 +206,7 @@ arangodb::iresearch::IResearchLinkMeta::Analyzer extractAnalyzerFromArg( analyzer = analyzerFeature.get(analyzerId, ctx.trx->vocbase(), *sysVocbase); shortName = arangodb::iresearch::IResearchAnalyzerFeature::normalize( // normalize - analyzerId, ctx.trx->vocbase(), *sysVocbase, false // args - ); + analyzerId, ctx.trx->vocbase(), *sysVocbase, false); // args } } else { analyzer = analyzerFeature.get(analyzerId); // verbatim @@ -221,17 +220,9 @@ arangodb::iresearch::IResearchLinkMeta::Analyzer extractAnalyzerFromArg( return result; } -arangodb::Result byTerm(irs::by_term* filter, arangodb::aql::AstNode const& attribute, - ScopedAqlValue const& value, QueryContext const& ctx, - FilterContext const& filterCtx) { - std::string name; - - if (filter && !arangodb::iresearch::nameFromAttributeAccess(name, attribute, ctx)) { - auto message = "Failed to generate field name from node " + arangodb::aql::AstNode::toString(&attribute); - LOG_TOPIC("d4b6e", WARN, arangodb::iresearch::TOPIC) << message; - return {TRI_ERROR_BAD_PARAMETER, message}; - } - +arangodb::Result byTerm(irs::by_term* filter, std::string name, + ScopedAqlValue const& value, QueryContext const& ctx, + FilterContext const& filterCtx) { switch (value.type()) { case arangodb::iresearch::SCOPED_VALUE_TYPE_NULL: if (filter) { @@ -294,6 +285,18 @@ arangodb::Result byTerm(irs::by_term* filter, arangodb::aql::AstNode const& attr } } +arangodb::Result byTerm(irs::by_term* filter, arangodb::aql::AstNode const& attribute, + ScopedAqlValue const& value, QueryContext const& ctx, + FilterContext const& filterCtx) { + std::string name; + if (filter && !arangodb::iresearch::nameFromAttributeAccess(name, attribute, ctx)) { + auto message = "Failed to generate field name from node " + arangodb::aql::AstNode::toString(&attribute); + LOG_TOPIC("d4b6e", WARN, arangodb::iresearch::TOPIC) << message; + return {TRI_ERROR_BAD_PARAMETER, message}; + } + return byTerm(filter, std::move(name), value, ctx, filterCtx); +} + arangodb::Result byTerm(irs::by_term* filter, arangodb::iresearch::NormalizedCmpNode const& node, QueryContext const& ctx, FilterContext const& filterCtx) { TRI_ASSERT(node.attribute && node.attribute->isDeterministic()); @@ -496,34 +499,8 @@ arangodb::Result byRange(irs::boolean_filter* filter, arangodb::aql::AstNode con } template -arangodb::Result byRange(irs::boolean_filter* filter, - arangodb::iresearch::NormalizedCmpNode const& node, bool const incl, - QueryContext const& ctx, FilterContext const& filterCtx) { - TRI_ASSERT(node.attribute && node.attribute->isDeterministic()); - TRI_ASSERT(node.value && node.value->isDeterministic()); - - ScopedAqlValue value(*node.value); - - if (!value.isConstant()) { - if (!filter) { - // can't evaluate non constant filter before the execution - return {}; - } - - if (!value.execute(ctx)) { - // could not execute expression - return {TRI_ERROR_BAD_PARAMETER, "can not execute expression"}; - } - } - - std::string name; - - if (filter && !nameFromAttributeAccess(name, *node.attribute, ctx)) { - auto message = "Failed to generate field name from node " + arangodb::aql::AstNode::toString(node.attribute); - LOG_TOPIC("1a218", WARN, arangodb::iresearch::TOPIC) << message; - return {TRI_ERROR_BAD_PARAMETER, message}; - } - +arangodb::Result byRange(irs::boolean_filter* filter, std::string name, const ScopedAqlValue& value, + bool const incl, QueryContext const& ctx, FilterContext const& filterCtx) { switch (value.type()) { case arangodb::iresearch::SCOPED_VALUE_TYPE_NULL: { if (filter) { @@ -601,6 +578,34 @@ arangodb::Result byRange(irs::boolean_filter* filter, } } +template +arangodb::Result byRange(irs::boolean_filter* filter, + arangodb::iresearch::NormalizedCmpNode const& node, bool const incl, + QueryContext const& ctx, FilterContext const& filterCtx) { + TRI_ASSERT(node.attribute && node.attribute->isDeterministic()); + TRI_ASSERT(node.value && node.value->isDeterministic()); + + std::string name; + if (filter && !nameFromAttributeAccess(name, *node.attribute, ctx)) { + auto message = "Failed to generate field name from node " + arangodb::aql::AstNode::toString(node.attribute); + LOG_TOPIC("1a218", WARN, arangodb::iresearch::TOPIC) << message; + return {TRI_ERROR_BAD_PARAMETER, message}; + } + auto value = ScopedAqlValue(*node.value); + if (!value.isConstant()) { + if (!filter) { + // can't evaluate non constant filter before the execution + return {}; + } + + if (!value.execute(ctx)) { + // could not execute expression + return { TRI_ERROR_BAD_PARAMETER, "can not execute expression" }; + } + } + return byRange(filter, name, value, incl, ctx, filterCtx); +} + arangodb::Result fromExpression(irs::boolean_filter* filter, QueryContext const& ctx, FilterContext const& filterCtx, std::shared_ptr&& node) { @@ -739,6 +744,398 @@ arangodb::Result fromRange(irs::boolean_filter* filter, QueryContext const& /*ct return {}; } +std::pair buildBinaryArrayComparsionPreFilter( + irs::boolean_filter* &filter, arangodb::aql::AstNodeType arrayComparsion, + const arangodb::aql::AstNode* qualifierNode, size_t arraySize) { + TRI_ASSERT(qualifierNode); + auto qualifierType = qualifierNode->getIntValue(true); + arangodb::aql::AstNodeType expansionNodeType = arangodb::aql::NODE_TYPE_ROOT; + if (0 == arraySize) { + expansionNodeType = arangodb::aql::NODE_TYPE_ROOT; // no subfilters expansion needed + switch (qualifierType) { + case arangodb::aql::Quantifier::ANY: + if (filter) { + filter->add(); + } + break; + case arangodb::aql::Quantifier::ALL: + case arangodb::aql::Quantifier::NONE: + if (filter) { + filter->add(); + } + break; + default: + TRI_ASSERT(false); // new qualifier added ? + return std::make_pair( + arangodb::Result(TRI_ERROR_NOT_IMPLEMENTED, "Unknown qualifier in Array comparison operator"), + arangodb::aql::AstNodeType::NODE_TYPE_ROOT); + } + } else { + // NONE is inverted ALL so do conversion + if (arangodb::aql::Quantifier::NONE == qualifierType) { + qualifierType = arangodb::aql::Quantifier::ALL; + switch (arrayComparsion) { + case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_ARRAY_NIN: + case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_ARRAY_NE: + arrayComparsion = arangodb::aql::NODE_TYPE_OPERATOR_BINARY_ARRAY_IN; + break; + case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_ARRAY_IN: + case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_ARRAY_EQ: + arrayComparsion = arangodb::aql::NODE_TYPE_OPERATOR_BINARY_ARRAY_NIN; + break; + case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_ARRAY_GE: + arrayComparsion = arangodb::aql::NODE_TYPE_OPERATOR_BINARY_ARRAY_LT; + break; + case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_ARRAY_GT: + arrayComparsion = arangodb::aql::NODE_TYPE_OPERATOR_BINARY_ARRAY_LE; + break; + case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_ARRAY_LE: + arrayComparsion = arangodb::aql::NODE_TYPE_OPERATOR_BINARY_ARRAY_GT; + break; + case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_ARRAY_LT: + arrayComparsion = arangodb::aql::NODE_TYPE_OPERATOR_BINARY_ARRAY_GE; + break; + default: + TRI_ASSERT(false); // new array comparsion operator? + return std::make_pair( + arangodb::Result(TRI_ERROR_NOT_IMPLEMENTED, "Unknown Array NONE comparison operator"), + arangodb::aql::AstNodeType::NODE_TYPE_ROOT); + } + } + switch (qualifierType) { + case arangodb::aql::Quantifier::ALL: + // calculate node type for expanding operation + // As soon as array is left argument but for filter we place document to the left + // we reverse comparsion operation + switch (arrayComparsion) { + case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_ARRAY_IN: + case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_ARRAY_EQ: + if (filter) { + filter = static_cast(&filter->add()); + } + expansionNodeType = arangodb::aql::NODE_TYPE_OPERATOR_BINARY_EQ; + break; + case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_ARRAY_NIN: + case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_ARRAY_NE: + if (filter) { + filter = static_cast(&filter->add().filter()); + } + expansionNodeType = arangodb::aql::NODE_TYPE_OPERATOR_BINARY_EQ; + break; + case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_ARRAY_LT: + if (filter) { + filter = static_cast(&filter->add()); + } + expansionNodeType = arangodb::aql::NODE_TYPE_OPERATOR_BINARY_GT; + break; + case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_ARRAY_LE: + if (filter) { + filter = static_cast(&filter->add()); + } + expansionNodeType = arangodb::aql::NODE_TYPE_OPERATOR_BINARY_GE; + break; + case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_ARRAY_GT: + if (filter) { + filter = static_cast(&filter->add()); + } + expansionNodeType = arangodb::aql::NODE_TYPE_OPERATOR_BINARY_LT; + break; + case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_ARRAY_GE: + if (filter) { + filter = static_cast(&filter->add()); + } + expansionNodeType = arangodb::aql::NODE_TYPE_OPERATOR_BINARY_LE; + break; + default: + TRI_ASSERT(false); // new array comparsion operator? + return std::make_pair( + arangodb::Result(TRI_ERROR_NOT_IMPLEMENTED, "Unknown Array ALL/NONE comparison operator"), + arangodb::aql::AstNodeType::NODE_TYPE_ROOT); + } + break; + case arangodb::aql::Quantifier::ANY: { + switch (arrayComparsion) { + case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_ARRAY_IN: + case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_ARRAY_EQ: + if (filter) { + filter = static_cast(&filter->add()); + } + expansionNodeType = arangodb::aql::NODE_TYPE_OPERATOR_BINARY_EQ; + break; + case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_ARRAY_NIN: + case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_ARRAY_NE: + if (filter) { + filter = static_cast(&filter->add().filter()); + } + expansionNodeType = arangodb::aql::NODE_TYPE_OPERATOR_BINARY_EQ; + break; + case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_ARRAY_GT: + if (filter) { + filter = static_cast(&filter->add()); + } + expansionNodeType = arangodb::aql::NODE_TYPE_OPERATOR_BINARY_LT; + break; + case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_ARRAY_GE: + if (filter) { + filter = static_cast(&filter->add()); + } + expansionNodeType = arangodb::aql::NODE_TYPE_OPERATOR_BINARY_LE; + break; + case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_ARRAY_LT: + if (filter) { + filter = static_cast(&filter->add()); + } + expansionNodeType = arangodb::aql::NODE_TYPE_OPERATOR_BINARY_GT; + break; + case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_ARRAY_LE: + if (filter) { + filter = static_cast(&filter->add()); + } + expansionNodeType = arangodb::aql::NODE_TYPE_OPERATOR_BINARY_GE; + break; + default: + TRI_ASSERT(false); // new array comparsion operator? + return std::make_pair( + arangodb::Result(TRI_ERROR_NOT_IMPLEMENTED, "Unknown Array ANY comparison operator"), + arangodb::aql::AstNodeType::NODE_TYPE_ROOT); + } + break; + } + default: + TRI_ASSERT(false); // new qualifier added ? + return std::make_pair( + arangodb::Result(TRI_ERROR_NOT_IMPLEMENTED, "Unknown qualifier in Array comparison operator"), + arangodb::aql::AstNodeType::NODE_TYPE_ROOT); + } + } + return std::make_pair(TRI_ERROR_NO_ERROR, expansionNodeType); +} + +class ByTermSubFilterFactory { + public: + static arangodb::Result byNodeSubFilter(irs::boolean_filter* filter, + arangodb::iresearch::NormalizedCmpNode const& node, + QueryContext const& ctx, FilterContext const& filterCtx) { + TRI_ASSERT(arangodb::aql::NODE_TYPE_OPERATOR_BINARY_EQ == node.cmp); + iresearch::by_term* termFilter = nullptr; + if (filter) { + termFilter = &filter->add(); + } + return byTerm(termFilter, node, ctx, filterCtx); + } + + static arangodb::Result byValueSubFilter(irs::boolean_filter* filter, std::string fieldName, const ScopedAqlValue& value, + arangodb::aql::AstNodeType arrayExpansionNodeType, + QueryContext const& ctx, FilterContext const& filterCtx) { + TRI_ASSERT(arangodb::aql::NODE_TYPE_OPERATOR_BINARY_EQ == arrayExpansionNodeType); + iresearch::by_term* termFilter = nullptr; + if (filter) { + termFilter = &filter->add(); + } + return byTerm(termFilter, std::move(fieldName), value, ctx, filterCtx); + } +}; + +class ByRangeSubFilterFactory { + public: + static arangodb::Result byNodeSubFilter(irs::boolean_filter* filter, + arangodb::iresearch::NormalizedCmpNode const& node, + QueryContext const& ctx, FilterContext const& filterCtx) { + bool incl, min; + std::tie(min, incl) = calcMinInclude(node.cmp); + return min ? byRange(filter, node, incl, ctx, filterCtx) + : byRange(filter, node, incl, ctx, filterCtx); + } + + static arangodb::Result byValueSubFilter(irs::boolean_filter* filter, std::string fieldName, const ScopedAqlValue& value, + arangodb::aql::AstNodeType arrayExpansionNodeType, + QueryContext const& ctx, FilterContext const& filterCtx) { + bool incl, min; + std::tie(min, incl) = calcMinInclude(arrayExpansionNodeType); + return min ? byRange(filter, fieldName, value, incl, ctx, filterCtx) + : byRange(filter, fieldName, value, incl, ctx, filterCtx); + } + + private: + static std::pair calcMinInclude(arangodb::aql::AstNodeType arrayExpansionNodeType) { + TRI_ASSERT(arangodb::aql::NODE_TYPE_OPERATOR_BINARY_LT == arrayExpansionNodeType || + arangodb::aql::NODE_TYPE_OPERATOR_BINARY_LE == arrayExpansionNodeType || + arangodb::aql::NODE_TYPE_OPERATOR_BINARY_GT == arrayExpansionNodeType || + arangodb::aql::NODE_TYPE_OPERATOR_BINARY_GE == arrayExpansionNodeType); + return std::pair( + // min + arangodb::aql::NODE_TYPE_OPERATOR_BINARY_GT == arrayExpansionNodeType || + arangodb::aql::NODE_TYPE_OPERATOR_BINARY_GE == arrayExpansionNodeType, + // incl + arangodb::aql::NODE_TYPE_OPERATOR_BINARY_GE == arrayExpansionNodeType || + arangodb::aql::NODE_TYPE_OPERATOR_BINARY_LE == arrayExpansionNodeType); + } +}; + + +template +arangodb::Result fromArrayComparsion(irs::boolean_filter*& filter, QueryContext const& ctx, + FilterContext const& filterCtx, arangodb::aql::AstNode const& node) { + TRI_ASSERT(arangodb::aql::NODE_TYPE_OPERATOR_BINARY_ARRAY_LT == node.type || + arangodb::aql::NODE_TYPE_OPERATOR_BINARY_ARRAY_LE == node.type || + arangodb::aql::NODE_TYPE_OPERATOR_BINARY_ARRAY_GT == node.type || + arangodb::aql::NODE_TYPE_OPERATOR_BINARY_ARRAY_GE == node.type || + arangodb::aql::NODE_TYPE_OPERATOR_BINARY_ARRAY_EQ == node.type || + arangodb::aql::NODE_TYPE_OPERATOR_BINARY_ARRAY_NE == node.type || + arangodb::aql::NODE_TYPE_OPERATOR_BINARY_ARRAY_IN == node.type || + arangodb::aql::NODE_TYPE_OPERATOR_BINARY_ARRAY_NIN == node.type); + if (node.numMembers() != 3) { + auto rv = logMalformedNode(node.type); + return rv.reset(rv.errorNumber(), "error in Array comparsion operator " + rv.errorMessage()); + } + + auto const* valueNode = node.getMemberUnchecked(0); + TRI_ASSERT(valueNode); + + auto const* attributeNode = node.getMemberUnchecked(1); + TRI_ASSERT(attributeNode); + + auto const* qualifierNode = node.getMemberUnchecked(2); + TRI_ASSERT(qualifierNode); + + if (qualifierNode->type != arangodb::aql::NODE_TYPE_QUANTIFIER) { + return { TRI_ERROR_BAD_PARAMETER, "wrong qualifier node type for Array comparison operator" }; + } + if (arangodb::aql::NODE_TYPE_ARRAY == valueNode->type) { + if (!attributeNode->isDeterministic()) { + // not supported by IResearch, but could be handled by ArangoDB + return fromExpression(filter, ctx, filterCtx, node); + } + size_t const n = valueNode->numMembers(); + if (!arangodb::iresearch::checkAttributeAccess(attributeNode, *ctx.ref)) { + // no attribute access specified in attribute node, try to + // find it in value node + bool attributeAccessFound = false; + for (size_t i = 0; i < n; ++i) { + attributeAccessFound |= + (nullptr != arangodb::iresearch::checkAttributeAccess(valueNode->getMemberUnchecked(i), + *ctx.ref)); + } + if (!attributeAccessFound) { + return fromExpression(filter, ctx, filterCtx, node); + } + } + arangodb::Result buildRes; + arangodb::aql::AstNodeType arrayExpansionNodeType; + std::tie(buildRes, arrayExpansionNodeType) = buildBinaryArrayComparsionPreFilter(filter, node.type, qualifierNode, n); + if (!buildRes.ok()) { + return buildRes; + } + if (filter) { + filter->boost(filterCtx.boost); + } + if (arangodb::aql::NODE_TYPE_ROOT == arrayExpansionNodeType) { + // nothing to do more + return {}; + } + FilterContext const subFilterCtx{ + filterCtx.analyzer, + irs::no_boost() }; // reset boost + // Expand array interval as several binaryInterval nodes ('array' feature is ensured by pre-filter) + arangodb::iresearch::NormalizedCmpNode normalized; + arangodb::aql::AstNode toNormalize(arrayExpansionNodeType); + toNormalize.reserve(2); + for (size_t i = 0; i < n; ++i) { + auto const* member = valueNode->getMemberUnchecked(i); + TRI_ASSERT(member); + + // edit in place for now; TODO change so we can replace instead + TEMPORARILY_UNLOCK_NODE(&toNormalize); + toNormalize.clearMembers(); + toNormalize.addMember(attributeNode); + toNormalize.addMember(member); + toNormalize.flags = member->flags; + if (!arangodb::iresearch::normalizeCmpNode(toNormalize, *ctx.ref, normalized)) { + if (!filter) { + // can't evaluate non constant filter before the execution + return {}; + } + // use std::shared_ptr since AstNode is not copyable/moveable + auto exprNode = std::make_shared(arrayExpansionNodeType); + exprNode->reserve(2); + exprNode->addMember(attributeNode); + exprNode->addMember(member); + + // not supported by IResearch, but could be handled by ArangoDB + auto rv = fromExpression(filter, ctx, subFilterCtx, std::move(exprNode)); + if (rv.fail()) { + return rv.reset(rv.errorNumber(), "while getting array: " + rv.errorMessage()); + } + } else { + auto rv = SubFilterFactory::byNodeSubFilter(filter, normalized, ctx, subFilterCtx); + if (rv.fail()) { + return rv.reset(rv.errorNumber(), "while getting array: " + rv.errorMessage()); + } + } + } + return {}; + } + + if (!node.isDeterministic() || + !arangodb::iresearch::checkAttributeAccess(attributeNode, *ctx.ref) || + arangodb::iresearch::findReference(*valueNode, *ctx.ref)) { + return fromExpression(filter, ctx, filterCtx, node); + } + + if (!filter) { + // can't evaluate non constant filter before the execution + return {}; + } + + ScopedAqlValue value(*valueNode); + if (!value.execute(ctx)) { + // can't execute expression + auto message = "Unable to extract value from Array comparison operator"; + LOG_TOPIC("f7a13", WARN, arangodb::iresearch::TOPIC) << message; + return {TRI_ERROR_BAD_PARAMETER, message}; + } + + switch (value.type()) { + case arangodb::iresearch::SCOPED_VALUE_TYPE_ARRAY: { + size_t const n = value.size(); + arangodb::Result buildRes; + arangodb::aql::AstNodeType arrayExpansionNodeType; + std::tie(buildRes, arrayExpansionNodeType) = buildBinaryArrayComparsionPreFilter(filter, node.type, qualifierNode, n); + if (!buildRes.ok()) { + return buildRes; + } + filter->boost(filterCtx.boost); + if (arangodb::aql::NODE_TYPE_ROOT == arrayExpansionNodeType) { + // nothing to do more + return {}; + } + FilterContext const subFilterCtx{ + filterCtx.analyzer, + irs::no_boost() // reset boost + }; + + std::string fieldName; + if (filter && !nameFromAttributeAccess(fieldName, *attributeNode, ctx)) { + auto message = "Failed to generate field name from node " + arangodb::aql::AstNode::toString(attributeNode); + LOG_TOPIC("ff299", WARN, arangodb::iresearch::TOPIC) << message; + return { TRI_ERROR_BAD_PARAMETER, message }; + } + for (size_t i = 0; i < n; ++i) { + auto rv = SubFilterFactory::byValueSubFilter(filter, fieldName, value.at(i), arrayExpansionNodeType, ctx, subFilterCtx); + if (rv.fail()) { + return rv.reset(rv.errorNumber(), "failed to create filter because: " + rv.errorMessage()); + } + } + return {}; + } + default: + break; + } + + // wrong value node type + return {TRI_ERROR_BAD_PARAMETER, "wrong value node type for Array comparison operator"}; +} + arangodb::Result fromInArray(irs::boolean_filter* filter, QueryContext const& ctx, FilterContext const& filterCtx, arangodb::aql::AstNode const& node) { TRI_ASSERT(arangodb::aql::NODE_TYPE_OPERATOR_BINARY_IN == node.type || @@ -1181,8 +1578,7 @@ arangodb::Result fromFuncAnalyzer(irs::boolean_filter* filter, QueryContext cons analyzer = analyzerFeature.get(analyzerIdValue, ctx.trx->vocbase(), *sysVocbase); shortName = arangodb::iresearch::IResearchAnalyzerFeature::normalize( // normalize - analyzerIdValue, ctx.trx->vocbase(), *sysVocbase, false // args - ); + analyzerIdValue, ctx.trx->vocbase(), *sysVocbase, false); // args } } else { analyzer = analyzerFeature.get(analyzerIdValue); // verbatim @@ -1198,7 +1594,7 @@ arangodb::Result fromFuncAnalyzer(irs::boolean_filter* filter, QueryContext cons FilterContext const subFilterContext(analyzerValue, filterCtx.boost); // override analyzer auto rv = ::filter(filter, ctx, subFilterContext, *expressionArg); - if( rv.fail() ){ + if (rv.fail()) { return arangodb::Result{rv.errorNumber(), "failed to get filter for analyzer: " + analyzer->name() + " : " + rv.errorMessage()}; } return rv; @@ -1266,7 +1662,7 @@ arangodb::Result fromFuncBoost(irs::boolean_filter* filter, QueryContext const& filterCtx.boost * static_cast(boostValue)}; auto rv = ::filter(filter, ctx, subFilterContext, *expressionArg); - if(rv.fail()){ + if (rv.fail()) { return arangodb::Result{rv.errorNumber(), "error in sub-filter context: " + rv.errorMessage()}; } return {}; @@ -1903,19 +2299,19 @@ arangodb::Result fromFuncInRange(irs::boolean_filter* filter, QueryContext const // 4th argument defines inclusion of lower boundary bool lhsInclude = false; auto inc1 = getInclusion(args.getMemberUnchecked(3), lhsInclude, "4th"); - if (inc1.fail()){ + if (inc1.fail()) { return inc1; } // 5th argument defines inclusion of upper boundary bool rhsInclude = false; auto inc2 = getInclusion(args.getMemberUnchecked(4), rhsInclude, "5th"); - if (inc2.fail()){ + if (inc2.fail()) { return inc2; } auto rv = ::byRange(filter, *field, *lhsArg, lhsInclude, *rhsArg, rhsInclude, ctx, filterCtx); - if(rv.fail()){ + if (rv.fail()) { return {rv.errorNumber(), "error in byRange: " + rv.errorMessage()}; } return {}; @@ -2061,6 +2457,17 @@ arangodb::Result filter(irs::boolean_filter* filter, QueryContext const& queryCt return fromGroup(filter, queryCtx, filterCtx, node); case arangodb::aql::NODE_TYPE_OPERATOR_NARY_OR: // n-ary or return fromGroup(filter, queryCtx, filterCtx, node); + case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_ARRAY_IN: // compare ARRAY in + case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_ARRAY_NIN: // compare ARRAY not in + // for iresearch filters IN and EQ queries will be actually the same + case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_ARRAY_EQ: // compare ARRAY == + case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_ARRAY_NE: // compare ARRAY != + return fromArrayComparsion(filter, queryCtx, filterCtx, node); + case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_ARRAY_LT: // compare ARRAY < + case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_ARRAY_LE: // compare ARRAY <= + case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_ARRAY_GT: // compare ARRAY > + case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_ARRAY_GE: // compare ARRAY >= + return fromArrayComparsion(filter, queryCtx, filterCtx, node); default: return fromExpression(filter, queryCtx, filterCtx, node); } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 49943073cc..caa60b1fc5 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -137,6 +137,8 @@ set(ARANGODB_TESTS_SOURCES IResearch/IResearchComparer-test.cpp IResearch/IResearchDocument-test.cpp IResearch/IResearchFeature-test.cpp + IResearch/IResearchFilterArrayIn-test.cpp + IResearch/IResearchFilterArrayInterval-test.cpp IResearch/IResearchFilterBoolean-test.cpp IResearch/IResearchFilterCompare-test.cpp IResearch/IResearchFilterFunction-test.cpp diff --git a/tests/IResearch/IResearchFilterArrayIn-test.cpp b/tests/IResearch/IResearchFilterArrayIn-test.cpp new file mode 100644 index 0000000000..03d7195ccf --- /dev/null +++ b/tests/IResearch/IResearchFilterArrayIn-test.cpp @@ -0,0 +1,3178 @@ +//////////////////////////////////////////////////////////////////////////////// +/// 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 Andrei Lobov +//////////////////////////////////////////////////////////////////////////////// + +#include "gtest/gtest.h" + +#include "analysis/analyzers.hpp" +#include "analysis/token_attributes.hpp" +#include "analysis/token_streams.hpp" +#include "search/all_filter.hpp" +#include "search/boolean_filter.hpp" +#include "search/column_existence_filter.hpp" +#include "search/granular_range_filter.hpp" +#include "search/phrase_filter.hpp" +#include "search/prefix_filter.hpp" +#include "search/range_filter.hpp" +#include "search/term_filter.hpp" + +#include + +#include "IResearch/ExpressionContextMock.h" +#include "IResearch/common.h" +#include "Mocks/LogLevels.h" +#include "Mocks/Servers.h" +#include "Mocks/StorageEngineMock.h" + +#include "Aql/AqlFunctionFeature.h" +#include "Aql/Ast.h" +#include "Aql/ExecutionPlan.h" +#include "Aql/ExpressionContext.h" +#include "Aql/Query.h" +#include "Cluster/ClusterFeature.h" +#include "GeneralServer/AuthenticationFeature.h" +#include "IResearch/AqlHelper.h" +#include "IResearch/ExpressionFilter.h" +#include "IResearch/IResearchAnalyzerFeature.h" +#include "IResearch/IResearchCommon.h" +#include "IResearch/IResearchFeature.h" +#include "IResearch/IResearchFilterFactory.h" +#include "IResearch/IResearchLinkMeta.h" +#include "IResearch/IResearchViewMeta.h" +#include "Logger/LogTopic.h" +#include "Logger/Logger.h" +#include "RestServer/AqlFeature.h" +#include "RestServer/DatabaseFeature.h" +#include "RestServer/QueryRegistryFeature.h" +#include "RestServer/SystemDatabaseFeature.h" +#include "RestServer/TraverserEngineRegistryFeature.h" +#include "RestServer/ViewTypesFeature.h" +#include "StorageEngine/EngineSelectorFeature.h" +#include "Transaction/Methods.h" +#include "Transaction/StandaloneContext.h" +#include "V8Server/V8DealerFeature.h" +#include "VocBase/Methods/Collections.h" + +#if USE_ENTERPRISE +#include "Enterprise/Ldap/LdapFeature.h" +#endif + +static const VPackBuilder systemDatabaseBuilder = dbArgsBuilder(); +static const VPackSlice systemDatabaseArgs = systemDatabaseBuilder.slice(); + +// ----------------------------------------------------------------------------- +// --SECTION-- setup / tear-down +// ----------------------------------------------------------------------------- + +class IResearchFilterArrayInTest + : public ::testing::Test, + public arangodb::tests::LogSuppressor { + protected: + arangodb::tests::mocks::MockAqlServer server; + + private: + TRI_vocbase_t* _vocbase; + + protected: + IResearchFilterArrayInTest() { + arangodb::tests::init(); + + auto& functions = server.getFeature(); + + // 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 = server.getFeature(); + arangodb::iresearch::IResearchAnalyzerFeature::EmplaceResult result; + + auto& dbFeature = server.getFeature(); + dbFeature.createDatabase(testDBInfo(server.server()), _vocbase); // required for IResearchAnalyzerFeature::emplace(...) + arangodb::methods::Collections::createSystem(*_vocbase, arangodb::tests::AnalyzerCollectionName, + false); + analyzers.emplace( + result, "testVocbase::test_analyzer", "TestAnalyzer", + arangodb::velocypack::Parser::fromJson("{ \"args\": \"abc\"}")->slice()); // cache analyzer + } + + TRI_vocbase_t& vocbase() { return *_vocbase; } +}; // IResearchFilterSetup + +// ----------------------------------------------------------------------------- +// --SECTION-- test suite +// ----------------------------------------------------------------------------- + +TEST_F(IResearchFilterArrayInTest, BinaryIn) { + // simple attribute ANY + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("a")).term("1"); + root.add().field(mangleStringIdentity("a")).term("2"); + root.add().field(mangleStringIdentity("a")).term("3"); + + assertFilterSuccess( + vocbase(), "FOR d IN collection FILTER ['1','2','3'] ANY IN d.a RETURN d", expected); + assertFilterSuccess( + vocbase(), "FOR d IN collection FILTER ['1','2','3'] ANY IN d['a'] RETURN d", expected); + + assertFilterSuccess( + vocbase(), "FOR d IN collection FILTER ['1','2','3'] ANY == d.a RETURN d", expected); + assertFilterSuccess( + vocbase(), "FOR d IN collection FILTER ['1','2','3'] ANY == d['a'] RETURN d", expected); + } + + // simple attribute ALL + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("a")).term("1"); + root.add().field(mangleStringIdentity("a")).term("2"); + root.add().field(mangleStringIdentity("a")).term("3"); + + assertFilterSuccess( + vocbase(), "FOR d IN collection FILTER ['1','2','3'] ALL IN d.a RETURN d", expected); + assertFilterSuccess( + vocbase(), "FOR d IN collection FILTER ['1','2','3'] ALL IN d['a'] RETURN d", expected); + + assertFilterSuccess( + vocbase(), "FOR d IN collection FILTER ['1','2','3'] ALL == d.a RETURN d", expected); + assertFilterSuccess( + vocbase(), "FOR d IN collection FILTER ['1','2','3'] ALL == d['a'] RETURN d", expected); + } + + // simple attribute NONE + { + irs::Or expected; + auto& root = expected.add().filter(); + root.add().field(mangleStringIdentity("a")).term("1"); + root.add().field(mangleStringIdentity("a")).term("2"); + root.add().field(mangleStringIdentity("a")).term("3"); + + assertFilterSuccess( + vocbase(), "FOR d IN collection FILTER ['1','2','3'] NONE IN d.a RETURN d", expected); + assertFilterSuccess( + vocbase(), "FOR d IN collection FILTER ['1','2','3'] NONE IN d['a'] RETURN d", expected); + + assertFilterSuccess( + vocbase(), "FOR d IN collection FILTER ['1','2','3'] NONE == d.a RETURN d", expected); + assertFilterSuccess( + vocbase(), "FOR d IN collection FILTER ['1','2','3'] NONE == d['a'] RETURN d", expected); + } + + // simple offset ANY + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("[1]")).term("1"); + root.add().field(mangleStringIdentity("[1]")).term("2"); + root.add().field(mangleStringIdentity("[1]")).term("3"); + + assertFilterSuccess( + vocbase(), "FOR d IN collection FILTER ['1','2','3'] ANY IN d[1] RETURN d", expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ANALYZER(['1','2','3'] ANY IN d[1], " + "'identity') RETURN d", + expected); + + assertFilterSuccess( + vocbase(), "FOR d IN collection FILTER ['1','2','3'] ANY == d[1] RETURN d", expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ANALYZER(['1','2','3'] ANY == d[1], " + "'identity') RETURN d", + expected); + } + // simple offset ALL + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("[1]")).term("1"); + root.add().field(mangleStringIdentity("[1]")).term("2"); + root.add().field(mangleStringIdentity("[1]")).term("3"); + + assertFilterSuccess( + vocbase(), "FOR d IN collection FILTER ['1','2','3'] ALL IN d[1] RETURN d", expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ANALYZER(['1','2','3'] ALL IN d[1], " + "'identity') RETURN d", + expected); + + assertFilterSuccess( + vocbase(), "FOR d IN collection FILTER ['1','2','3'] ALL == d[1] RETURN d", expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ANALYZER(['1','2','3'] ALL == d[1], " + "'identity') RETURN d", + expected); + } + // simple offset NONE + { + irs::Or expected; + auto& root = expected.add().filter(); + root.add().field(mangleStringIdentity("[1]")).term("1"); + root.add().field(mangleStringIdentity("[1]")).term("2"); + root.add().field(mangleStringIdentity("[1]")).term("3"); + + assertFilterSuccess( + vocbase(), "FOR d IN collection FILTER ['1','2','3'] NONE IN d[1] RETURN d", expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ANALYZER(['1','2','3'] NONE IN d[1], " + "'identity') RETURN d", + expected); + + assertFilterSuccess( + vocbase(), "FOR d IN collection FILTER ['1','2','3'] NONE == d[1] RETURN d", expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ANALYZER(['1','2','3'] NONE == d[1], " + "'identity') RETURN d", + expected); + } + + // complex attribute name with offset, analyzer ANY + { + irs::Or expected; + auto& root = expected.add(); + root.add() + .field(mangleString("a.b.c[412].e.f", "test_analyzer")) + .term("1"); + root.add() + .field(mangleString("a.b.c[412].e.f", "test_analyzer")) + .term("2"); + root.add() + .field(mangleString("a.b.c[412].e.f", "test_analyzer")) + .term("3"); + + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ANALYZER(" + "['1','2','3'] ANY IN d.a['b']['c'][412].e.f, 'test_analyzer') RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ANALYZER(" + "['1','2','3'] ANY IN d.a.b.c[412].e.f, 'test_analyzer') RETURN d", + expected); + + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ANALYZER(" + "['1','2','3'] ANY == d.a['b']['c'][412].e.f, 'test_analyzer') RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ANALYZER(" + "['1','2','3'] ANY == d.a.b.c[412].e.f, 'test_analyzer') RETURN d", + expected); + } + // complex attribute name with offset, analyzer ALL + { + irs::Or expected; + auto& root = expected.add(); + root.add() + .field(mangleString("a.b.c[412].e.f", "test_analyzer")) + .term("1"); + root.add() + .field(mangleString("a.b.c[412].e.f", "test_analyzer")) + .term("2"); + root.add() + .field(mangleString("a.b.c[412].e.f", "test_analyzer")) + .term("3"); + + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ANALYZER(" + "['1','2','3'] ALL IN d.a['b']['c'][412].e.f, 'test_analyzer') RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ANALYZER(" + "['1','2','3'] ALL IN d.a.b.c[412].e.f, 'test_analyzer') RETURN d", + expected); + + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ANALYZER(" + "['1','2','3'] ALL == d.a['b']['c'][412].e.f, 'test_analyzer') RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ANALYZER(" + "['1','2','3'] ALL == d.a.b.c[412].e.f, 'test_analyzer') RETURN d", + expected); + } + // complex attribute name with offset, analyzer NONE + { + irs::Or expected; + auto& root = expected.add().filter(); + root.add() + .field(mangleString("a.b.c[412].e.f", "test_analyzer")) + .term("1"); + root.add() + .field(mangleString("a.b.c[412].e.f", "test_analyzer")) + .term("2"); + root.add() + .field(mangleString("a.b.c[412].e.f", "test_analyzer")) + .term("3"); + + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ANALYZER(" + "['1','2','3'] NONE IN d.a['b']['c'][412].e.f, 'test_analyzer') RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ANALYZER(" + "['1','2','3'] NONE IN d.a.b.c[412].e.f, 'test_analyzer') RETURN d", + expected); + + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ANALYZER(" + "['1','2','3'] NONE == d.a['b']['c'][412].e.f, 'test_analyzer') RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ANALYZER(" + "['1','2','3'] NONE == d.a.b.c[412].e.f, 'test_analyzer') RETURN d", + expected); + } + + // complex attribute name with offset, boost ANY + { + irs::Or expected; + auto& root = expected.add(); + root.boost(2.5); + root.add() + .field(mangleStringIdentity("a.b.c[412].e.f")) + .term("1"); + root.add() + .field(mangleStringIdentity("a.b.c[412].e.f")) + .term("2"); + root.add() + .field(mangleStringIdentity("a.b.c[412].e.f")) + .term("3"); + + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER BOOST(" + "['1','2','3'] ANY IN d.a['b']['c'][412].e.f, 2.5) RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER BOOST(['1','2','3'] ANY IN d.a.b.c[412].e.f, " + "2.5) RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER BOOST(" + "['1','2','3'] ANY == d.a['b']['c'][412].e.f, 2.5) RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER BOOST(['1','2','3'] ANY == d.a.b.c[412].e.f, " + "2.5) RETURN d", + expected); + } + + // complex attribute name with offset, boost ALL + { + irs::Or expected; + auto& root = expected.add(); + root.boost(2.5); + root.add() + .field(mangleStringIdentity("a.b.c[412].e.f")) + .term("1"); + root.add() + .field(mangleStringIdentity("a.b.c[412].e.f")) + .term("2"); + root.add() + .field(mangleStringIdentity("a.b.c[412].e.f")) + .term("3"); + + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER BOOST(" + "['1','2','3'] ALL IN d.a['b']['c'][412].e.f, 2.5) RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER BOOST(['1','2','3'] ALL IN d.a.b.c[412].e.f, " + "2.5) RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER BOOST(" + "['1','2','3'] ALL == d.a['b']['c'][412].e.f, 2.5) RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER BOOST(['1','2','3'] ALL == d.a.b.c[412].e.f, " + "2.5) RETURN d", + expected); + } + // complex attribute name with offset, boost NONE + { + irs::Or expected; + auto& root = expected.add().filter(); + root.boost(2.5); + root.add() + .field(mangleStringIdentity("a.b.c[412].e.f")) + .term("1"); + root.add() + .field(mangleStringIdentity("a.b.c[412].e.f")) + .term("2"); + root.add() + .field(mangleStringIdentity("a.b.c[412].e.f")) + .term("3"); + + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER BOOST(" + "['1','2','3'] NONE IN d.a['b']['c'][412].e.f, 2.5) RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER BOOST(['1','2','3'] NONE IN d.a.b.c[412].e.f, " + "2.5) RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER BOOST(" + "['1','2','3'] NONE == d.a['b']['c'][412].e.f, 2.5) RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER BOOST(['1','2','3'] NONE == d.a.b.c[412].e.f, " + "2.5) RETURN d", + expected); + } + + // complex attribute name with offset, boost, analyzer ANY + { + irs::Or expected; + auto& root = expected.add(); + root.boost(2.5); + root.add() + .field(mangleString("a.b.c[412].e.f", "test_analyzer")) + .term("1"); + root.add() + .field(mangleString("a.b.c[412].e.f", "test_analyzer")) + .term("2"); + root.add() + .field(mangleString("a.b.c[412].e.f", "test_analyzer")) + .term("3"); + + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ANALYZER(BOOST(" + "['1','2','3'] ANY IN d.a['b']['c'][412].e.f, 2.5), 'test_analyzer') RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER BOOST(ANALYZER(" + "['1','2','3'] ANY IN d.a.b.c[412].e.f, 'test_analyzer'), 2.5) RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ANALYZER(BOOST(" + "['1','2','3'] ANY == d.a['b']['c'][412].e.f, 2.5), 'test_analyzer') RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER BOOST(ANALYZER(" + "['1','2','3'] ANY == d.a.b.c[412].e.f, 'test_analyzer'), 2.5) RETURN d", + expected); + } + // complex attribute name with offset, boost, analyzer ALL + { + irs::Or expected; + auto& root = expected.add(); + root.boost(2.5); + root.add() + .field(mangleString("a.b.c[412].e.f", "test_analyzer")) + .term("1"); + root.add() + .field(mangleString("a.b.c[412].e.f", "test_analyzer")) + .term("2"); + root.add() + .field(mangleString("a.b.c[412].e.f", "test_analyzer")) + .term("3"); + + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ANALYZER(BOOST(" + "['1','2','3'] ALL IN d.a['b']['c'][412].e.f, 2.5), 'test_analyzer') RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER BOOST(ANALYZER(" + "['1','2','3'] ALL IN d.a.b.c[412].e.f, 'test_analyzer'), 2.5) RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ANALYZER(BOOST(" + "['1','2','3'] ALL == d.a['b']['c'][412].e.f, 2.5), 'test_analyzer') RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER BOOST(ANALYZER(" + "['1','2','3'] ALL == d.a.b.c[412].e.f, 'test_analyzer'), 2.5) RETURN d", + expected); + } + // complex attribute name with offset, boost, analyzer NONE + { + irs::Or expected; + auto& root = expected.add().filter(); + root.boost(2.5); + root.add() + .field(mangleString("a.b.c[412].e.f", "test_analyzer")) + .term("1"); + root.add() + .field(mangleString("a.b.c[412].e.f", "test_analyzer")) + .term("2"); + root.add() + .field(mangleString("a.b.c[412].e.f", "test_analyzer")) + .term("3"); + + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ANALYZER(BOOST(" + "['1','2','3'] NONE IN d.a['b']['c'][412].e.f, 2.5), 'test_analyzer') RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER BOOST(ANALYZER(" + "['1','2','3'] NONE IN d.a.b.c[412].e.f, 'test_analyzer'), 2.5) RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ANALYZER(BOOST(" + "['1','2','3'] NONE == d.a['b']['c'][412].e.f, 2.5), 'test_analyzer') RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER BOOST(ANALYZER(" + "['1','2','3'] NONE == d.a.b.c[412].e.f, 'test_analyzer'), 2.5) RETURN d", + expected); + } + // heterogeneous array values, analyzer, boost ANY + { + irs::Or expected; + auto& root = expected.add(); + root.boost(1.5); + root.add() + .field(mangleString("quick.brown.fox", "test_analyzer")) + .term("1"); + root.add().field(mangleNull("quick.brown.fox")).term(irs::null_token_stream::value_null()); + root.add().field(mangleBool("quick.brown.fox")).term(irs::boolean_token_stream::value_true()); + root.add().field(mangleBool("quick.brown.fox")).term(irs::boolean_token_stream::value_false()); + { + irs::numeric_token_stream stream; + auto& term = stream.attributes().get(); + stream.reset(2.); + EXPECT_TRUE(stream.next()); + root.add().field(mangleNumeric("quick.brown.fox")).term(term->value()); + } + + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ANALYZER(BOOST(" + "['1',null,true,false,2] ANY IN d.quick.brown.fox, 1.5), 'test_analyzer') RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER BOOST(ANALYZER( " + "['1',null,true,false,2] ANY IN d.quick['brown'].fox, 'test_analyzer'), 1.5) RETURN d", + expected); + + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ANALYZER(BOOST(" + "['1',null,true,false,2] ANY == d.quick.brown.fox, 1.5), 'test_analyzer') RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER BOOST(ANALYZER( " + "['1',null,true,false,2] ANY == d.quick['brown'].fox, 'test_analyzer'), 1.5) RETURN d", + expected); + } + // heterogeneous array values, analyzer, boost ALL + { + irs::Or expected; + auto& root = expected.add(); + root.boost(1.5); + root.add() + .field(mangleString("quick.brown.fox", "test_analyzer")) + .term("1"); + root.add().field(mangleNull("quick.brown.fox")).term(irs::null_token_stream::value_null()); + root.add().field(mangleBool("quick.brown.fox")).term(irs::boolean_token_stream::value_true()); + root.add().field(mangleBool("quick.brown.fox")).term(irs::boolean_token_stream::value_false()); + { + irs::numeric_token_stream stream; + auto& term = stream.attributes().get(); + stream.reset(2.); + EXPECT_TRUE(stream.next()); + root.add().field(mangleNumeric("quick.brown.fox")).term(term->value()); + } + + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ANALYZER(BOOST(" + "['1',null,true,false,2] ALL IN d.quick.brown.fox, 1.5), 'test_analyzer') RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER BOOST(ANALYZER( " + "['1',null,true,false,2] ALL IN d.quick['brown'].fox, 'test_analyzer'), 1.5) RETURN d", + expected); + + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ANALYZER(BOOST(" + "['1',null,true,false,2] ALL == d.quick.brown.fox, 1.5), 'test_analyzer') RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER BOOST(ANALYZER( " + "['1',null,true,false,2] ALL == d.quick['brown'].fox, 'test_analyzer'), 1.5) RETURN d", + expected); + } + // heterogeneous array values, analyzer, boost NONE + { + irs::Or expected; + auto& root = expected.add().filter(); + root.boost(1.5); + root.add() + .field(mangleString("quick.brown.fox", "test_analyzer")) + .term("1"); + root.add().field(mangleNull("quick.brown.fox")).term(irs::null_token_stream::value_null()); + root.add().field(mangleBool("quick.brown.fox")).term(irs::boolean_token_stream::value_true()); + root.add().field(mangleBool("quick.brown.fox")).term(irs::boolean_token_stream::value_false()); + { + irs::numeric_token_stream stream; + auto& term = stream.attributes().get(); + stream.reset(2.); + EXPECT_TRUE(stream.next()); + root.add().field(mangleNumeric("quick.brown.fox")).term(term->value()); + } + + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ANALYZER(BOOST(" + "['1',null,true,false,2] NONE IN d.quick.brown.fox, 1.5), 'test_analyzer') RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER BOOST(ANALYZER( " + "['1',null,true,false,2] NONE IN d.quick['brown'].fox, 'test_analyzer'), 1.5) RETURN d", + expected); + + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ANALYZER(BOOST(" + "['1',null,true,false,2] NONE == d.quick.brown.fox, 1.5), 'test_analyzer') RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER BOOST(ANALYZER( " + "['1',null,true,false,2] NONE == d.quick['brown'].fox, 'test_analyzer'), 1.5) RETURN d", + expected); + } + + // empty array ANY + { + irs::Or expected; + expected.add(); + + assertFilterSuccess( + vocbase(), "FOR d IN collection FILTER [] ANY IN d.quick.brown.fox RETURN d", expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER [] ANY IN d['quick'].brown.fox RETURN d", expected); + assertFilterSuccess( + vocbase(), "FOR d IN collection FILTER [] ANY == d.quick.brown.fox RETURN d", expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER [] ANY == d['quick'].brown.fox RETURN d", expected); + } + + // empty array ALL + { + irs::Or expected; + expected.add(); + expected.boost(2.5); + assertFilterSuccess( + vocbase(), "FOR d IN collection FILTER BOOST([] ALL IN d.quick.brown.fox, 2.5) RETURN d", expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER BOOST([] ALL IN d['quick'].brown.fox, 2.5) RETURN d", expected); + assertFilterSuccess( + vocbase(), "FOR d IN collection FILTER BOOST([] ALL == d.quick.brown.fox, 2.5) RETURN d", expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER BOOST([] ALL == d['quick'].brown.fox, 2.5) RETURN d", expected); + } + + // empty array NONE + { + irs::Or expected; + expected.add(); + expected.boost(2.5); + assertFilterSuccess( + vocbase(), "FOR d IN collection FILTER BOOST([] NONE IN d.quick.brown.fox, 2.5) RETURN d", expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER BOOST([] NONE IN d['quick'].brown.fox, 2.5) RETURN d", expected); + assertFilterSuccess( + vocbase(), "FOR d IN collection FILTER BOOST([] NONE == d.quick.brown.fox, 2.5) RETURN d", expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER BOOST([] NONE == d['quick'].brown.fox, 2.5) RETURN d", expected); + } + + // dynamic complex attribute name ANY + { + 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& root = expected.add(); + root.add() + .field(mangleStringIdentity("a.b.c.e[4].f[5].g[3].g.a")) + .term("1"); + root.add() + .field(mangleStringIdentity("a.b.c.e[4].f[5].g[3].g.a")) + .term("2"); + root.add() + .field(mangleStringIdentity("a.b.c.e[4].f[5].g[3].g.a")) + .term("3"); + + assertFilterSuccess( + vocbase(), + "LET a='a' LET c='c' LET offsetInt=4 LET offsetDbl=5.6 FOR d IN " + "collection FILTER " + " ['1','2','3'] ANY IN " + "d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_FORWARD_('a')] " + " RETURN d", + expected, &ctx); + } + // dynamic complex attribute name ALL + { + 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& root = expected.add(); + root.add() + .field(mangleStringIdentity("a.b.c.e[4].f[5].g[3].g.a")) + .term("1"); + root.add() + .field(mangleStringIdentity("a.b.c.e[4].f[5].g[3].g.a")) + .term("2"); + root.add() + .field(mangleStringIdentity("a.b.c.e[4].f[5].g[3].g.a")) + .term("3"); + + assertFilterSuccess( + vocbase(), + "LET a='a' LET c='c' LET offsetInt=4 LET offsetDbl=5.6 FOR d IN " + "collection FILTER " + " ['1','2','3'] ALL IN " + "d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_FORWARD_('a')] " + " RETURN d", + expected, &ctx); + } + // dynamic complex attribute name NONE + { + 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& root = expected.add().filter(); + root.add() + .field(mangleStringIdentity("a.b.c.e[4].f[5].g[3].g.a")) + .term("1"); + root.add() + .field(mangleStringIdentity("a.b.c.e[4].f[5].g[3].g.a")) + .term("2"); + root.add() + .field(mangleStringIdentity("a.b.c.e[4].f[5].g[3].g.a")) + .term("3"); + + assertFilterSuccess( + vocbase(), + "LET a='a' LET c='c' LET offsetInt=4 LET offsetDbl=5.6 FOR d IN " + "collection FILTER " + " ['1','2','3'] NONE IN " + "d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_FORWARD_('a')] " + " RETURN d", + expected, &ctx); + } + + // invalid dynamic attribute name + { + 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("offsetDbl", arangodb::aql::AqlValue(arangodb::aql::AqlValue( + arangodb::aql::AqlValueHintDouble{5.6}))); + + assertFilterExecutionFail( + vocbase(), + "LET a='a' LET c='c' LET offsetInt=4 LET offsetDbl=5.6 FOR d IN " + "collection FILTER " + " ['1','2','3'] ANY IN " + "d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_FORWARD_('a')] " + " RETURN d", + &ctx); + assertFilterExecutionFail( + vocbase(), + "LET a='a' LET c='c' LET offsetInt=4 LET offsetDbl=5.6 FOR d IN " + "collection FILTER " + " ['1','2','3'] ALL IN " + "d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_FORWARD_('a')] " + " RETURN d", + &ctx); + assertFilterExecutionFail( + vocbase(), + "LET a='a' LET c='c' LET offsetInt=4 LET offsetDbl=5.6 FOR d IN " + "collection FILTER " + " ['1','2','3'] NONE IN " + "d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_FORWARD_('a')] " + " RETURN d", + &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( + vocbase(), + "LET a=null LET c='c' LET offsetInt=4 LET offsetDbl=5.6 FOR d IN " + "collection FILTER " + " ['1','2','3'] ANY IN " + "d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_FORWARD_('a')] " + " RETURN d", + &ctx); + assertFilterExecutionFail( + vocbase(), + "LET a=null LET c='c' LET offsetInt=4 LET offsetDbl=5.6 FOR d IN " + "collection FILTER " + " ['1','2','3'] ALL IN " + "d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_FORWARD_('a')] " + " RETURN d", + &ctx); + assertFilterExecutionFail( + vocbase(), + "LET a=null LET c='c' LET offsetInt=4 LET offsetDbl=5.6 FOR d IN " + "collection FILTER " + " ['1','2','3'] NONE IN " + "d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_FORWARD_('a')] " + " RETURN d", + &ctx); + } + + // invalid dynamic attribute name (bool value) + { + ExpressionContextMock ctx; + ctx.vars.emplace("a", arangodb::aql::AqlValue(arangodb::aql::AqlValueHintBool{false})); // 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( + vocbase(), + "LET a=false 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')] " + "in ['1','2','3'] RETURN d", + &ctx); + } + + // reference in array ANY + { + arangodb::aql::Variable var("c", 0); + arangodb::aql::AqlValue value(arangodb::aql::AqlValueHintInt(2)); + arangodb::aql::AqlValueGuard guard(value, true); + + irs::numeric_token_stream stream; + stream.reset(2.); + EXPECT_TRUE(stream.next()); + auto& term = stream.attributes().get(); + + ExpressionContextMock ctx; + ctx.vars.emplace(var.name, value); + + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("a.b.c.e.f")).term("1"); + root.add().field(mangleNumeric("a.b.c.e.f")).term(term->value()); + root.add().field(mangleStringIdentity("a.b.c.e.f")).term("3"); + + // not a constant in array + assertFilterSuccess( + vocbase(), + "LET c=2 FOR d IN collection FILTER ['1', c, '3'] ANY IN d.a.b.c.e.f " + "RETURN d", + expected, + &ctx); + } + // reference in array ALL + { + arangodb::aql::Variable var("c", 0); + arangodb::aql::AqlValue value(arangodb::aql::AqlValueHintInt(2)); + arangodb::aql::AqlValueGuard guard(value, true); + + irs::numeric_token_stream stream; + stream.reset(2.); + EXPECT_TRUE(stream.next()); + auto& term = stream.attributes().get(); + + ExpressionContextMock ctx; + ctx.vars.emplace(var.name, value); + + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("a.b.c.e.f")).term("1"); + root.add().field(mangleNumeric("a.b.c.e.f")).term(term->value()); + root.add().field(mangleStringIdentity("a.b.c.e.f")).term("3"); + + // not a constant in array + assertFilterSuccess( + vocbase(), + "LET c=2 FOR d IN collection FILTER ['1', c, '3'] ALL IN d.a.b.c.e.f " + "RETURN d", + expected, + &ctx); + } + // reference in array NONE + { + arangodb::aql::Variable var("c", 0); + arangodb::aql::AqlValue value(arangodb::aql::AqlValueHintInt(2)); + arangodb::aql::AqlValueGuard guard(value, true); + + irs::numeric_token_stream stream; + stream.reset(2.); + EXPECT_TRUE(stream.next()); + auto& term = stream.attributes().get(); + + ExpressionContextMock ctx; + ctx.vars.emplace(var.name, value); + + irs::Or expected; + auto& root = expected.add().filter(); + root.add().field(mangleStringIdentity("a.b.c.e.f")).term("1"); + root.add().field(mangleNumeric("a.b.c.e.f")).term(term->value()); + root.add().field(mangleStringIdentity("a.b.c.e.f")).term("3"); + + // not a constant in array + assertFilterSuccess( + vocbase(), + "LET c=2 FOR d IN collection FILTER ['1', c, '3'] NONE IN d.a.b.c.e.f " + "RETURN d", + expected, + &ctx); + } + // array as reference, boost, analyzer ANY + { + auto obj = arangodb::velocypack::Parser::fromJson("[ \"1\", 2, \"3\"]"); + arangodb::aql::AqlValue value(obj->slice()); + arangodb::aql::AqlValueGuard guard(value, true); + + irs::numeric_token_stream stream; + stream.reset(2.); + EXPECT_TRUE(stream.next()); + auto& term = stream.attributes().get(); + + ExpressionContextMock ctx; + ctx.vars.emplace("x", value); + + irs::Or expected; + auto& root = expected.add(); + root.boost(1.5); + root.add() + .field(mangleString("a.b.c.e.f", "test_analyzer")) + .term("1"); + root.add().field(mangleNumeric("a.b.c.e.f")).term(term->value()); + root.add() + .field(mangleString("a.b.c.e.f", "test_analyzer")) + .term("3"); + + assertFilterSuccess( + vocbase(), + "LET x=['1', 2, '3'] FOR d IN collection FILTER " + "ANALYZER(BOOST(x ANY IN d.a.b.c.e.f, 1.5), 'test_analyzer') RETURN d", + expected, &ctx); + assertFilterSuccess( + vocbase(), + "LET x=['1', 2, '3'] FOR d IN collection FILTER " + "BOOST(ANALYZER(x ANY IN d.a.b.c.e.f, 'test_analyzer'), 1.5) RETURN d", + expected, &ctx); + } + // array as reference, boost, analyzer ALL + { + auto obj = arangodb::velocypack::Parser::fromJson("[ \"1\", 2, \"3\"]"); + arangodb::aql::AqlValue value(obj->slice()); + arangodb::aql::AqlValueGuard guard(value, true); + + irs::numeric_token_stream stream; + stream.reset(2.); + EXPECT_TRUE(stream.next()); + auto& term = stream.attributes().get(); + + ExpressionContextMock ctx; + ctx.vars.emplace("x", value); + + irs::Or expected; + auto& root = expected.add(); + root.boost(1.5); + root.add() + .field(mangleString("a.b.c.e.f", "test_analyzer")) + .term("1"); + root.add().field(mangleNumeric("a.b.c.e.f")).term(term->value()); + root.add() + .field(mangleString("a.b.c.e.f", "test_analyzer")) + .term("3"); + + assertFilterSuccess( + vocbase(), + "LET x=['1', 2, '3'] FOR d IN collection FILTER " + "ANALYZER(BOOST(x ALL IN d.a.b.c.e.f, 1.5), 'test_analyzer') RETURN d", + expected, &ctx); + assertFilterSuccess( + vocbase(), + "LET x=['1', 2, '3'] FOR d IN collection FILTER " + "BOOST(ANALYZER(x ALL IN d.a.b.c.e.f, 'test_analyzer'), 1.5) RETURN d", + expected, &ctx); + } + // array as reference, boost, analyzer NONE + { + auto obj = arangodb::velocypack::Parser::fromJson("[ \"1\", 2, \"3\"]"); + arangodb::aql::AqlValue value(obj->slice()); + arangodb::aql::AqlValueGuard guard(value, true); + + irs::numeric_token_stream stream; + stream.reset(2.); + EXPECT_TRUE(stream.next()); + auto& term = stream.attributes().get(); + + ExpressionContextMock ctx; + ctx.vars.emplace("x", value); + + irs::Or expected; + auto& root = expected.add().filter(); + root.boost(1.5); + root.add() + .field(mangleString("a.b.c.e.f", "test_analyzer")) + .term("1"); + root.add().field(mangleNumeric("a.b.c.e.f")).term(term->value()); + root.add() + .field(mangleString("a.b.c.e.f", "test_analyzer")) + .term("3"); + + assertFilterSuccess( + vocbase(), + "LET x=['1', 2, '3'] FOR d IN collection FILTER " + "ANALYZER(BOOST(x NONE IN d.a.b.c.e.f, 1.5), 'test_analyzer') RETURN d", + expected, &ctx); + assertFilterSuccess( + vocbase(), + "LET x=['1', 2, '3'] FOR d IN collection FILTER " + "BOOST(ANALYZER(x NONE IN d.a.b.c.e.f, 'test_analyzer'), 1.5) RETURN d", + expected, &ctx); + } + + // empty array ANY + { + irs::Or expected; + expected.add(); + + assertFilterSuccess( + vocbase(), "FOR d IN collection FILTER [] ANY IN d.a RETURN d", expected); + assertFilterSuccess( + vocbase(), "FOR d IN collection FILTER [] ANY IN d['a'] RETURN d", expected); + + assertFilterSuccess( + vocbase(), "FOR d IN collection FILTER [] ANY == d.a RETURN d", expected); + assertFilterSuccess( + vocbase(), "FOR d IN collection FILTER [] ANY == d['a'] RETURN d", expected); + } + // empty array ALL/NONE + { + irs::Or expected; + expected.add(); + + assertFilterSuccess( + vocbase(), "FOR d IN collection FILTER [] ALL IN d.a RETURN d", expected); + assertFilterSuccess( + vocbase(), "FOR d IN collection FILTER [] ALL IN d['a'] RETURN d", expected); + + assertFilterSuccess( + vocbase(), "FOR d IN collection FILTER [] ALL == d.a RETURN d", expected); + assertFilterSuccess( + vocbase(), "FOR d IN collection FILTER [] ALL == d['a'] RETURN d", expected); + + assertFilterSuccess( + vocbase(), "FOR d IN collection FILTER [] NONE IN d.a RETURN d", expected); + assertFilterSuccess( + vocbase(), "FOR d IN collection FILTER [] NONE IN d['a'] RETURN d", expected); + + assertFilterSuccess( + vocbase(), "FOR d IN collection FILTER [] NONE == d.a RETURN d", expected); + assertFilterSuccess( + vocbase(), "FOR d IN collection FILTER [] NONE == d['a'] RETURN d", expected); + } + + // Auxilary check lambdas. Need them to check root part of expected filterd + // direct == check is not possible as we will have byExpresssion filters generated on the fly + auto checkAny = [](irs::Or& actual, iresearch::boost_t boost) { + EXPECT_EQ(1, actual.size()); + auto& root = dynamic_cast(*actual.begin()); + EXPECT_EQ(irs::Or::type(), root.type()); + EXPECT_EQ(3, root.size()); + EXPECT_EQ(boost, root.boost()); + return root.begin(); + }; + auto checkAll = [](irs::Or& actual, iresearch::boost_t boost) { + EXPECT_EQ(1, actual.size()); + auto& root = dynamic_cast(*actual.begin()); + EXPECT_EQ(irs::And::type(), root.type()); + EXPECT_EQ(3, root.size()); + EXPECT_EQ(boost, root.boost()); + return root.begin(); + }; + auto checkNone = [](irs::Or& actual, iresearch::boost_t boost) { + EXPECT_EQ(1, actual.size()); + auto& notFilter = dynamic_cast(*actual.begin()); + auto& root = dynamic_cast(*notFilter.filter()); + EXPECT_EQ(irs::Or::type(), root.type()); + EXPECT_EQ(3, root.size()); + EXPECT_EQ(boost, root.boost()); + return root.begin(); + }; + + // nondeterministic value + { + std::vector>> const testCases = { + { "FOR d IN collection FILTER [ '1', RAND(), '3' ] ANY IN d.a.b.c.e.f RETURN d ", checkAny}, + { "FOR d IN collection FILTER [ '1', RAND(), '3' ] ALL IN d.a.b.c.e.f RETURN d ", checkAll}, + { "FOR d IN collection FILTER [ '1', RAND(), '3' ] NONE IN d.a.b.c.e.f RETURN d ", checkNone}, + { "FOR d IN collection FILTER [ '1', RAND(), '3' ] ANY == d.a.b.c.e.f RETURN d ", checkAny}, + { "FOR d IN collection FILTER [ '1', RAND(), '3' ] ALL == d.a.b.c.e.f RETURN d ", checkAll}, + { "FOR d IN collection FILTER [ '1', RAND(), '3' ] NONE == d.a.b.c.e.f RETURN d ", checkNone} + }; + + for (auto caseData : testCases) { + const auto& queryString = caseData.first; + SCOPED_TRACE(testing::Message("Testing with non-determenistic value. Query: ") << queryString); + std::string const refName = "d"; + + TRI_vocbase_t vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, testDBInfo(server.server())); + + auto options = std::make_shared(); + + arangodb::aql::Query query(false, vocbase, arangodb::aql::QueryString(queryString), + nullptr, options, arangodb::aql::PART_MAIN); + + auto const parseResult = query.parse(); + ASSERT_TRUE(parseResult.result.ok()); + + auto* ast = query.ast(); + ASSERT_TRUE(ast); + + auto* root = ast->root(); + ASSERT_TRUE(root); + + // find first FILTER node + arangodb::aql::AstNode* filterNode = nullptr; + for (size_t i = 0; i < root->numMembers(); ++i) { + auto* node = root->getMemberUnchecked(i); + ASSERT_TRUE(node); + + if (arangodb::aql::NODE_TYPE_FILTER == node->type) { + filterNode = node; + break; + } + } + ASSERT_TRUE(filterNode); + + // find referenced variable + auto* allVars = ast->variables(); + ASSERT_TRUE(allVars); + arangodb::aql::Variable* ref = nullptr; + for (auto entry : allVars->variables(true)) { + if (entry.second == refName) { + ref = allVars->getVariable(entry.first); + break; + } + } + ASSERT_TRUE(ref); + + + // iteratorForCondition + { + arangodb::transaction::Methods trx(arangodb::transaction::StandaloneContext::Create(vocbase), + {}, {}, {}, arangodb::transaction::Options()); + + auto dummyPlan = arangodb::tests::planFromQuery(vocbase, "RETURN 1"); + + irs::Or actual; + arangodb::iresearch::QueryContext const ctx{ &trx, dummyPlan.get(), ast, + &ExpressionContextMock::EMPTY, ref }; + EXPECT_TRUE( + (arangodb::iresearch::FilterFactory::filter(&actual, ctx, *filterNode).ok())); + + { + auto begin = caseData.second(actual, 1); + + // 1st filter + { + irs::by_term expected; + expected.field(mangleStringIdentity("a.b.c.e.f")).term("1"); + EXPECT_EQ(expected, *begin); + } + + // 2nd filter + { + ++begin; + EXPECT_EQ(arangodb::iresearch::ByExpression::type(), begin->type()); + EXPECT_NE(nullptr, + dynamic_cast(&*begin)); + } + + // 3rd filter + { + ++begin; + irs::by_term expected; + expected.field(mangleStringIdentity("a.b.c.e.f")).term("3"); + EXPECT_EQ(expected, *begin); + } + } + } + } + } + + // self-referenced value + { + std::vector>> const testCases = { + {"FOR d IN collection FILTER [ '1', d, '3' ] ANY IN d.a.b.c.e.f RETURN d", checkAny}, + {"FOR d IN collection FILTER [ '1', d, '3' ] ALL IN d.a.b.c.e.f RETURN d", checkAll}, + {"FOR d IN collection FILTER [ '1', d, '3' ] NONE IN d.a.b.c.e.f RETURN d", checkNone}, + {"FOR d IN collection FILTER [ '1', d, '3' ] ANY == d.a.b.c.e.f RETURN d", checkAny}, + {"FOR d IN collection FILTER [ '1', d, '3' ] ALL == d.a.b.c.e.f RETURN d", checkAll}, + {"FOR d IN collection FILTER [ '1', d, '3' ] NONE == d.a.b.c.e.f RETURN d", checkNone} + }; + for (auto caseData : testCases) { + const auto& queryString = caseData.first; + SCOPED_TRACE(testing::Message("Testing with self-referenced value. Query: ") << queryString); + std::string const refName = "d"; + + TRI_vocbase_t vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, testDBInfo(server.server())); + + auto options = std::make_shared(); + + arangodb::aql::Query query(false, vocbase, arangodb::aql::QueryString(queryString), + nullptr, options, arangodb::aql::PART_MAIN); + + auto const parseResult = query.parse(); + ASSERT_TRUE(parseResult.result.ok()); + + auto* ast = query.ast(); + ASSERT_TRUE(ast); + + auto* root = ast->root(); + ASSERT_TRUE(root); + + // find first FILTER node + arangodb::aql::AstNode* filterNode = nullptr; + for (size_t i = 0; i < root->numMembers(); ++i) { + auto* node = root->getMemberUnchecked(i); + ASSERT_TRUE(node); + + if (arangodb::aql::NODE_TYPE_FILTER == node->type) { + filterNode = node; + break; + } + } + ASSERT_TRUE(filterNode); + + // find referenced variable + auto* allVars = ast->variables(); + ASSERT_TRUE(allVars); + arangodb::aql::Variable* ref = nullptr; + for (auto entry : allVars->variables(true)) { + if (entry.second == refName) { + ref = allVars->getVariable(entry.first); + break; + } + } + ASSERT_TRUE(ref); + + // supportsFilterCondition + { + arangodb::iresearch::QueryContext const ctx{ nullptr, nullptr, nullptr, nullptr, ref }; + EXPECT_TRUE( + (arangodb::iresearch::FilterFactory::filter(nullptr, ctx, *filterNode).ok())); + } + + // iteratorForCondition + { + arangodb::transaction::Methods trx(arangodb::transaction::StandaloneContext::Create(vocbase), + {}, {}, {}, arangodb::transaction::Options()); + + auto dummyPlan = arangodb::tests::planFromQuery(vocbase, "RETURN 1"); + + irs::Or actual; + arangodb::iresearch::QueryContext const ctx{ &trx, dummyPlan.get(), ast, + &ExpressionContextMock::EMPTY, ref }; + EXPECT_TRUE( + (arangodb::iresearch::FilterFactory::filter(&actual, ctx, *filterNode).ok())); + + { + auto begin = caseData.second(actual, 1); + + // 1st filter + { + irs::by_term expected; + expected.field(mangleStringIdentity("a.b.c.e.f")).term("1"); + EXPECT_EQ(expected, *begin); + } + + // 2nd filter + { + ++begin; + EXPECT_EQ(arangodb::iresearch::ByExpression::type(), begin->type()); + EXPECT_NE(nullptr, + dynamic_cast(&*begin)); + } + + // 3rd filter + { + ++begin; + irs::by_term expected; + expected.field(mangleStringIdentity("a.b.c.e.f")).term("3"); + EXPECT_EQ(expected, *begin); + } + } + } + } + } + + // self-referenced value + { + std::vector>> const testCases = { + {"FOR d IN collection FILTER [ '1', d.e, d.a.b.c.e.f ] ANY IN d.a.b.c.e.f RETURN d", checkAny}, + {"FOR d IN collection FILTER [ '1', d.e, d.a.b.c.e.f ] ALL IN d.a.b.c.e.f RETURN d", checkAll}, + {"FOR d IN collection FILTER [ '1', d.e, d.a.b.c.e.f ] NONE IN d.a.b.c.e.f RETURN d", checkNone}, + {"FOR d IN collection FILTER [ '1', d.e, d.a.b.c.e.f ] ANY == d.a.b.c.e.f RETURN d", checkAny}, + {"FOR d IN collection FILTER [ '1', d.e, d.a.b.c.e.f ] ALL == d.a.b.c.e.f RETURN d", checkAll}, + {"FOR d IN collection FILTER [ '1', d.e, d.a.b.c.e.f ] NONE == d.a.b.c.e.f RETURN d", checkNone}, + }; + for (auto caseData : testCases) { + const auto& queryString = caseData.first; + SCOPED_TRACE(testing::Message("Testing with self-referenced value. Query: ") << queryString); + std::string const refName = "d"; + + TRI_vocbase_t vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, testDBInfo(server.server())); + + auto options = std::make_shared(); + + arangodb::aql::Query query(false, vocbase, arangodb::aql::QueryString(queryString), + nullptr, options, arangodb::aql::PART_MAIN); + + auto const parseResult = query.parse(); + ASSERT_TRUE(parseResult.result.ok()); + + auto* ast = query.ast(); + ASSERT_TRUE(ast); + + auto* root = ast->root(); + ASSERT_TRUE(root); + + // find first FILTER node + arangodb::aql::AstNode* filterNode = nullptr; + for (size_t i = 0; i < root->numMembers(); ++i) { + auto* node = root->getMemberUnchecked(i); + ASSERT_TRUE(node); + + if (arangodb::aql::NODE_TYPE_FILTER == node->type) { + filterNode = node; + break; + } + } + ASSERT_TRUE(filterNode); + + // find referenced variable + auto* allVars = ast->variables(); + ASSERT_TRUE(allVars); + arangodb::aql::Variable* ref = nullptr; + for (auto entry : allVars->variables(true)) { + if (entry.second == refName) { + ref = allVars->getVariable(entry.first); + break; + } + } + ASSERT_TRUE(ref); + + // supportsFilterCondition + { + arangodb::iresearch::QueryContext const ctx{ nullptr, nullptr, nullptr, nullptr, ref }; + EXPECT_TRUE( + (arangodb::iresearch::FilterFactory::filter(nullptr, ctx, *filterNode).ok())); + } + + // iteratorForCondition + { + arangodb::transaction::Methods trx(arangodb::transaction::StandaloneContext::Create(vocbase), + {}, {}, {}, arangodb::transaction::Options()); + + auto dummyPlan = arangodb::tests::planFromQuery(vocbase, "RETURN 1"); + + irs::Or actual; + arangodb::iresearch::QueryContext const ctx{ &trx, dummyPlan.get(), ast, + &ExpressionContextMock::EMPTY, ref }; + EXPECT_TRUE( + (arangodb::iresearch::FilterFactory::filter(&actual, ctx, *filterNode).ok())); + + { + auto begin = caseData.second(actual, 1); + + // 1st filter + { + irs::by_term expected; + expected.field(mangleStringIdentity("a.b.c.e.f")).term("1"); + EXPECT_EQ(expected, *begin); + } + + // 2nd filter + { + ++begin; + EXPECT_EQ(arangodb::iresearch::ByExpression::type(), begin->type()); + EXPECT_NE(nullptr, + dynamic_cast(&*begin)); + } + + // 3rd filter + { + ++begin; + EXPECT_EQ(arangodb::iresearch::ByExpression::type(), begin->type()); + EXPECT_TRUE(nullptr != + dynamic_cast(&*begin)); + } + } + } + } + } + + // self-referenced value + { + std::vector>> const testCases = { + {"FOR d IN collection FILTER BOOST([ '1', 1+d.b, '3' ] ANY IN d.a.b.c.e.f, 2.5) RETURN d", checkAny}, + {"FOR d IN collection FILTER BOOST([ '1', 1+d.b, '3' ] ALL IN d.a.b.c.e.f, 2.5) RETURN d", checkAll}, + {"FOR d IN collection FILTER BOOST([ '1', 1+d.b, '3' ] NONE IN d.a.b.c.e.f, 2.5) RETURN d", checkNone}, + {"FOR d IN collection FILTER BOOST([ '1', 1+d.b, '3' ] ANY == d.a.b.c.e.f, 2.5) RETURN d", checkAny}, + {"FOR d IN collection FILTER BOOST([ '1', 1+d.b, '3' ] ALL == d.a.b.c.e.f, 2.5) RETURN d", checkAll}, + {"FOR d IN collection FILTER BOOST([ '1', 1+d.b, '3' ] NONE == d.a.b.c.e.f, 2.5) RETURN d", checkNone}, + }; + for (auto caseData : testCases) { + const auto& queryString = caseData.first; + SCOPED_TRACE(testing::Message("Testing with self-referenced value. Query: ") << queryString); + std::string const refName = "d"; + + TRI_vocbase_t vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, testDBInfo(server.server())); + + auto options = std::make_shared(); + + arangodb::aql::Query query(false, vocbase, arangodb::aql::QueryString(queryString), + nullptr, options, arangodb::aql::PART_MAIN); + + auto const parseResult = query.parse(); + ASSERT_TRUE(parseResult.result.ok()); + + auto* ast = query.ast(); + ASSERT_TRUE(ast); + + auto* root = ast->root(); + ASSERT_TRUE(root); + + // find first FILTER node + arangodb::aql::AstNode* filterNode = nullptr; + for (size_t i = 0; i < root->numMembers(); ++i) { + auto* node = root->getMemberUnchecked(i); + ASSERT_TRUE(node); + + if (arangodb::aql::NODE_TYPE_FILTER == node->type) { + filterNode = node; + break; + } + } + ASSERT_TRUE(filterNode); + + // find referenced variable + auto* allVars = ast->variables(); + ASSERT_TRUE(allVars); + arangodb::aql::Variable* ref = nullptr; + for (auto entry : allVars->variables(true)) { + if (entry.second == refName) { + ref = allVars->getVariable(entry.first); + break; + } + } + ASSERT_TRUE(ref); + + // supportsFilterCondition + { + arangodb::iresearch::QueryContext const ctx{ nullptr, nullptr, nullptr, nullptr, ref }; + EXPECT_TRUE( + (arangodb::iresearch::FilterFactory::filter(nullptr, ctx, *filterNode).ok())); + } + + // iteratorForCondition + { + arangodb::transaction::Methods trx(arangodb::transaction::StandaloneContext::Create(vocbase), + {}, {}, {}, arangodb::transaction::Options()); + + auto dummyPlan = arangodb::tests::planFromQuery(vocbase, "RETURN 1"); + + irs::Or actual; + arangodb::iresearch::QueryContext const ctx{ &trx, dummyPlan.get(), ast, + &ExpressionContextMock::EMPTY, ref }; + EXPECT_TRUE( + (arangodb::iresearch::FilterFactory::filter(&actual, ctx, *filterNode).ok())); + + { + auto begin = caseData.second(actual, 2.5); + + // 1st filter + { + irs::by_term expected; + expected.field(mangleStringIdentity("a.b.c.e.f")).term("1"); + EXPECT_EQ(expected, *begin); + } + + // 2nd filter + { + ++begin; + EXPECT_EQ(arangodb::iresearch::ByExpression::type(), begin->type()); + EXPECT_NE(nullptr, + dynamic_cast(&*begin)); + } + + // 3rd filter + { + ++begin; + irs::by_term expected; + expected.field(mangleStringIdentity("a.b.c.e.f")).term("3"); + EXPECT_EQ(expected, *begin); + } + } + } + } + } + // not array as left argument + { + ExpressionContextMock ctx; + ctx.vars.emplace("a", arangodb::aql::AqlValue(arangodb::aql::AqlValueHintBool{false})); // invalid value type + ctx.vars.emplace("b", arangodb::aql::AqlValue(arangodb::aql::AqlValue{"c"})); + ctx.vars.emplace("c", arangodb::aql::AqlValue(arangodb::aql::AqlValue( + arangodb::aql::AqlValueHintInt{4}))); + ctx.vars.emplace("e", arangodb::aql::AqlValue(arangodb::aql::AqlValue( + arangodb::aql::AqlValueHintDouble{5.6}))); + assertFilterExecutionFail( + vocbase(), + "LET a=null LET b='b' LET c=4 LET e=5.6 FOR d IN collection FILTER a ANY IN d.a RETURN d", + &ctx); + assertFilterExecutionFail( + vocbase(), + "LET a=null LET b='b' LET c=4 LET e=5.6 FOR d IN collection FILTER b ANY == d.a RETURN d", + &ctx); + assertFilterExecutionFail( + vocbase(), + "LET a=null LET b='b' LET c=4 LET e=5.6 FOR d IN collection FILTER c ALL IN d.a RETURN d", + &ctx); + assertFilterExecutionFail( + vocbase(), + "LET a=null LET b='b' LET c=4 LET e=5.6 FOR d IN collection FILTER e ALL == d.a RETURN d", + &ctx); + } + + // heterogeneous references and expression in array, analyzer, boost ANY + { + SCOPED_TRACE("heterogeneous references and expression in array, analyzer, boost ANY"); + ExpressionContextMock ctx; + ctx.vars.emplace("strVal", arangodb::aql::AqlValue("str")); + ctx.vars.emplace("boolVal", + arangodb::aql::AqlValue(arangodb::aql::AqlValueHintBool(false))); + ctx.vars.emplace("numVal", arangodb::aql::AqlValue(arangodb::aql::AqlValueHintInt(2))); + ctx.vars.emplace("nullVal", arangodb::aql::AqlValue(arangodb::aql::AqlValueHintNull{})); + + irs::numeric_token_stream stream; + stream.reset(3.); + EXPECT_TRUE(stream.next()); + auto& term = stream.attributes().get(); + + irs::Or expected; + auto& root = expected.add(); + root.boost(2.5); + root.add() + .field(mangleString("a.b.c.e.f", "test_analyzer")) + .term("1"); + root.add() + .field(mangleString("a.b.c.e.f", "test_analyzer")) + .term("str"); + root.add().field(mangleBool("a.b.c.e.f")).term(irs::boolean_token_stream::value_false()); + root.add().field(mangleNumeric("a.b.c.e.f")).term(term->value()); + root.add().field(mangleNull("a.b.c.e.f")).term(irs::null_token_stream::value_null()); + + // not a constant in array + assertFilterSuccess( + vocbase(), + "LET strVal='str' LET boolVal=false LET numVal=2 LET nullVal=null FOR " + "d IN collection FILTER boost(ANALYZER(['1', strVal, " + "boolVal, numVal+1, nullVal] ANY IN d.a.b.c.e.f, 'test_analyzer'),2.5) RETURN d", + expected, + &ctx); + assertFilterSuccess( + vocbase(), + "LET strVal='str' LET boolVal=false LET numVal=2 LET nullVal=null FOR " + "d IN collection FILTER ANALYZER(boost(['1', strVal, " + "boolVal, numVal+1, nullVal] ANY IN d.a.b.c.e.f , 2.5), 'test_analyzer') RETURN d", + expected, + &ctx); + assertFilterSuccess( + vocbase(), + "LET strVal='str' LET boolVal=false LET numVal=2 LET nullVal=null FOR " + "d IN collection FILTER boost(ANALYZER(['1', strVal, " + "boolVal, numVal+1, nullVal] ANY == d.a.b.c.e.f, 'test_analyzer'),2.5) RETURN d", + expected, + &ctx); + assertFilterSuccess( + vocbase(), + "LET strVal='str' LET boolVal=false LET numVal=2 LET nullVal=null FOR " + "d IN collection FILTER ANALYZER(boost(['1', strVal, " + "boolVal, numVal+1, nullVal] ANY == d.a.b.c.e.f , 2.5), 'test_analyzer') RETURN d", + expected, + &ctx); + } + // heterogeneous references and expression in array, analyzer, boost ALL + { + SCOPED_TRACE("heterogeneous references and expression in array, analyzer, boost ALL"); + ExpressionContextMock ctx; + ctx.vars.emplace("strVal", arangodb::aql::AqlValue("str")); + ctx.vars.emplace("boolVal", + arangodb::aql::AqlValue(arangodb::aql::AqlValueHintBool(false))); + ctx.vars.emplace("numVal", arangodb::aql::AqlValue(arangodb::aql::AqlValueHintInt(2))); + ctx.vars.emplace("nullVal", arangodb::aql::AqlValue(arangodb::aql::AqlValueHintNull{})); + + irs::numeric_token_stream stream; + stream.reset(3.); + EXPECT_TRUE(stream.next()); + auto& term = stream.attributes().get(); + + irs::Or expected; + auto& root = expected.add(); + root.boost(2.5); + root.add() + .field(mangleString("a.b.c.e.f", "test_analyzer")) + .term("1"); + root.add() + .field(mangleString("a.b.c.e.f", "test_analyzer")) + .term("str"); + root.add().field(mangleBool("a.b.c.e.f")).term(irs::boolean_token_stream::value_false()); + root.add().field(mangleNumeric("a.b.c.e.f")).term(term->value()); + root.add().field(mangleNull("a.b.c.e.f")).term(irs::null_token_stream::value_null()); + + // not a constant in array + assertFilterSuccess( + vocbase(), + "LET strVal='str' LET boolVal=false LET numVal=2 LET nullVal=null FOR " + "d IN collection FILTER boost(ANALYZER(['1', strVal, " + "boolVal, numVal+1, nullVal] ALL IN d.a.b.c.e.f, 'test_analyzer'),2.5) RETURN d", + expected, + &ctx); + assertFilterSuccess( + vocbase(), + "LET strVal='str' LET boolVal=false LET numVal=2 LET nullVal=null FOR " + "d IN collection FILTER ANALYZER(boost(['1', strVal, " + "boolVal, numVal+1, nullVal] ALL IN d.a.b.c.e.f , 2.5), 'test_analyzer') RETURN d", + expected, + &ctx); + assertFilterSuccess( + vocbase(), + "LET strVal='str' LET boolVal=false LET numVal=2 LET nullVal=null FOR " + "d IN collection FILTER boost(ANALYZER(['1', strVal, " + "boolVal, numVal+1, nullVal] ALL == d.a.b.c.e.f, 'test_analyzer'),2.5) RETURN d", + expected, + &ctx); + assertFilterSuccess( + vocbase(), + "LET strVal='str' LET boolVal=false LET numVal=2 LET nullVal=null FOR " + "d IN collection FILTER ANALYZER(boost(['1', strVal, " + "boolVal, numVal+1, nullVal] ALL == d.a.b.c.e.f , 2.5), 'test_analyzer') RETURN d", + expected, + &ctx); + } + // heterogeneous references and expression in array, analyzer, boost NONE + { + SCOPED_TRACE("heterogeneous references and expression in array, analyzer, boost NONE"); + ExpressionContextMock ctx; + ctx.vars.emplace("strVal", arangodb::aql::AqlValue("str")); + ctx.vars.emplace("boolVal", + arangodb::aql::AqlValue(arangodb::aql::AqlValueHintBool(false))); + ctx.vars.emplace("numVal", arangodb::aql::AqlValue(arangodb::aql::AqlValueHintInt(2))); + ctx.vars.emplace("nullVal", arangodb::aql::AqlValue(arangodb::aql::AqlValueHintNull{})); + + irs::numeric_token_stream stream; + stream.reset(3.); + EXPECT_TRUE(stream.next()); + auto& term = stream.attributes().get(); + + irs::Or expected; + auto& root = expected.add().filter(); + root.boost(2.5); + root.add() + .field(mangleString("a.b.c.e.f", "test_analyzer")) + .term("1"); + root.add() + .field(mangleString("a.b.c.e.f", "test_analyzer")) + .term("str"); + root.add().field(mangleBool("a.b.c.e.f")).term(irs::boolean_token_stream::value_false()); + root.add().field(mangleNumeric("a.b.c.e.f")).term(term->value()); + root.add().field(mangleNull("a.b.c.e.f")).term(irs::null_token_stream::value_null()); + + // not a constant in array + assertFilterSuccess( + vocbase(), + "LET strVal='str' LET boolVal=false LET numVal=2 LET nullVal=null FOR " + "d IN collection FILTER boost(ANALYZER(['1', strVal, " + "boolVal, numVal+1, nullVal] NONE IN d.a.b.c.e.f, 'test_analyzer'),2.5) RETURN d", + expected, + &ctx); + assertFilterSuccess( + vocbase(), + "LET strVal='str' LET boolVal=false LET numVal=2 LET nullVal=null FOR " + "d IN collection FILTER ANALYZER(boost(['1', strVal, " + "boolVal, numVal+1, nullVal] NONE IN d.a.b.c.e.f , 2.5), 'test_analyzer') RETURN d", + expected, + &ctx); + assertFilterSuccess( + vocbase(), + "LET strVal='str' LET boolVal=false LET numVal=2 LET nullVal=null FOR " + "d IN collection FILTER boost(ANALYZER(['1', strVal, " + "boolVal, numVal+1, nullVal] NONE == d.a.b.c.e.f, 'test_analyzer'),2.5) RETURN d", + expected, + &ctx); + assertFilterSuccess( + vocbase(), + "LET strVal='str' LET boolVal=false LET numVal=2 LET nullVal=null FOR " + "d IN collection FILTER ANALYZER(boost(['1', strVal, " + "boolVal, numVal+1, nullVal] NONE == d.a.b.c.e.f , 2.5), 'test_analyzer') RETURN d", + expected, + &ctx); + } + + // self-reference + assertExpressionFilter(vocbase(), + "FOR d IN myView FILTER [1,2,'3'] ANY IN d RETURN d"); + assertExpressionFilter(vocbase(), + "FOR d IN myView FILTER [1,2,'3'] ALL IN d RETURN d"); + assertExpressionFilter(vocbase(), + "FOR d IN myView FILTER [1,2,'3'] NONE IN d RETURN d"); + assertExpressionFilter(vocbase(), + "FOR d IN myView FILTER [1,2,'3'] ANY == d RETURN d"); + assertExpressionFilter(vocbase(), + "FOR d IN myView FILTER [1,2,'3'] ALL == d RETURN d"); + assertExpressionFilter(vocbase(), + "FOR d IN myView FILTER [1,2,'3'] NONE == d RETURN d"); + + // non-deterministic expression name in array + assertExpressionFilter( + vocbase(), + "LET a='a' LET c='c' LET offsetInt=4 LET offsetDbl=5.6 FOR d IN " + "collection FILTER " + " ['1','2','3'] ANY IN d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_NONDETERM_('a')] RETURN d"); + assertExpressionFilter( + vocbase(), + "LET a='a' LET c='c' LET offsetInt=4 LET offsetDbl=5.6 FOR d IN " + "collection FILTER " + " ['1','2','3'] ALL IN d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_NONDETERM_('a')] RETURN d"); + assertExpressionFilter( + vocbase(), + "LET a='a' LET c='c' LET offsetInt=4 LET offsetDbl=5.6 FOR d IN " + "collection FILTER " + " ['1','2','3'] NONE IN d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_NONDETERM_('a')] RETURN d"); + assertExpressionFilter( + vocbase(), + "LET a='a' LET c='c' LET offsetInt=4 LET offsetDbl=5.6 FOR d IN " + "collection FILTER " + " ['1','2','3'] ANY == d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_NONDETERM_('a')] RETURN d"); + assertExpressionFilter( + vocbase(), + "LET a='a' LET c='c' LET offsetInt=4 LET offsetDbl=5.6 FOR d IN " + "collection FILTER " + " ['1','2','3'] ALL == d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_NONDETERM_('a')] RETURN d"); + assertExpressionFilter( + vocbase(), + "LET a='a' LET c='c' LET offsetInt=4 LET offsetDbl=5.6 FOR d IN " + "collection FILTER " + " ['1','2','3'] NONE == d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_NONDETERM_('a')] RETURN d"); + + // no reference provided + assertFilterExecutionFail( + vocbase(), "LET x={} FOR d IN myView FILTER [1,x.a,3] ANY IN d.a RETURN d", + &ExpressionContextMock::EMPTY); + assertFilterExecutionFail( + vocbase(), "LET x={} FOR d IN myView FILTER [1,x.a,3] ALL IN d.a RETURN d", + &ExpressionContextMock::EMPTY); + assertFilterExecutionFail( + vocbase(), "LET x={} FOR d IN myView FILTER [1,x.a,3] NONE IN d.a RETURN d", + &ExpressionContextMock::EMPTY); + assertFilterExecutionFail( + vocbase(), "LET x={} FOR d IN myView FILTER [1,x.a,3] ANY == d.a RETURN d", + &ExpressionContextMock::EMPTY); + assertFilterExecutionFail( + vocbase(), "LET x={} FOR d IN myView FILTER [1,x.a,3] ALL == d.a RETURN d", + &ExpressionContextMock::EMPTY); + assertFilterExecutionFail( + vocbase(), "LET x={} FOR d IN myView FILTER [1,x.a,3] NONE == d.a RETURN d", + &ExpressionContextMock::EMPTY); + + // not a value in array + assertFilterFail( + vocbase(), "FOR d IN collection FILTER ['1',['2'],'3'] ANY IN d.a RETURN d"); + assertFilterFail( + vocbase(), + "FOR d IN collection FILTER ['1', {\"abc\": \"def\"},'3'] ANY IN d.a RETURN d"); + assertFilterFail( + vocbase(), "FOR d IN collection FILTER ['1',['2'],'3'] ANY == d.a RETURN d"); + assertFilterFail( + vocbase(), + "FOR d IN collection FILTER ['1', {\"abc\": \"def\"},'3'] ANY == d.a RETURN d"); + assertFilterFail( + vocbase(), "FOR d IN collection FILTER ['1',['2'],'3'] ALL IN d.a RETURN d"); + assertFilterFail( + vocbase(), + "FOR d IN collection FILTER ['1', {\"abc\": \"def\"},'3'] ALL IN d.a RETURN d"); + assertFilterFail( + vocbase(), "FOR d IN collection FILTER ['1',['2'],'3'] ALL == d.a RETURN d"); + assertFilterFail( + vocbase(), + "FOR d IN collection FILTER ['1', {\"abc\": \"def\"},'3'] ALL == d.a RETURN d"); + assertFilterFail( + vocbase(), "FOR d IN collection FILTER ['1',['2'],'3'] NONE IN d.a RETURN d"); + assertFilterFail( + vocbase(), + "FOR d IN collection FILTER ['1', {\"abc\": \"def\"},'3'] NONE IN d.a RETURN d"); + assertFilterFail( + vocbase(), "FOR d IN collection FILTER ['1',['2'],'3'] NONE == d.a RETURN d"); + assertFilterFail( + vocbase(), + "FOR d IN collection FILTER ['1', {\"abc\": \"def\"},'3'] NONE == d.a RETURN d"); +} + +TEST_F(IResearchFilterArrayInTest, BinaryNotIn) { + // simple attribute ANY + { + irs::Or expected; + auto& root = expected.add().filter(); + root.add().field(mangleStringIdentity("a")).term("1"); + root.add().field(mangleStringIdentity("a")).term("2"); + root.add().field(mangleStringIdentity("a")).term("3"); + + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ['1','2','3'] ANY NOT IN d.a RETURN d", expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ['1','2','3'] ANY NOT IN d['a'] RETURN d", expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ['1','2','3'] ANY != d.a RETURN d", expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ['1','2','3'] ANY != d['a'] RETURN d", expected); + } + + // simple attribute ALL + { + irs::Or expected; + auto& root = expected.add().filter(); + root.add().field(mangleStringIdentity("a")).term("1"); + root.add().field(mangleStringIdentity("a")).term("2"); + root.add().field(mangleStringIdentity("a")).term("3"); + + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ['1','2','3'] ALL NOT IN d.a RETURN d", expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ['1','2','3'] ALL NOT IN d['a'] RETURN d", expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ['1','2','3'] ALL != d.a RETURN d", expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ['1','2','3'] ALL != d['a'] RETURN d", expected); + } + + // simple attribute NONE + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("a")).term("1"); + root.add().field(mangleStringIdentity("a")).term("2"); + root.add().field(mangleStringIdentity("a")).term("3"); + + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ['1','2','3'] NONE NOT IN d.a RETURN d", expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ['1','2','3'] NONE NOT IN d['a'] RETURN d", expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ['1','2','3'] NONE != d.a RETURN d", expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ['1','2','3'] NONE != d['a'] RETURN d", expected); + } + + // simple offset ANY + { + irs::Or expected; + auto& root = expected.add().filter(); + root.add().field(mangleStringIdentity("[1]")).term("1"); + root.add().field(mangleStringIdentity("[1]")).term("2"); + root.add().field(mangleStringIdentity("[1]")).term("3"); + + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ['1','2','3'] ANY NOT IN d[1] RETURN d", expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ['1','2','3'] ANY != d[1] RETURN d", expected); + } + + // simple offset ALL + { + irs::Or expected; + auto& root = expected.add().filter(); + root.add().field(mangleStringIdentity("[1]")).term("1"); + root.add().field(mangleStringIdentity("[1]")).term("2"); + root.add().field(mangleStringIdentity("[1]")).term("3"); + + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ['1','2','3'] ALL NOT IN d[1] RETURN d", expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ['1','2','3'] ALL != d[1] RETURN d", expected); + } + + // simple offset NONE + { + irs::Or expected; + auto& root = expected.add(); + root.add().field(mangleStringIdentity("[1]")).term("1"); + root.add().field(mangleStringIdentity("[1]")).term("2"); + root.add().field(mangleStringIdentity("[1]")).term("3"); + + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ['1','2','3'] NONE NOT IN d[1] RETURN d", expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ['1','2','3'] NONE != d[1] RETURN d", expected); + } + + // complex attribute name, offset, analyzer, boost ANY + { + irs::Or expected; + auto& root = expected.add().filter(); + root.boost(2.5); + root.add() + .field(mangleString("a.b.c[323].e.f", "test_analyzer")) + .term("1"); + root.add() + .field(mangleString("a.b.c[323].e.f", "test_analyzer")) + .term("2"); + root.add() + .field(mangleString("a.b.c[323].e.f", "test_analyzer")) + .term("3"); + + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER boost(analyzer( " + "['1','2','3'] ANY NOT IN d.a.b.c[323].e.f , 'test_analyzer'), 2.5) RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER analyzer(boost( " + "['1','2','3'] ANY NOT IN d.a['b'].c[323].e.f, 2.5), 'test_analyzer') RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER boost(analyzer(" + "['1','2','3'] ANY NOT IN d.a['b']['c'][323].e.f, 'test_analyzer'), 2.5) RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER boost(analyzer( " + "['1','2','3'] ANY != d.a.b.c[323].e.f , 'test_analyzer'), 2.5) RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER analyzer(boost( " + "['1','2','3'] ANY != d.a['b'].c[323].e.f, 2.5), 'test_analyzer') RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER boost(analyzer(" + "['1','2','3'] ANY != d.a['b']['c'][323].e.f, 'test_analyzer'), 2.5) RETURN d", + expected); + } + // complex attribute name, offset, analyzer, boost ALL + { + irs::Or expected; + auto& root = expected.add().filter(); + root.boost(2.5); + root.add() + .field(mangleString("a.b.c[323].e.f", "test_analyzer")) + .term("1"); + root.add() + .field(mangleString("a.b.c[323].e.f", "test_analyzer")) + .term("2"); + root.add() + .field(mangleString("a.b.c[323].e.f", "test_analyzer")) + .term("3"); + + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER boost(analyzer( " + "['1','2','3'] ALL NOT IN d.a.b.c[323].e.f , 'test_analyzer'), 2.5) RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER analyzer(boost( " + "['1','2','3'] ALL NOT IN d.a['b'].c[323].e.f, 2.5), 'test_analyzer') RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER boost(analyzer(" + "['1','2','3'] ALL NOT IN d.a['b']['c'][323].e.f, 'test_analyzer'), 2.5) RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER boost(analyzer( " + "['1','2','3'] ALL != d.a.b.c[323].e.f , 'test_analyzer'), 2.5) RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER analyzer(boost( " + "['1','2','3'] ALL != d.a['b'].c[323].e.f, 2.5), 'test_analyzer') RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER boost(analyzer(" + "['1','2','3'] ALL != d.a['b']['c'][323].e.f, 'test_analyzer'), 2.5) RETURN d", + expected); + } + // complex attribute name, offset, analyzer, boost NONE + { + irs::Or expected; + auto& root = expected.add(); + root.boost(2.5); + root.add() + .field(mangleString("a.b.c[323].e.f", "test_analyzer")) + .term("1"); + root.add() + .field(mangleString("a.b.c[323].e.f", "test_analyzer")) + .term("2"); + root.add() + .field(mangleString("a.b.c[323].e.f", "test_analyzer")) + .term("3"); + + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER boost(analyzer( " + "['1','2','3'] NONE NOT IN d.a.b.c[323].e.f , 'test_analyzer'), 2.5) RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER analyzer(boost( " + "['1','2','3'] NONE NOT IN d.a['b'].c[323].e.f, 2.5), 'test_analyzer') RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER boost(analyzer(" + "['1','2','3'] NONE NOT IN d.a['b']['c'][323].e.f, 'test_analyzer'), 2.5) RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER boost(analyzer( " + "['1','2','3'] NONE != d.a.b.c[323].e.f , 'test_analyzer'), 2.5) RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER analyzer(boost( " + "['1','2','3'] NONE != d.a['b'].c[323].e.f, 2.5), 'test_analyzer') RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER boost(analyzer(" + "['1','2','3'] NONE != d.a['b']['c'][323].e.f, 'test_analyzer'), 2.5) RETURN d", + expected); + } + // heterogeneous array values, analyzer, boost ANY + { + irs::Or expected; + auto& root = expected.add().filter(); + root.boost(1.5); + root.add() + .field(mangleString("quick.brown.fox", "test_analyzer")) + .term("1"); + root.add().field(mangleNull("quick.brown.fox")).term(irs::null_token_stream::value_null()); + root.add().field(mangleBool("quick.brown.fox")).term(irs::boolean_token_stream::value_true()); + root.add().field(mangleBool("quick.brown.fox")).term(irs::boolean_token_stream::value_false()); + { + irs::numeric_token_stream stream; + auto& term = stream.attributes().get(); + stream.reset(2.); + EXPECT_TRUE(stream.next()); + root.add().field(mangleNumeric("quick.brown.fox")).term(term->value()); + } + + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER BOOST(ANALYZER(['1',null,true,false,2] ANY NOT IN " + "d.quick.brown.fox, 'test_analyzer'), 1.5) RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ANALYZER(BOOST(['1',null,true,false,2] ANY NOT IN " + "d.quick['brown'].fox, 1.5), 'test_analyzer') RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER BOOST(ANALYZER(['1',null,true,false,2] ANY != " + "d.quick.brown.fox, 'test_analyzer'), 1.5) RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ANALYZER(BOOST(['1',null,true,false,2] ANY != " + "d.quick['brown'].fox, 1.5), 'test_analyzer') RETURN d", + expected); + } + // heterogeneous array values, analyzer, boost ALL + { + irs::Or expected; + auto& root = expected.add().filter(); + root.boost(1.5); + root.add() + .field(mangleString("quick.brown.fox", "test_analyzer")) + .term("1"); + root.add().field(mangleNull("quick.brown.fox")).term(irs::null_token_stream::value_null()); + root.add().field(mangleBool("quick.brown.fox")).term(irs::boolean_token_stream::value_true()); + root.add().field(mangleBool("quick.brown.fox")).term(irs::boolean_token_stream::value_false()); + { + irs::numeric_token_stream stream; + auto& term = stream.attributes().get(); + stream.reset(2.); + EXPECT_TRUE(stream.next()); + root.add().field(mangleNumeric("quick.brown.fox")).term(term->value()); + } + + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER BOOST(ANALYZER(['1',null,true,false,2] ALL NOT IN " + "d.quick.brown.fox, 'test_analyzer'), 1.5) RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ANALYZER(BOOST(['1',null,true,false,2] ALL NOT IN " + "d.quick['brown'].fox, 1.5), 'test_analyzer') RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER BOOST(ANALYZER(['1',null,true,false,2] ALL != " + "d.quick.brown.fox, 'test_analyzer'), 1.5) RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ANALYZER(BOOST(['1',null,true,false,2] ALL != " + "d.quick['brown'].fox, 1.5), 'test_analyzer') RETURN d", + expected); + } + // heterogeneous array values, analyzer, boost NONE + { + irs::Or expected; + auto& root = expected.add(); + root.boost(1.5); + root.add() + .field(mangleString("quick.brown.fox", "test_analyzer")) + .term("1"); + root.add().field(mangleNull("quick.brown.fox")).term(irs::null_token_stream::value_null()); + root.add().field(mangleBool("quick.brown.fox")).term(irs::boolean_token_stream::value_true()); + root.add().field(mangleBool("quick.brown.fox")).term(irs::boolean_token_stream::value_false()); + { + irs::numeric_token_stream stream; + auto& term = stream.attributes().get(); + stream.reset(2.); + EXPECT_TRUE(stream.next()); + root.add().field(mangleNumeric("quick.brown.fox")).term(term->value()); + } + + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER BOOST(ANALYZER(['1',null,true,false,2] NONE NOT IN " + "d.quick.brown.fox, 'test_analyzer'), 1.5) RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ANALYZER(BOOST(['1',null,true,false,2] NONE NOT IN " + "d.quick['brown'].fox, 1.5), 'test_analyzer') RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER BOOST(ANALYZER(['1',null,true,false,2] NONE != " + "d.quick.brown.fox, 'test_analyzer'), 1.5) RETURN d", + expected); + assertFilterSuccess( + vocbase(), + "FOR d IN collection FILTER ANALYZER(BOOST(['1',null,true,false,2] NONE != " + "d.quick['brown'].fox, 1.5), 'test_analyzer') RETURN d", + expected); + } + + // dynamic complex attribute name ANY + { + 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& root = expected.add().filter(); + root.add() + .field(mangleStringIdentity("a.b.c.e[4].f[5].g[3].g.a")) + .term("1"); + root.add() + .field(mangleStringIdentity("a.b.c.e[4].f[5].g[3].g.a")) + .term("2"); + root.add() + .field(mangleStringIdentity("a.b.c.e[4].f[5].g[3].g.a")) + .term("3"); + + assertFilterSuccess( + vocbase(), + "LET a='a' LET c='c' LET offsetInt=4 LET offsetDbl=5.6 FOR d IN " + "collection FILTER " + " ['1','2','3'] ANY NOT IN " + "d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_FORWARD_('a')] RETURN d", + expected, &ctx); + assertFilterSuccess( + vocbase(), + "LET a='a' LET c='c' LET offsetInt=4 LET offsetDbl=5.6 FOR d IN " + "collection FILTER " + " ['1','2','3'] ANY != " + "d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_FORWARD_('a')] RETURN d", + expected, &ctx); + } + // dynamic complex attribute name ALL + { + 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& root = expected.add().filter(); + root.add() + .field(mangleStringIdentity("a.b.c.e[4].f[5].g[3].g.a")) + .term("1"); + root.add() + .field(mangleStringIdentity("a.b.c.e[4].f[5].g[3].g.a")) + .term("2"); + root.add() + .field(mangleStringIdentity("a.b.c.e[4].f[5].g[3].g.a")) + .term("3"); + + assertFilterSuccess( + vocbase(), + "LET a='a' LET c='c' LET offsetInt=4 LET offsetDbl=5.6 FOR d IN " + "collection FILTER " + " ['1','2','3'] ALL NOT IN " + "d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_FORWARD_('a')] RETURN d", + expected, &ctx); + assertFilterSuccess( + vocbase(), + "LET a='a' LET c='c' LET offsetInt=4 LET offsetDbl=5.6 FOR d IN " + "collection FILTER " + " ['1','2','3'] ALL != " + "d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_FORWARD_('a')] RETURN d", + expected, &ctx); + } + // dynamic complex attribute name NONE + { + 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& root = expected.add(); + root.add() + .field(mangleStringIdentity("a.b.c.e[4].f[5].g[3].g.a")) + .term("1"); + root.add() + .field(mangleStringIdentity("a.b.c.e[4].f[5].g[3].g.a")) + .term("2"); + root.add() + .field(mangleStringIdentity("a.b.c.e[4].f[5].g[3].g.a")) + .term("3"); + + assertFilterSuccess( + vocbase(), + "LET a='a' LET c='c' LET offsetInt=4 LET offsetDbl=5.6 FOR d IN " + "collection FILTER " + " ['1','2','3'] NONE NOT IN " + "d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_FORWARD_('a')] RETURN d", + expected, &ctx); + assertFilterSuccess( + vocbase(), + "LET a='a' LET c='c' LET offsetInt=4 LET offsetDbl=5.6 FOR d IN " + "collection FILTER " + " ['1','2','3'] NONE != " + "d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_FORWARD_('a')] RETURN d", + expected, &ctx); + } + + // invalid dynamic attribute name ANY + { + 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("offsetDbl", arangodb::aql::AqlValue(arangodb::aql::AqlValue( + arangodb::aql::AqlValueHintDouble{5.6}))); + + assertFilterExecutionFail( + vocbase(), + "LET a='a' LET c='c' LET offsetInt=4 LET offsetDbl=5.6 FOR d IN " + "collection FILTER " + "['1','2','3'] ANY NOT IN d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_FORWARD_('a')] " + "RETURN d", + &ctx); + assertFilterExecutionFail( + vocbase(), + "LET a='a' LET c='c' LET offsetInt=4 LET offsetDbl=5.6 FOR d IN " + "collection FILTER " + "['1','2','3'] ANY != d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_FORWARD_('a')] " + "RETURN d", + &ctx); + } + // invalid dynamic attribute name ALL + { + 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("offsetDbl", arangodb::aql::AqlValue(arangodb::aql::AqlValue( + arangodb::aql::AqlValueHintDouble{5.6}))); + + assertFilterExecutionFail( + vocbase(), + "LET a='a' LET c='c' LET offsetInt=4 LET offsetDbl=5.6 FOR d IN " + "collection FILTER " + "['1','2','3'] ALL NOT IN d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_FORWARD_('a')] " + "RETURN d", + &ctx); + assertFilterExecutionFail( + vocbase(), + "LET a='a' LET c='c' LET offsetInt=4 LET offsetDbl=5.6 FOR d IN " + "collection FILTER " + "['1','2','3'] ALL != d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_FORWARD_('a')] " + "RETURN d", + &ctx); + } + // invalid dynamic attribute name NONE + { + 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("offsetDbl", arangodb::aql::AqlValue(arangodb::aql::AqlValue( + arangodb::aql::AqlValueHintDouble{5.6}))); + + assertFilterExecutionFail( + vocbase(), + "LET a='a' LET c='c' LET offsetInt=4 LET offsetDbl=5.6 FOR d IN " + "collection FILTER " + "['1','2','3'] NONE NOT IN d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_FORWARD_('a')] " + "RETURN d", + &ctx); + assertFilterExecutionFail( + vocbase(), + "LET a='a' LET c='c' LET offsetInt=4 LET offsetDbl=5.6 FOR d IN " + "collection FILTER " + "['1','2','3'] NONE != d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_FORWARD_('a')] " + "RETURN d", + &ctx); + } + + // array as reference, analyzer, boost ANY + { + auto obj = arangodb::velocypack::Parser::fromJson("[ \"1\", 2, \"3\"]"); + arangodb::aql::AqlValue value(obj->slice()); + arangodb::aql::AqlValueGuard guard(value, true); + + irs::numeric_token_stream stream; + stream.reset(2.); + EXPECT_TRUE(stream.next()); + auto& term = stream.attributes().get(); + + ExpressionContextMock ctx; + ctx.vars.emplace("x", value); + + irs::Or expected; + auto& root = expected.add().filter(); + root.boost(3.5); + root.add() + .field(mangleString("a.b.c.e.f", "test_analyzer")) + .term("1"); + root.add().field(mangleNumeric("a.b.c.e.f")).term(term->value()); + root.add() + .field(mangleString("a.b.c.e.f", "test_analyzer")) + .term("3"); + + assertFilterSuccess( + vocbase(), + "LET x=['1', 2, '3'] FOR d IN collection FILTER " + "boost(analyzer(x ANY NOT IN d.a.b.c.e.f, 'test_analyzer'), 3.5) RETURN d", + expected, &ctx); + assertFilterSuccess( + vocbase(), + "LET x=['1', 2, '3'] FOR d IN collection FILTER " + "analyzer(boost(x ANY NOT IN d.a.b.c.e.f, 3.5), 'test_analyzer') RETURN d", + expected, &ctx); + assertFilterSuccess( + vocbase(), + "LET x=['1', 2, '3'] FOR d IN collection FILTER " + "boost(analyzer(x ANY != d.a.b.c.e.f, 'test_analyzer'), 3.5) RETURN d", + expected, &ctx); + assertFilterSuccess( + vocbase(), + "LET x=['1', 2, '3'] FOR d IN collection FILTER " + "analyzer(boost(x ANY != d.a.b.c.e.f, 3.5), 'test_analyzer') RETURN d", + expected, &ctx); + } + + // array as reference, analyzer, boost ALL + { + auto obj = arangodb::velocypack::Parser::fromJson("[ \"1\", 2, \"3\"]"); + arangodb::aql::AqlValue value(obj->slice()); + arangodb::aql::AqlValueGuard guard(value, true); + + irs::numeric_token_stream stream; + stream.reset(2.); + EXPECT_TRUE(stream.next()); + auto& term = stream.attributes().get(); + + ExpressionContextMock ctx; + ctx.vars.emplace("x", value); + + irs::Or expected; + auto& root = expected.add().filter(); + root.boost(3.5); + root.add() + .field(mangleString("a.b.c.e.f", "test_analyzer")) + .term("1"); + root.add().field(mangleNumeric("a.b.c.e.f")).term(term->value()); + root.add() + .field(mangleString("a.b.c.e.f", "test_analyzer")) + .term("3"); + + assertFilterSuccess( + vocbase(), + "LET x=['1', 2, '3'] FOR d IN collection FILTER " + "boost(analyzer(x ALL NOT IN d.a.b.c.e.f, 'test_analyzer'), 3.5) RETURN d", + expected, &ctx); + assertFilterSuccess( + vocbase(), + "LET x=['1', 2, '3'] FOR d IN collection FILTER " + "analyzer(boost(x ALL NOT IN d.a.b.c.e.f, 3.5), 'test_analyzer') RETURN d", + expected, &ctx); + assertFilterSuccess( + vocbase(), + "LET x=['1', 2, '3'] FOR d IN collection FILTER " + "boost(analyzer(x ALL != d.a.b.c.e.f, 'test_analyzer'), 3.5) RETURN d", + expected, &ctx); + assertFilterSuccess( + vocbase(), + "LET x=['1', 2, '3'] FOR d IN collection FILTER " + "analyzer(boost(x ALL != d.a.b.c.e.f, 3.5), 'test_analyzer') RETURN d", + expected, &ctx); + } + + // array as reference, analyzer, boost NONE + { + auto obj = arangodb::velocypack::Parser::fromJson("[ \"1\", 2, \"3\"]"); + arangodb::aql::AqlValue value(obj->slice()); + arangodb::aql::AqlValueGuard guard(value, true); + + irs::numeric_token_stream stream; + stream.reset(2.); + EXPECT_TRUE(stream.next()); + auto& term = stream.attributes().get(); + + ExpressionContextMock ctx; + ctx.vars.emplace("x", value); + + irs::Or expected; + auto& root = expected.add(); + root.boost(3.5); + root.add() + .field(mangleString("a.b.c.e.f", "test_analyzer")) + .term("1"); + root.add().field(mangleNumeric("a.b.c.e.f")).term(term->value()); + root.add() + .field(mangleString("a.b.c.e.f", "test_analyzer")) + .term("3"); + + assertFilterSuccess( + vocbase(), + "LET x=['1', 2, '3'] FOR d IN collection FILTER " + "boost(analyzer(x NONE NOT IN d.a.b.c.e.f, 'test_analyzer'), 3.5) RETURN d", + expected, &ctx); + assertFilterSuccess( + vocbase(), + "LET x=['1', 2, '3'] FOR d IN collection FILTER " + "analyzer(boost(x NONE NOT IN d.a.b.c.e.f, 3.5), 'test_analyzer') RETURN d", + expected, &ctx); + assertFilterSuccess( + vocbase(), + "LET x=['1', 2, '3'] FOR d IN collection FILTER " + "boost(analyzer(x NONE != d.a.b.c.e.f, 'test_analyzer'), 3.5) RETURN d", + expected, &ctx); + assertFilterSuccess( + vocbase(), + "LET x=['1', 2, '3'] FOR d IN collection FILTER " + "analyzer(boost(x NONE != d.a.b.c.e.f, 3.5), 'test_analyzer') RETURN d", + expected, &ctx); + } + // Auxilary check lambdas. Need them to check root part of expected filterd + // direct == check is not possible as we will have byExpresssion filters generated on the fly + auto checkNotAny = [](irs::Or& actual, iresearch::boost_t boost) { + EXPECT_EQ(1, actual.size()); + auto & notFilter = dynamic_cast(*actual.begin()); + auto& root = dynamic_cast(*notFilter.filter()); + EXPECT_EQ(irs::And::type(), root.type()); + EXPECT_EQ(3, root.size()); + EXPECT_EQ(boost, root.boost()); + return root.begin(); + }; + auto checkNotAll = [](irs::Or& actual, iresearch::boost_t boost) { + EXPECT_EQ(1, actual.size()); + auto & notFilter = dynamic_cast(*actual.begin()); + auto& root = dynamic_cast(*notFilter.filter()); + EXPECT_EQ(irs::Or::type(), root.type()); + EXPECT_EQ(3, root.size()); + EXPECT_EQ(boost, root.boost()); + return root.begin(); + }; + auto checkNotNone = [](irs::Or& actual, iresearch::boost_t boost) { + EXPECT_EQ(1, actual.size()); + auto& root = dynamic_cast(*actual.begin()); + EXPECT_EQ(irs::And::type(), root.type()); + EXPECT_EQ(3, root.size()); + EXPECT_EQ(boost, root.boost()); + return root.begin(); + }; + // nondeterministic value + { + std::vector>> const testCases = { + {"FOR d IN collection FILTER [ '1', RAND(), '3' ] ANY NOT IN d.a.b.c.e.f RETURN d", checkNotAny}, + {"FOR d IN collection FILTER [ '1', RAND(), '3' ] ALL NOT IN d.a.b.c.e.f RETURN d", checkNotAll}, + {"FOR d IN collection FILTER [ '1', RAND(), '3' ] NONE NOT IN d.a.b.c.e.f RETURN d", checkNotNone}, + {"FOR d IN collection FILTER [ '1', RAND(), '3' ] ANY != d.a.b.c.e.f RETURN d", checkNotAny}, + {"FOR d IN collection FILTER [ '1', RAND(), '3' ] ALL != d.a.b.c.e.f RETURN d", checkNotAll}, + {"FOR d IN collection FILTER [ '1', RAND(), '3' ] NONE != d.a.b.c.e.f RETURN d", checkNotNone} + }; + for (auto& testData : testCases) { + auto const& queryString = testData.first; + SCOPED_TRACE(testing::Message("Query: ") << queryString); + std::string const refName = "d"; + + TRI_vocbase_t vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, testDBInfo(server.server())); + + auto options = std::make_shared(); + + arangodb::aql::Query query(false, vocbase, arangodb::aql::QueryString(queryString), + nullptr, options, arangodb::aql::PART_MAIN); + + auto const parseResult = query.parse(); + ASSERT_TRUE(parseResult.result.ok()); + + auto* ast = query.ast(); + ASSERT_TRUE(ast); + + auto* root = ast->root(); + ASSERT_TRUE(root); + + // find first FILTER node + arangodb::aql::AstNode* filterNode = nullptr; + for (size_t i = 0; i < root->numMembers(); ++i) { + auto* node = root->getMemberUnchecked(i); + ASSERT_TRUE(node); + + if (arangodb::aql::NODE_TYPE_FILTER == node->type) { + filterNode = node; + break; + } + } + ASSERT_TRUE(filterNode); + + // find referenced variable + auto* allVars = ast->variables(); + ASSERT_TRUE(allVars); + arangodb::aql::Variable* ref = nullptr; + for (auto entry : allVars->variables(true)) { + if (entry.second == refName) { + ref = allVars->getVariable(entry.first); + break; + } + } + ASSERT_TRUE(ref); + + // supportsFilterCondition + { + arangodb::iresearch::QueryContext const ctx{ nullptr, nullptr, nullptr, nullptr, ref }; + EXPECT_TRUE( + (arangodb::iresearch::FilterFactory::filter(nullptr, ctx, *filterNode).ok())); + } + + // iteratorForCondition + { + arangodb::transaction::Methods trx(arangodb::transaction::StandaloneContext::Create(vocbase), + {}, {}, {}, arangodb::transaction::Options()); + + auto dummyPlan = arangodb::tests::planFromQuery(vocbase, "RETURN 1"); + + irs::Or actual; + arangodb::iresearch::QueryContext const ctx{ &trx, dummyPlan.get(), ast, + &ExpressionContextMock::EMPTY, ref }; + EXPECT_TRUE( + (arangodb::iresearch::FilterFactory::filter(&actual, ctx, *filterNode).ok())); + + { + auto begin = testData.second(actual, 1); + + // 1st filter + { + irs::by_term expected; + expected.field(mangleStringIdentity("a.b.c.e.f")).term("1"); + EXPECT_EQ(expected, *begin); + } + + // 2nd filter + { + ++begin; + EXPECT_EQ(arangodb::iresearch::ByExpression::type(), begin->type()); + EXPECT_NE(nullptr, + dynamic_cast(&*begin)); + } + + // 3rd filter + { + ++begin; + irs::by_term expected; + expected.field(mangleStringIdentity("a.b.c.e.f")).term("3"); + EXPECT_EQ(expected, *begin); + } + } + } + } + } + + // self-referenced value + { + std::vector>> const testCases = { + {"FOR d IN collection FILTER [ '1', d.a, '3' ] ANY NOT IN d.a.b.c.e.f RETURN d", checkNotAny}, + {"FOR d IN collection FILTER [ '1', d.a, '3' ] ALL NOT IN d.a.b.c.e.f RETURN d", checkNotAll}, + {"FOR d IN collection FILTER [ '1', d.a, '3' ] NONE NOT IN d.a.b.c.e.f RETURN d", checkNotNone}, + {"FOR d IN collection FILTER [ '1', d.a, '3' ] ANY != d.a.b.c.e.f RETURN d", checkNotAny}, + {"FOR d IN collection FILTER [ '1', d.a, '3' ] ALL != d.a.b.c.e.f RETURN d", checkNotAll}, + {"FOR d IN collection FILTER [ '1', d.a, '3' ] NONE != d.a.b.c.e.f RETURN d", checkNotNone} + }; + for (auto testData : testCases) { + auto const& queryString = testData.first; + SCOPED_TRACE(testing::Message("Query:") << queryString); + + std::string const refName = "d"; + + TRI_vocbase_t vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, testDBInfo(server.server())); + + auto options = std::make_shared(); + + arangodb::aql::Query query(false, vocbase, arangodb::aql::QueryString(queryString), + nullptr, options, arangodb::aql::PART_MAIN); + + auto const parseResult = query.parse(); + ASSERT_TRUE(parseResult.result.ok()); + + auto* ast = query.ast(); + ASSERT_TRUE(ast); + + auto* root = ast->root(); + ASSERT_TRUE(root); + + // find first FILTER node + arangodb::aql::AstNode* filterNode = nullptr; + for (size_t i = 0; i < root->numMembers(); ++i) { + auto* node = root->getMemberUnchecked(i); + ASSERT_TRUE(node); + + if (arangodb::aql::NODE_TYPE_FILTER == node->type) { + filterNode = node; + break; + } + } + ASSERT_TRUE(filterNode); + + // find referenced variable + auto* allVars = ast->variables(); + ASSERT_TRUE(allVars); + arangodb::aql::Variable* ref = nullptr; + for (auto entry : allVars->variables(true)) { + if (entry.second == refName) { + ref = allVars->getVariable(entry.first); + break; + } + } + ASSERT_TRUE(ref); + + // supportsFilterCondition + { + arangodb::iresearch::QueryContext const ctx{ nullptr, nullptr, nullptr, nullptr, ref }; + EXPECT_TRUE( + (arangodb::iresearch::FilterFactory::filter(nullptr, ctx, *filterNode).ok())); + } + + // iteratorForCondition + { + arangodb::transaction::Methods trx(arangodb::transaction::StandaloneContext::Create(vocbase), + {}, {}, {}, arangodb::transaction::Options()); + + auto dummyPlan = arangodb::tests::planFromQuery(vocbase, "RETURN 1"); + + irs::Or actual; + arangodb::iresearch::QueryContext const ctx{ &trx, dummyPlan.get(), ast, + &ExpressionContextMock::EMPTY, ref }; + EXPECT_TRUE( + (arangodb::iresearch::FilterFactory::filter(&actual, ctx, *filterNode).ok())); + + { + auto begin = testData.second(actual, 1); + + // 1st filter + { + irs::by_term expected; + expected.field(mangleStringIdentity("a.b.c.e.f")).term("1"); + EXPECT_EQ(expected, *begin); + } + + // 2nd filter + { + ++begin; + EXPECT_EQ(arangodb::iresearch::ByExpression::type(), begin->type()); + EXPECT_NE(nullptr, + dynamic_cast(&*begin)); + } + + // 3rd filter + { + ++begin; + irs::by_term expected; + expected.field(mangleStringIdentity("a.b.c.e.f")).term("3"); + EXPECT_EQ(expected, *begin); + } + } + } + } + } + + // self-referenced value, boost + { + std::vector>> const testCases = { + {"FOR d IN collection FILTER boost([ '1', 1+d.a, '3'] ANY NOT IN d.a.b.c.e.f, 1.5) RETURN d", checkNotAny}, + {"FOR d IN collection FILTER boost([ '1', 1+d.a, '3'] ALL NOT IN d.a.b.c.e.f, 1.5) RETURN d", checkNotAll}, + {"FOR d IN collection FILTER boost([ '1', 1+d.a, '3'] NONE NOT IN d.a.b.c.e.f, 1.5) RETURN d", checkNotNone}, + {"FOR d IN collection FILTER boost([ '1', 1+d.a, '3'] ANY NOT IN d.a.b.c.e.f, 1.5) RETURN d", checkNotAny}, + {"FOR d IN collection FILTER boost([ '1', 1+d.a, '3'] ALL NOT IN d.a.b.c.e.f, 1.5) RETURN d", checkNotAll}, + {"FOR d IN collection FILTER boost([ '1', 1+d.a, '3'] NONE NOT IN d.a.b.c.e.f, 1.5) RETURN d", checkNotNone} + }; + + for (auto testData : testCases) { + auto const& queryString = testData.first; + SCOPED_TRACE(testing::Message("Query:") << queryString); + std::string const refName = "d"; + + TRI_vocbase_t vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, testDBInfo(server.server())); + + auto options = std::make_shared(); + + arangodb::aql::Query query(false, vocbase, arangodb::aql::QueryString(queryString), + nullptr, options, arangodb::aql::PART_MAIN); + + auto const parseResult = query.parse(); + ASSERT_TRUE(parseResult.result.ok()); + + auto* ast = query.ast(); + ASSERT_TRUE(ast); + + auto* root = ast->root(); + ASSERT_TRUE(root); + + // find first FILTER node + arangodb::aql::AstNode* filterNode = nullptr; + for (size_t i = 0; i < root->numMembers(); ++i) { + auto* node = root->getMemberUnchecked(i); + ASSERT_TRUE(node); + + if (arangodb::aql::NODE_TYPE_FILTER == node->type) { + filterNode = node; + break; + } + } + ASSERT_TRUE(filterNode); + + // find referenced variable + auto* allVars = ast->variables(); + ASSERT_TRUE(allVars); + arangodb::aql::Variable* ref = nullptr; + for (auto entry : allVars->variables(true)) { + if (entry.second == refName) { + ref = allVars->getVariable(entry.first); + break; + } + } + ASSERT_TRUE(ref); + + // supportsFilterCondition + { + arangodb::iresearch::QueryContext const ctx{ nullptr, nullptr, nullptr, nullptr, ref }; + EXPECT_TRUE( + (arangodb::iresearch::FilterFactory::filter(nullptr, ctx, *filterNode).ok())); + } + + // iteratorForCondition + { + arangodb::transaction::Methods trx(arangodb::transaction::StandaloneContext::Create(vocbase), + {}, {}, {}, arangodb::transaction::Options()); + + auto dummyPlan = arangodb::tests::planFromQuery(vocbase, "RETURN 1"); + + irs::Or actual; + arangodb::iresearch::QueryContext const ctx{ &trx, dummyPlan.get(), ast, + &ExpressionContextMock::EMPTY, ref }; + EXPECT_TRUE( + (arangodb::iresearch::FilterFactory::filter(&actual, ctx, *filterNode).ok())); + + { + auto begin = testData.second(actual, 1.5); + + // 1st filter + { + irs::by_term expected; + expected.field(mangleStringIdentity("a.b.c.e.f")).term("1"); + EXPECT_EQ(expected, *begin); + } + + // 2nd filter + { + ++begin; + EXPECT_EQ(arangodb::iresearch::ByExpression::type(), begin->type()); + EXPECT_NE(nullptr, + dynamic_cast(&*begin)); + } + + // 3rd filter + { + ++begin; + irs::by_term expected; + expected.field(mangleStringIdentity("a.b.c.e.f")).term("3"); + EXPECT_EQ(expected, *begin); + } + } + } + } + } + // heterogeneous references and expression in array ANY + { + ExpressionContextMock ctx; + ctx.vars.emplace("strVal", arangodb::aql::AqlValue("str")); + ctx.vars.emplace("boolVal", + arangodb::aql::AqlValue(arangodb::aql::AqlValueHintBool(false))); + ctx.vars.emplace("numVal", arangodb::aql::AqlValue(arangodb::aql::AqlValueHintInt(2))); + ctx.vars.emplace("nullVal", arangodb::aql::AqlValue(arangodb::aql::AqlValueHintNull{})); + + irs::numeric_token_stream stream; + stream.reset(3.); + EXPECT_TRUE(stream.next()); + auto& term = stream.attributes().get(); + + irs::Or expected; + auto& root = expected.add().filter(); + root.boost(2.5); + root.add().field(mangleStringIdentity("a.b.c.e.f")).term("1"); + root.add() + .field(mangleStringIdentity("a.b.c.e.f")) + .term("str"); + root.add().field(mangleBool("a.b.c.e.f")).term(irs::boolean_token_stream::value_false()); + root.add().field(mangleNumeric("a.b.c.e.f")).term(term->value()); + root.add().field(mangleNull("a.b.c.e.f")).term(irs::null_token_stream::value_null()); + + // not a constant in array + assertFilterSuccess( + vocbase(), + "LET strVal='str' LET boolVal=false LET numVal=2 LET nullVal=null FOR " + "d IN collection FILTER BOOST(['1', strVal, " + "boolVal, numVal+1, nullVal] ANY NOT IN d.a.b.c.e.f, 2.5) RETURN d", + expected, + &ctx); + + assertFilterSuccess( + vocbase(), + "LET strVal='str' LET boolVal=false LET numVal=2 LET nullVal=null FOR " + "d IN collection FILTER BOOST(['1', strVal, " + "boolVal, numVal+1, nullVal] ANY != d.a.b.c.e.f, 2.5) RETURN d", + expected, + &ctx); + } + // heterogeneous references and expression in array ALL + { + ExpressionContextMock ctx; + ctx.vars.emplace("strVal", arangodb::aql::AqlValue("str")); + ctx.vars.emplace("boolVal", + arangodb::aql::AqlValue(arangodb::aql::AqlValueHintBool(false))); + ctx.vars.emplace("numVal", arangodb::aql::AqlValue(arangodb::aql::AqlValueHintInt(2))); + ctx.vars.emplace("nullVal", arangodb::aql::AqlValue(arangodb::aql::AqlValueHintNull{})); + + irs::numeric_token_stream stream; + stream.reset(3.); + EXPECT_TRUE(stream.next()); + auto& term = stream.attributes().get(); + + irs::Or expected; + auto& root = expected.add().filter(); + root.boost(2.5); + root.add().field(mangleStringIdentity("a.b.c.e.f")).term("1"); + root.add() + .field(mangleStringIdentity("a.b.c.e.f")) + .term("str"); + root.add().field(mangleBool("a.b.c.e.f")).term(irs::boolean_token_stream::value_false()); + root.add().field(mangleNumeric("a.b.c.e.f")).term(term->value()); + root.add().field(mangleNull("a.b.c.e.f")).term(irs::null_token_stream::value_null()); + + // not a constant in array + assertFilterSuccess( + vocbase(), + "LET strVal='str' LET boolVal=false LET numVal=2 LET nullVal=null FOR " + "d IN collection FILTER BOOST(['1', strVal, " + "boolVal, numVal+1, nullVal] ALL NOT IN d.a.b.c.e.f, 2.5) RETURN d", + expected, + &ctx); + + assertFilterSuccess( + vocbase(), + "LET strVal='str' LET boolVal=false LET numVal=2 LET nullVal=null FOR " + "d IN collection FILTER BOOST(['1', strVal, " + "boolVal, numVal+1, nullVal] ALL != d.a.b.c.e.f, 2.5) RETURN d", + expected, + &ctx); + } + // heterogeneous references and expression in array NONE + { + ExpressionContextMock ctx; + ctx.vars.emplace("strVal", arangodb::aql::AqlValue("str")); + ctx.vars.emplace("boolVal", + arangodb::aql::AqlValue(arangodb::aql::AqlValueHintBool(false))); + ctx.vars.emplace("numVal", arangodb::aql::AqlValue(arangodb::aql::AqlValueHintInt(2))); + ctx.vars.emplace("nullVal", arangodb::aql::AqlValue(arangodb::aql::AqlValueHintNull{})); + + irs::numeric_token_stream stream; + stream.reset(3.); + EXPECT_TRUE(stream.next()); + auto& term = stream.attributes().get(); + + irs::Or expected; + auto& root = expected.add(); + root.boost(2.5); + root.add().field(mangleStringIdentity("a.b.c.e.f")).term("1"); + root.add() + .field(mangleStringIdentity("a.b.c.e.f")) + .term("str"); + root.add().field(mangleBool("a.b.c.e.f")).term(irs::boolean_token_stream::value_false()); + root.add().field(mangleNumeric("a.b.c.e.f")).term(term->value()); + root.add().field(mangleNull("a.b.c.e.f")).term(irs::null_token_stream::value_null()); + + // not a constant in array + assertFilterSuccess( + vocbase(), + "LET strVal='str' LET boolVal=false LET numVal=2 LET nullVal=null FOR " + "d IN collection FILTER BOOST(['1', strVal, " + "boolVal, numVal+1, nullVal] NONE NOT IN d.a.b.c.e.f, 2.5) RETURN d", + expected, + &ctx); + + assertFilterSuccess( + vocbase(), + "LET strVal='str' LET boolVal=false LET numVal=2 LET nullVal=null FOR " + "d IN collection FILTER BOOST(['1', strVal, " + "boolVal, numVal+1, nullVal] NONE != d.a.b.c.e.f, 2.5) RETURN d", + expected, + &ctx); + } + + // no reference provided + assertFilterExecutionFail( + vocbase(), "LET x={} FOR d IN myView FILTER [1,x.a,3] ANY NOT IN d.a RETURN d", + &ExpressionContextMock::EMPTY); + + assertFilterExecutionFail( + vocbase(), "LET x={} FOR d IN myView FILTER [1,x.a,3] ANY != d.a RETURN d", + &ExpressionContextMock::EMPTY); + + assertFilterExecutionFail( + vocbase(), "LET x={} FOR d IN myView FILTER [1,x.a,3] ALL NOT IN d.a RETURN d", + &ExpressionContextMock::EMPTY); + + assertFilterExecutionFail( + vocbase(), "LET x={} FOR d IN myView FILTER [1,x.a,3] ALL != d.a RETURN d", + &ExpressionContextMock::EMPTY); + + assertFilterExecutionFail( + vocbase(), "LET x={} FOR d IN myView FILTER [1,x.a,3] NONE NOT IN d.a RETURN d", + &ExpressionContextMock::EMPTY); + + assertFilterExecutionFail( + vocbase(), "LET x={} FOR d IN myView FILTER [1,x.a,3] NONE != d.a RETURN d", + &ExpressionContextMock::EMPTY); + + // empty array ANY + { + irs::Or expected; + expected.add(); + expected.boost(2.5); + assertFilterSuccess( + vocbase(), "FOR d IN collection FILTER BOOST([] ANY NOT IN d.a, 2.5) RETURN d", expected); + assertFilterSuccess( + vocbase(), "FOR d IN collection FILTER BOOST([] ANY NOT IN d['a'], 2.5) RETURN d", expected); + + assertFilterSuccess( + vocbase(), "FOR d IN collection FILTER BOOST([] ANY != d.a, 2.5) RETURN d", expected); + assertFilterSuccess( + vocbase(), "FOR d IN collection FILTER BOOST([] ANY != d['a'], 2.5) RETURN d", expected); + } + + // empty array ALL/NONE + { + irs::Or expected; + expected.add(); + expected.boost(2.5); + + assertFilterSuccess( + vocbase(), "FOR d IN collection FILTER BOOST([] ALL NOT IN d.a, 2.5) RETURN d", expected); + assertFilterSuccess( + vocbase(), "FOR d IN collection FILTER BOOST([] ALL NOT IN d['a'], 2.5) RETURN d", expected); + + assertFilterSuccess( + vocbase(), "FOR d IN collection FILTER BOOST([] ALL != d.a, 2.5) RETURN d", expected); + assertFilterSuccess( + vocbase(), "FOR d IN collection FILTER BOOST([] ALL != d['a'], 2.5) RETURN d", expected); + + assertFilterSuccess( + vocbase(), "FOR d IN collection FILTER BOOST([] NONE NOT IN d.a, 2.5) RETURN d", expected); + assertFilterSuccess( + vocbase(), "FOR d IN collection FILTER BOOST([] NONE NOT IN d['a'], 2.5) RETURN d", expected); + + assertFilterSuccess( + vocbase(), "FOR d IN collection FILTER BOOST([] NONE != d.a, 2.5) RETURN d", expected); + assertFilterSuccess( + vocbase(), "FOR d IN collection FILTER BOOST([] NONE != d['a'], 2.5) RETURN d", expected); + } +} diff --git a/tests/IResearch/IResearchFilterArrayInterval-test.cpp b/tests/IResearch/IResearchFilterArrayInterval-test.cpp new file mode 100644 index 0000000000..243eb141c9 --- /dev/null +++ b/tests/IResearch/IResearchFilterArrayInterval-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 Andrei Lobov +//////////////////////////////////////////////////////////////////////////////// +#include "gtest/gtest.h" + +#include "analysis/analyzers.hpp" +#include "analysis/token_attributes.hpp" +#include "analysis/token_streams.hpp" +#include "search/all_filter.hpp" +#include "search/boolean_filter.hpp" +#include "search/column_existence_filter.hpp" +#include "search/granular_range_filter.hpp" +#include "search/phrase_filter.hpp" +#include "search/prefix_filter.hpp" +#include "search/range_filter.hpp" +#include "search/term_filter.hpp" + +#include + +#include "IResearch/ExpressionContextMock.h" +#include "IResearch/common.h" +#include "Mocks/LogLevels.h" +#include "Mocks/Servers.h" +#include "Mocks/StorageEngineMock.h" + +#include "Aql/AqlFunctionFeature.h" +#include "Aql/Ast.h" +#include "Aql/ExecutionPlan.h" +#include "Aql/ExpressionContext.h" +#include "Aql/Query.h" +#include "Cluster/ClusterFeature.h" +#include "GeneralServer/AuthenticationFeature.h" +#include "IResearch/AqlHelper.h" +#include "IResearch/ExpressionFilter.h" +#include "IResearch/IResearchAnalyzerFeature.h" +#include "IResearch/IResearchCommon.h" +#include "IResearch/IResearchFeature.h" +#include "IResearch/IResearchFilterFactory.h" +#include "IResearch/IResearchLinkMeta.h" +#include "IResearch/IResearchViewMeta.h" +#include "Logger/LogTopic.h" +#include "Logger/Logger.h" +#include "RestServer/AqlFeature.h" +#include "RestServer/DatabaseFeature.h" +#include "RestServer/QueryRegistryFeature.h" +#include "RestServer/SystemDatabaseFeature.h" +#include "RestServer/TraverserEngineRegistryFeature.h" +#include "RestServer/ViewTypesFeature.h" +#include "StorageEngine/EngineSelectorFeature.h" +#include "Transaction/Methods.h" +#include "Transaction/StandaloneContext.h" +#include "V8Server/V8DealerFeature.h" +#include "VocBase/Methods/Collections.h" + +#if USE_ENTERPRISE +#include "Enterprise/Ldap/LdapFeature.h" +#endif + +static const VPackBuilder systemDatabaseBuilder = dbArgsBuilder(); +static const VPackSlice systemDatabaseArgs = systemDatabaseBuilder.slice(); + +// ----------------------------------------------------------------------------- +// --SECTION-- setup / tear-down +// ----------------------------------------------------------------------------- + +class IResearchFilterArrayIntervalTest + : public ::testing::Test, + public arangodb::tests::LogSuppressor { + protected: + arangodb::tests::mocks::MockAqlServer server; + + private: + TRI_vocbase_t* _vocbase; + + protected: + IResearchFilterArrayIntervalTest() { + arangodb::tests::init(); + + auto& functions = server.getFeature(); + + // 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 = server.getFeature(); + arangodb::iresearch::IResearchAnalyzerFeature::EmplaceResult result; + + auto& dbFeature = server.getFeature(); + dbFeature.createDatabase(testDBInfo(server.server()), _vocbase); // required for IResearchAnalyzerFeature::emplace(...) + arangodb::methods::Collections::createSystem(*_vocbase, arangodb::tests::AnalyzerCollectionName, + false); + analyzers.emplace( + result, "testVocbase::test_analyzer", "TestAnalyzer", + arangodb::velocypack::Parser::fromJson("{ \"args\": \"abc\"}")->slice()); // cache analyzer + } + + TRI_vocbase_t& vocbase() { return *_vocbase; } +}; // IResearchFilterSetup + +namespace { + // Auxilary check lambdas. Need them to check by_range part of expected filter + auto checkLess = [](irs::boolean_filter::const_iterator& filter, + irs::bytes_ref const& term, + irs::string_ref const& field) { + ASSERT_EQ(irs::by_range::type(), filter->type()); + auto& actual = dynamic_cast(*filter); + irs::by_range expected; + expected.field(field); + expected.term(term); + expected.include(false); + EXPECT_EQ(expected, actual); + }; + + auto checkLessEqual = [](irs::boolean_filter::const_iterator& filter, + irs::bytes_ref const& term, + irs::string_ref const& field) { + ASSERT_EQ(irs::by_range::type(), filter->type()); + auto& actual = dynamic_cast(*filter); + irs::by_range expected; + expected.field(field); + expected.term(term); + expected.include(true); + EXPECT_EQ(expected, actual); + }; + + auto checkGreaterEqual = [](irs::boolean_filter::const_iterator& filter, + irs::bytes_ref const& term, + irs::string_ref const& field) { + ASSERT_EQ(irs::by_range::type(), filter->type()); + auto& actual = dynamic_cast(*filter); + irs::by_range expected; + expected.field(field); + expected.term(term); + expected.include(true); + EXPECT_EQ(expected, actual); + }; + + auto checkGreater = [](irs::boolean_filter::const_iterator& filter, + irs::bytes_ref const& term, + irs::string_ref const& field) { + ASSERT_EQ(irs::by_range::type(), filter->type()); + auto& actual = dynamic_cast(*filter); + irs::by_range expected; + expected.field(field); + expected.term(term); + expected.include(false); + EXPECT_EQ(expected, actual); + }; + + // Auxilary check lambdas. Need them to check root part of expected filter + auto checkAny = [](irs::Or& actual, iresearch::boost_t boost) { + EXPECT_EQ(1, actual.size()); + EXPECT_EQ(irs::Or::type(), actual.begin()->type()); + auto& root = dynamic_cast(*actual.begin()); + EXPECT_EQ(3, root.size()); + EXPECT_EQ(boost, root.boost()); + return root.begin(); + }; + auto checkAll = [](irs::Or& actual, iresearch::boost_t boost) { + EXPECT_EQ(1, actual.size()); + EXPECT_EQ(irs::And::type(), actual.begin()->type()); + auto& root = dynamic_cast(*actual.begin()); + EXPECT_EQ(3, root.size()); + EXPECT_EQ(boost, root.boost()); + return root.begin(); + }; + auto checkNone = [](irs::Or& actual, iresearch::boost_t boost) { + // none for now is like All but with inverted interval check + EXPECT_EQ(1, actual.size()); + EXPECT_EQ(irs::And::type(), actual.begin()->type()); + auto& root = dynamic_cast(*actual.begin()); + EXPECT_EQ(3, root.size()); + EXPECT_EQ(boost, root.boost()); + return root.begin(); + }; + std::vector, + std::function>>> + intervalOperations = { + {"ANY >", {checkAny, checkGreater}}, {"ANY >=", {checkAny, checkGreaterEqual}}, + {"ANY <", {checkAny, checkLess}}, {"ANY <=", {checkAny, checkLessEqual}}, + {"ALL >", {checkAll, checkGreater}}, {"ALL >=", {checkAll, checkGreaterEqual}}, + {"ALL <", {checkAll, checkLess}}, {"ALL <=", {checkAll, checkLessEqual}}, + {"NONE >", {checkNone, checkLessEqual}}, {"NONE >=", {checkNone, checkLess}}, + {"NONE <", {checkNone, checkGreaterEqual}}, {"NONE <=", {checkNone, checkGreater}} + }; + + std::string buildQueryString(const irs::string_ref queryPrefix, + const irs::string_ref operation, + const irs::string_ref querySuffix) { + return std::string(queryPrefix).append(" ") + .append(operation).append(" ").append(querySuffix); + } +} // namespace + +TEST_F(IResearchFilterArrayIntervalTest, Interval) { + // simple attribute + { + for (auto operation : intervalOperations) { + auto queryString = buildQueryString("FOR d IN collection FILTER ['1','2','3']", + operation.first, "d.a RETURN d"); + SCOPED_TRACE(testing::Message("Query:") << queryString); + irs::Or actual; + buildActualFilter(vocbase(), queryString, actual); + auto subFiltersIterator = operation.second.first(actual, 1); + operation.second.second(subFiltersIterator, irs::ref_cast(irs::string_ref("1")), mangleStringIdentity("a")); + ++subFiltersIterator; + operation.second.second(subFiltersIterator, irs::ref_cast(irs::string_ref("2")), mangleStringIdentity("a")); + ++subFiltersIterator; + operation.second.second(subFiltersIterator, irs::ref_cast(irs::string_ref("3")), mangleStringIdentity("a")); + ++subFiltersIterator; + } + } + + // complex attribute name with offset, boost, analyzer + { + for (auto operation : intervalOperations) { + auto queryString = buildQueryString("FOR d IN collection FILTER BOOST(ANALYZER(['1','2','3']", + operation.first, "d.a['b']['c'][412].e.f, 'test_analyzer'), 2.5) RETURN d"); + SCOPED_TRACE(testing::Message("Query:") << queryString); + irs::Or actual; + buildActualFilter(vocbase(), queryString, actual); + auto subFiltersIterator = operation.second.first(actual, 2.5); + operation.second.second(subFiltersIterator, irs::ref_cast(irs::string_ref("1")), mangleString("a.b.c[412].e.f", "test_analyzer")); + ++subFiltersIterator; + operation.second.second(subFiltersIterator, irs::ref_cast(irs::string_ref("2")), mangleString("a.b.c[412].e.f", "test_analyzer")); + ++subFiltersIterator; + operation.second.second(subFiltersIterator, irs::ref_cast(irs::string_ref("3")), mangleString("a.b.c[412].e.f", "test_analyzer")); + ++subFiltersIterator; + } + } + // heterogeneous array values, analyzer, boost + { + for (auto operation : intervalOperations) { + auto queryString = buildQueryString("FOR d IN collection FILTER ANALYZER(BOOST(['1',null,true]", + operation.first, "d.quick.brown.fox, 1.5), 'test_analyzer') RETURN d"); + SCOPED_TRACE(testing::Message("Query:") << queryString); + irs::Or actual; + buildActualFilter(vocbase(), queryString, actual); + auto subFiltersIterator = operation.second.first(actual, 1.5); + operation.second.second(subFiltersIterator, + irs::ref_cast(irs::string_ref("1")), + mangleString("quick.brown.fox", "test_analyzer")); + ++subFiltersIterator; + operation.second.second(subFiltersIterator, + irs::null_token_stream::value_null(), + mangleNull("quick.brown.fox")); + ++subFiltersIterator; + operation.second.second(subFiltersIterator, + irs::boolean_token_stream::value_true(), + mangleBool("quick.brown.fox")); + ++subFiltersIterator; + } + } + // heterogeneous non string values, analyzer, boost + { + for (auto operation : intervalOperations) { + auto queryString = buildQueryString("FOR d IN collection FILTER ANALYZER(BOOST([2, null,false]", + operation.first, "d.quick.brown.fox, 1.5), 'test_analyzer') RETURN d"); + SCOPED_TRACE(testing::Message("Query:") << queryString); + irs::Or actual; + buildActualFilter(vocbase(), queryString, actual); + auto subFiltersIterator = operation.second.first(actual, 1.5); + irs::numeric_token_stream stream; + stream.reset(2.); + ASSERT_EQ(irs::by_granular_range::type(), subFiltersIterator->type()); + { + auto& by_range_actual = dynamic_cast(*subFiltersIterator); + irs::by_granular_range expected; + expected.field(mangleNumeric("quick.brown.fox")); + // granular range handled separately (it is used only for numerics, just check it here once) + if (operation.first == "ANY >" || operation.first == "ALL >" || operation.first == "NONE <=") { + expected.insert(stream); + expected.include(false); + } else if (operation.first == "ANY >=" || operation.first == "ALL >=" || operation.first == "NONE <") { + expected.insert(stream); + expected.include(true); + } else if (operation.first == "ANY <" || operation.first == "ALL <" || operation.first == "NONE >=") { + expected.insert(stream); + expected.include(false); + } else if (operation.first == "ANY <=" || operation.first == "ALL <=" || operation.first == "NONE >") { + expected.insert(stream); + expected.include(true); + } else { + ASSERT_TRUE(false); // new array comparsion operator added? Need to update checks here! + } + EXPECT_EQ(expected, by_range_actual); + } + ++subFiltersIterator; + operation.second.second(subFiltersIterator, + irs::null_token_stream::value_null(), + mangleNull("quick.brown.fox")); + ++subFiltersIterator; + operation.second.second(subFiltersIterator, + irs::boolean_token_stream::value_false(), + mangleBool("quick.brown.fox")); + ++subFiltersIterator; + } + } + // dynamic complex attribute name + { + for (auto operation : intervalOperations) { + auto queryString = buildQueryString("LET a='a' LET c='c' LET offsetInt=4 LET offsetDbl=5.6 FOR d IN " + "collection FILTER " + " ['1','2','3']", + operation.first, + "d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_FORWARD_('a')] " + " RETURN d"); + 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}))); + SCOPED_TRACE(testing::Message("Query:") << queryString); + irs::Or actual; + buildActualFilter(vocbase(), queryString, actual, &ctx); + auto subFiltersIterator = operation.second.first(actual, 1); + + operation.second.second(subFiltersIterator, + irs::ref_cast(irs::string_ref("1")), + mangleStringIdentity("a.b.c.e[4].f[5].g[3].g.a")); + ++subFiltersIterator; + operation.second.second(subFiltersIterator, + irs::ref_cast(irs::string_ref("2")), + mangleStringIdentity("a.b.c.e[4].f[5].g[3].g.a")); + ++subFiltersIterator; + operation.second.second(subFiltersIterator, + irs::ref_cast(irs::string_ref("3")), + mangleStringIdentity("a.b.c.e[4].f[5].g[3].g.a")); + ++subFiltersIterator; + } + } + // invalid dynamic attribute name (null value) + { + for (auto operation : intervalOperations) { + auto queryString = buildQueryString("LET a=null LET c='c' LET offsetInt=4 LET offsetDbl=5.6 FOR d IN " + "collection FILTER " + " ['1','2','3']", + operation.first, + "d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_FORWARD_('a')] " + " RETURN d"); + SCOPED_TRACE(testing::Message("Query:") << queryString); + 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(vocbase(), queryString, &ctx); + } + } + // invalid dynamic attribute name (missing value) + { + for (auto operation : intervalOperations) { + auto queryString = buildQueryString("LET a='a' LET c='c' LET offsetInt=4 LET offsetDbl=5.6 FOR d IN " + "collection FILTER " + " ['1','2','3']", + operation.first, + "d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_FORWARD_('a')] " + " RETURN d"); + SCOPED_TRACE(testing::Message("Query:") << queryString); + 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("offsetDbl", arangodb::aql::AqlValue(arangodb::aql::AqlValue( + arangodb::aql::AqlValueHintDouble{ 5.6 }))); + assertFilterExecutionFail(vocbase(), queryString, &ctx); + } + } + // invalid dynamic attribute name (bool value) + { + for (auto operation : intervalOperations) { + auto queryString = buildQueryString("LET a=false LET c='c' LET offsetInt=4 LET offsetDbl=5.6 FOR d IN " + "collection FILTER " + " ['1','2','3']", + operation.first, + "d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_FORWARD_('a')] " + " RETURN d"); + SCOPED_TRACE(testing::Message("Query:") << queryString); + ExpressionContextMock ctx; + ctx.vars.emplace("a", arangodb::aql::AqlValue(arangodb::aql::AqlValueHintBool{false})); + 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(vocbase(), queryString, &ctx); + } + } + // reference in array + { + for (auto operation : intervalOperations) { + auto queryString = buildQueryString("LET c=2 FOR d IN collection FILTER ['1', c, '3']", + operation.first, + "d.a.b.c.e.f RETURN d"); + SCOPED_TRACE(testing::Message("Query:") << queryString); + + arangodb::aql::Variable var("c", 0); + arangodb::aql::AqlValue value(arangodb::aql::AqlValue("2")); + arangodb::aql::AqlValueGuard guard(value, true); + ExpressionContextMock ctx; + ctx.vars.emplace(var.name, value); + irs::Or actual; + buildActualFilter(vocbase(), queryString, actual, &ctx); + auto subFiltersIterator = operation.second.first(actual, 1); + + operation.second.second(subFiltersIterator, + irs::ref_cast(irs::string_ref("1")), + mangleStringIdentity("a.b.c.e.f")); + ++subFiltersIterator; + operation.second.second(subFiltersIterator, + irs::ref_cast(irs::string_ref("2")), + mangleStringIdentity("a.b.c.e.f")); + ++subFiltersIterator; + operation.second.second(subFiltersIterator, + irs::ref_cast(irs::string_ref("3")), + mangleStringIdentity("a.b.c.e.f")); + ++subFiltersIterator; + } + } + // array as reference, boost, analyzer + { + for (auto operation : intervalOperations) { + auto queryString = buildQueryString("LET x=['1', '2', '3'] FOR d IN collection FILTER " + "ANALYZER(BOOST(x", + operation.first, + "d.a.b.c.e.f, 1.5), 'test_analyzer') RETURN d"); + SCOPED_TRACE(testing::Message("Query:") << queryString); + + auto obj = arangodb::velocypack::Parser::fromJson("[ \"1\", \"2\", \"3\"]"); + arangodb::aql::AqlValue value(obj->slice()); + arangodb::aql::AqlValueGuard guard(value, true); + + ExpressionContextMock ctx; + ctx.vars.emplace("x", value); + + irs::Or actual; + buildActualFilter(vocbase(), queryString, actual, &ctx); + auto subFiltersIterator = operation.second.first(actual, 1.5); + + operation.second.second(subFiltersIterator, + irs::ref_cast(irs::string_ref("1")), + mangleString("a.b.c.e.f", "test_analyzer")); + ++subFiltersIterator; + operation.second.second(subFiltersIterator, + irs::ref_cast(irs::string_ref("2")), + mangleString("a.b.c.e.f", "test_analyzer")); + ++subFiltersIterator; + operation.second.second(subFiltersIterator, + irs::ref_cast(irs::string_ref("3")), + mangleString("a.b.c.e.f", "test_analyzer")); + ++subFiltersIterator; + } + } + // nondeterministic value + { + for (auto operation : intervalOperations) { + auto queryString = buildQueryString("FOR d IN collection FILTER [ '1', RAND(), '3' ]", + operation.first, + "d.a.b.c.e.f RETURN d"); + SCOPED_TRACE(testing::Message("Query:") << queryString); + irs::Or actual; + buildActualFilter(vocbase(), queryString, actual, nullptr); + auto subFiltersIterator = operation.second.first(actual, 1); + operation.second.second(subFiltersIterator, + irs::ref_cast(irs::string_ref("1")), + mangleStringIdentity("a.b.c.e.f")); + ++subFiltersIterator; + EXPECT_EQ(arangodb::iresearch::ByExpression::type(), subFiltersIterator->type()); + EXPECT_NE(nullptr, dynamic_cast(&*subFiltersIterator)); + + ++subFiltersIterator; + operation.second.second(subFiltersIterator, + irs::ref_cast(irs::string_ref("3")), + mangleStringIdentity("a.b.c.e.f")); + ++subFiltersIterator; + } + } + // self-referenced value + { + for (auto operation : intervalOperations) { + auto queryString = buildQueryString("FOR d IN collection FILTER [ '1', d, '3' ]", + operation.first, + "d.a.b.c.e.f RETURN d"); + SCOPED_TRACE(testing::Message("Query:") << queryString); + irs::Or actual; + buildActualFilter(vocbase(), queryString, actual, nullptr); + auto subFiltersIterator = operation.second.first(actual, 1); + operation.second.second(subFiltersIterator, + irs::ref_cast(irs::string_ref("1")), + mangleStringIdentity("a.b.c.e.f")); + ++subFiltersIterator; + EXPECT_EQ(arangodb::iresearch::ByExpression::type(), subFiltersIterator->type()); + EXPECT_NE(nullptr, dynamic_cast(&*subFiltersIterator)); + + ++subFiltersIterator; + operation.second.second(subFiltersIterator, + irs::ref_cast(irs::string_ref("3")), + mangleStringIdentity("a.b.c.e.f")); + ++subFiltersIterator; + } + } + // self-referenced value + { + for (auto operation : intervalOperations) { + auto queryString = buildQueryString("FOR d IN collection FILTER [ '1', d.e, d.a.b.c.e.f ]", + operation.first, + "d.a.b.c.e.f RETURN d"); + SCOPED_TRACE(testing::Message("Query:") << queryString); + irs::Or actual; + buildActualFilter(vocbase(), queryString, actual, nullptr); + auto subFiltersIterator = operation.second.first(actual, 1); + operation.second.second(subFiltersIterator, + irs::ref_cast(irs::string_ref("1")), + mangleStringIdentity("a.b.c.e.f")); + ++subFiltersIterator; + EXPECT_EQ(arangodb::iresearch::ByExpression::type(), subFiltersIterator->type()); + EXPECT_NE(nullptr, dynamic_cast(&*subFiltersIterator)); + + ++subFiltersIterator; + EXPECT_EQ(arangodb::iresearch::ByExpression::type(), subFiltersIterator->type()); + EXPECT_NE(nullptr, dynamic_cast(&*subFiltersIterator)); + } + } + // self-referenced value + { + for (auto operation : intervalOperations) { + auto queryString = buildQueryString("FOR d IN collection FILTER [ '1', 1 + d.b, '3' ]", + operation.first, + "d.a.b.c.e.f RETURN d"); + SCOPED_TRACE(testing::Message("Query:") << queryString); + irs::Or actual; + buildActualFilter(vocbase(), queryString, actual, nullptr); + auto subFiltersIterator = operation.second.first(actual, 1); + operation.second.second(subFiltersIterator, + irs::ref_cast(irs::string_ref("1")), + mangleStringIdentity("a.b.c.e.f")); + ++subFiltersIterator; + EXPECT_EQ(arangodb::iresearch::ByExpression::type(), subFiltersIterator->type()); + EXPECT_NE(nullptr, dynamic_cast(&*subFiltersIterator)); + + ++subFiltersIterator; + operation.second.second(subFiltersIterator, + irs::ref_cast(irs::string_ref("3")), + mangleStringIdentity("a.b.c.e.f")); + } + } + // heterogeneous references and expression in array, analyzer, boost + { + for (auto operation : intervalOperations) { + auto queryString = buildQueryString( + "LET strVal='str' LET boolVal=false LET nullVal=null FOR " + "d IN collection FILTER boost(ANALYZER([CONCAT(strVal, '2'), boolVal, nullVal]", + operation.first, + "d.a.b.c.e.f, 'test_analyzer'),2.5) RETURN d"); + SCOPED_TRACE(testing::Message("Query:") << queryString); + ExpressionContextMock ctx; + ctx.vars.emplace("strVal", arangodb::aql::AqlValue("str")); + ctx.vars.emplace("boolVal", + arangodb::aql::AqlValue(arangodb::aql::AqlValueHintBool(false))); + ctx.vars.emplace("nullVal", arangodb::aql::AqlValue(arangodb::aql::AqlValueHintNull{})); + irs::Or actual; + buildActualFilter(vocbase(), queryString, actual, &ctx); + auto subFiltersIterator = operation.second.first(actual, 2.5); + + operation.second.second(subFiltersIterator, + irs::ref_cast(irs::string_ref("str2")), + mangleString("a.b.c.e.f", "test_analyzer")); + ++subFiltersIterator; + operation.second.second(subFiltersIterator, + irs::boolean_token_stream::value_false(), + mangleBool("a.b.c.e.f")); + ++subFiltersIterator; + operation.second.second(subFiltersIterator, + irs::null_token_stream::value_null(), + mangleNull("a.b.c.e.f")); + ++subFiltersIterator; + } + } + // not array as left argument + { + for (auto operation : intervalOperations) { + auto queryString = buildQueryString( + "LET a=null LET b='b' LET c=4 LET e=5.6 FOR d IN collection FILTER a ", + operation.first, + "d.a RETURN d"); + SCOPED_TRACE(testing::Message("Query:") << queryString); + ExpressionContextMock ctx; + ctx.vars.emplace("a", arangodb::aql::AqlValue(arangodb::aql::AqlValueHintBool{false})); // invalid value type + ctx.vars.emplace("b", arangodb::aql::AqlValue(arangodb::aql::AqlValue{"c"})); + ctx.vars.emplace("c", arangodb::aql::AqlValue(arangodb::aql::AqlValue( + arangodb::aql::AqlValueHintInt{4}))); + ctx.vars.emplace("e", arangodb::aql::AqlValue(arangodb::aql::AqlValue( + arangodb::aql::AqlValueHintDouble{5.6}))); + assertFilterExecutionFail( + vocbase(), + queryString, + &ctx); + } + } + // self-reference + { + for (auto operation : intervalOperations) { + auto queryString = buildQueryString( + "FOR d IN myView FILTER [1,2,'3']", + operation.first, + " d RETURN d"); + SCOPED_TRACE(testing::Message("Query:") << queryString); + assertExpressionFilter(vocbase(), queryString); + } + } + // non-deterministic expression name in array + { + for (auto operation : intervalOperations) { + auto queryString = buildQueryString( + "LET a='a' LET c='c' LET offsetInt=4 LET offsetDbl=5.6 FOR d IN " + "collection FILTER " + " ['1','2','3']", + operation.first, + " d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_NONDETERM_('a')] RETURN d "); + SCOPED_TRACE(testing::Message("Query:") << queryString); + assertExpressionFilter(vocbase(), queryString); + } + } + // no reference provided + { + for (auto operation : intervalOperations) { + auto queryString = buildQueryString( + "LET x={} FOR d IN myView FILTER [1,x.a,3] ", + operation.first, + "d.a RETURN d"); + SCOPED_TRACE(testing::Message("Query:") << queryString); + assertFilterExecutionFail( + vocbase(), queryString, &ExpressionContextMock::EMPTY); + } + } + // not a value in array + { + for (auto operation : intervalOperations) { + auto queryString = buildQueryString( + "FOR d IN collection FILTER ['1',['2'],'3'] ", + operation.first, + "d.a RETURN d"); + SCOPED_TRACE(testing::Message("Query:") << queryString); + assertFilterFail(vocbase(), queryString); + } + } + // empty array + { + for (auto operation : intervalOperations) { + auto queryString = buildQueryString( + "FOR d IN collection FILTER BOOST([]", + operation.first, + "d.a, 2.5) RETURN d"); + SCOPED_TRACE(testing::Message("Query") << queryString); + irs::Or expected; + if (operation.first.find("ANY") != std::string::npos) { + expected.add(); + } else { + expected.add(); + } + expected.boost(2.5); + assertFilterSuccess( + vocbase(), queryString, expected); + } + } +} diff --git a/tests/IResearch/IResearchViewNode-test.cpp b/tests/IResearch/IResearchViewNode-test.cpp index b1281f395b..1fedcd55d9 100644 --- a/tests/IResearch/IResearchViewNode-test.cpp +++ b/tests/IResearch/IResearchViewNode-test.cpp @@ -2250,6 +2250,196 @@ TEST_F(IResearchViewNodeTest, createBlockCoordinatorLateMaterialize) { emptyBlock.get())); } +TEST_F(IResearchViewNodeTest, registerPlanningLateMaterialized) { + TRI_vocbase_t vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, testDBInfo(server.server(), + "testVocbase", 1)); + auto createJson = arangodb::velocypack::Parser::fromJson( + "{ \"name\": \"testView\", \"type\": \"arangosearch\" }"); + auto logicalView = vocbase.createView(createJson->slice()); + ASSERT_TRUE((false == !logicalView)); + + // dummy query + arangodb::aql::Query query(false, vocbase, arangodb::aql::QueryString("RETURN 1"), + nullptr, arangodb::velocypack::Parser::fromJson("{}"), + arangodb::aql::PART_MAIN); + query.prepare(arangodb::QueryRegistryFeature::registry(), + arangodb::aql::SerializationFormat::SHADOWROWS); + + // dummy engine + arangodb::aql::ExecutionEngine engine(query, arangodb::aql::SerializationFormat::SHADOWROWS); + arangodb::aql::SingletonNode singleton(query.plan(), 0); + arangodb::aql::Variable const outVariable("variable", 0); + arangodb::aql::Variable const outNmColPtr("variable100", 100); + arangodb::aql::Variable const outNmDocId("variable101", 101); + + // no filter condition, no sort condition + arangodb::iresearch::IResearchViewNode node(*query.plan(), + 42, // id + vocbase, // database + logicalView, // view + outVariable, + nullptr, // no filter condition + nullptr, // no options + {}); // no sort condition + node.addDependency(&singleton); + node.setLateMaterialized(&outNmColPtr, &outNmDocId); + std::vector nrRegsHere{ 0 }; + std::vector nrRegs{ 0 }; + std::unordered_map varInfo; + unsigned int totalNrRegs = 0; + node.planNodeRegisters(nrRegsHere, nrRegs, varInfo, totalNrRegs, 1); + EXPECT_EQ(2, totalNrRegs); + EXPECT_EQ(2, nrRegs.size()); + EXPECT_EQ(2, nrRegs[1]); + EXPECT_EQ(2, nrRegsHere.size()); + EXPECT_EQ(2, nrRegsHere[1]); + EXPECT_EQ(2, varInfo.size()); + EXPECT_NE(varInfo.end(), varInfo.find(outNmColPtr.id)); + EXPECT_NE(varInfo.end(), varInfo.find(outNmDocId.id)); + EXPECT_EQ(varInfo.end(), varInfo.find(outVariable.id)); +} + +TEST_F(IResearchViewNodeTest, registerPlanningLateMaterializedWitScore) { + TRI_vocbase_t vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, testDBInfo(server.server(), + "testVocbase", 1)); + auto createJson = arangodb::velocypack::Parser::fromJson( + "{ \"name\": \"testView\", \"type\": \"arangosearch\" }"); + auto logicalView = vocbase.createView(createJson->slice()); + ASSERT_TRUE((false == !logicalView)); + + // dummy query + arangodb::aql::Query query(false, vocbase, arangodb::aql::QueryString("RETURN 1"), + nullptr, arangodb::velocypack::Parser::fromJson("{}"), + arangodb::aql::PART_MAIN); + query.prepare(arangodb::QueryRegistryFeature::registry(), + arangodb::aql::SerializationFormat::SHADOWROWS); + + // dummy engine + arangodb::aql::ExecutionEngine engine(query, arangodb::aql::SerializationFormat::SHADOWROWS); + arangodb::aql::SingletonNode singleton(query.plan(), 0); + arangodb::aql::Variable const outVariable("variable", 0); + arangodb::aql::Variable const outNmColPtr("variable100", 100); + arangodb::aql::Variable const outNmDocId("variable101", 101); + arangodb::aql::Variable const scoreVariable("score", 102); + + // no filter condition, no sort condition + arangodb::iresearch::IResearchViewNode node(*query.plan(), + 42, // id + vocbase, // database + logicalView, // view + outVariable, + nullptr, // no filter condition + nullptr, // no options + std::vector{ {&scoreVariable, nullptr} }); //sort condition + node.addDependency(&singleton); + node.setLateMaterialized(&outNmColPtr, &outNmDocId); + std::vector nrRegsHere{ 0 }; + std::vector nrRegs{ 0 }; + std::unordered_map varInfo; + unsigned int totalNrRegs = 0; + node.planNodeRegisters(nrRegsHere, nrRegs, varInfo, totalNrRegs, 1); + EXPECT_EQ(3, totalNrRegs); + EXPECT_EQ(2, nrRegs.size()); + EXPECT_EQ(3, nrRegs[1]); + EXPECT_EQ(2, nrRegsHere.size()); + EXPECT_EQ(3, nrRegsHere[1]); + EXPECT_EQ(3, varInfo.size()); + EXPECT_NE(varInfo.end(), varInfo.find(outNmColPtr.id)); + EXPECT_NE(varInfo.end(), varInfo.find(outNmDocId.id)); + EXPECT_EQ(varInfo.end(), varInfo.find(outVariable.id)); + EXPECT_NE(varInfo.end(), varInfo.find(scoreVariable.id)); +} + +TEST_F(IResearchViewNodeTest, registerPlanning) { + TRI_vocbase_t vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, testDBInfo(server.server(), + "testVocbase", 1)); + auto createJson = arangodb::velocypack::Parser::fromJson( + "{ \"name\": \"testView\", \"type\": \"arangosearch\" }"); + auto logicalView = vocbase.createView(createJson->slice()); + ASSERT_TRUE((false == !logicalView)); + + // dummy query + arangodb::aql::Query query(false, vocbase, arangodb::aql::QueryString("RETURN 1"), + nullptr, arangodb::velocypack::Parser::fromJson("{}"), + arangodb::aql::PART_MAIN); + query.prepare(arangodb::QueryRegistryFeature::registry(), + arangodb::aql::SerializationFormat::SHADOWROWS); + + // dummy engine + arangodb::aql::ExecutionEngine engine(query, arangodb::aql::SerializationFormat::SHADOWROWS); + arangodb::aql::SingletonNode singleton(query.plan(), 0); + arangodb::aql::Variable const outVariable("variable", 0); + + // no filter condition, no sort condition + arangodb::iresearch::IResearchViewNode node(*query.plan(), + 42, // id + vocbase, // database + logicalView, // view + outVariable, + nullptr, // no filter condition + nullptr, // no options + {}); // no sort condition + node.addDependency(&singleton); + std::vector nrRegsHere{ 0 }; + std::vector nrRegs{ 0 }; + std::unordered_map varInfo; + unsigned int totalNrRegs = 0; + node.planNodeRegisters(nrRegsHere, nrRegs, varInfo, totalNrRegs, 1); + EXPECT_EQ(1, totalNrRegs); + EXPECT_EQ(2, nrRegs.size()); + EXPECT_EQ(1, nrRegs[1]); + EXPECT_EQ(2, nrRegsHere.size()); + EXPECT_EQ(1, nrRegsHere[1]); + EXPECT_EQ(1, varInfo.size()); + EXPECT_NE(varInfo.end(), varInfo.find(outVariable.id)); +} + +TEST_F(IResearchViewNodeTest, registerPlanningWithScore) { + TRI_vocbase_t vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, testDBInfo(server.server(), + "testVocbase", 1)); + auto createJson = arangodb::velocypack::Parser::fromJson( + "{ \"name\": \"testView\", \"type\": \"arangosearch\" }"); + auto logicalView = vocbase.createView(createJson->slice()); + ASSERT_TRUE((false == !logicalView)); + + // dummy query + arangodb::aql::Query query(false, vocbase, arangodb::aql::QueryString("RETURN 1"), + nullptr, arangodb::velocypack::Parser::fromJson("{}"), + arangodb::aql::PART_MAIN); + query.prepare(arangodb::QueryRegistryFeature::registry(), + arangodb::aql::SerializationFormat::SHADOWROWS); + + // dummy engine + arangodb::aql::ExecutionEngine engine(query, arangodb::aql::SerializationFormat::SHADOWROWS); + arangodb::aql::SingletonNode singleton(query.plan(), 0); + arangodb::aql::Variable const outVariable("variable", 0); + arangodb::aql::Variable const scoreVariable("score", 100); + + // no filter condition, no sort condition + arangodb::iresearch::IResearchViewNode node(*query.plan(), + 42, // id + vocbase, // database + logicalView, // view + outVariable, + nullptr, // no filter condition + nullptr, // no options + std::vector{ {&scoreVariable, nullptr} }); //sort condition + node.addDependency(&singleton); + std::vector nrRegsHere{ 0 }; + std::vector nrRegs{ 0 }; + std::unordered_map varInfo; + unsigned int totalNrRegs = 0; + node.planNodeRegisters(nrRegsHere, nrRegs, varInfo, totalNrRegs, 1); + EXPECT_EQ(2, totalNrRegs); + EXPECT_EQ(2, nrRegs.size()); + EXPECT_EQ(2, nrRegs[1]); + EXPECT_EQ(2, nrRegsHere.size()); + EXPECT_EQ(2, nrRegsHere[1]); + EXPECT_EQ(2, varInfo.size()); + EXPECT_NE(varInfo.end(), varInfo.find(outVariable.id)); + EXPECT_NE(varInfo.end(), varInfo.find(scoreVariable.id)); +} + class IResearchViewBlockTest : public ::testing::Test, public arangodb::tests::LogSuppressor { diff --git a/tests/IResearch/common.cpp b/tests/IResearch/common.cpp index 6ba707b317..2dcefad854 100644 --- a/tests/IResearch/common.cpp +++ b/tests/IResearch/common.cpp @@ -627,6 +627,71 @@ void assertFilterBoost(irs::filter const& expected, irs::filter const& actual) { } } +void buildActualFilter(TRI_vocbase_t& vocbase, + std::string const& queryString, irs::filter& actual, + arangodb::aql::ExpressionContext* exprCtx /*= nullptr*/, + std::shared_ptr bindVars /*= nullptr*/, + std::string const& refName /*= "d"*/ +) { + auto options = std::make_shared(); + + arangodb::aql::Query query(false, vocbase, arangodb::aql::QueryString(queryString), + bindVars, options, arangodb::aql::PART_MAIN); + + auto const parseResult = query.parse(); + ASSERT_TRUE(parseResult.result.ok()); + + auto* ast = query.ast(); + ASSERT_TRUE(ast); + + auto* root = ast->root(); + ASSERT_TRUE(root); + + // find first FILTER node + arangodb::aql::AstNode* filterNode = nullptr; + for (size_t i = 0; i < root->numMembers(); ++i) { + auto* node = root->getMemberUnchecked(i); + ASSERT_TRUE(node); + + if (arangodb::aql::NODE_TYPE_FILTER == node->type) { + filterNode = node; + break; + } + } + ASSERT_TRUE(filterNode); + + // find referenced variable + auto* allVars = ast->variables(); + ASSERT_TRUE(allVars); + arangodb::aql::Variable* ref = nullptr; + for (auto entry : allVars->variables(true)) { + if (entry.second == refName) { + ref = allVars->getVariable(entry.first); + break; + } + } + ASSERT_TRUE(ref); + + // optimization time + { + arangodb::transaction ::Methods trx(arangodb::transaction::StandaloneContext::Create(vocbase), + {}, {}, {}, arangodb::transaction::Options()); + + arangodb::iresearch::QueryContext const ctx{&trx, nullptr, nullptr, nullptr, ref}; + ASSERT_TRUE(arangodb::iresearch::FilterFactory::filter(nullptr, ctx, *filterNode).ok()); + } + + // execution time + { + arangodb::transaction ::Methods trx(arangodb::transaction::StandaloneContext::Create(vocbase), + {}, {}, {}, arangodb::transaction::Options()); + + auto dummyPlan = arangodb::tests::planFromQuery(vocbase, "RETURN 1"); + arangodb::iresearch::QueryContext const ctx{&trx, dummyPlan.get(), ast, exprCtx, ref}; + ASSERT_TRUE(arangodb::iresearch::FilterFactory::filter(dynamic_cast(&actual), ctx, *filterNode).ok()); + } +} + void assertFilter(TRI_vocbase_t& vocbase, bool parseOk, bool execOk, std::string const& queryString, irs::filter const& expected, arangodb::aql::ExpressionContext* exprCtx /*= nullptr*/, diff --git a/tests/IResearch/common.h b/tests/IResearch/common.h index b66068e41a..b247918b90 100644 --- a/tests/IResearch/common.h +++ b/tests/IResearch/common.h @@ -187,6 +187,11 @@ void assertFilterExecutionFail(TRI_vocbase_t& vocbase, std::string const& queryS void assertFilterParseFail(TRI_vocbase_t& vocbase, std::string const& queryString, std::shared_ptr bindVars = nullptr); +void buildActualFilter(TRI_vocbase_t& vocbase, std::string const& queryString, irs::filter& actual, + arangodb::aql::ExpressionContext* exprCtx = nullptr, + std::shared_ptr bindVars = nullptr, + std::string const& refName = "d"); + inline VPackBuilder dbArgsBuilder(std::string const& name = "_system") { VPackOptions options; VPackBuilder builder(&options); diff --git a/tests/js/common/aql/aql-view-arangosearch-cluster.inc b/tests/js/common/aql/aql-view-arangosearch-cluster.inc index 8118303f89..9e97c3cf47 100644 --- a/tests/js/common/aql/aql-view-arangosearch-cluster.inc +++ b/tests/js/common/aql/aql-view-arangosearch-cluster.inc @@ -48,6 +48,11 @@ return { setUpAll : function () { analyzers.save("text_en", "text", "{ \"locale\": \"en.UTF-8\", \"stopwords\": [ ] }", [ "frequency", "norm", "position" ]); + + db._drop("AuxUnitTestsCollection"); + let auxCol = db._create("AuxUnitTestsCollection"); + auxCol.save({ foobar: ['foo', 'bar'], foo: ['foo'], bar:['bar'], empty: []}); + db._drop("UnitTestsCollection"); c = db._create("UnitTestsCollection", args); @@ -57,6 +62,12 @@ db._drop("AnotherUnitTestsCollection"); var ac = db._create("AnotherUnitTestsCollection", args); + db._drop("UnitTestsWithArrayCollection"); + let arrayCol = db._create("UnitTestsWithArrayCollection"); + arrayCol.save({ c: 0, a: ['foo', 'bar', 'baz']}); + // this will allow to catch if accidentally "all" filter will be used + arrayCol.save({ c: 1, a: ['afoo', 'abar', 'abaz']}); + db._dropView("UnitTestsView"); v = db._createView("UnitTestsView", "arangosearch", {}); meta = { @@ -130,6 +141,21 @@ g.save({ _from: "UnitTestsGraphCollection/begin", _to: "UnitTestsGraphCollection/intermediate" }); g.save({ _from: "UnitTestsGraphCollection/intermediate", _to: "UnitTestsGraphCollection/end" }); + + db._dropView("UnitTestsViewArrayView"); + + let arrayV = db._createView("UnitTestsWithArrayView", "arangosearch", {}); + meta = { + links: { + UnitTestsWithArrayCollection: { + includeAllFields: true, + fields: { + a: { analyzers: [ "identity" ] } + } + } + } + }; + arrayV.properties(meta); }, tearDownAll : function () { @@ -148,6 +174,9 @@ db._drop("AnotherUnitTestsCollection"); db._drop("UnitTestsGraph"); db._drop("UnitTestsGraphCollection"); + db._drop("AuxUnitTestsCollection"); + db._dropView("UnitTestsWithArrayView"); + db._drop("UnitTestsWithArrayCollection"); }, testViewInFunctionCall : function () { @@ -1028,6 +1057,508 @@ }); docs.forEach(function(element, i) { assertEqual(res[i].length, 1); }); + }, + testArrayComparsionOperatorsInOnSimpleField : function () { + { + let result = db._query("FOR d IN UnitTestsView SEARCH ['foo', 'bar'] ANY IN d.a OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(15, result.length); + result.forEach(function(doc) { + assertTrue(doc.a === 'foo' || doc.a === 'bar'); + }); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [] ANY IN d.a RETURN d").toArray(); + assertEqual(0, result.length); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH ['foo', 'bar'] ANY NOT IN d.a RETURN d").toArray(); + assertEqual(28, result.length); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [] ANY NOT IN d.a RETURN d").toArray(); + assertEqual(0, result.length); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH ['foo'] ANY NOT IN d.a RETURN d").toArray(); + assertEqual(18, result.length); + result.forEach(function(doc) { + assertTrue(doc.a !== 'foo'); + }); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH ['foo', 'bar'] NONE IN d.a RETURN d").toArray(); + assertEqual(13, result.length); + result.forEach(function(doc) { + assertTrue(doc.a !== 'foo' && doc.a !== 'bar'); + }); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [] NONE IN d.a RETURN d").toArray(); + assertEqual(28, result.length); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH ['foo', 'bar'] NONE NOT IN d.a RETURN d").toArray(); + assertEqual(0, result.length); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH ['bar'] NONE NOT IN d.a RETURN d").toArray(); + assertEqual(5, result.length); + result.forEach(function(doc) { + assertTrue(doc.a === 'bar'); + }); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [] NONE NOT IN d.a RETURN d").toArray(); + assertEqual(28, result.length); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH ['foo', 'bar'] ALL IN d.a RETURN d").toArray(); + assertEqual(0, result.length); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [] ALL IN d.a RETURN d").toArray(); + assertEqual(28, result.length); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH ['bar'] ALL IN d.a RETURN d").toArray(); + assertEqual(5, result.length); + result.forEach(function(doc) { + assertTrue(doc.a === 'bar'); + }); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH ['foo', 'bar'] ALL NOT IN d.a RETURN d").toArray(); + assertEqual(13, result.length); + result.forEach(function(doc) { + assertTrue(doc.a !== 'foo' && doc.a !== 'bar'); + }); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [] ALL NOT IN d.a RETURN d").toArray(); + assertEqual(28, result.length); + } + }, + testArrayComparsionOperatorsInOnSimpleFieldWitScopedValue : function () { + { + let result = db._query("FOR a IN AuxUnitTestsCollection " + + "FOR d IN UnitTestsView SEARCH a.foobar ANY IN d.a OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(15, result.length); + result.forEach(function(doc) { + assertTrue(doc.a === 'foo' || doc.a === 'bar'); + }); + } + { + let result = db._query("FOR a IN AuxUnitTestsCollection " + + "FOR d IN UnitTestsView SEARCH a.empty ANY IN d.a RETURN d").toArray(); + assertEqual(0, result.length); + } + { + let result = db._query("FOR a IN AuxUnitTestsCollection " + + "FOR d IN UnitTestsView SEARCH a.foobar ANY NOT IN d.a RETURN d").toArray(); + assertEqual(28, result.length); + } + { + let result = db._query("FOR a IN AuxUnitTestsCollection " + + "FOR d IN UnitTestsView SEARCH a.empty ANY NOT IN d.a RETURN d").toArray(); + assertEqual(0, result.length); + } + { + let result = db._query("FOR a IN AuxUnitTestsCollection " + + "FOR d IN UnitTestsView SEARCH a.foo ANY NOT IN d.a RETURN d").toArray(); + assertEqual(18, result.length); + result.forEach(function(doc) { + assertTrue(doc.a !== 'foo'); + }); + } + { + let result = db._query("FOR a IN AuxUnitTestsCollection " + + "FOR d IN UnitTestsView SEARCH a.foobar NONE IN d.a RETURN d").toArray(); + assertEqual(13, result.length); + result.forEach(function(doc) { + assertTrue(doc.a !== 'foo' && doc.a !== 'bar'); + }); + } + { + let result = db._query("FOR a IN AuxUnitTestsCollection " + + "FOR d IN UnitTestsView SEARCH a.empty NONE IN d.a RETURN d").toArray(); + assertEqual(28, result.length); + } + { + let result = db._query("FOR a IN AuxUnitTestsCollection " + + "FOR d IN UnitTestsView SEARCH a.foobar NONE NOT IN d.a RETURN d").toArray(); + assertEqual(0, result.length); + } + { + let result = db._query("FOR a IN AuxUnitTestsCollection " + + "FOR d IN UnitTestsView SEARCH a.bar NONE NOT IN d.a RETURN d").toArray(); + assertEqual(5, result.length); + result.forEach(function(doc) { + assertTrue(doc.a === 'bar'); + }); + } + { + let result = db._query("FOR a IN AuxUnitTestsCollection " + + "FOR d IN UnitTestsView SEARCH a.empty NONE NOT IN d.a RETURN d").toArray(); + assertEqual(28, result.length); + } + { + let result = db._query("FOR a IN AuxUnitTestsCollection " + + "FOR d IN UnitTestsView SEARCH a.foobar ALL IN d.a RETURN d").toArray(); + assertEqual(0, result.length); + } + { + let result = db._query("FOR a IN AuxUnitTestsCollection " + + "FOR d IN UnitTestsView SEARCH a.empty ALL IN d.a RETURN d").toArray(); + assertEqual(28, result.length); + } + { + let result = db._query("FOR a IN AuxUnitTestsCollection " + + "FOR d IN UnitTestsView SEARCH a.bar ALL IN d.a RETURN d").toArray(); + assertEqual(5, result.length); + result.forEach(function(doc) { + assertTrue(doc.a === 'bar'); + }); + } + { + let result = db._query("FOR a IN AuxUnitTestsCollection " + + "FOR d IN UnitTestsView SEARCH a.foobar ALL NOT IN d.a RETURN d").toArray(); + assertEqual(13, result.length); + result.forEach(function(doc) { + assertTrue(doc.a !== 'foo' && doc.a !== 'bar'); + }); + } + { + let result = db._query("FOR a IN AuxUnitTestsCollection " + + "FOR d IN UnitTestsView SEARCH a.empty ALL NOT IN d.a RETURN d").toArray(); + assertEqual(28, result.length); + } + }, + testArrayComparsionOperatorsInOnArrayField : function() { + { + let result = db._query("FOR d IN UnitTestsWithArrayView SEARCH ['foo', 'bar'] ALL IN d.a OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(1, result.length); + assertEqual(0, result[0].c); + } + { + let result = db._query("FOR d IN UnitTestsWithArrayView SEARCH ['foo', 'bar', 'none'] ALL IN d.a OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(0, result.length); + } + { + let result = db._query("FOR d IN UnitTestsWithArrayView SEARCH [ 'none', 'nani', 'afoo'] ALL NOT IN d.a OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(1, result.length); + assertEqual(0, result[0].c); + } + { + let result = db._query("FOR d IN UnitTestsWithArrayView SEARCH ['afoo', 'foo', 'none', 'nani'] ALL NOT IN d.a OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(0, result.length); + } + { + let result = db._query("FOR d IN UnitTestsWithArrayView SEARCH ['none', 'bar'] ANY IN d.a OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(1, result.length); + assertEqual(0, result[0].c); + } + { + let result = db._query("FOR d IN UnitTestsWithArrayView SEARCH [ 'nani', 'none'] ANY IN d.a OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(0, result.length); + } + { + let result = db._query("FOR d IN UnitTestsWithArrayView SEARCH [ 'foo', 'none', 'nani', 'afoo'] ANY NOT IN d.a OPTIONS { waitForSync : true } SORT d.c ASC RETURN d").toArray(); + assertEqual(2, result.length); + assertEqual(0, result[0].c); + assertEqual(1, result[1].c); + } + { + let result = db._query("FOR d IN UnitTestsWithArrayView SEARCH [ 'foo', 'bar'] ANY NOT IN d.a OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(1, result.length); + assertEqual(1, result[0].c); + } + { + let result = db._query("FOR d IN UnitTestsWithArrayView SEARCH ['none', 'nani', 'afoo'] NONE IN d.a OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(1, result.length); + assertEqual(0, result[0].c); + } + { + let result = db._query("FOR d IN UnitTestsWithArrayView SEARCH ['none', 'nani', 'bar', 'afoo'] NONE IN d.a OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(0, result.length); + } + { + let result = db._query("FOR d IN UnitTestsWithArrayView SEARCH ['none', 'foo', 'bar'] NONE NOT IN d.a OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(0, result.length); + } + { + let result = db._query("FOR d IN UnitTestsWithArrayView SEARCH ['bar', 'foo'] NONE NOT IN d.a OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(1, result.length); + assertEqual(0, result[0].c); + } + }, + testArrayComparsionOperatorsGreaterOnSimpleField : function() { + { + let result = db._query("FOR d IN UnitTestsView SEARCH [2, 3, 4] ALL > d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(8, result.length); + result.forEach(function(doc) { + assertTrue(doc.c === 0 || doc.c === 1); + }); + } + { + let result = db._query("FOR a IN [[2, 3, 4]] FOR d IN UnitTestsView SEARCH a ALL > d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(8, result.length); + result.forEach(function(doc) { + assertTrue(doc.c === 0 || doc.c === 1); + }); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [2, 3, 4] ALL >= d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(12, result.length); + result.forEach(function(doc) { + assertTrue(doc.c === 0 || doc.c === 1 || doc.c === 2); + }); + } + { + let result = db._query("FOR a IN [[2, 3, 4]] FOR d IN UnitTestsView SEARCH a ALL >= d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(12, result.length); + result.forEach(function(doc) { + assertTrue(doc.c === 0 || doc.c === 1 || doc.c === 2); + }); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [] ALL > d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(28, result.length); + } + { + let result = db._query("FOR a IN [[]] FOR d IN UnitTestsView SEARCH a ALL > d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(28, result.length); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [] ALL >= d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(28, result.length); + } + { + let result = db._query("FOR a IN [[]] FOR d IN UnitTestsView SEARCH a ALL >= d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(28, result.length); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [1, 2] ANY > d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(8, result.length); + result.forEach(function(doc) { + assertTrue(doc.c < 2); + }); + } + { + let result = db._query("FOR a IN [[1, 2]] FOR d IN UnitTestsView SEARCH a ANY > d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(8, result.length); + result.forEach(function(doc) { + assertTrue(doc.c < 2); + }); + + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [1, 2] ANY >= d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(12, result.length); + result.forEach(function(doc) { + assertTrue(doc.c <= 2); + }); + } + { + let result = db._query("FOR a IN [[1, 2]] FOR d IN UnitTestsView SEARCH a ANY >= d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(12, result.length); + result.forEach(function(doc) { + assertTrue(doc.c <= 2); + }); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [] ANY > d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(0, result.length); + } + { + let result = db._query("FOR a IN [[]] FOR d IN UnitTestsView SEARCH a ANY > d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(0, result.length); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [] ANY >= d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(0, result.length); + } + { + let result = db._query("FOR a IN [[]] FOR d IN UnitTestsView SEARCH a ANY >= d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(0, result.length); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [1, 2, 3] NONE > d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(8, result.length); + result.forEach(function(doc) { + assertTrue(doc.c === 4 || doc.c === 3); + }); + } + { + let result = db._query("FOR a IN [[1, 2, 3]] FOR d IN UnitTestsView SEARCH a NONE > d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(8, result.length); + result.forEach(function(doc) { + assertTrue(doc.c === 4 || doc.c === 3); + }); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [1, 2, 3] NONE >= d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(4, result.length); + result.forEach(function(doc) { + assertTrue(doc.c === 4); + }); + } + { + let result = db._query("FOR a IN [[1, 2, 3]] FOR d IN UnitTestsView SEARCH a NONE >= d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(4, result.length); + result.forEach(function(doc) { + assertTrue(doc.c === 4); + }); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [] NONE > d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(28, result.length); + } + { + let result = db._query("FOR a IN [[]] FOR d IN UnitTestsView SEARCH a NONE > d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(28, result.length); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [] NONE >= d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(28, result.length); + } + { + let result = db._query("FOR a IN [[]] FOR d IN UnitTestsView SEARCH a NONE >= d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(28, result.length); + } + }, + testArrayComparsionOperatorsLessOnSimpleField : function() { + { + let result = db._query("FOR d IN UnitTestsView SEARCH [2, 3] ALL < d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(4, result.length); + result.forEach(function(doc) { + assertTrue(doc.c === 4); + }); + } + { + let result = db._query("FOR a IN [[2,3]] FOR d IN UnitTestsView SEARCH a ALL < d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(4, result.length); + result.forEach(function(doc) { + assertTrue(doc.c === 4); + }); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [2, 3] ALL <= d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(8, result.length); + result.forEach(function(doc) { + assertTrue(doc.c === 4 || doc.c === 3); + }); + } + { + let result = db._query("FOR a IN [[2,3]] FOR d IN UnitTestsView SEARCH a ALL <= d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(8, result.length); + result.forEach(function(doc) { + assertTrue(doc.c === 4 || doc.c === 3); + }); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [] ALL < d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(28, result.length); + } + { + let result = db._query("FOR a IN [[]] FOR d IN UnitTestsView SEARCH a ALL < d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(28, result.length); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [] ALL <= d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(28, result.length); + } + { + let result = db._query("FOR a IN [[]] FOR d IN UnitTestsView SEARCH a ALL <= d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(28, result.length); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [3, 4] ANY < d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(4, result.length); + result.forEach(function(doc) { + assertTrue(doc.c === 4); + }); + } + { + let result = db._query("FOR a IN [[3,4]] FOR d IN UnitTestsView SEARCH a ANY < d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(4, result.length); + result.forEach(function(doc) { + assertTrue(doc.c === 4); + }); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [3, 4] ANY <= d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(8, result.length); + result.forEach(function(doc) { + assertTrue(doc.c === 4 || doc.c === 3); + }); + } + { + let result = db._query("FOR a IN [[3,4]] FOR d IN UnitTestsView SEARCH a ANY <= d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(8, result.length); + result.forEach(function(doc) { + assertTrue(doc.c === 4 || doc.c === 3); + }); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [] ANY < d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(0, result.length); + } + { + let result = db._query("FOR a IN [[]] FOR d IN UnitTestsView SEARCH a ANY < d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(0, result.length); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [] ANY <= d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(0, result.length); + } + { + let result = db._query("FOR a IN [[]] FOR d IN UnitTestsView SEARCH a ANY <= d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(0, result.length); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [1, 2, 3] NONE < d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(8, result.length); + result.forEach(function(doc) { + assertTrue(doc.c === 1 || doc.c === 0); + }); + } + { + let result = db._query("FOR a IN [[1,2,3]] FOR d IN UnitTestsView SEARCH a NONE < d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(8, result.length); + result.forEach(function(doc) { + assertTrue(doc.c === 1 || doc.c === 0); + }); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [1, 2, 3] NONE <= d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(4, result.length); + result.forEach(function(doc) { + assertTrue( doc.c === 0); + }); + } + { + let result = db._query("FOR a IN [[1,2,3]] FOR d IN UnitTestsView SEARCH a NONE <= d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(4, result.length); + result.forEach(function(doc) { + assertTrue(doc.c === 0); + }); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [] NONE < d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(28, result.length); + } + { + let result = db._query("FOR a IN [[]] FOR d IN UnitTestsView SEARCH a NONE < d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(28, result.length); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [] NONE <= d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(28, result.length); + } + { + let result = db._query("FOR a IN [[]] FOR d IN UnitTestsView SEARCH a NONE <= d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(28, result.length); + } } }; }; diff --git a/tests/js/common/aql/aql-view-arangosearch-noncluster.js b/tests/js/common/aql/aql-view-arangosearch-noncluster.js index fb142ccdb1..dac4724a20 100644 --- a/tests/js/common/aql/aql-view-arangosearch-noncluster.js +++ b/tests/js/common/aql/aql-view-arangosearch-noncluster.js @@ -42,8 +42,45 @@ function iResearchAqlTestSuite () { var c2; var v; var v2; - return { + setUpAll : function () { + db._drop("AuxUnitTestsCollection"); + let auxCol = db._create("AuxUnitTestsCollection"); + auxCol.save({ foobar: ['foo', 'bar'], foo: ['foo'], bar:['bar'], empty: []}); + + db._drop("AnotherUnitTestsCollection"); + let ac = db._create("AnotherUnitTestsCollection"); + ac.save({ a: "foo", id : 0 }); + ac.save({ a: "ba", id : 1 }); + + db._drop("UnitTestsWithArrayCollection"); + let arrayCol = db._create("UnitTestsWithArrayCollection"); + arrayCol.save({ c: 0, a: ['foo', 'bar', 'baz']}); + // this will allow to catch if accidentally "all" filter will be used + arrayCol.save({ c: 1, a: ['afoo', 'abar', 'abaz']}); + + db._dropView("UnitTestsViewArrayView"); + + let arrayV = db._createView("UnitTestsWithArrayView", "arangosearch", {}); + + let meta = { + links: { + UnitTestsWithArrayCollection: { + includeAllFields: true, + fields: { + a: { analyzers: [ "identity" ] } + } + } + } + }; + arrayV.properties(meta); + }, + tearDownAll : function () { + db._drop("AnotherUnitTestsCollection"); + db._drop("AuxUnitTestsCollection"); + db._dropView("UnitTestsWithArrayView"); + db._drop("UnitTestsWithArrayCollection"); + }, setUp : function () { db._drop("UnitTestsCollection"); c = db._create("UnitTestsCollection"); @@ -51,9 +88,6 @@ function iResearchAqlTestSuite () { db._drop("UnitTestsCollection2"); c2 = db._create("UnitTestsCollection2"); - db._drop("AnotherUnitTestsCollection"); - var ac = db._create("AnotherUnitTestsCollection"); - db._dropView("UnitTestsView"); v = db._createView("UnitTestsView", "arangosearch", {}); var meta = { @@ -77,9 +111,6 @@ function iResearchAqlTestSuite () { }} ); - ac.save({ a: "foo", id : 0 }); - ac.save({ a: "ba", id : 1 }); - for (let i = 0; i < 5; i++) { c.save({ a: "foo", b: "bar", c: i }); c.save({ a: "foo", b: "baz", c: i }); @@ -109,7 +140,7 @@ function iResearchAqlTestSuite () { v2.drop(); db._drop("UnitTestsCollection"); db._drop("UnitTestsCollection2"); - db._drop("AnotherUnitTestsCollection"); + }, testViewInFunctionCall : function () { @@ -1052,7 +1083,509 @@ function iResearchAqlTestSuite () { }); docs.forEach(function(element, i) { assertEqual(res[i].length, 1); }); - } + }, + testArrayComparsionOperatorsInOnSimpleField : function () { + { + let result = db._query("FOR d IN UnitTestsView SEARCH ['foo', 'bar'] ANY IN d.a OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(15, result.length); + result.forEach(function(doc) { + assertTrue(doc.a === 'foo' || doc.a === 'bar'); + }); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [] ANY IN d.a RETURN d").toArray(); + assertEqual(0, result.length); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH ['foo', 'bar'] ANY NOT IN d.a RETURN d").toArray(); + assertEqual(28, result.length); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [] ANY NOT IN d.a RETURN d").toArray(); + assertEqual(0, result.length); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH ['foo'] ANY NOT IN d.a RETURN d").toArray(); + assertEqual(18, result.length); + result.forEach(function(doc) { + assertTrue(doc.a !== 'foo'); + }); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH ['foo', 'bar'] NONE IN d.a RETURN d").toArray(); + assertEqual(13, result.length); + result.forEach(function(doc) { + assertTrue(doc.a !== 'foo' && doc.a !== 'bar'); + }); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [] NONE IN d.a RETURN d").toArray(); + assertEqual(28, result.length); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH ['foo', 'bar'] NONE NOT IN d.a RETURN d").toArray(); + assertEqual(0, result.length); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH ['bar'] NONE NOT IN d.a RETURN d").toArray(); + assertEqual(5, result.length); + result.forEach(function(doc) { + assertTrue(doc.a === 'bar'); + }); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [] NONE NOT IN d.a RETURN d").toArray(); + assertEqual(28, result.length); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH ['foo', 'bar'] ALL IN d.a RETURN d").toArray(); + assertEqual(0, result.length); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [] ALL IN d.a RETURN d").toArray(); + assertEqual(28, result.length); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH ['bar'] ALL IN d.a RETURN d").toArray(); + assertEqual(5, result.length); + result.forEach(function(doc) { + assertTrue(doc.a === 'bar'); + }); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH ['foo', 'bar'] ALL NOT IN d.a RETURN d").toArray(); + assertEqual(13, result.length); + result.forEach(function(doc) { + assertTrue(doc.a !== 'foo' && doc.a !== 'bar'); + }); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [] ALL NOT IN d.a RETURN d").toArray(); + assertEqual(28, result.length); + } + }, + testArrayComparsionOperatorsInOnSimpleFieldWitScopedValue : function () { + { + let result = db._query("FOR a IN AuxUnitTestsCollection " + + "FOR d IN UnitTestsView SEARCH a.foobar ANY IN d.a OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(15, result.length); + result.forEach(function(doc) { + assertTrue(doc.a === 'foo' || doc.a === 'bar'); + }); + } + { + let result = db._query("FOR a IN AuxUnitTestsCollection " + + "FOR d IN UnitTestsView SEARCH a.empty ANY IN d.a RETURN d").toArray(); + assertEqual(0, result.length); + } + { + let result = db._query("FOR a IN AuxUnitTestsCollection " + + "FOR d IN UnitTestsView SEARCH a.foobar ANY NOT IN d.a RETURN d").toArray(); + assertEqual(28, result.length); + } + { + let result = db._query("FOR a IN AuxUnitTestsCollection " + + "FOR d IN UnitTestsView SEARCH a.empty ANY NOT IN d.a RETURN d").toArray(); + assertEqual(0, result.length); + } + { + let result = db._query("FOR a IN AuxUnitTestsCollection " + + "FOR d IN UnitTestsView SEARCH a.foo ANY NOT IN d.a RETURN d").toArray(); + assertEqual(18, result.length); + result.forEach(function(doc) { + assertTrue(doc.a !== 'foo'); + }); + } + { + let result = db._query("FOR a IN AuxUnitTestsCollection " + + "FOR d IN UnitTestsView SEARCH a.foobar NONE IN d.a RETURN d").toArray(); + assertEqual(13, result.length); + result.forEach(function(doc) { + assertTrue(doc.a !== 'foo' && doc.a !== 'bar'); + }); + } + { + let result = db._query("FOR a IN AuxUnitTestsCollection " + + "FOR d IN UnitTestsView SEARCH a.empty NONE IN d.a RETURN d").toArray(); + assertEqual(28, result.length); + } + { + let result = db._query("FOR a IN AuxUnitTestsCollection " + + "FOR d IN UnitTestsView SEARCH a.foobar NONE NOT IN d.a RETURN d").toArray(); + assertEqual(0, result.length); + } + { + let result = db._query("FOR a IN AuxUnitTestsCollection " + + "FOR d IN UnitTestsView SEARCH a.bar NONE NOT IN d.a RETURN d").toArray(); + assertEqual(5, result.length); + result.forEach(function(doc) { + assertTrue(doc.a === 'bar'); + }); + } + { + let result = db._query("FOR a IN AuxUnitTestsCollection " + + "FOR d IN UnitTestsView SEARCH a.empty NONE NOT IN d.a RETURN d").toArray(); + assertEqual(28, result.length); + } + { + let result = db._query("FOR a IN AuxUnitTestsCollection " + + "FOR d IN UnitTestsView SEARCH a.foobar ALL IN d.a RETURN d").toArray(); + assertEqual(0, result.length); + } + { + let result = db._query("FOR a IN AuxUnitTestsCollection " + + "FOR d IN UnitTestsView SEARCH a.empty ALL IN d.a RETURN d").toArray(); + assertEqual(28, result.length); + } + { + let result = db._query("FOR a IN AuxUnitTestsCollection " + + "FOR d IN UnitTestsView SEARCH a.bar ALL IN d.a RETURN d").toArray(); + assertEqual(5, result.length); + result.forEach(function(doc) { + assertTrue(doc.a === 'bar'); + }); + } + { + let result = db._query("FOR a IN AuxUnitTestsCollection " + + "FOR d IN UnitTestsView SEARCH a.foobar ALL NOT IN d.a RETURN d").toArray(); + assertEqual(13, result.length); + result.forEach(function(doc) { + assertTrue(doc.a !== 'foo' && doc.a !== 'bar'); + }); + } + { + let result = db._query("FOR a IN AuxUnitTestsCollection " + + "FOR d IN UnitTestsView SEARCH a.empty ALL NOT IN d.a RETURN d").toArray(); + assertEqual(28, result.length); + } + }, + testArrayComparsionOperatorsInOnArrayField : function() { + { + let result = db._query("FOR d IN UnitTestsWithArrayView SEARCH ['foo', 'bar'] ALL IN d.a OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(1, result.length); + assertEqual(0, result[0].c); + } + { + let result = db._query("FOR d IN UnitTestsWithArrayView SEARCH ['foo', 'bar', 'none'] ALL IN d.a OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(0, result.length); + } + { + let result = db._query("FOR d IN UnitTestsWithArrayView SEARCH [ 'none', 'nani', 'afoo'] ALL NOT IN d.a OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(1, result.length); + assertEqual(0, result[0].c); + } + { + let result = db._query("FOR d IN UnitTestsWithArrayView SEARCH ['afoo', 'foo', 'none', 'nani'] ALL NOT IN d.a OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(0, result.length); + } + { + let result = db._query("FOR d IN UnitTestsWithArrayView SEARCH ['none', 'bar'] ANY IN d.a OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(1, result.length); + assertEqual(0, result[0].c); + } + { + let result = db._query("FOR d IN UnitTestsWithArrayView SEARCH [ 'nani', 'none'] ANY IN d.a OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(0, result.length); + } + { + let result = db._query("FOR d IN UnitTestsWithArrayView SEARCH [ 'foo', 'none', 'nani', 'afoo'] ANY NOT IN d.a OPTIONS { waitForSync : true } SORT d.c ASC RETURN d").toArray(); + assertEqual(2, result.length); + assertEqual(0, result[0].c); + assertEqual(1, result[1].c); + } + { + let result = db._query("FOR d IN UnitTestsWithArrayView SEARCH [ 'foo', 'bar'] ANY NOT IN d.a OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(1, result.length); + assertEqual(1, result[0].c); + } + { + let result = db._query("FOR d IN UnitTestsWithArrayView SEARCH ['none', 'nani', 'afoo'] NONE IN d.a OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(1, result.length); + assertEqual(0, result[0].c); + } + { + let result = db._query("FOR d IN UnitTestsWithArrayView SEARCH ['none', 'nani', 'bar', 'afoo'] NONE IN d.a OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(0, result.length); + } + { + let result = db._query("FOR d IN UnitTestsWithArrayView SEARCH ['none', 'foo', 'bar'] NONE NOT IN d.a OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(0, result.length); + } + { + let result = db._query("FOR d IN UnitTestsWithArrayView SEARCH ['bar', 'foo'] NONE NOT IN d.a OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(1, result.length); + assertEqual(0, result[0].c); + } + }, + testArrayComparsionOperatorsGreaterOnSimpleField : function() { + { + let result = db._query("FOR d IN UnitTestsView SEARCH [2, 3, 4] ALL > d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(8, result.length); + result.forEach(function(doc) { + assertTrue(doc.c === 0 || doc.c === 1); + }); + } + { + let result = db._query("FOR a IN [[2, 3, 4]] FOR d IN UnitTestsView SEARCH a ALL > d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(8, result.length); + result.forEach(function(doc) { + assertTrue(doc.c === 0 || doc.c === 1); + }); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [2, 3, 4] ALL >= d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(12, result.length); + result.forEach(function(doc) { + assertTrue(doc.c === 0 || doc.c === 1 || doc.c === 2); + }); + } + { + let result = db._query("FOR a IN [[2, 3, 4]] FOR d IN UnitTestsView SEARCH a ALL >= d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(12, result.length); + result.forEach(function(doc) { + assertTrue(doc.c === 0 || doc.c === 1 || doc.c === 2); + }); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [] ALL > d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(28, result.length); + } + { + let result = db._query("FOR a IN [[]] FOR d IN UnitTestsView SEARCH a ALL > d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(28, result.length); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [] ALL >= d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(28, result.length); + } + { + let result = db._query("FOR a IN [[]] FOR d IN UnitTestsView SEARCH a ALL >= d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(28, result.length); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [1, 2] ANY > d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(8, result.length); + result.forEach(function(doc) { + assertTrue(doc.c < 2); + }); + } + { + let result = db._query("FOR a IN [[1, 2]] FOR d IN UnitTestsView SEARCH a ANY > d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(8, result.length); + result.forEach(function(doc) { + assertTrue(doc.c < 2); + }); + + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [1, 2] ANY >= d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(12, result.length); + result.forEach(function(doc) { + assertTrue(doc.c <= 2); + }); + } + { + let result = db._query("FOR a IN [[1, 2]] FOR d IN UnitTestsView SEARCH a ANY >= d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(12, result.length); + result.forEach(function(doc) { + assertTrue(doc.c <= 2); + }); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [] ANY > d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(0, result.length); + } + { + let result = db._query("FOR a IN [[]] FOR d IN UnitTestsView SEARCH a ANY > d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(0, result.length); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [] ANY >= d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(0, result.length); + } + { + let result = db._query("FOR a IN [[]] FOR d IN UnitTestsView SEARCH a ANY >= d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(0, result.length); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [1, 2, 3] NONE > d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(8, result.length); + result.forEach(function(doc) { + assertTrue(doc.c === 4 || doc.c === 3); + }); + } + { + let result = db._query("FOR a IN [[1, 2, 3]] FOR d IN UnitTestsView SEARCH a NONE > d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(8, result.length); + result.forEach(function(doc) { + assertTrue(doc.c === 4 || doc.c === 3); + }); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [1, 2, 3] NONE >= d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(4, result.length); + result.forEach(function(doc) { + assertTrue(doc.c === 4); + }); + } + { + let result = db._query("FOR a IN [[1, 2, 3]] FOR d IN UnitTestsView SEARCH a NONE >= d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(4, result.length); + result.forEach(function(doc) { + assertTrue(doc.c === 4); + }); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [] NONE > d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(28, result.length); + } + { + let result = db._query("FOR a IN [[]] FOR d IN UnitTestsView SEARCH a NONE > d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(28, result.length); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [] NONE >= d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(28, result.length); + } + { + let result = db._query("FOR a IN [[]] FOR d IN UnitTestsView SEARCH a NONE >= d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(28, result.length); + } + }, + testArrayComparsionOperatorsLessOnSimpleField : function() { + { + let result = db._query("FOR d IN UnitTestsView SEARCH [2, 3] ALL < d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(4, result.length); + result.forEach(function(doc) { + assertTrue(doc.c === 4); + }); + } + { + let result = db._query("FOR a IN [[2,3]] FOR d IN UnitTestsView SEARCH a ALL < d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(4, result.length); + result.forEach(function(doc) { + assertTrue(doc.c === 4); + }); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [2, 3] ALL <= d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(8, result.length); + result.forEach(function(doc) { + assertTrue(doc.c === 4 || doc.c === 3); + }); + } + { + let result = db._query("FOR a IN [[2,3]] FOR d IN UnitTestsView SEARCH a ALL <= d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(8, result.length); + result.forEach(function(doc) { + assertTrue(doc.c === 4 || doc.c === 3); + }); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [] ALL < d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(28, result.length); + } + { + let result = db._query("FOR a IN [[]] FOR d IN UnitTestsView SEARCH a ALL < d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(28, result.length); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [] ALL <= d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(28, result.length); + } + { + let result = db._query("FOR a IN [[]] FOR d IN UnitTestsView SEARCH a ALL <= d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(28, result.length); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [3, 4] ANY < d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(4, result.length); + result.forEach(function(doc) { + assertTrue(doc.c === 4); + }); + } + { + let result = db._query("FOR a IN [[3,4]] FOR d IN UnitTestsView SEARCH a ANY < d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(4, result.length); + result.forEach(function(doc) { + assertTrue(doc.c === 4); + }); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [3, 4] ANY <= d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(8, result.length); + result.forEach(function(doc) { + assertTrue(doc.c === 4 || doc.c === 3); + }); + } + { + let result = db._query("FOR a IN [[3,4]] FOR d IN UnitTestsView SEARCH a ANY <= d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(8, result.length); + result.forEach(function(doc) { + assertTrue(doc.c === 4 || doc.c === 3); + }); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [] ANY < d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(0, result.length); + } + { + let result = db._query("FOR a IN [[]] FOR d IN UnitTestsView SEARCH a ANY < d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(0, result.length); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [] ANY <= d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(0, result.length); + } + { + let result = db._query("FOR a IN [[]] FOR d IN UnitTestsView SEARCH a ANY <= d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(0, result.length); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [1, 2, 3] NONE < d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(8, result.length); + result.forEach(function(doc) { + assertTrue(doc.c === 1 || doc.c === 0); + }); + } + { + let result = db._query("FOR a IN [[1,2,3]] FOR d IN UnitTestsView SEARCH a NONE < d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(8, result.length); + result.forEach(function(doc) { + assertTrue(doc.c === 1 || doc.c === 0); + }); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [1, 2, 3] NONE <= d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(4, result.length); + result.forEach(function(doc) { + assertTrue( doc.c === 0); + }); + } + { + let result = db._query("FOR a IN [[1,2,3]] FOR d IN UnitTestsView SEARCH a NONE <= d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(4, result.length); + result.forEach(function(doc) { + assertTrue(doc.c === 0); + }); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [] NONE < d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(28, result.length); + } + { + let result = db._query("FOR a IN [[]] FOR d IN UnitTestsView SEARCH a NONE < d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(28, result.length); + } + { + let result = db._query("FOR d IN UnitTestsView SEARCH [] NONE <= d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(28, result.length); + } + { + let result = db._query("FOR a IN [[]] FOR d IN UnitTestsView SEARCH a NONE <= d.c OPTIONS { waitForSync : true } RETURN d").toArray(); + assertEqual(28, result.length); + } + }, }; }