1
0
Fork 0
arangodb/3rdParty/iresearch/tests/iql/query_builder_test.cpp

1329 lines
46 KiB
C++

////////////////////////////////////////////////////////////////////////////////
/// DISCLAIMER
///
/// Copyright 2016 by EMC Corporation, All Rights Reserved
///
/// 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 EMC Corporation
///
/// @author Andrey Abramov
/// @author Vasiliy Nabatchikov
////////////////////////////////////////////////////////////////////////////////
#include "gtest/gtest.h"
#include "tests_config.hpp"
#include "tests_shared.hpp"
#include "analysis/analyzers.hpp"
#include "analysis/token_streams.hpp"
#include "formats/formats_10.hpp"
#include "store/memory_directory.hpp"
#include "index/doc_generator.hpp"
#include "index/index_reader.hpp"
#include "index/index_writer.hpp"
#include "index/index_tests.hpp"
#include "iql/query_builder.hpp"
#include "search/boolean_filter.hpp"
#include "search/scorers.hpp"
#include "search/term_filter.hpp"
#include "utils/runtime_utils.hpp"
namespace tests {
class test_sort: public iresearch::sort {
public:
DECLARE_SORT_TYPE();
DECLARE_FACTORY();
class prepared : sort::prepared {
public:
DEFINE_FACTORY_INLINE(prepared)
prepared() { }
virtual void collect(
irs::byte_type*,
const irs::index_reader& index,
const irs::sort::field_collector* field,
const irs::sort::term_collector* term
) const override {
// do not need to collect stats
}
virtual irs::sort::field_collector::ptr prepare_field_collector() const override {
return nullptr; // do not need to collect stats
}
virtual std::pair<score_ctx::ptr, irs::score_f> prepare_scorer(
const iresearch::sub_reader&,
const iresearch::term_reader&,
const irs::byte_type* query_attrs,
const irs::attribute_view& doc_attrs,
irs::boost_t
) const override {
return { nullptr, nullptr };
}
virtual irs::sort::term_collector::ptr prepare_term_collector() const override {
return nullptr; // do not need to collect stats
}
virtual const iresearch::flags& features() const override {
return iresearch::flags::empty_instance();
}
virtual void prepare_score(iresearch::byte_type* score) const override {}
virtual void prepare_stats(irs::byte_type*) const override { }
virtual void add(iresearch::byte_type* dst, const iresearch::byte_type* src) const override {}
virtual bool less(const iresearch::byte_type* lhs, const iresearch::byte_type* rhs) const override { throw std::bad_function_call(); }
std::pair<size_t, size_t> score_size() const override {
return std::make_pair(size_t(0), size_t(0));
}
std::pair<size_t, size_t> stats_size() const override {
return std::make_pair(size_t(0), size_t(0));
}
};
test_sort():sort(test_sort::type()) {}
virtual sort::prepared::ptr prepare() const { return test_sort::prepared::make<test_sort::prepared>(); }
};
DEFINE_SORT_TYPE(test_sort)
DEFINE_FACTORY_DEFAULT(test_sort)
class IqlQueryBuilderTestSuite: public ::testing::Test {
virtual void SetUp() {
// Code here will be called immediately after the constructor (right before each test).
// use the following code to enble parser debug outut
//::iresearch::iql::debug(parser, [true|false]);
// ensure stopwords are loaded/cached for the 'en' locale used for text analysis below
{
// same env variable name as iresearch::analysis::text_token_stream::STOPWORD_PATH_ENV_VARIABLE
const auto text_stopword_path_var = "IRESEARCH_TEXT_STOPWORD_PATH";
const char* czOldStopwordPath = iresearch::getenv(text_stopword_path_var);
std::string sOldStopwordPath = czOldStopwordPath == nullptr ? "" : czOldStopwordPath;
iresearch::setenv(text_stopword_path_var, IResearch_test_resource_dir, true);
auto locale = irs::locale_utils::locale("en");
const std::string tmp_str;
irs::analysis::analyzers::get("text", irs::text_format::text, "en"); // stream needed only to load stopwords
if (czOldStopwordPath) {
iresearch::setenv(text_stopword_path_var, sOldStopwordPath.c_str(), true);
}
}
}
virtual void TearDown() {
// Code here will be called immediately after each test (right before the destructor).
}
};
class analyzed_string_field: public templates::string_field {
public:
analyzed_string_field(const iresearch::string_ref& name, const iresearch::string_ref& value)
: templates::string_field(name, value),
token_stream_(irs::analysis::analyzers::get("text", irs::text_format::text, "en")) {
if (!token_stream_) {
throw std::runtime_error("Failed to get 'text' analyzer for args: en");
}
}
virtual ~analyzed_string_field() {}
virtual iresearch::token_stream& get_tokens() const override {
const iresearch::string_ref& value = this->value();
token_stream_->reset(value);
return *token_stream_;
}
private:
static std::unordered_set<std::string> ignore_set_; // there are no stopwords for the 'c' locale in tests
iresearch::analysis::analyzer::ptr token_stream_;
};
std::unordered_set<std::string> analyzed_string_field::ignore_set_;
iresearch::directory_reader load_json(
iresearch::directory& dir,
const std::string json_resource,
bool analyze_text = false
) {
static auto analyzed_field_factory = [](
tests::document& doc,
const std::string& name,
const tests::json_doc_generator::json_value& value) {
if (value.is_string()) {
doc.insert(std::make_shared<analyzed_string_field>(
iresearch::string_ref(name),
value.str
));
} else if (value.is_null()) {
doc.insert(std::make_shared<analyzed_string_field>(
iresearch::string_ref(name),
"null"
));
} else if (value.is_bool() && value.b) {
doc.insert(std::make_shared<analyzed_string_field>(
iresearch::string_ref(name),
"true"
));
} else if (value.is_bool() && !value.b) {
doc.insert(std::make_shared<analyzed_string_field>(
iresearch::string_ref(name),
"false"
));
} else if (value.is_number()) {
const auto str = std::to_string(value.as_number<uint64_t>());
doc.insert(std::make_shared<analyzed_string_field>(
iresearch::string_ref(name),
str
));
}
};
static auto generic_field_factory = [](
tests::document& doc,
const std::string& name,
const tests::json_doc_generator::json_value& data) {
if (data.is_string()) {
doc.insert(std::make_shared<templates::string_field>(
irs::string_ref(name),
data.str
));
} else if (data.is_null()) {
doc.insert(std::make_shared<tests::binary_field>());
auto& field = (doc.indexed.end() - 1).as<tests::binary_field>();
field.name(iresearch::string_ref(name));
field.value(irs::null_token_stream::value_null());
} else if (data.is_bool() && data.b) {
doc.insert(std::make_shared<tests::binary_field>());
auto& field = (doc.indexed.end() - 1).as<tests::binary_field>();
field.name(iresearch::string_ref(name));
field.value(irs::boolean_token_stream::value_true());
} else if (data.is_bool() && !data.b) {
doc.insert(std::make_shared<tests::binary_field>());
auto& field = (doc.indexed.end() - 1).as<tests::binary_field>();
field.name(iresearch::string_ref(name));
field.value(irs::boolean_token_stream::value_true());
} else if (data.is_number()) {
const double dValue = data.as_number<double_t>();
// 'value' can be interpreted as a double
doc.insert(std::make_shared<tests::double_field>());
auto& field = (doc.indexed.end() - 1).as<tests::double_field>();
field.name(iresearch::string_ref(name));
field.value(dValue);
}
};
auto codec_ptr = irs::formats::get("1_0");
auto writer =
iresearch::index_writer::make(dir, codec_ptr, iresearch::OM_CREATE);
json_doc_generator generator(
test_base::resource(json_resource),
analyze_text ? analyzed_field_factory : &tests::generic_json_field_factory);
const document* doc;
while ((doc = generator.next()) != nullptr) {
insert(*writer,
doc->indexed.begin(), doc->indexed.end(),
doc->stored.begin(), doc->stored.end()
);
}
writer->commit();
return iresearch::directory_reader::open(dir, codec_ptr);
}
}
using namespace tests;
using namespace iresearch::iql;
// -----------------------------------------------------------------------------
// --SECTION-- test suite
// -----------------------------------------------------------------------------
TEST_F(IqlQueryBuilderTestSuite, test_query_builder) {
sequence_function::contextual_function_t fnNum = [](
sequence_function::contextual_buffer_t& buf,
const std::locale& locale,
void* cookie,
const sequence_function::contextual_function_args_t& args
)->bool {
iresearch::bstring value;
bool bValue;
args[0].value(value, bValue, locale, cookie);
double dValue = strtod(iresearch::ref_cast<char>(value).c_str(), nullptr);
iresearch::numeric_token_stream stream;
stream.reset((double_t)dValue);
auto& term = stream.attributes().get<iresearch::term_attribute>();
while (stream.next()) {
buf.append(term->value());
}
return true;
};
iresearch::memory_directory dir;
auto reader = load_json(dir, "simple_sequential.json");
ASSERT_EQ(1, reader.size());
auto& segment = reader[0]; // assume 0 is id of first/only segment
// single string term
{
irs::bytes_ref actual_value;
auto column = segment.column_reader("name");
ASSERT_NE(nullptr, column);
auto values = column->values();
auto query = query_builder().build("name==A", std::locale::classic());
ASSERT_NE(nullptr, query.filter.get());
ASSERT_EQ(nullptr, query.error);
ASSERT_EQ(nullptr, query.limit);
auto pQuery = query.filter->prepare(reader, iresearch::order::prepared::unordered());
ASSERT_NE(nullptr, pQuery.get());
auto docsItr = pQuery->execute(segment);
ASSERT_NE(nullptr, docsItr.get());
ASSERT_TRUE(docsItr->next());
ASSERT_TRUE(values(docsItr->value(), actual_value));
ASSERT_EQ("A", irs::to_string<irs::string_ref>(actual_value.c_str()));
ASSERT_FALSE(docsItr->next());
}
/* FIXME reenable once bug is fixed
// single numeric term
{
sequence_function seq_function(fnNum, 1);
sequence_functions seq_functions = {
{ "#", seq_function },
};
functions functions(seq_functions);
auto query = query_builder(functions).build("seq==#(0)", std::locale::classic());
ASSERT_NE(nullptr, query.filter.get());
ASSERT_EQ(nullptr, query.error);
ASSERT_EQ(nullptr, query.limit);
auto pQuery = query.filter->prepare(reader);
ASSERT_NE(nullptr, pQuery.get());
auto docsItr = pQuery->execute(segment);
ASSERT_NE(nullptr, docsItr.get());
ASSERT_TRUE(docsItr->next());
tests::field_visitor visitor("name");
segment.document(docsItr->value(), visitor);
ASSERT_EQ(std::string("A"), visitor.v_string);
ASSERT_FALSE(docsItr->next());
}
*/
// term negation
{
irs::bytes_ref actual_value;
irs::bytes_ref_input in;
auto column = segment.column_reader("name");
ASSERT_NE(nullptr, column);
auto values = column->values();
auto query = query_builder().build("name!=A", std::locale::classic());
ASSERT_NE(nullptr, query.filter.get());
ASSERT_EQ(nullptr, query.error);
ASSERT_EQ(nullptr, query.limit);
auto pQuery = query.filter->prepare(reader, iresearch::order::prepared::unordered());
ASSERT_NE(nullptr, pQuery.get());
auto docsItr = pQuery->execute(segment);
ASSERT_NE(nullptr, docsItr.get());
for (size_t count = segment.docs_count() - 1; count > 0; --count) {
ASSERT_TRUE(docsItr->next());
ASSERT_TRUE(values(docsItr->value(), actual_value)); in.reset(actual_value);
ASSERT_NE("A", irs::read_string<std::string>(in));
}
ASSERT_FALSE(docsItr->next());
}
// term union
{
irs::bytes_ref actual_value;
auto column = segment.column_reader("name");
ASSERT_NE(nullptr, column);
auto values = column->values();
auto query = query_builder().build("name==A || name==B OR name==C", std::locale::classic());
ASSERT_NE(nullptr, query.filter.get());
ASSERT_EQ(nullptr, query.error);
ASSERT_EQ(nullptr, query.limit);
auto pQuery = query.filter->prepare(reader, iresearch::order::prepared::unordered());
ASSERT_NE(nullptr, pQuery.get());
auto docsItr = pQuery->execute(segment);
ASSERT_NE(nullptr, docsItr.get());
ASSERT_TRUE(docsItr->next());
ASSERT_TRUE(values(docsItr->value(), actual_value));
ASSERT_EQ("A", irs::to_string<irs::string_ref>(actual_value.c_str()));
ASSERT_TRUE(docsItr->next());
ASSERT_TRUE(values(docsItr->value(), actual_value));
ASSERT_EQ("B", irs::to_string<irs::string_ref>(actual_value.c_str()));
ASSERT_TRUE(docsItr->next());
ASSERT_TRUE(values(docsItr->value(), actual_value));
ASSERT_EQ("C", irs::to_string<irs::string_ref>(actual_value.c_str()));
ASSERT_FALSE(docsItr->next());
}
/* FIXME reenable once bug is fixed
// term intersection
{
sequence_function seq_function(fnNum, 1);
sequence_functions seq_functions = {
{ "#", seq_function },
};
functions functions(seq_functions);
auto query = query_builder(functions).build("name==A && seq==#(0) AND same==xyz", std::locale::classic());
ASSERT_NE(nullptr, query.filter.get());
ASSERT_EQ(nullptr, query.error);
ASSERT_EQ(nullptr, query.limit);
auto pQuery = query.filter->prepare(reader);
ASSERT_NE(nullptr, pQuery.get());
auto docsItr = pQuery->execute(segment);
ASSERT_NE(nullptr, docsItr.get());
ASSERT_TRUE(docsItr->next());
tests::field_visitor visitor("name");
segment.document(docsItr->value(), visitor);
ASSERT_EQ(std::string("A"), visitor.v_string);
ASSERT_FALSE(docsItr->next());
}
*/
// single term greater ranges
{
irs::bytes_ref actual_value;
irs::bytes_ref_input in;
std::unordered_set<irs::string_ref> expected = { "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z" };
double_t seq;
auto seq_column = segment.column_reader("seq");
ASSERT_NE(nullptr, seq_column);
auto seq_values = seq_column->values();
auto name_column = segment.column_reader("name");
ASSERT_NE(nullptr, name_column);
auto name_values = name_column->values();
auto query = query_builder().build("name > M", std::locale::classic());
ASSERT_NE(nullptr, query.filter.get());
ASSERT_EQ(nullptr, query.error);
ASSERT_EQ(nullptr, query.limit);
auto pQuery = query.filter->prepare(reader, iresearch::order::prepared::unordered());
ASSERT_NE(nullptr, pQuery.get());
auto docsItr = pQuery->execute(segment);
ASSERT_NE(nullptr, docsItr.get());
while (docsItr->next()) {
ASSERT_TRUE(seq_values(docsItr->value(), actual_value)); in.reset(actual_value);
seq = irs::read_zvdouble(in);
if (seq < 26) { // validate only first 26 records [A-Z]
ASSERT_TRUE(name_values(docsItr->value(), actual_value)); in.reset(actual_value);
ASSERT_EQ(1, expected.erase(irs::to_string<irs::string_ref>(actual_value.c_str())));
}
}
ASSERT_TRUE(expected.empty());
}
// single term greater-equal ranges
{
irs::bytes_ref actual_value;
irs::bytes_ref_input in;
std::unordered_set<irs::string_ref> expected = { "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z" };
double_t seq;
auto seq_column = segment.column_reader("seq");
ASSERT_NE(nullptr, seq_column);
auto seq_values = seq_column->values();
auto name_column = segment.column_reader("name");
ASSERT_NE(nullptr, name_column);
auto name_values = name_column->values();
auto query = query_builder().build("name >= M", std::locale::classic());
ASSERT_NE(nullptr, query.filter.get());
ASSERT_EQ(nullptr, query.error);
ASSERT_EQ(nullptr, query.limit);
auto pQuery = query.filter->prepare(reader, iresearch::order::prepared::unordered());
ASSERT_NE(nullptr, pQuery.get());
auto docsItr = pQuery->execute(segment);
ASSERT_NE(nullptr, docsItr.get());
while (docsItr->next()) {
ASSERT_TRUE(seq_values(docsItr->value(), actual_value)); in.reset(actual_value);
seq = irs::read_zvdouble(in);
if (seq < 26) { // validate only first 26 records [A-Z]
ASSERT_TRUE(name_values(docsItr->value(), actual_value)); in.reset(actual_value);
ASSERT_EQ(1, expected.erase(irs::to_string<irs::string_ref>(actual_value.c_str())));
}
}
ASSERT_TRUE(expected.empty());
}
// single term lesser-equal ranges
{
irs::bytes_ref actual_value;
irs::bytes_ref_input in;
std::unordered_set<irs::string_ref> expected = { "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N" };
double_t seq;
auto seq_column = segment.column_reader("seq");
ASSERT_NE(nullptr, seq_column);
auto seq_values = seq_column->values();
auto name_column = segment.column_reader("name");
ASSERT_NE(nullptr, name_column);
auto name_values = name_column->values();
auto query = query_builder().build("name <= N", std::locale::classic());
ASSERT_NE(nullptr, query.filter.get());
ASSERT_EQ(nullptr, query.error);
ASSERT_EQ(nullptr, query.limit);
auto pQuery = query.filter->prepare(reader, iresearch::order::prepared::unordered());
ASSERT_NE(nullptr, pQuery.get());
auto docsItr = pQuery->execute(segment);
ASSERT_NE(nullptr, docsItr.get());
while (docsItr->next()) {
ASSERT_TRUE(seq_values(docsItr->value(), actual_value)); in.reset(actual_value);
seq = irs::read_zvdouble(in);
if (seq < 26) { // validate only first 26 records [A-Z]
ASSERT_TRUE(name_values(docsItr->value(), actual_value)); in.reset(actual_value);
ASSERT_EQ(1, expected.erase(irs::to_string<irs::string_ref>(actual_value.c_str())));
}
}
ASSERT_TRUE(expected.empty());
}
// single term lesser ranges
{
irs::bytes_ref actual_value;
irs::bytes_ref_input in;
std::unordered_set<irs::string_ref> expected = { "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M" };
double_t seq;
auto seq_column = segment.column_reader("seq");
ASSERT_NE(nullptr, seq_column);
auto seq_values = seq_column->values();
auto name_column = segment.column_reader("name");
ASSERT_NE(nullptr, name_column);
auto name_values = name_column->values();
auto query = query_builder().build("name < N", std::locale::classic());
ASSERT_NE(nullptr, query.filter.get());
ASSERT_EQ(nullptr, query.error);
ASSERT_EQ(nullptr, query.limit);
auto pQuery = query.filter->prepare(reader, iresearch::order::prepared::unordered());
ASSERT_NE(nullptr, pQuery.get());
auto docsItr = pQuery->execute(segment);
ASSERT_NE(nullptr, docsItr.get());
while (docsItr->next()) {
ASSERT_TRUE(seq_values(docsItr->value(), actual_value)); in.reset(actual_value);
seq = irs::read_zvdouble(in);
if (seq < 26) { // validate only first 26 records [A-Z]
ASSERT_TRUE(name_values(docsItr->value(), actual_value)); in.reset(actual_value);
ASSERT_EQ(1, expected.erase(irs::to_string<irs::string_ref>(actual_value.c_str())));
}
}
ASSERT_TRUE(expected.empty());
}
// limit
{
irs::bytes_ref actual_value;
auto column = segment.column_reader("name");
ASSERT_NE(nullptr, column);
auto values = column->values();
auto query = query_builder().build("name==A limit 42", std::locale::classic());
ASSERT_NE(nullptr, query.filter.get());
ASSERT_EQ(nullptr, query.error);
ASSERT_NE(nullptr, query.limit);
auto pQuery = query.filter->prepare(reader, iresearch::order::prepared::unordered());
ASSERT_NE(nullptr, pQuery.get());
auto docsItr = pQuery->execute(segment);
ASSERT_NE(nullptr, docsItr.get());
ASSERT_TRUE(docsItr->next());
ASSERT_TRUE(values(docsItr->value(), actual_value));
ASSERT_EQ("A", irs::to_string<irs::string_ref>(actual_value.c_str()));
ASSERT_FALSE(docsItr->next());
ASSERT_EQ(42, *(query.limit));
}
// ...........................................................................
// invalid
// ...........................................................................
// invalid query
{
auto query = query_builder().build("name==A bcd", std::locale::classic());
ASSERT_NE(nullptr, query.filter.get());
ASSERT_NE(nullptr, query.error);
ASSERT_EQ(nullptr, query.limit);
auto pQuery = query.filter->prepare(reader, iresearch::order::prepared::unordered());
ASSERT_EQ(nullptr, pQuery.get());
ASSERT_EQ(0, query.error->find("@([8 - 11], 11): syntax error"));
}
// valid query with missing dependencies (e.g. missing functions)
{
auto query = query_builder().build("name(A)", std::locale::classic());
ASSERT_NE(nullptr, query.filter.get());
ASSERT_NE(nullptr, query.error);
ASSERT_EQ(nullptr, query.limit);
auto pQuery = query.filter->prepare(reader, iresearch::order::prepared::unordered());
ASSERT_EQ(nullptr, pQuery.get());
ASSERT_EQ(std::string("@(7): parse error"), *(query.error));
}
// unsupported functionality by iResearch queries (e.g. like, ranges)
{
query_builder::branch_builder_function_t fnFail = [](
boolean_function::contextual_buffer_t&,
const std::locale&,
const iresearch::string_ref&,
void* cookie,
const boolean_function::contextual_function_args_t&
)->bool {
return false;
};
query_builder::branch_builders builders(&fnFail, nullptr, nullptr, nullptr, nullptr);
auto query = query_builder(builders).build("name==(A, bcd)", std::locale::classic());
ASSERT_NE(nullptr, query.filter.get());
ASSERT_NE(nullptr, query.error);
ASSERT_EQ(nullptr, query.limit);
auto pQuery = query.filter->prepare(reader, iresearch::order::prepared::unordered());
ASSERT_EQ(nullptr, pQuery.get());
ASSERT_EQ(std::string("filter conversion error, node: @5\n('name'@2 == ('A'@3, 'bcd'@4)@5)@6"), *(query.error));
}
}
TEST_F(IqlQueryBuilderTestSuite, test_query_builder_builders_default) {
irs::bytes_ref actual_value;
iresearch::memory_directory dir;
auto reader = load_json(dir, "simple_sequential.json");
ASSERT_EQ(1, reader.size());
auto& segment = reader[0]; // assume 0 is id of first/only segment
auto column = segment.column_reader("name");
ASSERT_NE(nullptr, column);
auto values = column->values();
// default range builder functr ()
{
query_builder::branch_builders builders;
auto query = query_builder(builders).build("name==(A, C)", std::locale::classic());
ASSERT_NE(nullptr, query.filter.get());
ASSERT_EQ(nullptr, query.error);
ASSERT_EQ(nullptr, query.limit);
auto pQuery = query.filter->prepare(reader);
ASSERT_NE(nullptr, pQuery.get());
auto docsItr = pQuery->execute(segment);
ASSERT_NE(nullptr, docsItr.get());
ASSERT_TRUE(docsItr->next());
ASSERT_TRUE(values(docsItr->value(), actual_value));
ASSERT_EQ("B", irs::to_string<irs::string_ref>(actual_value.c_str()));
ASSERT_FALSE(docsItr->next());
}
// default range builder functr (]
{
query_builder::branch_builders builders;
auto query = query_builder(builders).build("name==(A, B]", std::locale::classic());
ASSERT_NE(nullptr, query.filter.get());
ASSERT_EQ(nullptr, query.error);
ASSERT_EQ(nullptr, query.limit);
auto pQuery = query.filter->prepare(reader);
ASSERT_NE(nullptr, pQuery.get());
auto docsItr = pQuery->execute(segment);
ASSERT_NE(nullptr, docsItr.get());
ASSERT_TRUE(docsItr->next());
ASSERT_TRUE(values(docsItr->value(), actual_value));
ASSERT_EQ("B", irs::to_string<irs::string_ref>(actual_value.c_str()));
ASSERT_FALSE(docsItr->next());
}
// default range builder functr [)
{
query_builder::branch_builders builders;
auto query = query_builder(builders).build("name==[A, B)", std::locale::classic());
ASSERT_NE(nullptr, query.filter.get());
ASSERT_EQ(nullptr, query.error);
ASSERT_EQ(nullptr, query.limit);
auto pQuery = query.filter->prepare(reader);
ASSERT_NE(nullptr, pQuery.get());
auto docsItr = pQuery->execute(segment);
ASSERT_NE(nullptr, docsItr.get());
ASSERT_TRUE(docsItr->next());
ASSERT_TRUE(values(docsItr->value(), actual_value));
ASSERT_EQ("A", irs::to_string<irs::string_ref>(actual_value.c_str()));
ASSERT_FALSE(docsItr->next());
}
// default range builder functr []
{
query_builder::branch_builders builders;
auto query = query_builder(builders).build("name==[A, B]", std::locale::classic());
ASSERT_NE(nullptr, query.filter.get());
ASSERT_EQ(nullptr, query.error);
ASSERT_EQ(nullptr, query.limit);
auto pQuery = query.filter->prepare(reader);
ASSERT_NE(nullptr, pQuery.get());
auto docsItr = pQuery->execute(segment);
ASSERT_NE(nullptr, docsItr.get());
ASSERT_TRUE(docsItr->next());
ASSERT_TRUE(values(docsItr->value(), actual_value));
ASSERT_EQ("A", irs::to_string<irs::string_ref>(actual_value.c_str()));
ASSERT_TRUE(docsItr->next());
ASSERT_TRUE(values(docsItr->value(), actual_value));
ASSERT_EQ("B", irs::to_string<irs::string_ref>(actual_value.c_str()));
ASSERT_FALSE(docsItr->next());
}
// default similar '~=' operator
{
iresearch::memory_directory analyzed_dir;
auto analyzed_reader = load_json(analyzed_dir, "simple_sequential.json", true);
ASSERT_EQ(1, analyzed_reader.size());
auto& analyzed_segment = analyzed_reader[0]; // assume 0 is id of first/only segment
auto column = analyzed_segment.column_reader("name");
ASSERT_NE(nullptr, column);
auto analyzed_segment_values = column->values();
query_builder::branch_builders builders;
auto locale = irs::locale_utils::locale("en"); // a locale that exists in tests
auto query = query_builder(builders).build("name~=B", locale);
ASSERT_NE(nullptr, query.filter.get());
ASSERT_EQ(nullptr, query.error);
ASSERT_EQ(nullptr, query.limit);
auto pQuery = query.filter->prepare(analyzed_reader);
ASSERT_NE(nullptr, pQuery.get());
auto docsItr = pQuery->execute(analyzed_segment);
ASSERT_NE(nullptr, docsItr.get());
ASSERT_TRUE(docsItr->next());
ASSERT_TRUE(analyzed_segment_values(docsItr->value(), actual_value));
ASSERT_EQ("B", irs::to_string<irs::string_ref>(actual_value.c_str()));
ASSERT_FALSE(docsItr->next());
}
}
TEST_F(IqlQueryBuilderTestSuite, test_query_builder_builders_custom) {
irs::bytes_ref actual_value;
query_builder::branch_builder_function_t fnFail = [](
boolean_function::contextual_buffer_t&,
const std::locale&,
const iresearch::string_ref&,
void* cookie,
const boolean_function::contextual_function_args_t&
)->bool {
std::cerr << "File: " << __FILE__ << " Line: " << __LINE__ << " Failed" << std::endl;
throw "Fail";
};
query_builder::branch_builder_function_t fnEqual = [](
boolean_function::contextual_buffer_t& node,
const std::locale& locale,
const iresearch::string_ref& field,
void* cookie,
const boolean_function::contextual_function_args_t& args
)->bool {
iresearch::bstring value;
bool bValue;
args[0].value(value, bValue, locale, cookie);
node.proxy<iresearch::by_term>().field(field).term(std::move(value));
return true;
};
iresearch::memory_directory dir;
auto reader = load_json(dir, "simple_sequential.json");
ASSERT_EQ(1, reader.size());
auto& segment = reader[0]; // assume 0 is id of first/only segment
auto column = segment.column_reader("name");
ASSERT_NE(nullptr, column);
auto values = column->values();
// custom range builder functr ()
{
query_builder::branch_builders builders(&fnEqual, &fnFail, &fnFail, &fnFail, &fnFail);
auto query = query_builder(builders).build("name==(A, B)", std::locale::classic());
ASSERT_NE(nullptr, query.filter.get());
ASSERT_EQ(nullptr, query.error);
ASSERT_EQ(nullptr, query.limit);
auto pQuery = query.filter->prepare(reader);
ASSERT_NE(nullptr, pQuery.get());
auto docsItr = pQuery->execute(segment);
ASSERT_NE(nullptr, docsItr.get());
ASSERT_TRUE(docsItr->next());
ASSERT_TRUE(values(docsItr->value(), actual_value));
ASSERT_EQ("A", irs::to_string<irs::string_ref>(actual_value.c_str()));
ASSERT_FALSE(docsItr->next());
ASSERT_FALSE(docsItr->next());
}
// custom range builder functr (]
{
query_builder::branch_builders builders(&fnFail, &fnEqual, &fnFail, &fnFail, &fnFail);
auto query = query_builder(builders).build("name==(A, B]", std::locale::classic());
ASSERT_NE(nullptr, query.filter.get());
ASSERT_EQ(nullptr, query.error);
ASSERT_EQ(nullptr, query.limit);
auto pQuery = query.filter->prepare(reader);
ASSERT_NE(nullptr, pQuery.get());
auto docsItr = pQuery->execute(segment);
ASSERT_NE(nullptr, docsItr.get());
ASSERT_TRUE(docsItr->next());
ASSERT_TRUE(values(docsItr->value(), actual_value));
ASSERT_EQ("A", irs::to_string<irs::string_ref>(actual_value.c_str()));
ASSERT_FALSE(docsItr->next());
}
// custom range builder functr [)
{
query_builder::branch_builders builders(&fnFail, &fnFail, &fnEqual, &fnFail, &fnFail);
auto query = query_builder(builders).build("name==[A, B)", std::locale::classic());
ASSERT_NE(nullptr, query.filter.get());
ASSERT_EQ(nullptr, query.error);
ASSERT_EQ(nullptr, query.limit);
auto pQuery = query.filter->prepare(reader);
ASSERT_NE(nullptr, pQuery.get());
auto docsItr = pQuery->execute(segment);
ASSERT_NE(nullptr, docsItr.get());
ASSERT_TRUE(docsItr->next());
ASSERT_TRUE(values(docsItr->value(), actual_value));
ASSERT_EQ("A", irs::to_string<irs::string_ref>(actual_value.c_str()));
ASSERT_FALSE(docsItr->next());
}
// custom range builder functr []
{
query_builder::branch_builders builders(&fnFail, &fnFail, &fnFail, &fnEqual, &fnFail);
auto query = query_builder(builders).build("name==[A, B]", std::locale::classic());
ASSERT_NE(nullptr, query.filter.get());
ASSERT_EQ(nullptr, query.error);
ASSERT_EQ(nullptr, query.limit);
auto pQuery = query.filter->prepare(reader);
ASSERT_NE(nullptr, pQuery.get());
auto docsItr = pQuery->execute(segment);
ASSERT_NE(nullptr, docsItr.get());
ASSERT_TRUE(docsItr->next());
ASSERT_TRUE(values(docsItr->value(), actual_value));
ASSERT_EQ("A", irs::to_string<irs::string_ref>(actual_value.c_str()));
ASSERT_FALSE(docsItr->next());
}
// custom similar '~=' operator
{
query_builder::branch_builders builders(&fnFail, &fnFail, &fnFail, &fnFail, &fnEqual);
auto query = query_builder(builders).build("name~=A", std::locale::classic());
ASSERT_NE(nullptr, query.filter.get());
ASSERT_EQ(nullptr, query.error);
ASSERT_EQ(nullptr, query.limit);
auto pQuery = query.filter->prepare(reader);
ASSERT_NE(nullptr, pQuery.get());
auto docsItr = pQuery->execute(segment);
ASSERT_NE(nullptr, docsItr.get());
ASSERT_TRUE(docsItr->next());
ASSERT_TRUE(values(docsItr->value(), actual_value));
ASSERT_EQ("A", irs::to_string<irs::string_ref>(actual_value.c_str()));
ASSERT_FALSE(docsItr->next());
}
}
TEST_F(IqlQueryBuilderTestSuite, test_query_builder_bool_fns) {
boolean_function::contextual_function_t fnEqual = [](
boolean_function::contextual_buffer_t& node,
const std::locale& locale,
void* cookie,
const boolean_function::contextual_function_args_t& args
)->bool {
iresearch::bstring field;
iresearch::bstring value;
bool bField;
bool bValue;
args[0].value(field, bField, locale, cookie);
args[1].value(value, bValue, locale, cookie);
node.proxy<iresearch::by_term>().field(iresearch::ref_cast<char>(field)).term(std::move(value));
return true;
};
iresearch::memory_directory dir;
auto reader = load_json(dir, "simple_sequential.json");
ASSERT_EQ(1, reader.size());
auto& segment = reader[0]; // assume 0 is id of first/only segment
// user supplied boolean_function
{
irs::bytes_ref actual_value;
auto column = segment.column_reader("name");
ASSERT_NE(nullptr, column);
auto values = column->values();
boolean_function bool_function(fnEqual, 2);
boolean_functions bool_functions = {
{ "~", bool_function },
};
functions functions(bool_functions);
auto query = query_builder(functions).build("~(name, A)", std::locale::classic());
ASSERT_NE(nullptr, query.filter.get());
ASSERT_EQ(nullptr, query.error);
ASSERT_EQ(nullptr, query.limit);
auto pQuery = query.filter->prepare(reader);
ASSERT_NE(nullptr, pQuery.get());
auto docsItr = pQuery->execute(segment);
ASSERT_NE(nullptr, docsItr.get());
ASSERT_TRUE(docsItr->next());
ASSERT_TRUE(values(docsItr->value(), actual_value));
ASSERT_EQ("A", irs::to_string<irs::string_ref>(actual_value.c_str()));
ASSERT_FALSE(docsItr->next());
}
// user supplied negated boolean_function
{
irs::bytes_ref actual_value;
irs::bytes_ref_input in;
auto column = segment.column_reader("name");
ASSERT_NE(nullptr, column);
auto values = column->values();
boolean_function bool_function(fnEqual, 2);
boolean_functions bool_functions = {
{ "~", bool_function },
};
functions functions(bool_functions);
auto query = query_builder(functions).build("!~(name, A)", std::locale::classic());
ASSERT_NE(nullptr, query.filter.get());
ASSERT_EQ(nullptr, query.error);
ASSERT_EQ(nullptr, query.limit);
auto pQuery = query.filter->prepare(reader);
ASSERT_NE(nullptr, pQuery.get());
auto docsItr = pQuery->execute(segment);
ASSERT_NE(nullptr, docsItr.get());
for (size_t count = segment.docs_count() - 1; count > 0; --count) {
ASSERT_TRUE(docsItr->next());
ASSERT_TRUE(values(docsItr->value(), actual_value)); in.reset(actual_value);
ASSERT_NE("A", irs::read_string<std::string>(in));
}
ASSERT_FALSE(docsItr->next());
}
// user supplied boolean_function with expression args
{
irs::bytes_ref actual_value;
boolean_function::contextual_function_t fnCplx = [](
boolean_function::contextual_buffer_t& node,
const std::locale& locale,
void* cookie,
const boolean_function::contextual_function_args_t& args
)->bool {
auto& root = node.proxy<iresearch::Or>();
iresearch::bstring value;
bool bValueNil;
if (args.size() != 3 ||
!args[0].value(value, bValueNil, locale, cookie) ||
bValueNil ||
!args[1].branch(root.add<iresearch::iql::proxy_filter>(), locale, cookie) ||
!args[2].branch(root.add<iresearch::iql::proxy_filter>(), locale, cookie)) {
return false;
}
root.add<iresearch::by_term>().field("name").term(std::move(value));
return true;
};
boolean_function bool_function(fnCplx, 0, true);
boolean_function expr_function(fnEqual, 2);
boolean_functions bool_functions = {
{ "cplx", bool_function },
{ "expr", expr_function },
};
functions functions(bool_functions);
auto query = query_builder(functions).build("cplx(A, (name==C || name==D), expr(name, E))", std::locale::classic());
ASSERT_NE(nullptr, query.filter.get());
ASSERT_EQ(nullptr, query.error);
ASSERT_EQ(nullptr, query.limit);
auto pQuery = query.filter->prepare(reader);
ASSERT_NE(nullptr, pQuery.get());
auto docsItr = pQuery->execute(segment);
ASSERT_NE(nullptr, docsItr.get());
std::unordered_set<irs::string_ref> expected = { "A", "C", "D", "E" };
auto column = segment.column_reader("name");
ASSERT_NE(nullptr, column);
auto values = column->values();
while (docsItr->next()) {
ASSERT_TRUE(values(docsItr->value(), actual_value));
ASSERT_EQ(1, expected.erase(irs::to_string<irs::string_ref>(actual_value.c_str())));
}
ASSERT_TRUE(expected.empty());
}
}
TEST_F(IqlQueryBuilderTestSuite, test_query_builder_sequence_fns) {
irs::bytes_ref actual_value;
sequence_function::contextual_function_t fnValue = [](
sequence_function::contextual_buffer_t& buf,
const std::locale&,
void*,
const sequence_function::contextual_function_args_t& args
)->bool {
iresearch::bytes_ref value(reinterpret_cast<const iresearch::byte_type*>("A"), 1);
buf.append(value);
return true;
};
iresearch::memory_directory dir;
auto reader = load_json(dir, "simple_sequential.json");
ASSERT_EQ(1, reader.size());
auto& segment = reader[0]; // assume 0 is id of first/only segment
auto column = segment.column_reader("name");
ASSERT_NE(nullptr, column);
auto values = column->values();
// user supplied sequence_function
{
sequence_function seq_function(fnValue);
sequence_functions seq_functions = {
{ "valueA", seq_function },
};
functions functions(seq_functions);
auto query = query_builder(functions).build("name==valueA()", std::locale::classic());
ASSERT_NE(nullptr, query.filter.get());
ASSERT_EQ(nullptr, query.error);
ASSERT_EQ(nullptr, query.limit);
auto pQuery = query.filter->prepare(reader);
ASSERT_NE(nullptr, pQuery.get());
auto docsItr = pQuery->execute(segment);
ASSERT_NE(nullptr, docsItr.get());
ASSERT_TRUE(docsItr->next());
ASSERT_TRUE(values(docsItr->value(), actual_value));
ASSERT_EQ("A", irs::to_string<irs::string_ref>(actual_value.c_str()));
ASSERT_FALSE(docsItr->next());
}
}
TEST_F(IqlQueryBuilderTestSuite, test_query_builder_order) {
iresearch::memory_directory dir;
auto reader = load_json(dir, "simple_sequential.json");
ASSERT_EQ(1, reader.size());
auto& segment = reader[0]; // assume 0 is id of first/only segment
/* FIXME field-value order is not yet supported by iresearch::search
// order by sequence
{
auto query = query_builder().build("name==A || name == B order seq desc", std::locale::classic());
ASSERT_NE(nullptr, query.filter.get());
ASSERT_NE(nullptr, query.order);
ASSERT_EQ(nullptr, query.error);
ASSERT_EQ(nullptr, query.limit);
auto pQuery = query.filter->prepare(reader);
ASSERT_NE(nullptr, pQuery.get());
auto docsItr = pQuery->execute(segment);
ASSERT_NE(nullptr, docsItr.get());
tests::field_visitor visitor("name");
ASSERT_TRUE(docsItr->next());
segment.document(docsItr->value(), visitor);
ASSERT_EQ(std::string("B"), visitor.v_string);
ASSERT_TRUE(docsItr->next());
segment.document(docsItr->value(), visitor);
ASSERT_EQ(std::string("A"), visitor.v_string);
ASSERT_FALSE(docsItr->next());
}
// custom deterministic order function
{
order_function::deterministic_function_t fnTest = [](
order_function::deterministic_buffer_t& buf,
const order_function::deterministic_function_args_t& args
)->bool {
buf.append("name");
return true;
};
order_function order_function(fnTest);
order_functions order_functions = {
{ "c", order_function },
};
functions functions(order_functions);
auto query = query_builder(functions).build("name==A || name == B order c() desc", std::locale::classic());
ASSERT_NE(nullptr, query.filter.get());
ASSERT_NE(nullptr, query.order);
ASSERT_EQ(nullptr, query.error);
ASSERT_EQ(nullptr, query.limit);
auto pQuery = query.filter->prepare(reader);
ASSERT_NE(nullptr, pQuery.get());
auto docsItr = pQuery->execute(segment);
ASSERT_NE(nullptr, docsItr.get());
tests::field_visitor visitor("name");
ASSERT_TRUE(docsItr->next());
segment.document(docsItr->value(), visitor);
ASSERT_EQ(std::string("B"), visitor.v_string);
ASSERT_TRUE(docsItr->next());
segment.document(docsItr->value(), visitor);
ASSERT_EQ(std::string("A"), visitor.v_string);
ASSERT_FALSE(docsItr->next());
}
*/
// custom contextual order function
{
irs::bytes_ref actual_value;
auto column = segment.column_reader("name");
ASSERT_NE(nullptr, column);
auto values = column->values();
std::vector<std::pair<bool, std::string>> direction;
sequence_function::deterministic_function_t fnTestSeq = [](
sequence_function::deterministic_buffer_t& buf,
const order_function::deterministic_function_args_t&
)->bool {
buf.append("xyz");
return true;
};
order_function::contextual_function_t fnTest = [&direction](
order_function::contextual_buffer_t& buf,
const std::locale& locale,
void* cookie,
const bool& ascending,
const order_function::contextual_function_args_t& args
)->bool {
std::stringstream out;
for (auto& arg: args) {
iresearch::bstring value;
bool bValue;
arg.value(value, bValue, locale, cookie);
out << iresearch::ref_cast<char>(value) << "|";
}
direction.emplace_back(ascending, out.str());
buf.add<test_sort>(false);
return true;
};
order_function order_function0(fnTest, 0);
order_function order_function2(fnTest, 2);
order_functions order_functions = {
{ "c", order_function0 },
{ "d", order_function2 },
};
sequence_function sequence_function(fnTestSeq);
sequence_functions sequence_functions = {
{ "e", sequence_function },
};
functions functions(sequence_functions, order_functions);
auto query = query_builder(functions).build("name==A order c() desc, d(e(), f) asc", std::locale::classic());
ASSERT_NE(nullptr, query.filter.get());
ASSERT_NE(nullptr, query.order);
ASSERT_EQ(nullptr, query.error);
ASSERT_EQ(nullptr, query.limit);
auto pQuery = query.filter->prepare(reader);
ASSERT_NE(nullptr, pQuery.get());
auto docsItr = pQuery->execute(segment);
ASSERT_NE(nullptr, docsItr.get());
ASSERT_TRUE(docsItr->next());
ASSERT_TRUE(values(docsItr->value(), actual_value));
ASSERT_EQ("A", irs::to_string<irs::string_ref>(actual_value.c_str()));
ASSERT_FALSE(docsItr->next());
ASSERT_EQ(2, direction.size());
ASSERT_FALSE(direction[0].first);
ASSERT_EQ("", direction[0].second);
ASSERT_TRUE(direction[1].first);
ASSERT_EQ("xyz|f|", direction[1].second);
}
// ...........................................................................
// invalid
// ...........................................................................
// non-existent function
{
auto query = query_builder().build("name==A order b()", std::locale::classic());
ASSERT_NE(nullptr, query.filter.get());
ASSERT_EQ(nullptr, query.order);
ASSERT_NE(nullptr, query.error);
ASSERT_EQ(nullptr, query.limit);
auto pQuery = query.filter->prepare(reader);
ASSERT_EQ(nullptr, pQuery.get());
ASSERT_EQ(0, query.error->find("@(17): parse error"));
}
// failed deterministic function
{
order_function::deterministic_function_t fnFail = [](
order_function::deterministic_buffer_t&,
const order_function::deterministic_function_args_t&
)->bool {
return false;
};
order_function order_function(fnFail);
order_functions order_functions = {
{ "b", order_function },
};
functions functions(order_functions);
auto query = query_builder(functions).build("name==A order b()", std::locale::classic());
ASSERT_NE(nullptr, query.filter.get());
ASSERT_EQ(nullptr, query.order);
ASSERT_NE(nullptr, query.error);
ASSERT_EQ(nullptr, query.limit);
auto pQuery = query.filter->prepare(reader);
ASSERT_EQ(nullptr, pQuery.get());
ASSERT_EQ(0, query.error->find("order conversion error, node: @7\n'b'()@7 ASC"));
}
// failed contextual function
{
order_function::contextual_function_t fnFail = [](
order_function::contextual_buffer_t&,
const std::locale&,
void*,
const bool&,
const order_function::contextual_function_args_t&
)->bool {
return false;
};
order_function order_function(fnFail);
order_functions order_functions = {
{ "b", order_function },
};
functions functions(order_functions);
auto query = query_builder(functions).build("name==A order b()", std::locale::classic());
ASSERT_NE(nullptr, query.filter.get());
ASSERT_EQ(nullptr, query.order);
ASSERT_NE(nullptr, query.error);
ASSERT_EQ(nullptr, query.limit);
auto pQuery = query.filter->prepare(reader);
ASSERT_EQ(nullptr, pQuery.get());
ASSERT_EQ(0, query.error->find("order conversion error, node: @7\n'b'()@7 ASC"));
}
// failed nested function
{
sequence_function::deterministic_function_t fnFail = [](
sequence_function::deterministic_buffer_t&,
const sequence_function::deterministic_function_args_t&
)->bool {
return false;
};
order_function::contextual_function_t fnTest = [](
order_function::contextual_buffer_t&,
const std::locale& locale,
void* cookie,
const bool&,
const order_function::contextual_function_args_t& args
)->bool {
iresearch::bstring buf;
bool bNil;
return args[0].value(buf, bNil, locale, cookie); // expect false from above
};
order_function order_function(fnTest);
order_functions order_functions = {
{ "b", order_function },
};
sequence_function sequence_function(fnFail);
sequence_functions sequence_functions = {
{ "c", sequence_function },
};
functions functions(sequence_functions, order_functions);
auto query = query_builder(functions).build("name==A order b(c())", std::locale::classic());
ASSERT_NE(nullptr, query.filter.get());
ASSERT_EQ(nullptr, query.order);
ASSERT_NE(nullptr, query.error);
ASSERT_EQ(nullptr, query.limit);
auto pQuery = query.filter->prepare(reader);
ASSERT_EQ(nullptr, pQuery.get());
ASSERT_EQ(0, query.error->find("order conversion error, node: @9\n'b'('c'()@8)@9 ASC"));
}
}
// -----------------------------------------------------------------------------
// --SECTION-- END-OF-FILE
// -----------------------------------------------------------------------------