From f47750c5bb42b1f47fd867cfe9b952cfac4698a1 Mon Sep 17 00:00:00 2001 From: Andre Senna <“andre.senna@gmail.com”> Date: Fri, 6 Mar 2026 09:58:26 -0300 Subject: [PATCH 1/9] Generalized link selection and tail/head definition in link's targets --- .../query_engine/query_element/Chain.cc | 110 ++++++++++-------- src/agents/query_engine/query_element/Chain.h | 73 ++++++++---- src/tests/cpp/chain_operator_test.cc | 8 +- 3 files changed, 117 insertions(+), 74 deletions(-) diff --git a/src/agents/query_engine/query_element/Chain.cc b/src/agents/query_engine/query_element/Chain.cc index 9cca531a..b7210762 100644 --- a/src/agents/query_engine/query_element/Chain.cc +++ b/src/agents/query_engine/query_element/Chain.cc @@ -28,11 +28,27 @@ static string convert_handle(const string& handle) { Chain::Chain(const array, 1>& clauses, 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), + 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, + source_handle, + target_handle, + QueryAnswerElement(0), + 1, + 2) {} + Chain::~Chain() { LOG_DEBUG("Chain::~Chain() BEGIN"); graceful_shutdown(); @@ -256,52 +272,50 @@ 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,16 +336,16 @@ 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 + for (auto pair : path.edges) { + query_answer->add_handle(pair.second->get(this->link_selector)); if (!query_answer->merge(pair.second.get())) { Utils::error("Incompatible assignments in Chain operator answer: " + query_answer->to_string() + " + " + pair.second->to_string()); } } } else { - for (auto pair = path.links.rbegin(); pair != path.links.rend(); ++pair) { - query_answer->add_handle(pair->first->handle()); + for (auto pair = path.edges.rbegin(); pair != path.edges.rend(); ++pair) { + query_answer->add_handle(pair->second->get(this->link_selector)); if (!query_answer->merge(pair->second.get())) { Utils::error("Incompatible assignments in Chain operator answer: " + query_answer->to_string() + " + " + pair->second->to_string()); @@ -393,20 +407,20 @@ 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]); + 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]); + 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]); + 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..8d0e6685 100644 --- a/src/agents/query_engine/query_element/Chain.h +++ b/src/agents/query_engine/query_element/Chain.h @@ -109,19 +109,25 @@ 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 +136,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 +182,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 +216,18 @@ class Chain : public Operator<1>, public ThreadMethod { /** * Constructor. */ + Chain(const array, 1>& clauses, + 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); @@ -306,6 +330,9 @@ class Chain : public Operator<1>, public ThreadMethod { 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/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); From 3e45c558207c2f1f9d10f9ebce665e4a58581c4d Mon Sep 17 00:00:00 2001 From: Andre Senna <“andre.senna@gmail.com”> Date: Tue, 10 Mar 2026 11:10:31 -0300 Subject: [PATCH 2/9] Added a new utility function to convert string->uint --- src/commons/Utils.cc | 11 +++++++++++ src/commons/Utils.h | 1 + 2 files changed, 12 insertions(+) diff --git a/src/commons/Utils.cc b/src/commons/Utils.cc index 7f69c58a..04891c0d 100644 --- a/src/commons/Utils.cc +++ b/src/commons/Utils.cc @@ -176,6 +176,17 @@ 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(); From 107611d19208c5438d398b279811c8556e5d2643 Mon Sep 17 00:00:00 2001 From: Andre Senna <“andre.senna@gmail.com”> Date: Tue, 10 Mar 2026 11:11:40 -0300 Subject: [PATCH 3/9] Added a method to allow setting handles and variables the same way we get these values with get() --- src/agents/query_engine/QueryAnswer.h | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/agents/query_engine/QueryAnswer.h b/src/agents/query_engine/QueryAnswer.h index e4fc9b29..003a231f 100644 --- a/src/agents/query_engine/QueryAnswer.h +++ b/src/agents/query_engine/QueryAnswer.h @@ -7,6 +7,7 @@ #include "Assignment.h" #include "QueryAnswer.h" #include "expression_hasher.h" +#include "Utils.h" using namespace std; using namespace commons; @@ -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); From bdcdc2c2dc3014d7604072bb573a12b2bc16b356 Mon Sep 17 00:00:00 2001 From: Andre Senna <“andre.senna@gmail.com”> Date: Tue, 10 Mar 2026 11:12:38 -0300 Subject: [PATCH 4/9] Added a method to compute the handle of nodes and links --- .../query_engine/query_element/Terminal.cc | 23 +++++++++++++++++++ .../query_engine/query_element/Terminal.h | 1 + 2 files changed, 24 insertions(+) diff --git a/src/agents/query_engine/query_element/Terminal.cc b/src/agents/query_engine/query_element/Terminal.cc index 76011c35..fed7f263 100644 --- a/src/agents/query_engine/query_element/Terminal.cc +++ b/src/agents/query_engine/query_element/Terminal.cc @@ -5,8 +5,10 @@ #include "Logger.h" #include "Node.h" #include "UntypedVariable.h" +#include "Hasher.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 From f4a1f7e16a97b0fad1372399b79748148c98fbd1 Mon Sep 17 00:00:00 2001 From: Andre Senna <“andre.senna@gmail.com”> Date: Tue, 10 Mar 2026 11:13:14 -0300 Subject: [PATCH 5/9] Integrate the new CHAIN operator in the tokens-based queries --- .../PatternMatchingQueryProcessor.cc | 71 ++++++++++++++- .../PatternMatchingQueryProcessor.h | 5 ++ src/agents/query_engine/query_element/BUILD | 2 + .../query_engine/query_element/Chain.cc | 13 ++- src/agents/query_engine/query_element/Chain.h | 7 +- src/tests/cpp/pattern_matching_query_test.cc | 86 +++++++++++++++++++ 6 files changed, 174 insertions(+), 10 deletions(-) diff --git a/src/agents/query_engine/PatternMatchingQueryProcessor.cc b/src/agents/query_engine/PatternMatchingQueryProcessor.cc index 22b53272..9187c1b5 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,63 @@ 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/query_element/BUILD b/src/agents/query_engine/query_element/BUILD index c0cffdeb..72ac1933 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:source", + "//agents/query_engine/query_element:link_template", "//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 b7210762..ecb54286 100644 --- a/src/agents/query_engine/query_element/Chain.cc +++ b/src/agents/query_engine/query_element/Chain.cc @@ -27,11 +27,13 @@ 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, 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), @@ -43,6 +45,7 @@ Chain::Chain(const array, 1>& clauses, Chain::Chain(const array, 1>& clauses, const string& source_handle, const string& target_handle) : Chain(clauses, + nullptr, source_handle, target_handle, QueryAnswerElement(0), @@ -217,6 +220,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++; } } @@ -235,6 +239,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 { @@ -338,18 +343,10 @@ void Chain::report_path(Path& path) { if (path.forward_flag) { for (auto pair : path.edges) { query_answer->add_handle(pair.second->get(this->link_selector)); - if (!query_answer->merge(pair.second.get())) { - Utils::error("Incompatible assignments in Chain operator answer: " + - query_answer->to_string() + " + " + pair.second->to_string()); - } } } else { for (auto pair = path.edges.rbegin(); pair != path.edges.rend(); ++pair) { query_answer->add_handle(pair->second->get(this->link_selector)); - if (!query_answer->merge(pair->second.get())) { - Utils::error("Incompatible assignments in Chain operator answer: " + - query_answer->to_string() + " + " + pair->second->to_string()); - } } } string answer_hash = Hasher::composite_handle(query_answer->handles); diff --git a/src/agents/query_engine/query_element/Chain.h b/src/agents/query_engine/query_element/Chain.h index 8d0e6685..74c84f57 100644 --- a/src/agents/query_engine/query_element/Chain.h +++ b/src/agents/query_engine/query_element/Chain.h @@ -5,6 +5,7 @@ #include "Operator.h" #include "ThreadSafeHeap.h" #include "ThreadSafeQueue.h" +#include "LinkTemplate.h" #include "map" #include "mutex" #include "set" @@ -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 @@ -217,6 +220,7 @@ 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, @@ -328,6 +332,7 @@ 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; diff --git a/src/tests/cpp/pattern_matching_query_test.cc b/src/tests/cpp/pattern_matching_query_test.cc index 082ce9df..da3c4875 100644 --- a/src/tests/cpp/pattern_matching_query_test.cc +++ b/src/tests/cpp/pattern_matching_query_test.cc @@ -1,5 +1,6 @@ #include "AtomDBSingleton.h" #include "Hasher.h" +#include "Chain.h" #include "PatternMatchingQueryProcessor.h" #include "PatternMatchingQueryProxy.h" #include "ServiceBus.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,61 @@ 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 +314,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 +350,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 From 4f4dd67ec4993e7e83376199ef8427b213ee25cf Mon Sep 17 00:00:00 2001 From: Andre Senna <“andre.senna@gmail.com”> Date: Tue, 10 Mar 2026 11:55:22 -0300 Subject: [PATCH 6/9] Format fixes --- .../PatternMatchingQueryProcessor.cc | 15 +++--- src/agents/query_engine/QueryAnswer.h | 2 +- src/agents/query_engine/query_element/BUILD | 2 +- .../query_engine/query_element/Chain.cc | 49 +++++++++---------- src/agents/query_engine/query_element/Chain.h | 16 +++--- .../query_engine/query_element/Terminal.cc | 2 +- src/commons/Utils.cc | 3 +- src/tests/cpp/pattern_matching_query_test.cc | 11 +++-- 8 files changed, 48 insertions(+), 52 deletions(-) diff --git a/src/agents/query_engine/PatternMatchingQueryProcessor.cc b/src/agents/query_engine/PatternMatchingQueryProcessor.cc index 9187c1b5..32ac18e8 100644 --- a/src/agents/query_engine/PatternMatchingQueryProcessor.cc +++ b/src/agents/query_engine/PatternMatchingQueryProcessor.cc @@ -508,14 +508,13 @@ shared_ptr PatternMatchingQueryProcessor::build_chain( 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); + 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; diff --git a/src/agents/query_engine/QueryAnswer.h b/src/agents/query_engine/QueryAnswer.h index 003a231f..a64f4873 100644 --- a/src/agents/query_engine/QueryAnswer.h +++ b/src/agents/query_engine/QueryAnswer.h @@ -6,8 +6,8 @@ #include "Assignment.h" #include "QueryAnswer.h" -#include "expression_hasher.h" #include "Utils.h" +#include "expression_hasher.h" using namespace std; using namespace commons; diff --git a/src/agents/query_engine/query_element/BUILD b/src/agents/query_engine/query_element/BUILD index 72ac1933..91634778 100644 --- a/src/agents/query_engine/query_element/BUILD +++ b/src/agents/query_engine/query_element/BUILD @@ -51,8 +51,8 @@ cc_library( hdrs = ["Chain.h"], deps = [ ":operator", - "//agents/query_engine/query_element:source", "//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 ecb54286..5c35745a 100644 --- a/src/agents/query_engine/query_element/Chain.cc +++ b/src/agents/query_engine/query_element/Chain.cc @@ -32,25 +32,21 @@ Chain::Chain(const array, 1>& clauses, 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) { + 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) {} + const string& target_handle) + : Chain(clauses, nullptr, source_handle, target_handle, QueryAnswerElement(0), 1, 2) {} Chain::~Chain() { LOG_DEBUG("Chain::~Chain() BEGIN"); @@ -297,26 +293,28 @@ bool Chain::thread_one_step() { { lock_guard semaphore(this->source_index_mutex); for (string key : {tail, head}) { - if (this->source_index.find(key) == - this->source_index.end()) { + if (this->source_index.find(key) == this->source_index.end()) { this->source_index[key] = make_shared(); } } - this->source_index[tail]->push(Path(tail, head, answer, true), answer->importance); + 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()) { + if (this->target_index.find(key) == this->target_index.end()) { this->target_index[key] = make_shared(); } } - this->target_index[head]->push(Path(tail, head, QueryAnswer::copy(answer), false), answer->importance); + this->target_index[head]->push( + Path(tail, head, QueryAnswer::copy(answer), false), answer->importance); } } else { 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)); + 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] " @@ -407,17 +405,14 @@ string Chain::Path::to_string() { for (auto pair : this->edges) { if (first) { first = false; - last_handle = - convert_handle(this->forward_flag ? pair.first.first : pair.first.second); + 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.first : pair.first.second); + 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.second : pair.first.first); + 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 74c84f57..d2aef34b 100644 --- a/src/agents/query_engine/query_element/Chain.h +++ b/src/agents/query_engine/query_element/Chain.h @@ -2,10 +2,10 @@ #include "DedicatedThread.h" #include "Link.h" +#include "LinkTemplate.h" #include "Operator.h" #include "ThreadSafeHeap.h" #include "ThreadSafeQueue.h" -#include "LinkTemplate.h" #include "map" #include "mutex" #include "set" @@ -123,12 +123,10 @@ class Chain : public Operator<1>, public ThreadMethod { 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(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->edges = other.edges; this->path_sti = other.path_sti; @@ -185,8 +183,8 @@ 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); + // 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) { diff --git a/src/agents/query_engine/query_element/Terminal.cc b/src/agents/query_engine/query_element/Terminal.cc index fed7f263..4774a65c 100644 --- a/src/agents/query_engine/query_element/Terminal.cc +++ b/src/agents/query_engine/query_element/Terminal.cc @@ -1,11 +1,11 @@ #include "Terminal.h" #define LOG_LEVEL INFO_LEVEL +#include "Hasher.h" #include "Link.h" #include "Logger.h" #include "Node.h" #include "UntypedVariable.h" -#include "Hasher.h" using namespace query_element; using namespace commons; diff --git a/src/commons/Utils.cc b/src/commons/Utils.cc index 04891c0d..ab549ca1 100644 --- a/src/commons/Utils.cc +++ b/src/commons/Utils.cc @@ -182,7 +182,8 @@ unsigned int Utils::string_to_uint(const string& s) { } int n = stoi(s); if (n < 0) { - throw invalid_argument("Can not convert string to unsigned int: Invalid negative number (" + s + ")"); + throw invalid_argument("Can not convert string to unsigned int: Invalid negative number (" + s + + ")"); } return (unsigned int) n; } diff --git a/src/tests/cpp/pattern_matching_query_test.cc b/src/tests/cpp/pattern_matching_query_test.cc index da3c4875..8c587787 100644 --- a/src/tests/cpp/pattern_matching_query_test.cc +++ b/src/tests/cpp/pattern_matching_query_test.cc @@ -1,6 +1,6 @@ #include "AtomDBSingleton.h" -#include "Hasher.h" #include "Chain.h" +#include "Hasher.h" #include "PatternMatchingQueryProcessor.h" #include "PatternMatchingQueryProxy.h" #include "ServiceBus.h" @@ -187,11 +187,14 @@ void check_query_chain(const string& query_tag, 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))) { + 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_TRUE((query_answer->get(Chain::ORIGIN_VARIABLE_NAME) == source) || + (query_answer->get(Chain::ORIGIN_VARIABLE_NAME) == target)); } } EXPECT_EQ(count, expected_count); From 8c2e3aaf3621b9a3a2495c565d5d7fff6ed27c2e Mon Sep 17 00:00:00 2001 From: Andre Senna <“andre.senna@gmail.com”> Date: Wed, 18 Mar 2026 12:22:17 +0300 Subject: [PATCH 7/9] Code documentation update --- src/agents/query_engine/query_element/Chain.h | 88 +++++++------------ 1 file changed, 30 insertions(+), 58 deletions(-) diff --git a/src/agents/query_engine/query_element/Chain.h b/src/agents/query_engine/query_element/Chain.h index d2aef34b..655c0f53 100644 --- a/src/agents/query_engine/query_element/Chain.h +++ b/src/agents/query_engine/query_element/Chain.h @@ -17,71 +17,43 @@ using namespace processor; 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. 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. + * This operator takes as input another query element, a specification of how to extract edges + * information from each query answer and two handles (SOURCE and TARGET) and + * outputs QueryAnswers which represent paths between SOURCE and TARGET. * - * 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 - * handle of a ternary link such as + * We'll use the term "edge" here in order to avoid confusion if "links" to explain how pths + * are created. So, a path between SOURCE and TARGET is a chain of edges (Hi, Hj) that + * goes from SOURCE to TARGET. Something like (SOURCE, H1), (H1, H2), (H2, H3), ... (HN, TARGET) + * which can also be represented as SOURCE -> H1 -> H2 -> H3 -> ... -> HN -> TARGET. * - * LINK - * TARGET1 - * TARGET2 - * TARGET3 + * So while processing its input (query answers), the chain operator extractc one edge from + * each query answer and try to combine it with the edges it extracted from other query answers + * in order to form paths from SOURCE to TARGET. To do so, it uses link_selector, tail_reference and + * head_reference. * - * TARGET1 is disregarded. TARGET2 and TARGET3 are used to connect the paths. Just to make it - * easy to write an example, lets assume the input handles represent links like these: + * Suppose we have a KB with several Similarity links like these: * - * (Similarity H1 H2) - * - * Each QueryAnswer in the Chain Operator output will contain N handles, representing a path - * with N links connecting SOURCE and TARGET. Suppose we have a QueryAnswer with N=4, the handles - * in query_answer->handles will point to links like these: - * - * (Similarity SOURCE H1) - * (Similarity H1 H2) - * (Similarity H2 H3) - * (Similarity H3 TARGET) + * (Similarity "human" "chimp") + * (Similarity "chimp" "monkey") + * (Similarity "human" "monkey") + * (Similarity "human" "ent") * - * Chained to form a path between SOURCE and TARGET. Note that the first link target is - * disregarded so you may have something like: - * - * CHAIN SOURCE TARGET - * OR 2 - * LinkTemplate 3 - * Node Equivalence - * Variable v1 - * Variable v2 - * LinkTemplate 3 - * Node Similarity - * Variable v1 - * Variable v2 - * - * This cold produce QueryAnswers paths chaining Similarity and Equivalence links in the same path. - * E.g.: - * - * (Similarity SOURCE H1) - * (Similarity H1 H2) - * (Equivalence H2 H3) - * (Similarity H3 H4) - * (Equivalence H4 H5) - * (Similarrity H5 TARGET) + * We may use a Chain operator to find a path conecting "human" and "ent" through SImilarity links. + * To do this, we can use a query like this: * - * Also note that, because input QueryAnswer are supposed to have exatcly 1 handle, the following - * query IS NOT valid: + * CHAIN, "0", "1", "2", + * "NODE", "Symbol", "chimp", + * "NODE", "Symbol", "ent", + * "LINK_TEMPLATE", "Expression", "3", + * "NODE", "Symbol", "Similarity", + * "VARIABLE", "v1", + * "VARIABLE", "v2" * - * CHAIN SOURCE TARGET - * AND 2 - * LinkTemplate 3 - * Node Equivalence - * Variable v1 - * Variable v2 - * LinkTemplate 3 - * Node Equivalence - * Variable v2 - * Variable v3 + * In this example, the LINK_TEMPLATE is the query. Its query answers will be exactly the Similarity + * links we listed above. So "0" is used to select the first handle of each query answer (which represents + * the Similarity links themselves) and "1" and "2" are the target indexes that will be used to get the + * tail and the head of each edge. So, for instance, for the query answer for (Similarity "human" "chimp"), + * the edge "human" -> "chimp" is extracted. * * Optionally, ALLOW_INCOMPLETE_CHAIN_PATH can be set true to determine that the Chain Operator * should output incomplete paths as well as complete ones (the same prioritizartion by From 0fd4a3bba93f47fa08e2ff163a15077e766e377e Mon Sep 17 00:00:00 2001 From: Andre Senna <“andre.senna@gmail.com”> Date: Wed, 8 Apr 2026 11:11:54 -0300 Subject: [PATCH 8/9] Format fixes --- src/agents/query_engine/query_element/Chain.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/agents/query_engine/query_element/Chain.h b/src/agents/query_engine/query_element/Chain.h index 655c0f53..574091c0 100644 --- a/src/agents/query_engine/query_element/Chain.h +++ b/src/agents/query_engine/query_element/Chain.h @@ -50,10 +50,10 @@ namespace query_element { * "VARIABLE", "v2" * * In this example, the LINK_TEMPLATE is the query. Its query answers will be exactly the Similarity - * links we listed above. So "0" is used to select the first handle of each query answer (which represents - * the Similarity links themselves) and "1" and "2" are the target indexes that will be used to get the - * tail and the head of each edge. So, for instance, for the query answer for (Similarity "human" "chimp"), - * the edge "human" -> "chimp" is extracted. + * links we listed above. So "0" is used to select the first handle of each query answer (which + * represents the Similarity links themselves) and "1" and "2" are the target indexes that will be used + * to get the tail and the head of each edge. So, for instance, for the query answer for (Similarity + * "human" "chimp"), the edge "human" -> "chimp" is extracted. * * Optionally, ALLOW_INCOMPLETE_CHAIN_PATH can be set true to determine that the Chain Operator * should output incomplete paths as well as complete ones (the same prioritizartion by From 1bba9ee709c661af9caa7dc8929ac60d95fdd1fb Mon Sep 17 00:00:00 2001 From: Andre Senna <“andre.senna@gmail.com”> Date: Wed, 8 Apr 2026 11:54:45 -0300 Subject: [PATCH 9/9] Fix LOG level --- src/agents/query_engine/PatternMatchingQueryProcessor.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/agents/query_engine/PatternMatchingQueryProcessor.cc b/src/agents/query_engine/PatternMatchingQueryProcessor.cc index 32ac18e8..72989cfe 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 DEBUG_LEVEL +#define LOG_LEVEL INFO_LEVEL #endif #include "Logger.h"