diff --git a/src/agents/query_engine/PatternMatchingQueryProcessor.cc b/src/agents/query_engine/PatternMatchingQueryProcessor.cc index 22b53272..32ac18e8 100644 --- a/src/agents/query_engine/PatternMatchingQueryProcessor.cc +++ b/src/agents/query_engine/PatternMatchingQueryProcessor.cc @@ -2,7 +2,7 @@ // clang-format off #ifndef LOG_LEVEL -#define LOG_LEVEL INFO_LEVEL +#define LOG_LEVEL DEBUG_LEVEL #endif #include "Logger.h" @@ -17,6 +17,7 @@ #include "MettaParserActions.h" #include "Node.h" #include "Or.h" +#include "Chain.h" #include "PatternMatchingQueryProxy.h" #include "ServiceBus.h" #include "Sink.h" @@ -33,6 +34,7 @@ using namespace attention_broker; string PatternMatchingQueryProcessor::AND = "AND"; string PatternMatchingQueryProcessor::OR = "OR"; +string PatternMatchingQueryProcessor::CHAIN = "CHAIN"; // ------------------------------------------------------------------------------------------------- // Constructors and destructors @@ -274,6 +276,8 @@ shared_ptr PatternMatchingQueryProcessor::setup_query_tree( (query_tokens[cursor] == LinkSchema::ATOM) || (query_tokens[cursor] == AND) || (query_tokens[cursor] == OR)) { cursor += 2; + } else if (query_tokens[cursor] == CHAIN) { + cursor += 4; } else { Utils::error("Invalid token in query: " + query_tokens[cursor]); } @@ -309,6 +313,8 @@ shared_ptr PatternMatchingQueryProcessor::setup_query_tree( if (proxy->parameters.get(BaseQueryProxy::UNIQUE_ASSIGNMENT_FLAG)) { element_stack.push(build_unique_assignment_filter(proxy, cursor, element_stack)); } + } else if (query_tokens[cursor] == CHAIN) { + element_stack.push(build_chain(proxy, cursor, element_stack)); } else { Utils::error("Invalid token " + query_tokens[cursor] + " in PATTERN_MATCHING_QUERY message"); } @@ -326,6 +332,7 @@ shared_ptr PatternMatchingQueryProcessor::build_link_template( shared_ptr proxy, unsigned int cursor, stack>& element_stack) { + LOG_DEBUG("Building LinkTemplate..."); const vector query_tokens = proxy->get_query_tokens(); unsigned int arity = std::stoi(query_tokens[cursor + 2]); if (element_stack.size() < arity) { @@ -346,6 +353,8 @@ shared_ptr PatternMatchingQueryProcessor::build_link_template( proxy->parameters.get(PatternMatchingQueryProxy::DISREGARD_IMPORTANCE_FLAG), proxy->parameters.get(PatternMatchingQueryProxy::UNIQUE_VALUE_FLAG), proxy->parameters.get(BaseQueryProxy::USE_LINK_TEMPLATE_CACHE)); + LOG_DEBUG("New LinkTemplate: " + link_template->to_string()); + LOG_DEBUG("Building LinkTemplate... Done."); return link_template; } @@ -411,9 +420,11 @@ shared_ptr PatternMatchingQueryProcessor::build_and( link_templates.push_back(element_stack.top()); \ link_template->build(); \ clauses[i] = link_template->get_source_element(); \ + LOG_DEBUG("OR input[" << i << "]: " << element_stack.top()->to_string()); \ } else { \ if (element_stack.top()->is_operator) { \ clauses[i] = element_stack.top(); \ + LOG_DEBUG("OR input[" << i << "]: " << element_stack.top()->to_string()); \ } else { \ Utils::error("All OR clauses are supposed to be LinkTemplate or Operator"); \ } \ @@ -427,6 +438,7 @@ shared_ptr PatternMatchingQueryProcessor::build_or( shared_ptr proxy, unsigned int cursor, stack>& element_stack) { + LOG_DEBUG("Building OR operator"); const vector query_tokens = proxy->get_query_tokens(); unsigned int num_clauses = std::stoi(query_tokens[cursor + 1]); if (element_stack.size() < num_clauses) { @@ -452,6 +464,62 @@ shared_ptr PatternMatchingQueryProcessor::build_or( return NULL; // Just to avoid warnings. This is not actually reachable. } +shared_ptr PatternMatchingQueryProcessor::build_chain( + shared_ptr proxy, + unsigned int cursor, + stack>& element_stack) { + LOG_DEBUG("Building CHAIN operator..."); + const vector query_tokens = proxy->get_query_tokens(); + QueryAnswerElement link_selector; + if (isdigit(static_cast(query_tokens[cursor + 1][0]))) { + link_selector.set(Utils::string_to_uint(query_tokens[cursor + 1])); + LOG_DEBUG("Link selector is handle index: " + query_tokens[cursor + 1]); + } else { + link_selector.set(query_tokens[cursor + 1]); + LOG_DEBUG("Link selector is variable: " + query_tokens[cursor + 1]); + } + unsigned int tail_reference = Utils::string_to_uint(query_tokens[cursor + 2]); + unsigned int head_reference = Utils::string_to_uint(query_tokens[cursor + 3]); + LOG_DEBUG("Tail reference: " + std::to_string(tail_reference)); + LOG_DEBUG("Head reference: " + std::to_string(head_reference)); + + if (element_stack.size() < 3) { + Utils::error( + "PATTERN_MATCHING_QUERY message: parse error in tokens - too few arguments for " + "CHAIN"); + } + + shared_ptr source = dynamic_pointer_cast(element_stack.top()); + element_stack.pop(); + shared_ptr target = dynamic_pointer_cast(element_stack.top()); + element_stack.pop(); + LOG_DEBUG("Source terminal: " + source->to_string()); + LOG_DEBUG("Target terminal: " + target->to_string()); + LOG_DEBUG("Source handle: " + source->compute_handle()); + LOG_DEBUG("Target handle: " + target->compute_handle()); + + array, 1> clauses; + clauses[0] = element_stack.top(); + shared_ptr link_template = dynamic_pointer_cast(clauses[0]); + if (link_template != nullptr) { + link_template->build(); + clauses[0] = link_template->get_source_element(); + } + LOG_DEBUG("Input: " + clauses[0]->to_string()); + element_stack.pop(); + + auto chain_operator = make_shared(clauses, + link_template, + source->compute_handle(), + target->compute_handle(), + link_selector, + tail_reference, + head_reference); + LOG_DEBUG("Building CHAIN operator... DONE"); + + return chain_operator; +} + shared_ptr PatternMatchingQueryProcessor::build_link( shared_ptr proxy, unsigned int cursor, diff --git a/src/agents/query_engine/PatternMatchingQueryProcessor.h b/src/agents/query_engine/PatternMatchingQueryProcessor.h index 3f30b6ec..a56e6f55 100644 --- a/src/agents/query_engine/PatternMatchingQueryProcessor.h +++ b/src/agents/query_engine/PatternMatchingQueryProcessor.h @@ -71,6 +71,10 @@ class PatternMatchingQueryProcessor : public BusCommandProcessor { unsigned int cursor, stack>& element_stack); + shared_ptr build_chain(shared_ptr proxy, + unsigned int cursor, + stack>& element_stack); + shared_ptr build_link(shared_ptr proxy, unsigned int cursor, stack>& element_stack); @@ -88,6 +92,7 @@ class PatternMatchingQueryProcessor : public BusCommandProcessor { shared_ptr atomdb; static string AND; static string OR; + static string CHAIN; }; } // namespace atomdb diff --git a/src/agents/query_engine/QueryAnswer.h b/src/agents/query_engine/QueryAnswer.h index e4fc9b29..a64f4873 100644 --- a/src/agents/query_engine/QueryAnswer.h +++ b/src/agents/query_engine/QueryAnswer.h @@ -6,6 +6,7 @@ #include "Assignment.h" #include "QueryAnswer.h" +#include "Utils.h" #include "expression_hasher.h" using namespace std; @@ -34,6 +35,22 @@ class QueryAnswerElement { this->name = other.name; return *this; } + void set(const string& key) { + if (this->type == UNDEFINED) { + this->type = VARIABLE; + this->name = key; + } else { + Utils::error("Invalid attempt to reset a QueryAnswerElement"); + } + } + void set(unsigned int key) { + if (this->type == UNDEFINED) { + this->type = HANDLE; + this->index = key; + } else { + Utils::error("Invalid attempt to reset a QueryAnswerElement"); + } + } string to_string() { if (this->type == HANDLE) { return "_" + std::to_string(this->index); diff --git a/src/agents/query_engine/query_element/BUILD b/src/agents/query_engine/query_element/BUILD index c0cffdeb..91634778 100644 --- a/src/agents/query_engine/query_element/BUILD +++ b/src/agents/query_engine/query_element/BUILD @@ -51,6 +51,8 @@ cc_library( hdrs = ["Chain.h"], deps = [ ":operator", + "//agents/query_engine/query_element:link_template", + "//agents/query_engine/query_element:source", "//atomdb:atomdb_singleton", "//commons:commons_lib", "//commons/atoms:atoms_lib", diff --git a/src/agents/query_engine/query_element/Chain.cc b/src/agents/query_engine/query_element/Chain.cc index 9cca531a..5c35745a 100644 --- a/src/agents/query_engine/query_element/Chain.cc +++ b/src/agents/query_engine/query_element/Chain.cc @@ -27,12 +27,27 @@ static string convert_handle(const string& handle) { // Public methods Chain::Chain(const array, 1>& clauses, + shared_ptr link_template, const string& source_handle, - const string& target_handle) - : Operator<1>(clauses), source_handle(source_handle), target_handle(target_handle) { + const string& target_handle, + const QueryAnswerElement& link_selector, + unsigned int tail_reference, + unsigned int head_reference) + : Operator<1>(clauses), + input_link_template(link_template), + source_handle(source_handle), + target_handle(target_handle), + link_selector(link_selector), + tail_reference(tail_reference), + head_reference(head_reference) { initialize(clauses); } +Chain::Chain(const array, 1>& clauses, + const string& source_handle, + const string& target_handle) + : Chain(clauses, nullptr, source_handle, target_handle, QueryAnswerElement(0), 1, 2) {} + Chain::~Chain() { LOG_DEBUG("Chain::~Chain() BEGIN"); graceful_shutdown(); @@ -201,6 +216,7 @@ bool Chain::PathFinder::thread_one_step() { << "Pushing new path: " << new_path.to_string()); base_heap->push(new_path, new_path.path_sti); } else { + LOG_DEBUG("[PATH_FINDER] Discarding because candidate would lead to a cycle."); count_cycles++; } } @@ -219,6 +235,7 @@ bool Chain::PathFinder::thread_one_step() { void Chain::refeed_paths() { while (!this->refeeding_buffer.empty()) { Path path = refeeding_buffer.front_and_pop(); + LOG_DEBUG("Refeeding: " << path.to_string()); if (path.forward_flag) { this->source_index[this->source_handle]->push(path, path.path_sti); } else { @@ -256,52 +273,52 @@ bool Chain::thread_one_step() { if ((answer = dynamic_cast(this->input_buffer[0]->pop_query_answer())) != NULL) { LOG_DEBUG("[CHAIN OPERATOR] " << "New query answer: " << answer->to_string()); - for (string handle : answer->handles) { - auto iterator = this->known_links.find(handle); - if (iterator == this->known_links.end()) { - this->known_links.insert(iterator, handle); - shared_ptr link = - dynamic_pointer_cast(AtomDBSingleton::get_instance()->get_atom(handle)); - if (link == nullptr) { - Utils::error("Invalid query answer in Chain operator."); - } else { - LOG_DEBUG("[CHAIN OPERATOR] " - << "Valid link"); - } + string handle = answer->get(this->link_selector); + auto iterator = this->known_links.find(handle); + if (iterator == this->known_links.end()) { + this->known_links.insert(iterator, handle); + shared_ptr link = + dynamic_pointer_cast(AtomDBSingleton::get_instance()->get_atom(handle)); + if (link == nullptr) { + Utils::error("Invalid query answer in Chain operator."); + } else { LOG_DEBUG("[CHAIN OPERATOR] " - << "New link: " << link->to_string()); - if (link->arity() == 3) { - { - lock_guard semaphore(this->source_index_mutex); - for (unsigned int i = 1; i <= 2; i++) { - if (this->source_index.find(link->targets[i]) == - this->source_index.end()) { - this->source_index[link->targets[i]] = make_shared(); - } + << "Valid link"); + } + LOG_DEBUG("[CHAIN OPERATOR] " + << "New link: " << link->to_string()); + if (link->arity() > max(this->tail_reference, this->head_reference)) { + string tail = link->targets[this->tail_reference]; + string head = link->targets[this->head_reference]; + { + lock_guard semaphore(this->source_index_mutex); + for (string key : {tail, head}) { + if (this->source_index.find(key) == this->source_index.end()) { + this->source_index[key] = make_shared(); } - this->source_index[link->targets[1]]->push(Path(link, answer, true), - answer->importance); } - { - lock_guard semaphore(this->target_index_mutex); - for (unsigned int i = 1; i <= 2; i++) { - if (this->target_index.find(link->targets[i]) == - this->target_index.end()) { - this->target_index[link->targets[i]] = make_shared(); - } + this->source_index[tail]->push(Path(tail, head, answer, true), + answer->importance); + } + { + lock_guard semaphore(this->target_index_mutex); + for (string key : {tail, head}) { + if (this->target_index.find(key) == this->target_index.end()) { + this->target_index[key] = make_shared(); } - this->target_index[link->targets[2]]->push( - Path(link, QueryAnswer::copy(answer), false), answer->importance); } - } else { - Utils::error("Invalid Link " + link->to_string() + " with arity " + - std::to_string(link->arity()) + " in CHAIN operator."); - break; + this->target_index[head]->push( + Path(tail, head, QueryAnswer::copy(answer), false), answer->importance); } } else { - LOG_DEBUG("[CHAIN OPERATOR] " - << "Discarding already inserted handle: " << convert_handle(handle)); + Utils::error("Invalid Link " + link->to_string() + " with arity " + + std::to_string(link->arity()) + " in CHAIN operator. Tail reference: " + + std::to_string(this->tail_reference) + + ". Head reference: " + std::to_string(this->head_reference)); } + } else { + LOG_DEBUG("[CHAIN OPERATOR] " + << "Discarding already inserted handle: " << convert_handle(handle)); } refeed_paths(); return true; @@ -322,20 +339,12 @@ bool Chain::thread_one_step() { void Chain::report_path(Path& path) { QueryAnswer* query_answer = new QueryAnswer(path.path_sti); if (path.forward_flag) { - for (auto pair : path.links) { - query_answer->add_handle(pair.first->handle()); // TODO change to use handle in query_answer - if (!query_answer->merge(pair.second.get())) { - Utils::error("Incompatible assignments in Chain operator answer: " + - query_answer->to_string() + " + " + pair.second->to_string()); - } + for (auto pair : path.edges) { + query_answer->add_handle(pair.second->get(this->link_selector)); } } else { - for (auto pair = path.links.rbegin(); pair != path.links.rend(); ++pair) { - query_answer->add_handle(pair->first->handle()); - if (!query_answer->merge(pair->second.get())) { - Utils::error("Incompatible assignments in Chain operator answer: " + - query_answer->to_string() + " + " + pair->second->to_string()); - } + for (auto pair = path.edges.rbegin(); pair != path.edges.rend(); ++pair) { + query_answer->add_handle(pair->second->get(this->link_selector)); } } string answer_hash = Hasher::composite_handle(query_answer->handles); @@ -393,20 +402,17 @@ string Chain::Path::to_string() { bool first = true; string last_handle = ""; string check_handle = ""; - for (auto pair : this->links) { + for (auto pair : this->edges) { if (first) { first = false; - last_handle = - convert_handle(this->forward_flag ? pair.first->targets[1] : pair.first->targets[2]); + last_handle = convert_handle(this->forward_flag ? pair.first.first : pair.first.second); answer = last_handle; } - check_handle = - convert_handle(this->forward_flag ? pair.first->targets[1] : pair.first->targets[2]); + check_handle = convert_handle(this->forward_flag ? pair.first.first : pair.first.second); if (check_handle != last_handle) { LOG_ERROR("Invalid Path"); } - last_handle = - convert_handle(this->forward_flag ? pair.first->targets[2] : pair.first->targets[1]); + last_handle = convert_handle(this->forward_flag ? pair.first.second : pair.first.first); answer += this->forward_flag ? " -> " : " <- "; answer += last_handle; } diff --git a/src/agents/query_engine/query_element/Chain.h b/src/agents/query_engine/query_element/Chain.h index 600ed038..d2aef34b 100644 --- a/src/agents/query_engine/query_element/Chain.h +++ b/src/agents/query_engine/query_element/Chain.h @@ -2,6 +2,7 @@ #include "DedicatedThread.h" #include "Link.h" +#include "LinkTemplate.h" #include "Operator.h" #include "ThreadSafeHeap.h" #include "ThreadSafeQueue.h" @@ -17,7 +18,9 @@ namespace query_element { /** * This operator takes as input a single query element and two handles (SOURCE and TARGET) and - * outputs QueryAnswers which represent paths between SOURCE and TARGET. + * outputs QueryAnswers which represent paths between SOURCE and TARGET. Additional parameters + * (link selector and tail/head reference) are used to determine which handle in the QueryAnswer + * is supposed to be used and which link targets are supposed to be used as edge tail and head. * * Each QueryAnswer in the input is supposed to have exatcly 1 handle, * i.e. query_answer->handles.size() == 1. In addition to this, the handle is supposed to be the @@ -109,19 +112,23 @@ class Chain : public Operator<1>, public ThreadMethod { class Path { public: - vector, shared_ptr>> links; + vector, shared_ptr>> edges; double path_sti; bool forward_flag; - Path(shared_ptr link, QueryAnswer* answer, bool forward_flag) { - if (link->targets[1] == link->targets[2]) { - Utils::error("Invalid cyclic link: " + link->to_string()); + Path(const string& tail, const string& head, QueryAnswer* answer, bool forward_flag) { + if (tail == head) { + Utils::error("Invalid cyclic edge: " + tail + " -> " + head); } - this->links.push_back({link, shared_ptr(answer)}); + this->edges.push_back({{tail, head}, shared_ptr(answer)}); this->path_sti = answer->importance; this->forward_flag = forward_flag; } + Path(shared_ptr link, // Used in tests + QueryAnswer* answer, + bool forward_flag) + : Path(link->targets[1], link->targets[2], answer, forward_flag) {} Path(const Path& other) { - this->links = other.links; + this->edges = other.edges; this->path_sti = other.path_sti; this->forward_flag = other.forward_flag; } @@ -130,41 +137,41 @@ class Chain : public Operator<1>, public ThreadMethod { this->forward_flag = forward_flag; } Path& operator=(const Path& other) { - this->links = other.links; + this->edges = other.edges; this->path_sti = other.path_sti; this->forward_flag = other.forward_flag; return *this; } - inline bool empty() { return this->links.size() == 0; } - inline unsigned int size() { return this->links.size(); } + inline bool empty() { return this->edges.size() == 0; } + inline unsigned int size() { return this->edges.size(); } inline void clear() { - this->links.clear(); + this->edges.clear(); this->path_sti = 0; } inline void concatenate(const Path& other) { if (this->forward_flag != other.forward_flag) { Utils::error("Invalid attempt to merge incompatible HeapElements"); } - this->links.insert(this->links.end(), other.links.begin(), other.links.end()); + this->edges.insert(this->edges.end(), other.edges.begin(), other.edges.end()); this->path_sti = max(this->path_sti, other.path_sti); } inline string end_point() { if (this->forward_flag) { - return this->links.back().first->targets[2]; + return this->edges.back().first.second; } else { - return this->links.back().first->targets[1]; + return this->edges.back().first.first; } } inline string start_point() { if (this->forward_flag) { - return this->links.front().first->targets[1]; + return this->edges.front().first.first; } else { - return this->links.front().first->targets[2]; + return this->edges.front().first.second; } } inline bool contains(string handle) { - for (auto pair : this->links) { - if ((pair.first->targets[1] == handle) || (pair.first->targets[2] == handle)) { + for (auto pair : this->edges) { + if ((pair.first.first == handle) || (pair.first.second == handle)) { return true; } } @@ -176,12 +183,18 @@ class Chain : public Operator<1>, public ThreadMethod { } else if (this->end_point() != other.start_point()) { return false; } - unsigned int this_index = (this->forward_flag ? 1 : 2); - unsigned int other_index = (this->forward_flag ? 2 : 1); - for (auto pair_other : other.links) { - for (auto pair_this : this->links) { - if (pair_other.first->targets[other_index] == pair_this.first->targets[this_index]) { - return false; + // unsigned int this_index = (this->forward_flag ? 1 : 2); + // unsigned int other_index = (this->forward_flag ? 2 : 1); + for (auto pair_other : other.edges) { + for (auto pair_this : this->edges) { + if (this->forward_flag) { + if (pair_other.first.second == pair_this.first.first) { + return false; + } + } else { + if (pair_other.first.first == pair_this.first.second) { + return false; + } } } } @@ -204,6 +217,19 @@ class Chain : public Operator<1>, public ThreadMethod { /** * Constructor. */ + Chain(const array, 1>& clauses, + shared_ptr link_template, + const string& source_handle, + const string& target_handle, + const QueryAnswerElement& link_selector, + unsigned int tail_reference, + unsigned int head_reference); + + /** + * Constructor. Typically used in tests, defaulting the link selector to the first handle in + * the query answer and assuming a link like (disregarded $v1 $v2) where chaining will be made + * assuming $v1 -> $v2. + */ Chain(const array, 1>& clauses, const string& source_handle, const string& target_handle); @@ -304,8 +330,12 @@ class Chain : public Operator<1>, public ThreadMethod { void initialize(const array, 1>& clauses); + shared_ptr input_link_template; string source_handle; string target_handle; + QueryAnswerElement link_selector; + unsigned int tail_reference; + unsigned int head_reference; PathFinder* forward_path_finder; PathFinder* backward_path_finder; shared_ptr operator_thread; diff --git a/src/agents/query_engine/query_element/Terminal.cc b/src/agents/query_engine/query_element/Terminal.cc index 76011c35..4774a65c 100644 --- a/src/agents/query_engine/query_element/Terminal.cc +++ b/src/agents/query_engine/query_element/Terminal.cc @@ -1,12 +1,14 @@ #include "Terminal.h" #define LOG_LEVEL INFO_LEVEL +#include "Hasher.h" #include "Link.h" #include "Logger.h" #include "Node.h" #include "UntypedVariable.h" using namespace query_element; +using namespace commons; Terminal::Terminal() : QueryElement() { // Atom @@ -47,6 +49,27 @@ void Terminal::init() { this->is_terminal = true; } +string Terminal::compute_handle() { + if (this->is_node) { + return Hasher::node_handle(this->type, this->name); + } else if (this->is_link) { + vector target_handles; + for (auto element : this->targets) { + shared_ptr terminal = dynamic_pointer_cast(element); + if (terminal == nullptr) { + Utils::error("Invalid non-terminal target in Terminal"); + } + target_handles.push_back(terminal->compute_handle()); + } + return Hasher::link_handle(this->type, target_handles); + } else if (this->is_atom) { + return handle; + } else { + Utils::error("Invalid attempt to generate the handle of a variable terminal"); + return ""; + } +} + string Terminal::to_string() { if (this->is_atom) { return this->handle; diff --git a/src/agents/query_engine/query_element/Terminal.h b/src/agents/query_engine/query_element/Terminal.h index 2993cd4e..1eb179c5 100644 --- a/src/agents/query_engine/query_element/Terminal.h +++ b/src/agents/query_engine/query_element/Terminal.h @@ -32,6 +32,7 @@ class Terminal : public QueryElement { Terminal(const string& type, const vector>& targets); // Link Terminal(const string& name); // Variable string to_string(); + string compute_handle(); // QueryElement virtual API diff --git a/src/commons/Utils.cc b/src/commons/Utils.cc index 7f69c58a..ab549ca1 100644 --- a/src/commons/Utils.cc +++ b/src/commons/Utils.cc @@ -176,6 +176,18 @@ int Utils::string_to_int(const string& s) { return stoi(s); } +unsigned int Utils::string_to_uint(const string& s) { + if (!is_number(s)) { + throw invalid_argument("Can not convert string to unsigned int: Invalid arguments (" + s + ")"); + } + int n = stoi(s); + if (n < 0) { + throw invalid_argument("Can not convert string to unsigned int: Invalid negative number (" + s + + ")"); + } + return (unsigned int) n; +} + string Utils::trim(const string& s) { const string whitespace = " \n\r\t\f\v"; size_t start = s.find_first_not_of(whitespace); diff --git a/src/commons/Utils.h b/src/commons/Utils.h index 6a726d9c..7db8da36 100644 --- a/src/commons/Utils.h +++ b/src/commons/Utils.h @@ -63,6 +63,7 @@ class Utils { static string random_string(size_t length); static bool is_number(const string& s); static int string_to_int(const string& s); + static unsigned int string_to_uint(const string& s); static float string_to_float(const string& s); static string trim(const string& s); static unsigned long long get_current_time_millis(); diff --git a/src/tests/cpp/chain_operator_test.cc b/src/tests/cpp/chain_operator_test.cc index bbef0842..87021aae 100644 --- a/src/tests/cpp/chain_operator_test.cc +++ b/src/tests/cpp/chain_operator_test.cc @@ -27,10 +27,12 @@ using namespace commons; #define NODE_COUNT ((unsigned int) 20) // Just to help in debugging -#define RUN_allow_concatenation ((bool) true) +// clang-format off +#define RUN_allow_concatenation ((bool) true) #define RUN_allow_concatenation_reverse ((bool) true) -#define RUN_back_after_dead_end ((bool) true) -#define RUN_basics ((bool) true) +#define RUN_back_after_dead_end ((bool) true) +#define RUN_basics ((bool) true) +// clang-format on static string EVALUATION_HANDLE = Hasher::node_handle(NODE_TYPE, EVALUATION); diff --git a/src/tests/cpp/pattern_matching_query_test.cc b/src/tests/cpp/pattern_matching_query_test.cc index 082ce9df..8c587787 100644 --- a/src/tests/cpp/pattern_matching_query_test.cc +++ b/src/tests/cpp/pattern_matching_query_test.cc @@ -1,4 +1,5 @@ #include "AtomDBSingleton.h" +#include "Chain.h" #include "Hasher.h" #include "PatternMatchingQueryProcessor.h" #include "PatternMatchingQueryProxy.h" @@ -11,6 +12,7 @@ #include "Logger.h" using namespace query_engine; +using namespace query_element; using namespace atomdb; string handle_to_atom(const string& handle) { @@ -145,6 +147,64 @@ void check_query(const string& query_tag, } } +void check_query_chain(const string& query_tag, + const vector& query, + const string& source, + const string& target, + unsigned int expected_count, + ServiceBus* client_bus, + const string& context, + bool update_attention_broker, + bool unique_assignment, + bool positive_importance, + bool error_flag, + bool unique_value_flag) { + LOG_INFO("==================== Query tag: " + query_tag); + + shared_ptr proxy1(new PatternMatchingQueryProxy(query, context)); + proxy1->parameters[BaseQueryProxy::UNIQUE_ASSIGNMENT_FLAG] = unique_assignment; + proxy1->parameters[BaseQueryProxy::ATTENTION_UPDATE_FLAG] = update_attention_broker; + proxy1->parameters[BaseQueryProxy::POPULATE_METTA_MAPPING] = false; + proxy1->parameters[PatternMatchingQueryProxy::POSITIVE_IMPORTANCE_FLAG] = positive_importance; + proxy1->parameters[PatternMatchingQueryProxy::UNIQUE_VALUE_FLAG] = unique_value_flag; + LOG_INFO("proxy1: " + proxy1->to_string()); + + unsigned int count = 0; + shared_ptr query_answer; + + client_bus->issue_bus_command(proxy1); + count = 0; + while (!proxy1->finished()) { + while (!(query_answer = proxy1->pop())) { + if (proxy1->finished()) { + break; + } else { + Utils::sleep(); + } + } + if (query_answer) { + LOG_INFO(">>>>>>>>>> " << query_answer->assignment.to_string()); + for (auto pair : query_answer->assignment.table) { + LOG_INFO(">>>>>>>>>>>>>> " << pair.first << " " << handle_to_atom(pair.second)); + } + if (((query_answer->get(Chain::ORIGIN_VARIABLE_NAME) == source) && + (query_answer->get(Chain::DESTINY_VARIABLE_NAME) == target)) || + ((query_answer->get(Chain::ORIGIN_VARIABLE_NAME) == target) && + (query_answer->get(Chain::DESTINY_VARIABLE_NAME) == source))) { + count++; + } + EXPECT_TRUE((query_answer->get(Chain::ORIGIN_VARIABLE_NAME) == source) || + (query_answer->get(Chain::ORIGIN_VARIABLE_NAME) == target)); + } + } + EXPECT_EQ(count, expected_count); + EXPECT_EQ(proxy1->error_flag, error_flag); + + // giving time to the server to close the previous connection + // otherwise the test fails with "Node ID already in the network" + Utils::sleep(); +} + TEST(PatternMatchingQuery, queries) { TestConfig::load_environment(); @@ -257,6 +317,33 @@ TEST(PatternMatchingQuery, queries) { string q7m = ""; int q7_expected_count = 4; + vector q8 = { + "CHAIN", "0", "1", "2", + "NODE", "Symbol", "\"chimp\"", + "ATOM", Hasher::node_handle("Symbol", "\"ent\""), + "LINK_TEMPLATE", "Expression", "3", + "NODE", "Symbol", "Similarity", + "VARIABLE", "v1", + "VARIABLE", "v2", + }; + int q8_expected_count = 2; + + vector q9 = { + "CHAIN", "0", "1", "2", + "ATOM", Hasher::node_handle("Symbol", "\"ent\""), + "NODE", "Symbol", "\"animal\"", + "OR", "2", + "LINK_TEMPLATE", "Expression", "3", + "NODE", "Symbol", "Similarity", + "VARIABLE", "v1", + "VARIABLE", "v2", + "LINK_TEMPLATE", "Expression", "3", + "NODE", "Symbol", "Inheritance", + "VARIABLE", "v1", + "VARIABLE", "v2", + }; + int q9_expected_count = 5; + // Regular queries check_query("q1", q1, q1m, q1_expected_count, client_bus, "PatternMatchingQuery.queries", false, false, false, false, false); check_query("q2", q2, q2m, q2_expected_count, client_bus, "PatternMatchingQuery.queries", false, false, false, false, false); @@ -266,6 +353,8 @@ TEST(PatternMatchingQuery, queries) { check_query("q5", q5, q5m, q5_expected_count, client_bus, "PatternMatchingQuery.queries", false, false, false, false, false); check_query("q6", q6, q6m, q6_expected_count, client_bus, "PatternMatchingQuery.queries", false, true, false, false, false); check_query("q7", q7, q7m, q7_expected_count, client_bus, "PatternMatchingQuery.queries", false, true, false, false, false); + check_query_chain("q8", q8, Hasher::node_handle("Symbol", "\"chimp\""), Hasher::node_handle("Symbol", "\"ent\""), q8_expected_count, client_bus, "PatternMatchingQuery.queries", false, true, false, false, false); + check_query_chain("q9", q9, Hasher::node_handle("Symbol", "\"ent\""), Hasher::node_handle("Symbol", "\"animal\""), q9_expected_count, client_bus, "PatternMatchingQuery.queries", false, true, false, false, false); // Importance filtering // XXX AttentionBroker is being revised so its dynamics is a bit unpredictable right now