1
0
Fork 0
arangodb/tests/IResearch/IResearchFilterArrayInterva...

712 lines
32 KiB
C++

////////////////////////////////////////////////////////////////////////////////
/// 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 <velocypack/Parser.h>
#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<arangodb::Logger::AUTHENTICATION, arangodb::LogLevel::ERR> {
protected:
arangodb::tests::mocks::MockAqlServer server;
private:
TRI_vocbase_t* _vocbase;
protected:
IResearchFilterArrayIntervalTest() {
arangodb::tests::init();
auto& functions = server.getFeature<arangodb::aql::AqlFunctionFeature>();
// 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>();
arangodb::iresearch::IResearchAnalyzerFeature::EmplaceResult result;
auto& dbFeature = server.getFeature<arangodb::DatabaseFeature>();
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<irs::by_range const&>(*filter);
irs::by_range expected;
expected.field(field);
expected.term<irs::Bound::MIN>(term);
expected.include<irs::Bound::MIN>(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<irs::by_range const&>(*filter);
irs::by_range expected;
expected.field(field);
expected.term<irs::Bound::MIN>(term);
expected.include<irs::Bound::MIN>(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<irs::by_range const&>(*filter);
irs::by_range expected;
expected.field(field);
expected.term<irs::Bound::MAX>(term);
expected.include<irs::Bound::MAX>(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<irs::by_range const&>(*filter);
irs::by_range expected;
expected.field(field);
expected.term<irs::Bound::MAX>(term);
expected.include<irs::Bound::MAX>(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<const irs::Or&>(*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<const irs::And&>(*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<const irs::And&>(*actual.begin());
EXPECT_EQ(3, root.size());
EXPECT_EQ(boost, root.boost());
return root.begin();
};
std::vector<std::pair<std::string,
std::pair<std::function<irs::boolean_filter::const_iterator(irs::Or&, iresearch::boost_t)>,
std::function<void(irs::boolean_filter::const_iterator&,
irs::bytes_ref const&,
irs::string_ref const&)>>>>
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::byte_type>(irs::string_ref("1")), mangleStringIdentity("a"));
++subFiltersIterator;
operation.second.second(subFiltersIterator, irs::ref_cast<irs::byte_type>(irs::string_ref("2")), mangleStringIdentity("a"));
++subFiltersIterator;
operation.second.second(subFiltersIterator, irs::ref_cast<irs::byte_type>(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::byte_type>(irs::string_ref("1")), mangleString("a.b.c[412].e.f", "test_analyzer"));
++subFiltersIterator;
operation.second.second(subFiltersIterator, irs::ref_cast<irs::byte_type>(irs::string_ref("2")), mangleString("a.b.c[412].e.f", "test_analyzer"));
++subFiltersIterator;
operation.second.second(subFiltersIterator, irs::ref_cast<irs::byte_type>(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::byte_type>(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<irs::by_granular_range const&>(*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<irs::Bound::MAX>(stream);
expected.include<irs::Bound::MAX>(false);
} else if (operation.first == "ANY >=" || operation.first == "ALL >=" || operation.first == "NONE <") {
expected.insert<irs::Bound::MAX>(stream);
expected.include<irs::Bound::MAX>(true);
} else if (operation.first == "ANY <" || operation.first == "ALL <" || operation.first == "NONE >=") {
expected.insert<irs::Bound::MIN>(stream);
expected.include<irs::Bound::MIN>(false);
} else if (operation.first == "ANY <=" || operation.first == "ALL <=" || operation.first == "NONE >") {
expected.insert<irs::Bound::MIN>(stream);
expected.include<irs::Bound::MIN>(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::byte_type>(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::byte_type>(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::byte_type>(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::byte_type>(irs::string_ref("1")),
mangleStringIdentity("a.b.c.e.f"));
++subFiltersIterator;
operation.second.second(subFiltersIterator,
irs::ref_cast<irs::byte_type>(irs::string_ref("2")),
mangleStringIdentity("a.b.c.e.f"));
++subFiltersIterator;
operation.second.second(subFiltersIterator,
irs::ref_cast<irs::byte_type>(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::byte_type>(irs::string_ref("1")),
mangleString("a.b.c.e.f", "test_analyzer"));
++subFiltersIterator;
operation.second.second(subFiltersIterator,
irs::ref_cast<irs::byte_type>(irs::string_ref("2")),
mangleString("a.b.c.e.f", "test_analyzer"));
++subFiltersIterator;
operation.second.second(subFiltersIterator,
irs::ref_cast<irs::byte_type>(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::byte_type>(irs::string_ref("1")),
mangleStringIdentity("a.b.c.e.f"));
++subFiltersIterator;
EXPECT_EQ(arangodb::iresearch::ByExpression::type(), subFiltersIterator->type());
EXPECT_NE(nullptr, dynamic_cast<arangodb::iresearch::ByExpression const*>(&*subFiltersIterator));
++subFiltersIterator;
operation.second.second(subFiltersIterator,
irs::ref_cast<irs::byte_type>(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::byte_type>(irs::string_ref("1")),
mangleStringIdentity("a.b.c.e.f"));
++subFiltersIterator;
EXPECT_EQ(arangodb::iresearch::ByExpression::type(), subFiltersIterator->type());
EXPECT_NE(nullptr, dynamic_cast<arangodb::iresearch::ByExpression const*>(&*subFiltersIterator));
++subFiltersIterator;
operation.second.second(subFiltersIterator,
irs::ref_cast<irs::byte_type>(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::byte_type>(irs::string_ref("1")),
mangleStringIdentity("a.b.c.e.f"));
++subFiltersIterator;
EXPECT_EQ(arangodb::iresearch::ByExpression::type(), subFiltersIterator->type());
EXPECT_NE(nullptr, dynamic_cast<arangodb::iresearch::ByExpression const*>(&*subFiltersIterator));
++subFiltersIterator;
EXPECT_EQ(arangodb::iresearch::ByExpression::type(), subFiltersIterator->type());
EXPECT_NE(nullptr, dynamic_cast<arangodb::iresearch::ByExpression const*>(&*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::byte_type>(irs::string_ref("1")),
mangleStringIdentity("a.b.c.e.f"));
++subFiltersIterator;
EXPECT_EQ(arangodb::iresearch::ByExpression::type(), subFiltersIterator->type());
EXPECT_NE(nullptr, dynamic_cast<arangodb::iresearch::ByExpression const*>(&*subFiltersIterator));
++subFiltersIterator;
operation.second.second(subFiltersIterator,
irs::ref_cast<irs::byte_type>(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::byte_type>(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<irs::empty>();
} else {
expected.add<irs::all>();
}
expected.boost(2.5);
assertFilterSuccess(
vocbase(), queryString, expected);
}
}
}