1
0
Fork 0

[3.4] bug-fix/issue-#8294 (#8430) (#8585)

* [3.4] bug-fix/issue-#8294 (#8430)

* fix invalid optimizations for multi-valued attributes

* fix broken optimizations for multivalued attributes

* adjust tests

* add `IN_RANGE` function

* add tests

* add some shallow integration tests

* fix optimization for IN operator

# Conflicts:
#	arangod/IResearch/IResearchFeature.cpp
#	arangod/IResearch/IResearchViewOptimizerRules.cpp
#	tests/IResearch/IResearchFilterBoolean-test.cpp

* fix compilation errors

* fix another compilation issue

* address compilation errors

* fix tests
This commit is contained in:
Andrey Abramov 2019-03-27 18:33:16 +03:00 committed by GitHub
parent e4594b72a8
commit 930b09cd93
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 9960 additions and 158 deletions

View File

@ -113,8 +113,10 @@ struct PermutationState {
size_t const n;
};
} // namespace
//------------------------------------------------------------------------
// Rules for single-valued variables
//------------------------------------------------------------------------
// | | a == y | a != y | a < y | a <= y | a >= y | a > y
// -------|------------------|--------|--------|--------|--------|--------
// x < y | | IMP | OIS | OIS | OIS | IMP | IMP
@ -140,6 +142,7 @@ struct PermutationState {
// x < y | | SIO | DIJ | DIJ | DIJ | SIO | SIO
// x == y | a > x | IMP | OIS | IMP | IMP | OIS | OIS
// x > y | | IMP | OIS | IMP | IMP | OIS | OIS
//------------------------------------------------------------------------
// the 7th column is here as fallback if the operation is not in the table
// above.
// IMP -> IMPOSSIBLE -> empty result -> the complete AND set of conditions can
@ -155,7 +158,7 @@ struct PermutationState {
// larger than that of B
// -> A can be dropped.
ConditionPartCompareResult const ConditionPart::ResultsTable[3][7][7] = {
ConditionPartCompareResult const ResultsTable[3][7][7] = {
{// X < Y
{IMPOSSIBLE, OTHER_CONTAINED_IN_SELF, OTHER_CONTAINED_IN_SELF,
OTHER_CONTAINED_IN_SELF, IMPOSSIBLE, IMPOSSIBLE, DISJOINT},
@ -199,6 +202,96 @@ ConditionPartCompareResult const ConditionPart::ResultsTable[3][7][7] = {
OTHER_CONTAINED_IN_SELF, OTHER_CONTAINED_IN_SELF, DISJOINT},
{DISJOINT, DISJOINT, DISJOINT, DISJOINT, DISJOINT, DISJOINT, DISJOINT}}};
//------------------------------------------------------------------------
// Rules for multi-valued variables
//------------------------------------------------------------------------
// | | a == y | a != y | a < y | a <= y | a >= y | a > y
// -------|------------------|--------|--------|--------|--------|--------
// x < y | | DIJ | DIJ | OIS | OIS | DIJ | DIJ
// x == y | a == x | OIS | IMP | DIJ | OIS | OIS | DIJ
// x > y | | DIJ | DIJ | DIJ | DIJ | OIS | OIS
// -------|------------------|--------|--------|--------|--------|--------
// x < y | | DIJ | DIJ | DIJ | DIJ | DIJ | DIJ
// x == y | a != x | IMP | OIS | DIJ | DIJ | DIJ | DIJ
// x > y | | DIJ | DIJ | DIJ | DIJ | DIJ | DIJ
// -------|------------------|--------|--------|--------|--------|--------
// x < y | | DIJ | DIJ | OIS | OIS | DIJ | DIJ
// x == y | a < x | DIJ | DIJ | OIS | OIS | DIJ | DIJ
// x > y | | SIO | DIJ | SIO | SIO | DIJ | DIJ
// -------|------------------|--------|--------|--------|--------|--------
// x < y | | DIJ | DIJ | OIS | OIS | DIJ | DIJ
// x == y | a <= x | SIO | DIJ | SIO | OIS | DIJ | DIJ
// x > y | | SIO | DIJ | SIO | SIO | DIJ | DIJ
// -------|------------------|--------|--------|--------|--------|--------
// x < y | | SIO | DIJ | DIJ | DIJ | SIO | SIO
// x == y | a >= x | SIO | DIJ | DIJ | DIJ | OIS | SIO
// x > y | | DIJ | DIJ | DIJ | DIJ | OIS | OIS
// -------|------------------|--------|--------|--------|--------|--------
// x < y | | SIO | DIJ | DIJ | DIJ | SIO | SIO
// x == y | a > x | DIJ | DIJ | DIJ | DIJ | OIS | OIS
// x > y | | DIJ | DIJ | DIJ | DIJ | OIS | OIS
//------------------------------------------------------------------------
// the 7th column is here as fallback if the operation is not in the table
// above.
// IMP -> IMPOSSIBLE -> empty result -> the complete AND set of conditions can
// be dropped.
// CEQ -> CONVERT_EQUAL -> both conditions can be combined to a equals x.
// DIJ -> DISJOINT -> neither condition is a consequence of the other -> both
// have to stay in place.
// SIO -> SELF_CONTAINED_IN_OTHER -> the left condition is a consequence of the
// right condition
// OIS -> OTHER_CONTAINED_IN_SELF -> the right condition is a consequence of the
// left condition
// If a condition (A) is a consequence of another (B), the solution set of A is
// larger than that of B
// -> A can be dropped.
ConditionPartCompareResult const ResultsTableMultiValued[3][7][7] = {
{// X < Y
{DISJOINT, DISJOINT, OTHER_CONTAINED_IN_SELF,
OTHER_CONTAINED_IN_SELF, DISJOINT, DISJOINT, DISJOINT},
{DISJOINT, DISJOINT, DISJOINT, DISJOINT,
DISJOINT, DISJOINT, DISJOINT},
{DISJOINT, DISJOINT, OTHER_CONTAINED_IN_SELF,
OTHER_CONTAINED_IN_SELF, DISJOINT, DISJOINT, DISJOINT},
{DISJOINT, DISJOINT, OTHER_CONTAINED_IN_SELF,
OTHER_CONTAINED_IN_SELF, DISJOINT, DISJOINT, DISJOINT},
{SELF_CONTAINED_IN_OTHER, DISJOINT, DISJOINT, DISJOINT,
SELF_CONTAINED_IN_OTHER, SELF_CONTAINED_IN_OTHER, DISJOINT},
{SELF_CONTAINED_IN_OTHER, DISJOINT, DISJOINT, DISJOINT,
SELF_CONTAINED_IN_OTHER, SELF_CONTAINED_IN_OTHER, DISJOINT},
{DISJOINT, DISJOINT, DISJOINT, DISJOINT, DISJOINT, DISJOINT, DISJOINT}},
{// X == Y
{OTHER_CONTAINED_IN_SELF, IMPOSSIBLE, DISJOINT, OTHER_CONTAINED_IN_SELF,
OTHER_CONTAINED_IN_SELF, DISJOINT, DISJOINT},
{IMPOSSIBLE, OTHER_CONTAINED_IN_SELF, DISJOINT, DISJOINT,
DISJOINT, DISJOINT, DISJOINT},
{DISJOINT, DISJOINT, OTHER_CONTAINED_IN_SELF,
OTHER_CONTAINED_IN_SELF, DISJOINT, DISJOINT, DISJOINT},
{SELF_CONTAINED_IN_OTHER, DISJOINT, SELF_CONTAINED_IN_OTHER,
OTHER_CONTAINED_IN_SELF, DISJOINT, DISJOINT, DISJOINT},
{SELF_CONTAINED_IN_OTHER, DISJOINT, DISJOINT, DISJOINT,
OTHER_CONTAINED_IN_SELF, SELF_CONTAINED_IN_OTHER, DISJOINT},
{DISJOINT, DISJOINT, DISJOINT, DISJOINT,
OTHER_CONTAINED_IN_SELF, OTHER_CONTAINED_IN_SELF, DISJOINT},
{DISJOINT, DISJOINT, DISJOINT, DISJOINT, DISJOINT, DISJOINT, DISJOINT}},
{// X > Y
{DISJOINT, DISJOINT, DISJOINT, DISJOINT,
OTHER_CONTAINED_IN_SELF, OTHER_CONTAINED_IN_SELF, DISJOINT},
{DISJOINT, DISJOINT, DISJOINT,
DISJOINT, DISJOINT, DISJOINT, DISJOINT},
{SELF_CONTAINED_IN_OTHER, DISJOINT, SELF_CONTAINED_IN_OTHER,
SELF_CONTAINED_IN_OTHER, DISJOINT, DISJOINT, DISJOINT},
{SELF_CONTAINED_IN_OTHER, DISJOINT, SELF_CONTAINED_IN_OTHER,
SELF_CONTAINED_IN_OTHER, DISJOINT, DISJOINT, DISJOINT},
{DISJOINT, DISJOINT, DISJOINT, DISJOINT,
OTHER_CONTAINED_IN_SELF, OTHER_CONTAINED_IN_SELF, DISJOINT},
{DISJOINT, DISJOINT, DISJOINT, DISJOINT,
OTHER_CONTAINED_IN_SELF, OTHER_CONTAINED_IN_SELF, DISJOINT},
{DISJOINT, DISJOINT, DISJOINT, DISJOINT, DISJOINT, DISJOINT, DISJOINT}}};
} // namespace
ConditionPart::ConditionPart(Variable const* variable, std::string const& attributeName,
AstNode const* operatorNode, AttributeSideType side, void* data)
: variable(variable),
@ -271,7 +364,7 @@ bool ConditionPart::isCoveredBy(ConditionPart const& other, bool isReversed) con
auto w = other.valueNode->getMemberUnchecked(j);
ConditionPartCompareResult res =
ConditionPart::ResultsTable[CompareAstNodes(v, w, true) + 1][0][0];
ResultsTable[CompareAstNodes(v, w, true) + 1][0][0];
if (res != CompareResult::OTHER_CONTAINED_IN_SELF &&
res != CompareResult::CONVERT_EQUAL && res != CompareResult::IMPOSSIBLE) {
@ -333,8 +426,8 @@ bool ConditionPart::isCoveredBy(ConditionPart const& other, bool isReversed) con
// Results are -1, 0, 1, move to 0, 1, 2 for the lookup:
ConditionPartCompareResult res =
ConditionPart::ResultsTable[CompareAstNodes(other.valueNode, valueNode, true) + 1]
[other.whichCompareOperation()][whichCompareOperation()];
ResultsTable[CompareAstNodes(other.valueNode, valueNode, true) + 1]
[other.whichCompareOperation()][whichCompareOperation()];
if (res == CompareResult::OTHER_CONTAINED_IN_SELF ||
res == CompareResult::CONVERT_EQUAL || res == CompareResult::IMPOSSIBLE) {
@ -521,7 +614,7 @@ std::vector<std::vector<arangodb::basics::AttributeName>> Condition::getConstAtt
/// @brief normalize the condition
/// this will convert the condition into its disjunctive normal form
void Condition::normalize(ExecutionPlan* plan) {
void Condition::normalize(ExecutionPlan* plan, bool multivalued /*= false*/) {
if (_isNormalized) {
// already normalized
return;
@ -531,7 +624,7 @@ void Condition::normalize(ExecutionPlan* plan) {
_root = transformNodePostorder(_root);
_root = fixRoot(_root, 0);
optimize(plan);
optimize(plan, multivalued);
#ifdef ARANGODB_ENABLE_MAINTAINER_MODE
if (_root != nullptr) {
@ -802,7 +895,7 @@ bool Condition::removeInvalidVariables(arangodb::HashSet<Variable const*> const&
}
/// @brief optimize the condition expression tree
void Condition::optimize(ExecutionPlan* plan) {
void Condition::optimize(ExecutionPlan* plan, bool multivalued) {
if (_root == nullptr) {
return;
}
@ -822,6 +915,10 @@ void Condition::optimize(ExecutionPlan* plan) {
size_t n = _root->numMembers();
size_t r = 0;
const auto* resultsTable = multivalued
? ResultsTableMultiValued
: ResultsTable;
while (r < n) { // foreach OR-Node
bool retry = false;
auto oldAnd = _root->getMemberUnchecked(r);
@ -981,7 +1078,8 @@ void Condition::optimize(ExecutionPlan* plan) {
// IN-merging
if (leftNode->type == NODE_TYPE_OPERATOR_BINARY_IN &&
leftNode->getMemberUnchecked(1)->isConstant()) {
leftNode->getMemberUnchecked(1)->isConstant() &&
!multivalued) {
TRI_ASSERT(leftNode->numMembers() == 2);
if (rightNode->type == NODE_TYPE_OPERATOR_BINARY_IN &&
@ -1007,8 +1105,8 @@ void Condition::optimize(ExecutionPlan* plan) {
for (size_t k = 0; k < values->numMembers(); ++k) {
auto value = values->getMemberUnchecked(k);
ConditionPartCompareResult res =
ConditionPart::ResultsTable[CompareAstNodes(value, other.valueNode, true) + 1][0 /*NODE_TYPE_OPERATOR_BINARY_EQ*/]
[other.whichCompareOperation()];
ResultsTable[CompareAstNodes(value, other.valueNode, true) + 1][0 /*NODE_TYPE_OPERATOR_BINARY_EQ*/]
[other.whichCompareOperation()];
bool const keep = (res == CompareResult::OTHER_CONTAINED_IN_SELF ||
res == CompareResult::CONVERT_EQUAL);
@ -1036,7 +1134,7 @@ void Condition::optimize(ExecutionPlan* plan) {
// end of IN-merging
// Results are -1, 0, 1, move to 0, 1, 2 for the lookup:
ConditionPartCompareResult res = ConditionPart::ResultsTable
ConditionPartCompareResult res = resultsTable
[CompareAstNodes(current.valueNode, other.valueNode, true) + 1]
[current.whichCompareOperation()][other.whichCompareOperation()];

View File

@ -56,8 +56,6 @@ enum ConditionPartCompareResult {
enum AttributeSideType { ATTRIBUTE_LEFT, ATTRIBUTE_RIGHT };
struct ConditionPart {
static ConditionPartCompareResult const ResultsTable[3][7][7];
ConditionPart() = delete;
ConditionPart(Variable const*, std::string const&, AstNode const*,
@ -215,7 +213,9 @@ class Condition {
/// @brief normalize the condition
/// this will convert the condition into its disjunctive normal form
void normalize(ExecutionPlan*);
/// @param mutlivalued attributes may have more than one value
/// (ArangoSearch view case)
void normalize(ExecutionPlan*, bool multivalued = false);
/// @brief normalize the condition
/// this will convert the condition into its disjunctive normal form
@ -251,7 +251,7 @@ class Condition {
bool sortOrs(Variable const*, std::vector<Index const*>&);
/// @brief optimize the condition expression tree
void optimize(ExecutionPlan*);
void optimize(ExecutionPlan*, bool multivalued);
/// @brief registers an attribute access for a particular (collection)
/// variable

View File

@ -137,7 +137,7 @@ arangodb::aql::AqlValue dummyFilterFunc(arangodb::aql::ExpressionContext*,
arangodb::SmallVector<arangodb::aql::AqlValue> const&) {
THROW_ARANGO_EXCEPTION_MESSAGE(
TRI_ERROR_NOT_IMPLEMENTED,
"ArangoSearch filter functions EXISTS, STARTS_WITH, PHRASE, MIN_MATCH, "
"ArangoSearch filter functions EXISTS, STARTS_WITH, IN_RANGE, PHRASE, MIN_MATCH, "
"BOOST and ANALYZER "
" are designed to be used only within a corresponding SEARCH statement "
"of ArangoSearch view."
@ -346,15 +346,11 @@ void registerFilters(arangodb::aql::AqlFunctionFeature& functions) {
arangodb::aql::Function::makeFlags(arangodb::aql::Function::Flags::Deterministic,
arangodb::aql::Function::Flags::Cacheable,
arangodb::aql::Function::Flags::CanRunOnDBServer);
addFunction(functions, {"EXISTS", ".|.,.", flags, &dummyFilterFunc}); // (attribute, [
// "analyzer"|"type"|"string"|"numeric"|"bool"|"null"
// ])
addFunction(functions, {"EXISTS", ".|.,.", flags, &dummyFilterFunc}); // (attribute, [ // "analyzer"|"type"|"string"|"numeric"|"bool"|"null" // ])
addFunction(functions, {"STARTS_WITH", ".,.|.", flags, &dummyFilterFunc}); // (attribute, prefix, scoring-limit)
addFunction(functions, {"PHRASE", ".,.|.+", flags,
&dummyFilterFunc}); // (attribute, input [, offset,
// input... ] [, analyzer])
addFunction(functions, {"MIN_MATCH", ".,.|.+", flags, &dummyFilterFunc}); // (filter expression [, filter expression,
// ... ], min match count)
addFunction(functions, {"PHRASE", ".,.|.+", flags, &dummyFilterFunc}); // (attribute, input [, offset, input... ] [, analyzer])
addFunction(functions, {"IN_RANGE", ".,.,.,.,.", flags, &dummyFilterFunc}); // (attribute, lower, upper, include lower, include upper)
addFunction(functions, {"MIN_MATCH", ".,.|.+", flags, &dummyFilterFunc}); // (filter expression [, filter expression, ... ], min match count)
addFunction(functions, {"BOOST", ".,.", flags, &dummyFilterFunc}); // (filter expression, boost)
addFunction(functions, {"ANALYZER", ".,.", flags, &dummyFilterFunc}); // (filter expression, analyzer)
}
@ -1097,4 +1093,4 @@ void IResearchFeature::validateOptions(std::shared_ptr<arangodb::options::Progra
// -----------------------------------------------------------------------------
// --SECTION-- END-OF-FILE
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------

View File

@ -353,7 +353,10 @@ bool byRange(irs::boolean_filter* filter, arangodb::aql::AstNode const& attribut
}
if (!min.execute(ctx)) {
// failed to execute expression
LOG_TOPIC("1e23d", WARN, arangodb::iresearch::TOPIC)
<< "Failed to evaluate lower boundary from node '"
<< arangodb::aql::AstNode::toString(&minValueNode)
<< "'";
return false;
}
}
@ -367,13 +370,18 @@ bool byRange(irs::boolean_filter* filter, arangodb::aql::AstNode const& attribut
}
if (!max.execute(ctx)) {
// failed to execute expression
LOG_TOPIC("5a0a4", WARN, arangodb::iresearch::TOPIC)
<< "Failed to evaluate upper boundary from node '"
<< arangodb::aql::AstNode::toString(&maxValueNode)
<< "'";
return false;
}
}
if (min.type() != max.type()) {
// type mismatch
LOG_TOPIC("03078", WARN, arangodb::iresearch::TOPIC)
<< "Failed to build range query, lower boundary '" << arangodb::aql::AstNode::toString(&minValueNode)
<< "' mismatches upper boundary '" << arangodb::aql::AstNode::toString(&maxValueNode) << "'";
return false;
}
@ -396,10 +404,8 @@ bool byRange(irs::boolean_filter* filter, arangodb::aql::AstNode const& attribut
range.boost(filterCtx.boost);
range.term<irs::Bound::MIN>(irs::null_token_stream::value_null());
range.include<irs::Bound::MIN>(minInclude);
;
range.term<irs::Bound::MAX>(irs::null_token_stream::value_null());
range.include<irs::Bound::MAX>(maxInclude);
;
}
return true;
@ -981,6 +987,7 @@ bool fromNegation(irs::boolean_filter* filter, QueryContext const& ctx,
return ::filter(filter, ctx, subFilterCtx, *member);
}
/*
bool rangeFromBinaryAnd(irs::boolean_filter* filter, QueryContext const& ctx,
FilterContext const& filterCtx,
arangodb::aql::AstNode const& node) {
@ -1029,6 +1036,7 @@ bool rangeFromBinaryAnd(irs::boolean_filter* filter, QueryContext const& ctx,
// unable to create range
return false;
}
*/
template <typename Filter>
bool fromGroup(irs::boolean_filter* filter, QueryContext const& ctx,
@ -1048,12 +1056,6 @@ bool fromGroup(irs::boolean_filter* filter, QueryContext const& ctx,
// Note: cannot optimize for single member in AND/OR since 'a OR NOT b'
// translates to 'a OR (OR NOT b)'
if (std::is_same<Filter, irs::And>::value && 2 == n &&
rangeFromBinaryAnd(filter, ctx, filterCtx, node)) {
// range case
return true;
}
if (filter) {
filter = &filter->add<Filter>();
filter->boost(filterCtx.boost);
@ -1821,6 +1823,103 @@ bool fromFuncStartsWith(irs::boolean_filter* filter, QueryContext const& ctx,
return true;
}
// IN_RANGE(<attribute>, <low>, <high>, <include-low>, <include-high>)
bool fromFuncInRange(irs::boolean_filter* filter, QueryContext const& ctx,
FilterContext const& filterCtx,
arangodb::aql::AstNode const& args) {
if (!args.isDeterministic()) {
LOG_TOPIC("dff45", WARN, arangodb::iresearch::TOPIC)
<< "Unable to handle non-deterministic arguments for 'IN_RANGE' "
"function";
return false; // nondeterministic
}
auto const argc = args.numMembers();
if (argc != 5) {
LOG_TOPIC("2f5a8", WARN, arangodb::iresearch::TOPIC)
<< "'IN_RANGE' AQL function: Invalid number of arguments passed "
"(should be 5)";
return false;
}
// 1st argument defines a field
auto const* field =
arangodb::iresearch::checkAttributeAccess(args.getMemberUnchecked(0), *ctx.ref);
if (!field) {
LOG_TOPIC("7c56a", WARN, arangodb::iresearch::TOPIC)
<< "'IN_RANGE' AQL function: Unable to parse 1st argument as an "
"attribute identifier";
return false;
}
ScopedAqlValue includeValue;
auto getInclusion = [&ctx, filter, &includeValue](
arangodb::aql::AstNode const* arg,
bool& include,
irs::string_ref const& argName) -> bool {
if (!arg) {
LOG_TOPIC("8ec00", WARN, arangodb::iresearch::TOPIC)
<< "'IN_RANGE' AQL function: " << argName << " argument is invalid";
return false;
}
includeValue.reset(*arg);
if (filter || includeValue.isConstant()) {
if (!includeValue.execute(ctx)) {
LOG_TOPIC("32f3b", WARN, arangodb::iresearch::TOPIC)
<< "'IN_RANGE' AQL function: Failed to evaluate " << argName << " argument";
return false;
}
if (arangodb::iresearch::SCOPED_VALUE_TYPE_BOOL != includeValue.type()) {
LOG_TOPIC("57a29", WARN, arangodb::iresearch::TOPIC)
<< "'IN_RANGE' AQL function: " << argName << " argument has invalid type '"
<< includeValue.type() << "' (boolean expected)";
return false;
}
include = includeValue.getBoolean();
}
return true;
};
// 2nd argument defines a lower boundary
auto const* lhsArg = args.getMemberUnchecked(1);
if (!lhsArg) {
LOG_TOPIC("f1167", WARN, arangodb::iresearch::TOPIC)
<< "'IN_RANGE' AQL function: 2nd argument is invalid";
return false;
}
// 3rd argument defines an upper boundary
auto const* rhsArg = args.getMemberUnchecked(2);
if (!rhsArg) {
LOG_TOPIC("d5fe6", WARN, arangodb::iresearch::TOPIC)
<< "'IN_RANGE' AQL function: 3rd argument is invalid";
return false;
}
// 4th argument defines inclusion of lower boundary
bool lhsInclude = false;
if (!getInclusion(args.getMemberUnchecked(3), lhsInclude, "4th")) {
return false;
}
// 5th argument defines inclusion of upper boundary
bool rhsInclude = false;
if (!getInclusion(args.getMemberUnchecked(4), rhsInclude, "5th")) {
return false;
}
return byRange(filter, *field, *lhsArg, lhsInclude, *rhsArg, rhsInclude, ctx, filterCtx);
}
std::map<irs::string_ref, ConvertionHandler> const FCallUserConvertionHandlers;
bool fromFCallUser(irs::boolean_filter* filter, QueryContext const& ctx,
@ -1868,7 +1967,9 @@ bool fromFCallUser(irs::boolean_filter* filter, QueryContext const& ctx,
std::map<std::string, ConvertionHandler> const FCallSystemConvertionHandlers{
{"PHRASE", fromFuncPhrase}, {"STARTS_WITH", fromFuncStartsWith},
{"EXISTS", fromFuncExists}, {"BOOST", fromFuncBoost},
{"ANALYZER", fromFuncAnalyzer}, {"MIN_MATCH", fromFuncMinMatch}};
{"ANALYZER", fromFuncAnalyzer}, {"MIN_MATCH", fromFuncMinMatch},
{"IN_RANGE", fromFuncInRange}
};
bool fromFCall(irs::boolean_filter* filter, QueryContext const& ctx,
FilterContext const& filterCtx, arangodb::aql::AstNode const& node) {
@ -1987,4 +2088,4 @@ namespace iresearch {
// -----------------------------------------------------------------------------
// --SECTION-- END-OF-FILE
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------

View File

@ -177,6 +177,11 @@ class IResearchView final: public arangodb::LogicalView {
///////////////////////////////////////////////////////////////////////////////
bool visitCollections(CollectionVisitor const& visitor) const override;
//////////////////////////////////////////////////////////////////////////////
/// @brief persist data store states for all known links to permanent storage
//////////////////////////////////////////////////////////////////////////////
arangodb::Result commit();
protected:
//////////////////////////////////////////////////////////////////////////////
@ -219,11 +224,6 @@ class IResearchView final: public arangodb::LogicalView {
IResearchViewMeta&& meta // view meta
);
//////////////////////////////////////////////////////////////////////////////
/// @brief persist data store states for all known links to permanent storage
//////////////////////////////////////////////////////////////////////////////
arangodb::Result commit();
//////////////////////////////////////////////////////////////////////////////
/// @brief called when a view's properties are updated (i.e. delta-modified)
//////////////////////////////////////////////////////////////////////////////
@ -241,4 +241,4 @@ class IResearchView final: public arangodb::LogicalView {
} // iresearch
} // arangodb
#endif
#endif

View File

@ -80,7 +80,7 @@ bool optimizeSearchCondition(IResearchViewNode& viewNode, Query& query, Executio
if (!viewNode.filterConditionIsEmpty()) {
searchCondition.andCombine(&viewNode.filterCondition());
searchCondition.normalize(&plan); // normalize the condition
searchCondition.normalize(&plan, true); // normalize the condition
if (searchCondition.isEmpty()) {
// condition is always false
@ -102,11 +102,11 @@ bool optimizeSearchCondition(IResearchViewNode& viewNode, Query& query, Executio
}
// check filter condition
auto const conditionValid =
!searchCondition.root() ||
FilterFactory::filter(nullptr,
{ query.trx(), nullptr, nullptr, nullptr, &viewNode.outVariable() },
*searchCondition.root());
auto const conditionValid = !searchCondition.root() || FilterFactory::filter(
nullptr,
{ query.trx(), nullptr, nullptr, nullptr, &viewNode.outVariable() },
*searchCondition.root()
);
if (!conditionValid) {
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_QUERY_PARSE,
@ -308,4 +308,4 @@ void scatterViewInClusterRule(arangodb::aql::Optimizer* opt,
// -----------------------------------------------------------------------------
// --SECTION-- END-OF-FILE
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------

View File

@ -47,10 +47,12 @@ if (USE_IRESEARCH)
IResearch/IResearchQueryNullTerm-test.cpp
IResearch/IResearchQueryNumericTerm-test.cpp
IResearch/IResearchQueryOr-test.cpp
IResearch/IResearchQueryOptimization-test.cpp
IResearch/IResearchQueryPhrase-test.cpp
IResearch/IResearchQueryScorer-test.cpp
IResearch/IResearchQuerySelectAll-test.cpp
IResearch/IResearchQueryStartsWith-test.cpp
IResearch/IResearchQueryInRange-test.cpp
IResearch/IResearchQueryStringTerm-test.cpp
IResearch/IResearchQueryTokens-test.cpp
IResearch/IResearchQueryValue-test.cpp

View File

@ -2160,9 +2160,12 @@ SECTION("BinaryAnd") {
irs::numeric_token_stream maxTerm; maxTerm.reset(40.);
irs::Or expected;
auto& range = expected.add<irs::by_granular_range>();
range.field(mangleNumeric("a.b.c"))
.include<irs::Bound::MIN>(false).insert<irs::Bound::MIN>(minTerm)
auto& root = expected.add<irs::And>();
root.add<irs::by_granular_range>()
.field(mangleNumeric("a.b.c"))
.include<irs::Bound::MIN>(false).insert<irs::Bound::MIN>(minTerm);
root.add<irs::by_granular_range>()
.field(mangleNumeric("a.b.c"))
.include<irs::Bound::MAX>(false).insert<irs::Bound::MAX>(maxTerm);
assertFilterSuccess("FOR d IN collection FILTER d.a.b.c > 15 and d.a.b.c < 40 RETURN d", expected);
@ -2188,11 +2191,16 @@ SECTION("BinaryAnd") {
irs::numeric_token_stream maxTerm; maxTerm.reset(40.);
irs::Or expected;
auto& range = expected.add<irs::by_granular_range>();
range.boost(1.5);
range.field(mangleNumeric("a.b.c"))
.include<irs::Bound::MIN>(false).insert<irs::Bound::MIN>(minTerm)
.include<irs::Bound::MAX>(false).insert<irs::Bound::MAX>(maxTerm);
auto& root = expected.add<irs::And>();
root.boost(1.5);
root.add<irs::by_granular_range>()
.field(mangleNumeric("a.b.c"))
.include<irs::Bound::MIN>(false)
.insert<irs::Bound::MIN>(minTerm);
root.add<irs::by_granular_range>()
.field(mangleNumeric("a.b.c"))
.include<irs::Bound::MAX>(false)
.insert<irs::Bound::MAX>(maxTerm);
assertFilterSuccess("FOR d IN collection FILTER boost(d.a.b.c > 15 and d.a.b.c < 40, 1.5) RETURN d", expected);
}
@ -2445,10 +2453,15 @@ SECTION("BinaryAnd") {
irs::numeric_token_stream maxTerm; maxTerm.reset(40.);
irs::Or expected;
auto& range = expected.add<irs::by_granular_range>();
range.field(mangleNumeric("a.b[42].c"))
.include<irs::Bound::MIN>(false).insert<irs::Bound::MIN>(minTerm)
.include<irs::Bound::MAX>(false).insert<irs::Bound::MAX>(maxTerm);
auto& root = expected.add<irs::And>();
root.add<irs::by_granular_range>()
.field(mangleNumeric("a.b[42].c"))
.include<irs::Bound::MIN>(false)
.insert<irs::Bound::MIN>(minTerm);
root.add<irs::by_granular_range>()
.field(mangleNumeric("a.b[42].c"))
.include<irs::Bound::MAX>(false)
.insert<irs::Bound::MAX>(maxTerm);
assertFilterSuccess("FOR d IN collection FILTER d.a.b[42].c > 15 and d.a.b[42].c < 40 RETURN d", expected);
assertFilterSuccess("FOR d IN collection FILTER d['a'].b[42].c > 15 and d['a']['b'][42]['c'] < 40 RETURN d", expected);
@ -2473,10 +2486,15 @@ SECTION("BinaryAnd") {
irs::numeric_token_stream maxTerm; maxTerm.reset(40.);
irs::Or expected;
auto& range = expected.add<irs::by_granular_range>();
range.field(mangleNumeric("a.b.c"))
.include<irs::Bound::MIN>(true).insert<irs::Bound::MIN>(minTerm)
.include<irs::Bound::MAX>(false).insert<irs::Bound::MAX>(maxTerm);
auto& root = expected.add<irs::And>();
root.add<irs::by_granular_range>()
.field(mangleNumeric("a.b.c"))
.include<irs::Bound::MIN>(true)
.insert<irs::Bound::MIN>(minTerm);
root.add<irs::by_granular_range>()
.field(mangleNumeric("a.b.c"))
.include<irs::Bound::MAX>(false)
.insert<irs::Bound::MAX>(maxTerm);
assertFilterSuccess("FOR d IN collection FILTER d.a.b.c >= 15 and d.a.b.c < 40 RETURN d", expected);
assertFilterSuccess("FOR d IN collection FILTER d.a['b']['c'] >= 15 and d['a']['b']['c'] < 40 RETURN d", expected);
@ -2497,10 +2515,15 @@ SECTION("BinaryAnd") {
irs::numeric_token_stream maxTerm; maxTerm.reset(40.);
irs::Or expected;
auto& range = expected.add<irs::by_granular_range>();
range.field(mangleNumeric("a.b.c"))
.include<irs::Bound::MIN>(true).insert<irs::Bound::MIN>(minTerm)
.include<irs::Bound::MAX>(true).insert<irs::Bound::MAX>(maxTerm);
auto& root = expected.add<irs::And>();
root.add<irs::by_granular_range>()
.field(mangleNumeric("a.b.c"))
.include<irs::Bound::MIN>(true)
.insert<irs::Bound::MIN>(minTerm);
root.add<irs::by_granular_range>()
.field(mangleNumeric("a.b.c"))
.include<irs::Bound::MAX>(true)
.insert<irs::Bound::MAX>(maxTerm);
assertFilterSuccess("FOR d IN collection FILTER d.a.b.c >= 15 and d.a.b.c <= 40 RETURN d", expected);
assertFilterSuccess("FOR d IN collection FILTER d.a['b']['c'] >= 15 and d.a.b.c <= 40 RETURN d", expected);
@ -2612,10 +2635,15 @@ SECTION("BinaryAnd") {
irs::numeric_token_stream maxTerm; maxTerm.reset(40.);
irs::Or expected;
auto& range = expected.add<irs::by_granular_range>();
range.field(mangleNumeric("a.b.c"))
.include<irs::Bound::MIN>(false).insert<irs::Bound::MIN>(minTerm)
.include<irs::Bound::MAX>(true).insert<irs::Bound::MAX>(maxTerm);
auto& root = expected.add<irs::And>();
root.add<irs::by_granular_range>()
.field(mangleNumeric("a.b.c"))
.include<irs::Bound::MIN>(false)
.insert<irs::Bound::MIN>(minTerm);
root.add<irs::by_granular_range>()
.field(mangleNumeric("a.b.c"))
.include<irs::Bound::MAX>(true)
.insert<irs::Bound::MAX>(maxTerm);
assertFilterSuccess("FOR d IN collection FILTER d.a.b.c > 15 and d.a.b.c <= 40 RETURN d", expected);
assertFilterSuccess("FOR d IN collection FILTER d['a'].b.c > 15 and d.a.b.c <= 40 RETURN d", expected);
@ -2733,10 +2761,15 @@ SECTION("BinaryAnd") {
irs::numeric_token_stream maxTerm; maxTerm.reset(40.);
irs::Or expected;
auto& range = expected.add<irs::by_granular_range>();
range.field(mangleNumeric("a.b.c.e[4].f[5].g[3].g.a"))
.include<irs::Bound::MIN>(false).insert<irs::Bound::MIN>(minTerm)
.include<irs::Bound::MAX>(true).insert<irs::Bound::MAX>(maxTerm);
auto& root = expected.add<irs::And>();
root.add<irs::by_granular_range>()
.field(mangleNumeric("a.b.c.e[4].f[5].g[3].g.a"))
.include<irs::Bound::MIN>(false)
.insert<irs::Bound::MIN>(minTerm);
root.add<irs::by_granular_range>()
.field(mangleNumeric("a.b.c.e[4].f[5].g[3].g.a"))
.include<irs::Bound::MAX>(true)
.insert<irs::Bound::MAX>(maxTerm);
assertFilterSuccess("LET a='a' LET c='c' LET offsetInt=4 LET offsetDbl=5.6 FOR d IN collection FILTER d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_FORWARD_('a')] > 15 && d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_FORWARD_('a')] <= 40 RETURN d", expected, &ctx);
assertFilterSuccess("LET a='a' LET c='c' LET offsetInt=4 LET offsetDbl=5.6 FOR d IN collection FILTER 15 < d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_FORWARD_('a')] && 40 >= d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_FORWARD_('a')] RETURN d", expected, &ctx);
@ -2777,10 +2810,15 @@ SECTION("BinaryAnd") {
// string range
{
irs::Or expected;
auto& range = expected.add<irs::by_range>();
range.field(mangleStringIdentity("a.b.c"))
.include<irs::Bound::MIN>(false).term<irs::Bound::MIN>("15")
.include<irs::Bound::MAX>(false).term<irs::Bound::MAX>("40");
auto& root = expected.add<irs::And>();
root.add<irs::by_range>()
.field(mangleStringIdentity("a.b.c"))
.include<irs::Bound::MIN>(false)
.term<irs::Bound::MIN>("15");
root.add<irs::by_range>()
.field(mangleStringIdentity("a.b.c"))
.include<irs::Bound::MAX>(false)
.term<irs::Bound::MAX>("40");
assertFilterSuccess("FOR d IN collection FILTER d.a.b.c > '15' and d.a.b.c < '40' RETURN d", expected);
assertFilterSuccess("FOR d IN collection FILTER d['a']['b']['c'] > '15' and d.a.b.c < '40' RETURN d", expected);
@ -2795,10 +2833,15 @@ SECTION("BinaryAnd") {
// string range
{
irs::Or expected;
auto& range = expected.add<irs::by_range>();
range.field(mangleStringIdentity("a.b.c"))
.include<irs::Bound::MIN>(true).term<irs::Bound::MIN>("15")
.include<irs::Bound::MAX>(false).term<irs::Bound::MAX>("40");
auto& root = expected.add<irs::And>();
root.add<irs::by_range>()
.field(mangleStringIdentity("a.b.c"))
.include<irs::Bound::MIN>(true)
.term<irs::Bound::MIN>("15");
root.add<irs::by_range>()
.field(mangleStringIdentity("a.b.c"))
.include<irs::Bound::MAX>(false)
.term<irs::Bound::MAX>("40");
assertFilterSuccess("FOR d IN collection FILTER d.a.b.c >= '15' and d.a.b.c < '40' RETURN d", expected);
assertFilterSuccess("FOR d IN collection FILTER d['a']['b'].c >= '15' and d['a']['b']['c'] < '40' RETURN d", expected);
@ -2813,11 +2856,16 @@ SECTION("BinaryAnd") {
// string range, boost, analyzer
{
irs::Or expected;
auto& range = expected.add<irs::by_range>();
range.boost(0.5);
range.field(mangleString("a.b.c", "testVocbase::test_analyzer"))
.include<irs::Bound::MIN>(true).term<irs::Bound::MIN>("15")
.include<irs::Bound::MAX>(false).term<irs::Bound::MAX>("40");
auto& root = expected.add<irs::And>();
root.boost(0.5);
root.add<irs::by_range>()
.field(mangleString("a.b.c", "testVocbase::test_analyzer"))
.include<irs::Bound::MIN>(true)
.term<irs::Bound::MIN>("15");
root.add<irs::by_range>()
.field(mangleString("a.b.c", "testVocbase::test_analyzer"))
.include<irs::Bound::MAX>(false)
.term<irs::Bound::MAX>("40");
assertFilterSuccess("FOR d IN collection FILTER analyzer(boost(d.a.b.c >= '15' and d.a.b.c < '40', 0.5), 'test_analyzer') RETURN d", expected);
assertFilterSuccess("FOR d IN collection FILTER boost(analyzer(d['a']['b'].c >= '15' and d['a']['b']['c'] < '40', 'test_analyzer'), 0.5) RETURN d", expected);
@ -2826,10 +2874,15 @@ SECTION("BinaryAnd") {
// string range
{
irs::Or expected;
auto& range = expected.add<irs::by_range>();
range.field(mangleStringIdentity("a.b.c"))
.include<irs::Bound::MIN>(true).term<irs::Bound::MIN>("15")
.include<irs::Bound::MAX>(true).term<irs::Bound::MAX>("40");
auto& root = expected.add<irs::And>();
root.add<irs::by_range>()
.field(mangleStringIdentity("a.b.c"))
.include<irs::Bound::MIN>(true)
.term<irs::Bound::MIN>("15");
root.add<irs::by_range>()
.field(mangleStringIdentity("a.b.c"))
.include<irs::Bound::MAX>(true)
.term<irs::Bound::MAX>("40");
assertFilterSuccess("FOR d IN collection FILTER d.a.b.c >= '15' and d.a.b.c <= '40' RETURN d", expected);
assertFilterSuccess("FOR d IN collection FILTER d['a']['b']['c'] >= '15' and d.a.b.c <= '40' RETURN d", expected);
@ -2896,10 +2949,15 @@ SECTION("BinaryAnd") {
// string range
{
irs::Or expected;
auto& range = expected.add<irs::by_range>();
range.field(mangleStringIdentity("a.b.c"))
.include<irs::Bound::MIN>(false).term<irs::Bound::MIN>("15")
.include<irs::Bound::MAX>(true).term<irs::Bound::MAX>("40");
auto& root = expected.add<irs::And>();
root.add<irs::by_range>()
.field(mangleStringIdentity("a.b.c"))
.include<irs::Bound::MIN>(false)
.term<irs::Bound::MIN>("15");
root.add<irs::by_range>()
.field(mangleStringIdentity("a.b.c"))
.include<irs::Bound::MAX>(true)
.term<irs::Bound::MAX>("40");
assertFilterSuccess("FOR d IN collection FILTER d.a.b.c > '15' and d.a.b.c <= '40' RETURN d", expected);
assertFilterSuccess("FOR d IN collection FILTER d.a.b.c > '15' and d.a.b.c <= '40' RETURN d", expected);
@ -2917,10 +2975,15 @@ SECTION("BinaryAnd") {
ctx.vars.emplace("numVal", arangodb::aql::AqlValue(arangodb::aql::AqlValueHintInt(2)));
irs::Or expected;
auto& range = expected.add<irs::by_range>();
range.field(mangleStringIdentity("a.b.c.e.f"))
.include<irs::Bound::MIN>(false).term<irs::Bound::MIN>("15")
.include<irs::Bound::MAX>(true).term<irs::Bound::MAX>("40");
auto& root = expected.add<irs::And>();
root.add<irs::by_range>()
.field(mangleStringIdentity("a.b.c.e.f"))
.include<irs::Bound::MIN>(false)
.term<irs::Bound::MIN>("15");
root.add<irs::by_range>()
.field(mangleStringIdentity("a.b.c.e.f"))
.include<irs::Bound::MAX>(true)
.term<irs::Bound::MAX>("40");
assertFilterSuccess(
"LET numVal=2 FOR d IN collection FILTER d.a.b.c.e.f > TO_STRING(numVal+13) && d.a.b.c.e.f <= TO_STRING(numVal+38) RETURN d",
@ -2941,11 +3004,16 @@ SECTION("BinaryAnd") {
ctx.vars.emplace("numVal", arangodb::aql::AqlValue(arangodb::aql::AqlValueHintInt(2)));
irs::Or expected;
auto& range = expected.add<irs::by_range>();
range.boost(2.f);
range.field(mangleString("a.b.c.e.f", "testVocbase::test_analyzer"))
.include<irs::Bound::MIN>(false).term<irs::Bound::MIN>("15")
.include<irs::Bound::MAX>(true).term<irs::Bound::MAX>("40");
auto& root = expected.add<irs::And>();
root.boost(2.f);
root.add<irs::by_range>()
.field(mangleString("a.b.c.e.f", "testVocbase::test_analyzer"))
.include<irs::Bound::MIN>(false)
.term<irs::Bound::MIN>("15");
root.add<irs::by_range>()
.field(mangleString("a.b.c.e.f", "testVocbase::test_analyzer"))
.include<irs::Bound::MAX>(true)
.term<irs::Bound::MAX>("40");
assertFilterSuccess(
"LET numVal=2 FOR d IN collection FILTER boost(analyzer(d.a.b.c.e.f > TO_STRING(numVal+13) && d.a.b.c.e.f <= TO_STRING(numVal+38), 'test_analyzer'), numVal) RETURN d",
@ -2969,10 +3037,15 @@ SECTION("BinaryAnd") {
ctx.vars.emplace("offsetDbl", arangodb::aql::AqlValue(arangodb::aql::AqlValue(arangodb::aql::AqlValueHintDouble{5.6})));
irs::Or expected;
auto& range = expected.add<irs::by_range>();
range.field(mangleStringIdentity("a.b.c.e[4].f[5].g[3].g.a"))
.include<irs::Bound::MIN>(false).term<irs::Bound::MIN>("15")
.include<irs::Bound::MAX>(true).term<irs::Bound::MAX>("40");
auto& root = expected.add<irs::And>();
root.add<irs::by_range>()
.field(mangleStringIdentity("a.b.c.e[4].f[5].g[3].g.a"))
.include<irs::Bound::MIN>(false)
.term<irs::Bound::MIN>("15");
root.add<irs::by_range>()
.field(mangleStringIdentity("a.b.c.e[4].f[5].g[3].g.a"))
.include<irs::Bound::MAX>(true)
.term<irs::Bound::MAX>("40");
assertFilterSuccess("LET a='a' LET c='c' LET offsetInt=4 LET offsetDbl=5.6 FOR d IN collection FILTER d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_FORWARD_('a')] > '15' && d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_FORWARD_('a')] <= '40' RETURN d", expected, &ctx);
assertFilterSuccess("LET a='a' LET c='c' LET offsetInt=4 LET offsetDbl=5.6 FOR d IN collection FILTER '15' < d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_FORWARD_('a')] && '40' >= d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_FORWARD_('a')] RETURN d", expected, &ctx);
@ -3113,10 +3186,15 @@ SECTION("BinaryAnd") {
irs::numeric_token_stream maxTerm; maxTerm.reset(40.);
irs::Or expected;
expected.add<irs::by_granular_range>()
.field(mangleNumeric("a.b.c"))
.include<irs::Bound::MIN>(true).insert<irs::Bound::MIN>(minTerm)
.include<irs::Bound::MIN>(true).insert<irs::Bound::MAX>(maxTerm);
auto& root = expected.add<irs::And>();
root.add<irs::by_granular_range>()
.field(mangleNumeric("a.b.c"))
.include<irs::Bound::MIN>(true)
.insert<irs::Bound::MIN>(minTerm);
root.add<irs::by_granular_range>()
.field(mangleNumeric("a.b.c"))
.include<irs::Bound::MAX>(false)
.insert<irs::Bound::MAX>(maxTerm);
assertFilterSuccess("FOR d IN collection FILTER d.a.b.c >= 15.5 and d.a.b.c < 40 RETURN d", expected);
assertFilterSuccess("FOR d IN collection FILTER d['a']['b'].c >= 15.5 and d['a']['b'].c < 40 RETURN d", expected);
@ -3414,10 +3492,15 @@ SECTION("BinaryAnd") {
ctx.vars.emplace("numVal", arangodb::aql::AqlValue(arangodb::aql::AqlValueHintInt(2)));
irs::Or expected;
auto& range = expected.add<irs::by_range>();
range.field(mangleBool("a.b.c.e.f"))
.include<irs::Bound::MIN>(true).term<irs::Bound::MIN>(irs::boolean_token_stream::value_true())
.include<irs::Bound::MAX>(true).term<irs::Bound::MAX>(irs::boolean_token_stream::value_true());
auto& root = expected.add<irs::And>();
root.add<irs::by_range>()
.field(mangleBool("a.b.c.e.f"))
.include<irs::Bound::MIN>(true)
.term<irs::Bound::MIN>(irs::boolean_token_stream::value_true());
root.add<irs::by_range>()
.field(mangleBool("a.b.c.e.f"))
.include<irs::Bound::MAX>(true)
.term<irs::Bound::MAX>(irs::boolean_token_stream::value_true());
assertFilterSuccess(
"LET numVal=2 FOR d IN collection FILTER d.a.b.c.e.f >= (numVal < 13) && d.a.b.c.e.f <= (numVal > 1) RETURN d",
@ -3438,11 +3521,16 @@ SECTION("BinaryAnd") {
ctx.vars.emplace("numVal", arangodb::aql::AqlValue(arangodb::aql::AqlValueHintInt(2)));
irs::Or expected;
auto& range = expected.add<irs::by_range>();
range.boost(1.5);
range.field(mangleBool("a.b.c.e.f"))
.include<irs::Bound::MIN>(true).term<irs::Bound::MIN>(irs::boolean_token_stream::value_true())
.include<irs::Bound::MAX>(true).term<irs::Bound::MAX>(irs::boolean_token_stream::value_true());
auto& root = expected.add<irs::And>();
root.boost(1.5);
root.add<irs::by_range>()
.field(mangleBool("a.b.c.e.f"))
.include<irs::Bound::MIN>(true)
.term<irs::Bound::MIN>(irs::boolean_token_stream::value_true());
root.add<irs::by_range>()
.field(mangleBool("a.b.c.e.f"))
.include<irs::Bound::MAX>(true)
.term<irs::Bound::MAX>(irs::boolean_token_stream::value_true());
assertFilterSuccess(
"LET numVal=2 FOR d IN collection FILTER boost(d.a.b.c.e.f >= (numVal < 13) && d.a.b.c.e.f <= (numVal > 1), 1.5) RETURN d",
@ -3492,10 +3580,16 @@ SECTION("BinaryAnd") {
ctx.vars.emplace("nullVal", arangodb::aql::AqlValue(arangodb::aql::AqlValueHintNull{}));
irs::Or expected;
auto& range = expected.add<irs::by_range>();
range.field(mangleNull("a.b.c.e.f"))
.include<irs::Bound::MIN>(true).term<irs::Bound::MIN>(irs::null_token_stream::value_null())
.include<irs::Bound::MAX>(true).term<irs::Bound::MAX>(irs::null_token_stream::value_null());
auto& root = expected.add<irs::And>();
root.add<irs::by_range>()
.field(mangleNull("a.b.c.e.f"))
.include<irs::Bound::MIN>(true)
.term<irs::Bound::MIN>(irs::null_token_stream::value_null());
root.add<irs::by_range>()
.field(mangleNull("a.b.c.e.f"))
.include<irs::Bound::MAX>(true)
.term<irs::Bound::MAX>(irs::null_token_stream::value_null());
assertFilterSuccess(
"LET nullVal=null FOR d IN collection FILTER d.a.b.c.e.f >= (nullVal && true) && d.a.b.c.e.f <= (nullVal && false) RETURN d",
@ -3516,11 +3610,16 @@ SECTION("BinaryAnd") {
ctx.vars.emplace("nullVal", arangodb::aql::AqlValue(arangodb::aql::AqlValueHintNull{}));
irs::Or expected;
auto& range = expected.add<irs::by_range>();
range.boost(1.5);
range.field(mangleNull("a.b.c.e.f"))
.include<irs::Bound::MIN>(true).term<irs::Bound::MIN>(irs::null_token_stream::value_null())
.include<irs::Bound::MAX>(true).term<irs::Bound::MAX>(irs::null_token_stream::value_null());
auto& root = expected.add<irs::And>();
root.boost(1.5);
root.add<irs::by_range>()
.field(mangleNull("a.b.c.e.f"))
.include<irs::Bound::MIN>(true)
.term<irs::Bound::MIN>(irs::null_token_stream::value_null());
root.add<irs::by_range>()
.field(mangleNull("a.b.c.e.f"))
.include<irs::Bound::MAX>(true)
.term<irs::Bound::MAX>(irs::null_token_stream::value_null());
assertFilterSuccess(
"LET nullVal=null FOR d IN collection FILTER boost(d.a.b.c.e.f >= (nullVal && true) && d.a.b.c.e.f <= (nullVal && false), 1.5) RETURN d",
@ -3544,10 +3643,15 @@ SECTION("BinaryAnd") {
ctx.vars.emplace("numVal", arangodb::aql::AqlValue(arangodb::aql::AqlValueHintInt(2)));
irs::Or expected;
expected.add<irs::by_granular_range>()
.field(mangleNumeric("a.b.c.e.f"))
.include<irs::Bound::MIN>(true).insert<irs::Bound::MIN>(minTerm)
.include<irs::Bound::MAX>(false).insert<irs::Bound::MAX>(maxTerm);
auto& root = expected.add<irs::And>();
root.add<irs::by_granular_range>()
.field(mangleNumeric("a.b.c.e.f"))
.include<irs::Bound::MIN>(true)
.insert<irs::Bound::MIN>(minTerm);
root.add<irs::by_granular_range>()
.field(mangleNumeric("a.b.c.e.f"))
.include<irs::Bound::MAX>(false)
.insert<irs::Bound::MAX>(maxTerm);
assertFilterSuccess(
"LET numVal=2 FOR d IN collection FILTER d.a['b'].c.e.f >= (numVal + 13.5) && d.a.b.c.e.f < (numVal + 38) RETURN d",
@ -3653,4 +3757,4 @@ SECTION("BinaryAnd") {
// -----------------------------------------------------------------------------
// --SECTION-- END-OF-FILE
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------

View File

@ -2283,8 +2283,199 @@ SECTION("StartsWith") {
assertFilterFail("FOR d IN myView FILTER starts_with(d.name, 'abc', RAND() ? 128 : 10) RETURN d");
}
SECTION("IN_RANGE") {
// d.name > 'a' && d.name < 'z'
{
irs::Or expected;
auto& range = expected.add<irs::by_range>();
range.field(mangleStringIdentity("name"))
.include<irs::Bound::MIN>(false).term<irs::Bound::MIN>("a")
.include<irs::Bound::MAX>(false).term<irs::Bound::MAX>("z");
assertFilterSuccess("FOR d IN myView FILTER in_range(d['name'], 'a', 'z', false, false) RETURN d", expected);
assertFilterSuccess("FOR d IN myView FILTER in_range(d.name, 'a', 'z', false, false) RETURN d", expected);
}
// BOOST(d.name >= 'a' && d.name <= 'z', 1.5)
{
irs::Or expected;
auto& range = expected.add<irs::by_range>();
range.boost(1.5);
range.field(mangleStringIdentity("name"))
.include<irs::Bound::MIN>(true).term<irs::Bound::MIN>("a")
.include<irs::Bound::MAX>(true).term<irs::Bound::MAX>("z");
assertFilterSuccess("FOR d IN myView FILTER boost(in_range(d['name'], 'a', 'z', true, true), 1.5) RETURN d", expected);
assertFilterSuccess("FOR d IN myView FILTER boost(in_range(d.name, 'a', 'z', true, true), 1.5) RETURN d", expected);
}
// ANALYZER(BOOST(d.name > 'a' && d.name <= 'z', 1.5), "testVocbase::test_analyzer")
{
irs::Or expected;
auto& range = expected.add<irs::by_range>();
range.boost(1.5);
range.field(mangleString("name", "testVocbase::test_analyzer"))
.include<irs::Bound::MIN>(false).term<irs::Bound::MIN>("a")
.include<irs::Bound::MAX>(true).term<irs::Bound::MAX>("z");
assertFilterSuccess("FOR d IN myView FILTER analyzer(boost(in_range(d['name'], 'a', 'z', false, true), 1.5), 'testVocbase::test_analyzer') RETURN d", expected);
assertFilterSuccess("FOR d IN myView FILTER analyzer(boost(in_range(d.name, 'a', 'z', false, true), 1.5), 'testVocbase::test_analyzer') RETURN d", expected);
}
// dynamic complex attribute field
{
ExpressionContextMock ctx;
ctx.vars.emplace("a", arangodb::aql::AqlValue(arangodb::aql::AqlValue{"a"}));
ctx.vars.emplace("c", arangodb::aql::AqlValue(arangodb::aql::AqlValue{"c"}));
ctx.vars.emplace("offsetInt", arangodb::aql::AqlValue(arangodb::aql::AqlValue(arangodb::aql::AqlValueHintInt{4})));
ctx.vars.emplace("offsetDbl", arangodb::aql::AqlValue(arangodb::aql::AqlValue(arangodb::aql::AqlValueHintDouble{5.6})));
irs::Or expected;
auto& range = expected.add<irs::by_range>();
range.field(mangleStringIdentity("a.b.c.e[4].f[5].g[3].g.a"))
.include<irs::Bound::MIN>(true).term<irs::Bound::MIN>("abc")
.include<irs::Bound::MAX>(false).term<irs::Bound::MAX>("bce");
assertFilterSuccess("LET a='a' LET c='c' LET offsetInt=4 LET offsetDbl=5.6 FOR d IN collection FILTER in_range(d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_FORWARD_('a')], 'abc', 'bce', true, false) RETURN d", expected, &ctx);
assertFilterSuccess("LET a='a' LET c='c' LET offsetInt=4 LET offsetDbl=5.6 FOR d IN collection FILTER in_range(d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_FORWARD_('a')], CONCAT(_FORWARD_('a'), _FORWARD_('bc')), CONCAT(_FORWARD_('bc'), _FORWARD_('e')), _FORWARD_(5) > _FORWARD_(4), _FORWARD_(5) > _FORWARD_(6)) RETURN d", expected, &ctx);
}
// invalid dynamic attribute name (null value)
{
ExpressionContextMock ctx;
ctx.vars.emplace("a", arangodb::aql::AqlValue(arangodb::aql::AqlValueHintNull{})); // invalid value type
ctx.vars.emplace("c", arangodb::aql::AqlValue(arangodb::aql::AqlValue{"c"}));
ctx.vars.emplace("offsetInt", arangodb::aql::AqlValue(arangodb::aql::AqlValue(arangodb::aql::AqlValueHintInt{4})));
ctx.vars.emplace("offsetDbl", arangodb::aql::AqlValue(arangodb::aql::AqlValue(arangodb::aql::AqlValueHintDouble{5.6})));
assertFilterExecutionFail("LET a=null LET c='c' LET offsetInt=4 LET offsetDbl=5.6 FOR d IN collection FILTER in_range(d[a].b[c].e[offsetInt].f[offsetDbl].g[_FORWARD_(3)].g[_FORWARD_('a')], 'abc', 'bce', true, false) RETURN d", &ctx);
}
// boolean expression in range, boost
{
ExpressionContextMock ctx;
ctx.vars.emplace("numVal", arangodb::aql::AqlValue(arangodb::aql::AqlValueHintInt(2)));
irs::Or expected;
auto& range = expected.add<irs::by_range>();
range.boost(1.5);
range.field(mangleBool("a.b.c.e.f"))
.include<irs::Bound::MIN>(true).term<irs::Bound::MIN>(irs::boolean_token_stream::value_true())
.include<irs::Bound::MAX>(true).term<irs::Bound::MAX>(irs::boolean_token_stream::value_true());
assertFilterSuccess(
"LET numVal=2 FOR d IN collection FILTER boost(in_rangE(d.a.b.c.e.f, (numVal < 13), (numVal > 1), true, true), 1.5) RETURN d",
expected,
&ctx // expression context
);
assertFilterSuccess(
"LET numVal=2 FOR d IN collection FILTER boost(in_rangE(d.a.b.c.e.f, (numVal < 13), (numVal > 1), true, true), 1.5) RETURN d",
expected,
&ctx // expression context
);
}
// null expression in range, boost
{
ExpressionContextMock ctx;
ctx.vars.emplace("nullVal", arangodb::aql::AqlValue(arangodb::aql::AqlValueHintNull{}));
irs::Or expected;
auto& range = expected.add<irs::by_range>();
range.boost(1.5);
range.field(mangleNull("a.b.c.e.f"))
.include<irs::Bound::MIN>(true).term<irs::Bound::MIN>(irs::null_token_stream::value_null())
.include<irs::Bound::MAX>(true).term<irs::Bound::MAX>(irs::null_token_stream::value_null());
assertFilterSuccess(
"LET nullVal=null FOR d IN collection FILTER BOOST(in_range(d.a.b.c.e.f, (nullVal && true), (nullVal && false), true, true), 1.5) RETURN d",
expected,
&ctx // expression context
);
assertFilterSuccess(
"LET nullVal=null FOR d IN collection FILTER bOoST(in_range(d.a.b.c.e.f, (nullVal && false), (nullVal && true), true, true), 1.5) RETURN d",
expected,
&ctx // expression context
);
}
// numeric expression in range, boost
{
irs::numeric_token_stream minTerm; minTerm.reset(15.5);
irs::numeric_token_stream maxTerm; maxTerm.reset(40.);
ExpressionContextMock ctx;
ctx.vars.emplace("numVal", arangodb::aql::AqlValue(arangodb::aql::AqlValueHintInt(2)));
irs::Or expected;
auto& range = expected.add<irs::by_granular_range>();
range.boost(1.5);
range.field(mangleNumeric("a.b.c.e.f"))
.include<irs::Bound::MIN>(true).insert<irs::Bound::MIN>(minTerm)
.include<irs::Bound::MAX>(false).insert<irs::Bound::MAX>(maxTerm);
assertFilterSuccess(
"LET numVal=2 FOR d IN collection FILTER boost(in_range(d.a['b'].c.e.f, (numVal + 13.5), (numVal + 38), true, false), 1.5) RETURN d",
expected,
&ctx // expression context
);
assertFilterSuccess(
"LET numVal=2 FOR d IN collection FILTER boost(IN_RANGE(d.a.b.c.e.f, (numVal + 13.5), (numVal + 38), true, false), 1.5) RETURN d",
expected,
&ctx // expression context
);
assertFilterSuccess(
"LET numVal=2 FOR d IN collection FILTER analyzer(boost(in_range(d.a.b.c.e.f, (numVal + 13.5), (numVal + 38), true, false), 1.5), 'test_analyzer') RETURN d",
expected,
&ctx // expression context
);
}
// invalid attribute access
assertFilterFail("FOR d IN myView FILTER in_range(['d'], 'abc', true, 'z', false) RETURN d");
assertFilterFail("FOR d IN myView FILTER in_range([d], 'abc', true, 'z', false) RETURN d");
assertFilterFail("FOR d IN myView FILTER in_range(d, 'abc', true, 'z', false) RETURN d");
assertFilterFail("FOR d IN myView FILTER in_range(d[*], 'abc', true, 'z', false) RETURN d");
assertFilterFail("FOR d IN myView FILTER in_range(d.a[*].c, 'abc', true, 'z', false) RETURN d");
assertFilterFail("FOR d IN myView FILTER in_range('d.name', 'abc', true, 'z', false) RETURN d");
assertFilterFail("FOR d IN myView FILTER in_range(123, 'abc', true, 'z', false) RETURN d");
assertFilterFail("FOR d IN myView FILTER in_range(123.5, 'abc', true, 'z', false) RETURN d");
assertFilterFail("FOR d IN myView FILTER in_range(null, 'abc', true, 'z', false) RETURN d");
assertFilterFail("FOR d IN myView FILTER in_range(true, 'abc', true, 'z', false) RETURN d");
assertFilterFail("FOR d IN myView FILTER in_range(false, 'abc', true, 'z', false) RETURN d");
// invalid type of inclusion argument
assertFilterFail("FOR d IN myView FILTER in_range(d.name, 'abc', true, 'z', 'false') RETURN d");
assertFilterFail("FOR d IN myView FILTER in_range(d.name, 'abc', true, 'z', 0) RETURN d");
assertFilterFail("FOR d IN myView FILTER in_range(d.name, 'abc', true, 'z', null) RETURN d");
assertFilterFail("FOR d IN myView FILTER in_range(d.name, 'abc', 'true', 'z', false) RETURN d");
assertFilterFail("FOR d IN myView FILTER in_range(d.name, 'abc', 1, 'z', false) RETURN d");
assertFilterFail("FOR d IN myView FILTER in_range(d.name, 'abc', null, 'z', false) RETURN d");
// non-deterministic argument
assertFilterFail("FOR d IN myView FILTER in_range(d[RAND() ? 'name' : 'x'], 'abc', true, 'z', false) RETURN d");
assertFilterFail("FOR d IN myView FILTER in_range(d.name, RAND() ? 'abc' : 'def', true, 'z', false) RETURN d");
assertFilterFail("FOR d IN myView FILTER in_range(d.name, 'abc', RAND() ? true : false, 'z', false) RETURN d");
assertFilterFail("FOR d IN myView FILTER in_range(d.name, 'abc', true, RAND() ? 'z' : 'x', false) RETURN d");
assertFilterFail("FOR d IN myView FILTER in_range(d.name, 'abc', true, 'z', RAND() ? false : true) RETURN d");
// lower/upper boundary type mismatch
assertFilterFail("FOR d IN myView FILTER in_range(d.name, 1, true, 'z', false) RETURN d");
assertFilterFail("FOR d IN myView FILTER in_range(d.name, 'abc', true, null, false) RETURN d");
assertFilterFail("FOR d IN myView FILTER in_range(d.name, bool, true, null, false) RETURN d");
assertFilterFail("FOR d IN myView FILTER in_range(d.name, bool, true, 1, false) RETURN d");
// wrong number of arguments
assertFilterParseFail("FOR d IN myView FILTER in_range(d.name, 'abc', true, 'z') RETURN d");
assertFilterParseFail("FOR d IN myView FILTER in_range(d.name, 'abc', true, 'z', false, false) RETURN d");
}
}
// -----------------------------------------------------------------------------
// --SECTION-- END-OF-FILE
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------

View File

@ -0,0 +1,711 @@
////////////////////////////////////////////////////////////////////////////////
/// DISCLAIMER
///
/// Copyright 2017 ArangoDB GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is ArangoDB GmbH, Cologne, Germany
///
/// @author Andrey Abramov
/// @author Vasiliy Nabatchikov
////////////////////////////////////////////////////////////////////////////////
#include "catch.hpp"
#include "common.h"
#include "../Mocks/StorageEngineMock.h"
#if USE_ENTERPRISE
#include "Enterprise/Ldap/LdapFeature.h"
#endif
#include "Cluster/ClusterFeature.h"
#include "V8Server/V8DealerFeature.h"
#include "Aql/AqlFunctionFeature.h"
#include "Aql/Ast.h"
#include "Aql/OptimizerRulesFeature.h"
#include "Aql/Query.h"
#include "Basics/VelocyPackHelper.h"
#include "GeneralServer/AuthenticationFeature.h"
#include "IResearch/IResearchCommon.h"
#include "IResearch/IResearchFeature.h"
#include "IResearch/IResearchFilterFactory.h"
#include "IResearch/IResearchView.h"
#include "IResearch/IResearchAnalyzerFeature.h"
#include "Logger/Logger.h"
#include "Logger/LogTopic.h"
#include "StorageEngine/EngineSelectorFeature.h"
#include "RestServer/DatabasePathFeature.h"
#include "RestServer/ViewTypesFeature.h"
#include "RestServer/AqlFeature.h"
#include "RestServer/DatabaseFeature.h"
#include "RestServer/QueryRegistryFeature.h"
#include "RestServer/SystemDatabaseFeature.h"
#include "RestServer/TraverserEngineRegistryFeature.h"
#include "Sharding/ShardingFeature.h"
#include "V8/v8-globals.h"
#include "VocBase/LogicalCollection.h"
#include "VocBase/LogicalView.h"
#include "3rdParty/iresearch/tests/tests_config.hpp"
#include "Transaction/StandaloneContext.h"
#include "Utils/SingleCollectionTransaction.h"
#include "IResearch/VelocyPackHelper.h"
#include "analysis/analyzers.hpp"
#include "analysis/token_attributes.hpp"
#include "utils/utf8_path.hpp"
#include <velocypack/Iterator.h>
extern const char* ARGV0; // defined in main.cpp
NS_LOCAL
// -----------------------------------------------------------------------------
// --SECTION-- setup / tear-down
// -----------------------------------------------------------------------------
struct IResearchQueryInRangeSetup {
StorageEngineMock engine;
arangodb::application_features::ApplicationServer server;
std::unique_ptr<TRI_vocbase_t> system;
std::vector<std::pair<arangodb::application_features::ApplicationFeature*, bool>> features;
IResearchQueryInRangeSetup(): engine(server), server(nullptr, nullptr) {
arangodb::EngineSelectorFeature::ENGINE = &engine;
arangodb::tests::init(true);
// suppress INFO {authentication} Authentication is turned on (system only), authentication for unix sockets is turned on
arangodb::LogTopic::setLogLevel(arangodb::Logger::AUTHENTICATION.name(), arangodb::LogLevel::WARN);
// suppress log messages since tests check error conditions
arangodb::LogTopic::setLogLevel(arangodb::Logger::FIXME.name(), arangodb::LogLevel::ERR); // suppress WARNING DefaultCustomTypeHandler called
arangodb::LogTopic::setLogLevel(arangodb::iresearch::TOPIC.name(), arangodb::LogLevel::FATAL);
irs::logger::output_le(iresearch::logger::IRL_FATAL, stderr);
// setup required application features
features.emplace_back(new arangodb::V8DealerFeature(server), false); // required for DatabaseFeature::createDatabase(...)
features.emplace_back(new arangodb::ViewTypesFeature(server), true);
features.emplace_back(new arangodb::AuthenticationFeature(server), true);
features.emplace_back(new arangodb::DatabasePathFeature(server), false);
features.emplace_back(new arangodb::DatabaseFeature(server), false);
features.emplace_back(new arangodb::ShardingFeature(server), false); //
features.emplace_back(new arangodb::QueryRegistryFeature(server), false); // must be first
arangodb::application_features::ApplicationServer::server->addFeature(features.back().first); // need QueryRegistryFeature feature to be added now in order to create the system database
system = irs::memory::make_unique<TRI_vocbase_t>(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 0, TRI_VOC_SYSTEM_DATABASE);
features.emplace_back(new arangodb::SystemDatabaseFeature(server, system.get()), false); // required for IResearchAnalyzerFeature
features.emplace_back(new arangodb::TraverserEngineRegistryFeature(server), false); // must be before AqlFeature
features.emplace_back(new arangodb::AqlFeature(server), true);
features.emplace_back(new arangodb::aql::OptimizerRulesFeature(server), true);
features.emplace_back(new arangodb::aql::AqlFunctionFeature(server), true); // required for IResearchAnalyzerFeature
features.emplace_back(new arangodb::iresearch::IResearchAnalyzerFeature(server), true);
features.emplace_back(new arangodb::iresearch::IResearchFeature(server), true);
#if USE_ENTERPRISE
features.emplace_back(new arangodb::LdapFeature(server), false); // required for AuthenticationFeature with USE_ENTERPRISE
#endif
// required for V8DealerFeature::prepare(), ClusterFeature::prepare() not required
arangodb::application_features::ApplicationServer::server->addFeature(
new arangodb::ClusterFeature(server)
);
for (auto& f : features) {
arangodb::application_features::ApplicationServer::server->addFeature(f.first);
}
for (auto& f : features) {
f.first->prepare();
}
for (auto& f : features) {
if (f.second) {
f.first->start();
}
}
TRI_vocbase_t* vocbase;
auto* dbFeature = arangodb::application_features::ApplicationServer::lookupFeature<
arangodb::DatabaseFeature
>("Database");
dbFeature->createDatabase(1, "testVocbase", vocbase); // required for IResearchAnalyzerFeature::emplace(...)
auto* analyzers = arangodb::application_features::ApplicationServer::lookupFeature<
arangodb::iresearch::IResearchAnalyzerFeature
>();
arangodb::iresearch::IResearchAnalyzerFeature::EmplaceResult result;
analyzers->emplace(
result,
"testVocbase::test_csv_analyzer",
"TestDelimAnalyzer",
","
); // cache analyzer
analyzers->emplace(
result,
"testVocbase::test_analyzer",
"TestAnalyzer",
"abc"
); // cache analyzer
auto* dbPathFeature = arangodb::application_features::ApplicationServer::getFeature<arangodb::DatabasePathFeature>("DatabasePath");
arangodb::tests::setDatabasePath(*dbPathFeature); // ensure test data is stored in a unique directory
}
~IResearchQueryInRangeSetup() {
system.reset(); // destroy before reseting the 'ENGINE'
arangodb::AqlFeature(server).stop(); // unset singleton instance
arangodb::LogTopic::setLogLevel(arangodb::iresearch::TOPIC.name(), arangodb::LogLevel::DEFAULT);
arangodb::LogTopic::setLogLevel(arangodb::Logger::FIXME.name(), arangodb::LogLevel::DEFAULT);
arangodb::application_features::ApplicationServer::server = nullptr;
// destroy application features
for (auto& f : features) {
if (f.second) {
f.first->stop();
}
}
for (auto& f : features) {
f.first->unprepare();
}
arangodb::LogTopic::setLogLevel(arangodb::Logger::AUTHENTICATION.name(), arangodb::LogLevel::DEFAULT);
arangodb::EngineSelectorFeature::ENGINE = nullptr;
}
}; // IResearchQueryInRangeSetup
NS_END
// -----------------------------------------------------------------------------
// --SECTION-- test suite
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @brief setup
////////////////////////////////////////////////////////////////////////////////
TEST_CASE("IResearchQueryInRange", "[iresearch][iresearch-query]") {
IResearchQueryInRangeSetup s;
UNUSED(s);
TRI_vocbase_t vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase");
std::vector<arangodb::velocypack::Builder> insertedDocs;
arangodb::LogicalView* view;
// create collection0
{
auto createJson = arangodb::velocypack::Parser::fromJson("{ \"name\": \"testCollection0\" }");
auto collection = vocbase.createCollection(createJson->slice());
REQUIRE((nullptr != collection));
std::vector<std::shared_ptr<arangodb::velocypack::Builder>> docs {
arangodb::velocypack::Parser::fromJson("{ \"seq\": -6, \"value\": null }"),
arangodb::velocypack::Parser::fromJson("{ \"seq\": -5, \"value\": true }"),
arangodb::velocypack::Parser::fromJson("{ \"seq\": -4, \"value\": \"abc\" }"),
arangodb::velocypack::Parser::fromJson("{ \"seq\": -3, \"value\": [ 3.14, -3.14 ] }"),
arangodb::velocypack::Parser::fromJson("{ \"seq\": -2, \"value\": [ 1, \"abc\" ] }"),
arangodb::velocypack::Parser::fromJson("{ \"seq\": -1, \"value\": { \"a\": 7, \"b\": \"c\" } }"),
};
arangodb::OperationOptions options;
options.returnNew = true;
arangodb::SingleCollectionTransaction trx(
arangodb::transaction::StandaloneContext::Create(vocbase),
*collection,
arangodb::AccessMode::Type::WRITE
);
CHECK((trx.begin().ok()));
for (auto& entry: docs) {
auto res = trx.insert(collection->name(), entry->slice(), options);
CHECK((res.ok()));
insertedDocs.emplace_back(res.slice().get("new"));
}
CHECK((trx.commit().ok()));
}
// create collection1
{
auto createJson = arangodb::velocypack::Parser::fromJson("{ \"name\": \"testCollection1\" }");
auto collection = vocbase.createCollection(createJson->slice());
REQUIRE((nullptr != collection));
irs::utf8_path resource;
resource/=irs::string_ref(IResearch_test_resource_dir);
resource/=irs::string_ref("simple_sequential.json");
auto builder = arangodb::basics::VelocyPackHelper::velocyPackFromFile(resource.utf8());
auto slice = builder.slice();
REQUIRE(slice.isArray());
arangodb::OperationOptions options;
options.returnNew = true;
arangodb::SingleCollectionTransaction trx(
arangodb::transaction::StandaloneContext::Create(vocbase),
*collection,
arangodb::AccessMode::Type::WRITE
);
CHECK((trx.begin().ok()));
for (arangodb::velocypack::ArrayIterator itr(slice); itr.valid(); ++itr) {
auto res = trx.insert(collection->name(), itr.value(), options);
CHECK((res.ok()));
insertedDocs.emplace_back(res.slice().get("new"));
}
CHECK((trx.commit().ok()));
}
// create view
{
auto createJson = arangodb::velocypack::Parser::fromJson("{ \"name\": \"testView\", \"type\": \"arangosearch\" }");
auto logicalView = vocbase.createView(createJson->slice());
REQUIRE((false == !logicalView));
view = logicalView.get();
auto* impl = dynamic_cast<arangodb::iresearch::IResearchView*>(view);
REQUIRE((false == !impl));
auto updateJson = arangodb::velocypack::Parser::fromJson(
"{ \"links\": {"
"\"testCollection0\": { \"analyzers\": [ \"test_analyzer\", \"identity\" ], \"includeAllFields\": true, \"trackListPositions\": false, \"storeValues\":\"id\" },"
"\"testCollection1\": { \"analyzers\": [ \"test_analyzer\", \"identity\" ], \"includeAllFields\": true, \"storeValues\":\"id\" }"
"}}"
);
CHECK((impl->properties(updateJson->slice(), true).ok()));
std::set<TRI_voc_cid_t> cids;
impl->visitCollections([&cids](TRI_voc_cid_t cid)->bool { cids.emplace(cid); return true; });
CHECK((2 == cids.size()));
CHECK(impl->commit().ok());
}
// d.value > false && d.value <= true
{
std::vector<arangodb::velocypack::Slice> expected = {
insertedDocs[1].slice(),
};
auto result = arangodb::tests::executeQuery(
vocbase,
"FOR d IN testView SEARCH IN_RANGE(d.value, false, true, false, true) SORT d.seq RETURN d"
);
REQUIRE(TRI_ERROR_NO_ERROR == result.code);
auto slice = result.result->slice();
CHECK(slice.isArray());
size_t i = 0;
for (arangodb::velocypack::ArrayIterator itr(slice); itr.valid(); ++itr) {
auto const resolved = itr.value().resolveExternals();
CHECK((i < expected.size()));
CHECK((0 == arangodb::basics::VelocyPackHelper::compare(expected[i++], resolved, true)));
}
CHECK((i == expected.size()));
}
// d.value >= null && d.value <= null
{
std::vector<arangodb::velocypack::Slice> expected = {
insertedDocs[0].slice(),
};
auto result = arangodb::tests::executeQuery(
vocbase,
"FOR d IN testView SEARCH IN_RANGE(d.value, null, null, true, true) SORT d.seq RETURN d"
);
REQUIRE(TRI_ERROR_NO_ERROR == result.code);
auto slice = result.result->slice();
CHECK(slice.isArray());
size_t i = 0;
for (arangodb::velocypack::ArrayIterator itr(slice); itr.valid(); ++itr) {
auto const resolved = itr.value().resolveExternals();
CHECK((i < expected.size()));
CHECK((0 == arangodb::basics::VelocyPackHelper::compare(expected[i++], resolved, true)));
}
CHECK((i == expected.size()));
}
// d.value > null && d.value <= null
{
std::vector<arangodb::velocypack::Slice> expected = {
};
auto result = arangodb::tests::executeQuery(
vocbase,
"FOR d IN testView SEARCH IN_RANGE(d.value, null, null, false, true) SORT d.seq RETURN d"
);
REQUIRE(TRI_ERROR_NO_ERROR == result.code);
auto slice = result.result->slice();
CHECK(slice.isArray());
size_t i = 0;
for (arangodb::velocypack::ArrayIterator itr(slice); itr.valid(); ++itr) {
auto const resolved = itr.value().resolveExternals();
CHECK((i < expected.size()));
CHECK((0 == arangodb::basics::VelocyPackHelper::compare(expected[i++], resolved, true)));
}
CHECK((i == expected.size()));
}
// d.name >= 'A' && d.name <= 'A'
{
std::vector<arangodb::velocypack::Slice> expected = {
insertedDocs[6].slice(),
};
auto result = arangodb::tests::executeQuery(
vocbase,
"FOR d IN testView SEARCH IN_RANGE(d.name, 'A', 'A', true, true) SORT d.seq RETURN d"
);
REQUIRE(TRI_ERROR_NO_ERROR == result.code);
auto slice = result.result->slice();
CHECK(slice.isArray());
size_t i = 0;
for (arangodb::velocypack::ArrayIterator itr(slice); itr.valid(); ++itr) {
auto const resolved = itr.value().resolveExternals();
CHECK((i < expected.size()));
CHECK((0 == arangodb::basics::VelocyPackHelper::compare(expected[i++], resolved, true)));
}
CHECK((i == expected.size()));
}
// d.name >= 'B' && d.name <= 'A'
{
std::vector<arangodb::velocypack::Slice> expected = {
};
auto result = arangodb::tests::executeQuery(
vocbase,
"FOR d IN testView SEARCH IN_RANGE(d.name, 'B', 'A', true, true) SORT d.seq RETURN d"
);
REQUIRE(TRI_ERROR_NO_ERROR == result.code);
auto slice = result.result->slice();
CHECK(slice.isArray());
size_t i = 0;
for (arangodb::velocypack::ArrayIterator itr(slice); itr.valid(); ++itr) {
auto const resolved = itr.value().resolveExternals();
CHECK((i < expected.size()));
CHECK((0 == arangodb::basics::VelocyPackHelper::compare(expected[i++], resolved, true)));
}
CHECK((i == expected.size()));
}
// d.name >= 'A' && d.name <= 'E'
{
std::vector<arangodb::velocypack::Slice> expected = {
insertedDocs[6].slice(),
insertedDocs[7].slice(),
insertedDocs[8].slice(),
insertedDocs[9].slice(),
insertedDocs[10].slice(),
};
auto result = arangodb::tests::executeQuery(
vocbase,
"FOR d IN testView SEARCH IN_RANGE(d.name, 'A', 'E', true, true) SORT d.seq RETURN d"
);
REQUIRE(TRI_ERROR_NO_ERROR == result.code);
auto slice = result.result->slice();
CHECK(slice.isArray());
size_t i = 0;
for (arangodb::velocypack::ArrayIterator itr(slice); itr.valid(); ++itr) {
auto const resolved = itr.value().resolveExternals();
CHECK((i < expected.size()));
CHECK((0 == arangodb::basics::VelocyPackHelper::compare(expected[i++], resolved, true)));
}
CHECK((i == expected.size()));
}
// d.name >= 'A' && d.name < 'E'
{
std::vector<arangodb::velocypack::Slice> expected = {
insertedDocs[6].slice(),
insertedDocs[7].slice(),
insertedDocs[8].slice(),
insertedDocs[9].slice(),
};
auto result = arangodb::tests::executeQuery(
vocbase,
"FOR d IN testView SEARCH IN_RANGE(d.name, 'A', 'E', true, false) SORT d.seq RETURN d"
);
REQUIRE(TRI_ERROR_NO_ERROR == result.code);
auto slice = result.result->slice();
CHECK(slice.isArray());
size_t i = 0;
for (arangodb::velocypack::ArrayIterator itr(slice); itr.valid(); ++itr) {
auto const resolved = itr.value().resolveExternals();
CHECK((i < expected.size()));
CHECK((0 == arangodb::basics::VelocyPackHelper::compare(expected[i++], resolved, true)));
}
CHECK((i == expected.size()));
}
// d.name > 'A' && d.name <= 'E'
{
std::vector<arangodb::velocypack::Slice> expected = {
insertedDocs[7].slice(),
insertedDocs[8].slice(),
insertedDocs[9].slice(),
insertedDocs[10].slice(),
};
auto result = arangodb::tests::executeQuery(
vocbase,
"FOR d IN testView SEARCH IN_RANGE(d.name, 'A', 'E', false, true) SORT d.seq RETURN d"
);
REQUIRE(TRI_ERROR_NO_ERROR == result.code);
auto slice = result.result->slice();
CHECK(slice.isArray());
size_t i = 0;
for (arangodb::velocypack::ArrayIterator itr(slice); itr.valid(); ++itr) {
auto const resolved = itr.value().resolveExternals();
CHECK((i < expected.size()));
CHECK((0 == arangodb::basics::VelocyPackHelper::compare(expected[i++], resolved, true)));
}
CHECK((i == expected.size()));
}
// d.name > 'A' && d.name < 'E'
{
std::vector<arangodb::velocypack::Slice> expected = {
insertedDocs[7].slice(),
insertedDocs[8].slice(),
insertedDocs[9].slice(),
};
auto result = arangodb::tests::executeQuery(
vocbase,
"FOR d IN testView SEARCH IN_RANGE(d.name, 'A', 'E', false, false) SORT d.seq RETURN d"
);
REQUIRE(TRI_ERROR_NO_ERROR == result.code);
auto slice = result.result->slice();
CHECK(slice.isArray());
size_t i = 0;
for (arangodb::velocypack::ArrayIterator itr(slice); itr.valid(); ++itr) {
auto const resolved = itr.value().resolveExternals();
CHECK((i < expected.size()));
CHECK((0 == arangodb::basics::VelocyPackHelper::compare(expected[i++], resolved, true)));
}
CHECK((i == expected.size()));
}
// d.seq >= 5 && d.seq <= -1
{
std::vector<arangodb::velocypack::Slice> expected = {
};
auto result = arangodb::tests::executeQuery(
vocbase,
"FOR d IN testView SEARCH IN_RANGE(d.seq, 5, -1, true, true) SORT d.seq RETURN d"
);
REQUIRE(TRI_ERROR_NO_ERROR == result.code);
auto slice = result.result->slice();
CHECK(slice.isArray());
size_t i = 0;
for (arangodb::velocypack::ArrayIterator itr(slice); itr.valid(); ++itr) {
auto const resolved = itr.value().resolveExternals();
CHECK((i < expected.size()));
CHECK((0 == arangodb::basics::VelocyPackHelper::compare(expected[i++], resolved, true)));
}
CHECK((i == expected.size()));
}
// d.seq >= 1 && d.seq <= 5
{
std::vector<arangodb::velocypack::Slice> expected = {
insertedDocs[7].slice(),
insertedDocs[8].slice(),
insertedDocs[9].slice(),
insertedDocs[10].slice(),
insertedDocs[11].slice(),
};
auto result = arangodb::tests::executeQuery(
vocbase,
"FOR d IN testView SEARCH IN_RANGE(d.seq, 1, 5, true, true) SORT d.seq RETURN d"
);
REQUIRE(TRI_ERROR_NO_ERROR == result.code);
auto slice = result.result->slice();
CHECK(slice.isArray());
size_t i = 0;
for (arangodb::velocypack::ArrayIterator itr(slice); itr.valid(); ++itr) {
auto const resolved = itr.value().resolveExternals();
CHECK((i < expected.size()));
CHECK((0 == arangodb::basics::VelocyPackHelper::compare(expected[i++], resolved, true)));
}
CHECK((i == expected.size()));
}
// d.seq > -2 && d.seq <= 5
{
std::vector<arangodb::velocypack::Slice> expected = {
insertedDocs[5].slice(),
insertedDocs[6].slice(),
insertedDocs[7].slice(),
insertedDocs[8].slice(),
insertedDocs[9].slice(),
insertedDocs[10].slice(),
insertedDocs[11].slice(),
};
auto result = arangodb::tests::executeQuery(
vocbase,
"FOR d IN testView SEARCH IN_RANGE(d.seq, -2, 5, false, true) SORT d.seq RETURN d"
);
REQUIRE(TRI_ERROR_NO_ERROR == result.code);
auto slice = result.result->slice();
CHECK(slice.isArray());
size_t i = 0;
for (arangodb::velocypack::ArrayIterator itr(slice); itr.valid(); ++itr) {
auto const resolved = itr.value().resolveExternals();
CHECK((i < expected.size()));
CHECK((0 == arangodb::basics::VelocyPackHelper::compare(expected[i++], resolved, true)));
}
CHECK((i == expected.size()));
}
// d.seq > 1 && d.seq < 5
{
std::vector<arangodb::velocypack::Slice> expected = {
insertedDocs[8].slice(),
insertedDocs[9].slice(),
insertedDocs[10].slice(),
};
auto result = arangodb::tests::executeQuery(
vocbase,
"FOR d IN testView SEARCH IN_RANGE(d.seq, 1, 5, false, false) SORT d.seq RETURN d"
);
REQUIRE(TRI_ERROR_NO_ERROR == result.code);
auto slice = result.result->slice();
CHECK(slice.isArray());
size_t i = 0;
for (arangodb::velocypack::ArrayIterator itr(slice); itr.valid(); ++itr) {
auto const resolved = itr.value().resolveExternals();
CHECK((i < expected.size()));
CHECK((0 == arangodb::basics::VelocyPackHelper::compare(expected[i++], resolved, true)));
}
CHECK((i == expected.size()));
}
// d.seq >= 1 && d.seq < 5
{
std::vector<arangodb::velocypack::Slice> expected = {
insertedDocs[7].slice(),
insertedDocs[8].slice(),
insertedDocs[9].slice(),
insertedDocs[10].slice(),
};
auto result = arangodb::tests::executeQuery(
vocbase,
"FOR d IN testView SEARCH IN_RANGE(d.seq, 1, 5, true, false) SORT d.seq RETURN d"
);
REQUIRE(TRI_ERROR_NO_ERROR == result.code);
auto slice = result.result->slice();
CHECK(slice.isArray());
size_t i = 0;
for (arangodb::velocypack::ArrayIterator itr(slice); itr.valid(); ++itr) {
auto const resolved = itr.value().resolveExternals();
CHECK((i < expected.size()));
CHECK((0 == arangodb::basics::VelocyPackHelper::compare(expected[i++], resolved, true)));
}
CHECK((i == expected.size()));
}
// d.value > 3 && d.value < 4
{
std::vector<arangodb::velocypack::Slice> expected = {
insertedDocs[3].slice(),
};
auto result = arangodb::tests::executeQuery(
vocbase,
"FOR d IN testView SEARCH IN_RANGE(d.value, 3, 4, false, false) SORT d.seq RETURN d"
);
REQUIRE(TRI_ERROR_NO_ERROR == result.code);
auto slice = result.result->slice();
CHECK(slice.isArray());
size_t i = 0;
for (arangodb::velocypack::ArrayIterator itr(slice); itr.valid(); ++itr) {
auto const resolved = itr.value().resolveExternals();
CHECK((i < expected.size()));
CHECK((0 == arangodb::basics::VelocyPackHelper::compare(expected[i++], resolved, true)));
}
CHECK((i == expected.size()));
}
// d.value > -4 && d.value < -3
{
std::vector<arangodb::velocypack::Slice> expected = {
insertedDocs[3].slice(),
};
auto result = arangodb::tests::executeQuery(
vocbase,
"FOR d IN testView SEARCH IN_RANGE(d.value, -4, -3, false, false) SORT d.seq RETURN d"
);
REQUIRE(TRI_ERROR_NO_ERROR == result.code);
auto slice = result.result->slice();
CHECK(slice.isArray());
size_t i = 0;
for (arangodb::velocypack::ArrayIterator itr(slice); itr.valid(); ++itr) {
auto const resolved = itr.value().resolveExternals();
CHECK((i < expected.size()));
CHECK((0 == arangodb::basics::VelocyPackHelper::compare(expected[i++], resolved, true)));
}
CHECK((i == expected.size()));
}
}
// -----------------------------------------------------------------------------
// --SECTION-- END-OF-FILE
// -----------------------------------------------------------------------------

File diff suppressed because it is too large Load Diff

View File

@ -366,7 +366,7 @@ TEST_CASE("IResearchQueryScorer", "[iresearch][iresearch-query]") {
"LET arr = [0,1] "
"FOR i in 0..1 "
" LET rnd = _NONDETERM_(i) "
" FOR d IN testView SEARCH d.name >= 'A' AND d.name <= 'C' "
" FOR d IN testView SEARCH IN_RANGE(d.name, 'A', 'C', true, true) "
"LIMIT 10 "
"RETURN { d, score: d.seq + 3*customscorer(d, arr[TO_NUMBER(rnd != 0)]) }";
@ -478,7 +478,7 @@ TEST_CASE("IResearchQueryScorer", "[iresearch][iresearch-query]") {
{
std::string const queryString =
"LET i = 1"
"FOR d IN testView SEARCH d.name >= 'A' AND d.name < 'B' "
"FOR d IN testView SEARCH IN_RANGE(d.name, 'A', 'B', true, false) "
"RETURN [ customscorer(d, i), customscorer(d, 1) ] ";
CHECK(arangodb::tests::assertRules(
@ -584,7 +584,7 @@ TEST_CASE("IResearchQueryScorer", "[iresearch][iresearch-query]") {
{
std::string const queryString =
"LET obj = _NONDETERM_({ value : 2 }) "
"FOR d IN testView SEARCH d.name >= 'A' AND d.name <= 'C' "
"FOR d IN testView SEARCH IN_RANGE(d.name, 'A', 'C', true, true) "
"RETURN [ customscorer(d, obj.value), customscorer(d, obj.value) ] ";
CHECK(arangodb::tests::assertRules(
@ -695,7 +695,7 @@ TEST_CASE("IResearchQueryScorer", "[iresearch][iresearch-query]") {
{
std::string const queryString =
"LET obj = _NONDETERM_({ value : 2 }) "
"FOR d IN testView SEARCH d.name >= 'A' AND d.name <= 'C' "
"FOR d IN testView SEARCH IN_RANGE(d.name, 'A', 'C', true, true) "
"RETURN [ customscorer(d, obj.value+1), customscorer(d, obj.value+1) ] ";
CHECK(arangodb::tests::assertRules(
@ -806,7 +806,7 @@ TEST_CASE("IResearchQueryScorer", "[iresearch][iresearch-query]") {
{
std::string const queryString =
"LET obj = _NONDETERM_([ 2, 5 ]) "
"FOR d IN testView SEARCH d.name >= 'A' AND d.name <= 'C' "
"FOR d IN testView SEARCH IN_RANGE(d.name, 'A', 'C', true, true) "
"RETURN [ customscorer(d, obj[1]), customscorer(d, obj[1]) ] ";
CHECK(arangodb::tests::assertRules(
@ -917,7 +917,7 @@ TEST_CASE("IResearchQueryScorer", "[iresearch][iresearch-query]") {
{
std::string const queryString =
"LET obj = _NONDETERM_([ 2, 5 ]) "
"FOR d IN testView SEARCH d.name >= 'A' AND d.name <= 'C' "
"FOR d IN testView SEARCH IN_RANGE(d.name, 'A', 'C', true, true) "
"RETURN [ customscorer(d, obj[0] > obj[1] ? 1 : 2), customscorer(d, obj[0] > obj[1] ? 1 : 2) ] ";
CHECK(arangodb::tests::assertRules(
@ -1028,7 +1028,7 @@ TEST_CASE("IResearchQueryScorer", "[iresearch][iresearch-query]") {
{
std::string const queryString =
"LET obj = _NONDETERM_([ 2, 5 ]) "
"FOR d IN testView SEARCH d.name >= 'A' AND d.name <= 'C' "
"FOR d IN testView SEARCH IN_RANGE(d.name, 'A', 'C', true, true) "
"RETURN [ customscorer(d, obj[0] > obj[1] ? 1 : 2), customscorer(d, obj[1] > obj[2] ? 1 : 2) ] ";
CHECK(arangodb::tests::assertRules(
@ -1148,7 +1148,7 @@ TEST_CASE("IResearchQueryScorer", "[iresearch][iresearch-query]") {
{
std::string const queryString =
"LET obj = _NONDETERM_([ 2, 5 ]) "
"FOR d IN testView SEARCH d.name >= 'A' AND d.name <= 'C' "
"FOR d IN testView SEARCH IN_RANGE(d.name, 'A', 'C', true, true) "
"RETURN [ customscorer(d, 5*obj[0]*TO_NUMBER(obj[1] > obj[2])/obj[1] - 1), customscorer(d, 5*obj[0]*TO_NUMBER(obj[1] > obj[2])/obj[1] - 1) ] ";
CHECK(arangodb::tests::assertRules(
@ -1259,7 +1259,7 @@ TEST_CASE("IResearchQueryScorer", "[iresearch][iresearch-query]") {
{
std::string const queryString =
"LET obj = _NONDETERM_([ 2, 5 ]) "
"FOR d IN testView SEARCH d.name >= 'A' AND d.name <= 'C' "
"FOR d IN testView SEARCH IN_RANGE(d.name, 'A', 'C', true, true) "
"RETURN [ customscorer(d, { [ CONCAT(obj[0], obj[1]) ] : 1 }), customscorer(d, { [ CONCAT(obj[0], obj[1]) ] : 1 }) ]";
CHECK(arangodb::tests::assertRules(
@ -1346,7 +1346,7 @@ TEST_CASE("IResearchQueryScorer", "[iresearch][iresearch-query]") {
{
std::string const queryString =
"LET obj = _NONDETERM_([ 2, 5 ]) "
"FOR d IN testView SEARCH d.name >= 'A' AND d.name <= 'C' "
"FOR d IN testView SEARCH IN_RANGE(d.name, 'A', 'C', true, true) "
"RETURN [ customscorer(d, { foo : obj[1] }), customscorer(d, { foo : obj[1] }) ]";
CHECK(arangodb::tests::assertRules(
@ -1433,7 +1433,7 @@ TEST_CASE("IResearchQueryScorer", "[iresearch][iresearch-query]") {
{
std::string const queryString =
"LET obj = _NONDETERM_([ 2, 5 ]) "
"FOR d IN testView SEARCH d.name >= 'A' AND d.name <= 'C' "
"FOR d IN testView SEARCH IN_RANGE(d.name, 'A', 'C', true, true) "
"RETURN [ customscorer(d, 5*obj[0]*TO_NUMBER(obj[1] > obj[2])/obj[1] - 1), customscorer(d, 5*obj[0]*TO_NUMBER(obj[1] > obj[2])/obj[1] - 2) ] ";
CHECK(arangodb::tests::assertRules(
@ -1553,7 +1553,7 @@ TEST_CASE("IResearchQueryScorer", "[iresearch][iresearch-query]") {
{
std::string const queryString =
"LET obj = _NONDETERM_([ 2, 5 ]) "
"FOR d IN testView SEARCH d.name >= 'A' AND d.name <= 'C' "
"FOR d IN testView SEARCH IN_RANGE(d.name, 'A', 'C', true, true) "
"RETURN [ customscorer(d, obj any == 3), customscorer(d, obj any == 3) ]";
CHECK(arangodb::tests::assertRules(
@ -1640,7 +1640,7 @@ TEST_CASE("IResearchQueryScorer", "[iresearch][iresearch-query]") {
{
std::string const queryString =
"LET obj = _NONDETERM_([ 2, 5 ]) "
"FOR d IN testView SEARCH d.name >= 'A' AND d.name <= 'C' "
"FOR d IN testView SEARCH IN_RANGE(d.name, 'A', 'C', true, true) "
"RETURN [ customscorer(d, obj any == 3), customscorer(d, obj all == 3) ]";
CHECK(arangodb::tests::assertRules(
@ -1724,7 +1724,7 @@ TEST_CASE("IResearchQueryScorer", "[iresearch][iresearch-query]") {
// con't deduplicate scorers with default values
{
std::string const queryString =
"FOR d IN testView SEARCH d.name >= 'A' AND d.name <= 'C' "
"FOR d IN testView SEARCH IN_RANGE(d.name, 'A', 'C', true, true) "
"RETURN [ tfidf(d), tfidf(d, false) ] ";
CHECK(arangodb::tests::assertRules(
@ -1828,4 +1828,4 @@ TEST_CASE("IResearchQueryScorer", "[iresearch][iresearch-query]") {
// -----------------------------------------------------------------------------
// --SECTION-- END-OF-FILE
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------

View File

@ -42,6 +42,7 @@
#include "IResearch/AqlHelper.h"
#include "IResearch/ExpressionFilter.h"
#include "IResearch/IResearchFilterFactory.h"
#include "IResearch/IResearchViewNode.h"
#include "IResearch/IResearchKludge.h"
#include "IResearch/VelocyPackHelper.h"
#include "tests/Basics/icu-helper.h"
@ -440,6 +441,59 @@ std::string mangleStringIdentity(std::string name) {
return name;
}
void assertFilterOptimized(
TRI_vocbase_t& vocbase,
std::string const& queryString,
irs::filter const& expectedFilter,
arangodb::aql::ExpressionContext* exprCtx /*= nullptr*/,
std::shared_ptr<arangodb::velocypack::Builder> bindVars /* = nullptr */
) {
auto options = arangodb::velocypack::Parser::fromJson(
// "{ \"tracing\" : 1 }"
"{ }"
);
arangodb::aql::Query query(
false,
vocbase,
arangodb::aql::QueryString(queryString),
bindVars,
options,
arangodb::aql::PART_MAIN
);
query.prepare(arangodb::QueryRegistryFeature::registry());
CHECK(query.plan());
auto& plan = *query.plan();
arangodb::SmallVector<arangodb::aql::ExecutionNode*>::allocator_type::arena_type a;
arangodb::SmallVector<arangodb::aql::ExecutionNode*> nodes{a};
plan.findNodesOfType(nodes, arangodb::aql::ExecutionNode::ENUMERATE_IRESEARCH_VIEW, true);
CHECK(nodes.size() == 1);
auto* viewNode = arangodb::aql::ExecutionNode::castTo<arangodb::iresearch::IResearchViewNode*>(nodes.front());
CHECK(viewNode);
// execution time
{
arangodb::transaction ::Methods trx(
arangodb::transaction::StandaloneContext::Create(vocbase),
{},
{},
{},
arangodb::transaction::Options()
);
irs::Or actualFilter;
arangodb::iresearch::QueryContext const ctx{ &trx, &plan, plan.getAst(), exprCtx, &viewNode->outVariable() };
CHECK(arangodb::iresearch::FilterFactory::filter(&actualFilter, ctx, viewNode->filterCondition()));
CHECK(!actualFilter.empty());
CHECK(expectedFilter == *actualFilter.begin());
}
}
void assertExpressionFilter(
std::string const& queryString,
irs::boost::boost_t boost /*= irs::boost::no_boost()*/,

View File

@ -132,6 +132,14 @@ void assertFilterBoost(
irs::filter const& actual
);
void assertFilterOptimized(
TRI_vocbase_t& vocbase,
std::string const& queryString,
irs::filter const& expectedFilter,
arangodb::aql::ExpressionContext* exprCtx = nullptr,
std::shared_ptr<arangodb::velocypack::Builder> bindVars = nullptr
);
void assertFilter(
bool parseOk,
bool execOk,
@ -169,4 +177,4 @@ void assertFilterParseFail(
std::shared_ptr<arangodb::velocypack::Builder> bindVars = nullptr
);
#endif
#endif

View File

@ -834,6 +834,24 @@ function IResearchAqlTestSuite(args) {
linksView.drop();
entities.drop();
links.drop();
},
testAttributeInRangeOpenInterval : function () {
var result = db._query("FOR doc IN UnitTestsView SEARCH IN_RANGE(doc.c, 1, 3, false, false) OPTIONS { waitForSync : true } RETURN doc").toArray();
assertEqual(result.length, 4);
result.forEach(function(res) {
assertTrue(res.c > 1 && res.c < 3);
});
},
testAttributeInRangeClosedInterval : function () {
var result = db._query("FOR doc IN UnitTestsView SEARCH IN_RANGE(doc.c, 1, 3, true, true) OPTIONS { waitForSync : true } RETURN doc").toArray();
assertEqual(result.length, 12);
result.forEach(function(res) {
assertTrue(res.c >= 1 && res.c <= 3);
});
}
};
}

View File

@ -857,6 +857,24 @@ function iResearchAqlTestSuite () {
linksView.drop();
entities.drop();
links.drop();
},
testAttributeInRangeOpenInterval : function () {
var result = db._query("FOR doc IN UnitTestsView SEARCH IN_RANGE(doc.c, 1, 3, false, false) OPTIONS { waitForSync : true } RETURN doc").toArray();
assertEqual(result.length, 4);
result.forEach(function(res) {
assertTrue(res.c > 1 && res.c < 3);
});
},
testAttributeInRangeClosedInterval : function () {
var result = db._query("FOR doc IN UnitTestsView SEARCH IN_RANGE(doc.c, 1, 3, true, true) OPTIONS { waitForSync : true } RETURN doc").toArray();
assertEqual(result.length, 12);
result.forEach(function(res) {
assertTrue(res.c >= 1 && res.c <= 3);
});
}
};
}